Blob Blame History Raw
diff -Naur vdr-2.1.6/config.c vdr-2.1.6-naludump-0.1/config.c
--- vdr-2.1.6/config.c	2013-08-31 14:41:28.000000000 +0200
+++ vdr-2.1.6-naludump-0.1/config.c	2014-03-30 17:47:25.000000000 +0200
@@ -462,6 +462,7 @@
   MaxVideoFileSize = MAXVIDEOFILESIZEDEFAULT;
   SplitEditedFiles = 0;
   DelTimeshiftRec = 0;
+  DumpNaluFill = 0;
   MinEventTimeout = 30;
   MinUserInactivity = 300;
   NextWakeupTime = 0;
@@ -673,6 +674,7 @@
   else if (!strcasecmp(Name, "MaxVideoFileSize"))    MaxVideoFileSize   = atoi(Value);
   else if (!strcasecmp(Name, "SplitEditedFiles"))    SplitEditedFiles   = atoi(Value);
   else if (!strcasecmp(Name, "DelTimeshiftRec"))     DelTimeshiftRec    = atoi(Value);
+  else if (!strcasecmp(Name, "DumpNaluFill"))        DumpNaluFill       = atoi(Value);
   else if (!strcasecmp(Name, "MinEventTimeout"))     MinEventTimeout    = atoi(Value);
   else if (!strcasecmp(Name, "MinUserInactivity"))   MinUserInactivity  = atoi(Value);
   else if (!strcasecmp(Name, "NextWakeupTime"))      NextWakeupTime     = atoi(Value);
@@ -788,6 +790,7 @@
   Store("MaxVideoFileSize",   MaxVideoFileSize);
   Store("SplitEditedFiles",   SplitEditedFiles);
   Store("DelTimeshiftRec",    DelTimeshiftRec);
+  Store("DumpNaluFill",       DumpNaluFill);
   Store("MinEventTimeout",    MinEventTimeout);
   Store("MinUserInactivity",  MinUserInactivity);
   Store("NextWakeupTime",     NextWakeupTime);
diff -Naur vdr-2.1.6/config.h vdr-2.1.6-naludump-0.1/config.h
--- vdr-2.1.6/config.h	2014-02-25 11:00:23.000000000 +0100
+++ vdr-2.1.6-naludump-0.1/config.h	2014-03-30 17:47:25.000000000 +0200
@@ -326,6 +326,7 @@
   int MaxVideoFileSize;
   int SplitEditedFiles;
   int DelTimeshiftRec;
+  int DumpNaluFill;
   int MinEventTimeout, MinUserInactivity;
   time_t NextWakeupTime;
   int MultiSpeedMode;
diff -Naur vdr-2.1.6/menu.c vdr-2.1.6-naludump-0.1/menu.c
--- vdr-2.1.6/menu.c	2014-03-16 11:38:31.000000000 +0100
+++ vdr-2.1.6-naludump-0.1/menu.c	2014-03-30 17:47:25.000000000 +0200
@@ -3547,6 +3547,7 @@
   Add(new cMenuEditIntItem( tr("Setup.Recording$Max. video file size (MB)"), &data.MaxVideoFileSize, MINVIDEOFILESIZE, MAXVIDEOFILESIZETS));
   Add(new cMenuEditBoolItem(tr("Setup.Recording$Split edited files"),        &data.SplitEditedFiles));
   Add(new cMenuEditStraItem(tr("Setup.Recording$Delete timeshift recording"),&data.DelTimeshiftRec, 3, delTimeshiftRecTexts));
+  Add(new cMenuEditBoolItem(tr("Setup.Recording$Dump NALU Fill data"),       &data.DumpNaluFill));
 }
 
 // --- cMenuSetupReplay ------------------------------------------------------
diff -Naur vdr-2.1.6/recorder.c vdr-2.1.6-naludump-0.1/recorder.c
--- vdr-2.1.6/recorder.c	2014-02-21 10:19:52.000000000 +0100
+++ vdr-2.1.6-naludump-0.1/recorder.c	2014-03-30 17:47:25.000000000 +0200
@@ -46,6 +46,14 @@
      Type = 0x06;
      }
   frameDetector = new cFrameDetector(Pid, Type);
+  if (   Type == 0x1B // MPEG4 video
+      && (Setup.DumpNaluFill ? (strstr(FileName, "NALUKEEP") == NULL) : (strstr(FileName, "NALUDUMP") != NULL))) { // MPEG4
+     isyslog("Starting NALU fill dumper");
+     naluStreamProcessor = new cNaluStreamProcessor();
+     naluStreamProcessor->SetPid(Pid);
+     }
+  else
+     naluStreamProcessor = NULL;
   index = NULL;
   fileSize = 0;
   lastDiskSpaceCheck = time(NULL);
@@ -67,6 +75,12 @@
 cRecorder::~cRecorder()
 {
   Detach();
+  if (naluStreamProcessor) {
+     long long int TotalPackets = naluStreamProcessor->GetTotalPackets();
+     long long int DroppedPackets = naluStreamProcessor->GetDroppedPackets();
+     isyslog("NALU fill dumper: %lld of %lld packets dropped, %lli%%", DroppedPackets, TotalPackets, TotalPackets ? DroppedPackets*100/TotalPackets : 0);
+     delete naluStreamProcessor;
+     }
   delete index;
   delete fileName;
   delete frameDetector;
@@ -157,11 +171,32 @@
                              }
                        t.Set(MAXBROKENTIMEOUT);
                        }
-                    if (recordFile->Write(b, Count) < 0) {
-                       LOG_ERROR_STR(fileName->Name());
-                       break;
+                    if (naluStreamProcessor) {
+                       naluStreamProcessor->PutBuffer(b, Count);
+                       bool Fail = false;
+                       while (true) {
+                             int OutLength = 0;
+                             uchar *OutData = naluStreamProcessor->GetBuffer(OutLength);
+                             if (!OutData || OutLength <= 0)
+                                break;
+                             if (recordFile->Write(OutData, OutLength) < 0) {
+                                LOG_ERROR_STR(fileName->Name());
+                                Fail = true;
+                                break;
+                                }
+                             fileSize += OutLength;
+                             }
+                       if (Fail)
+                          break;
+                       }
+                    else {
+                       if (recordFile->Write(b, Count) < 0) {
+                          LOG_ERROR_STR(fileName->Name());
+                          break;
+                          }
+                       fileSize += Count;
                        }
-                    fileSize += Count;
+
                     }
                  }
               ringBuffer->Del(Count);
diff -Naur vdr-2.1.6/recorder.h vdr-2.1.6-naludump-0.1/recorder.h
--- vdr-2.1.6/recorder.h	2010-12-27 12:17:04.000000000 +0100
+++ vdr-2.1.6-naludump-0.1/recorder.h	2014-03-30 17:47:25.000000000 +0200
@@ -21,6 +21,7 @@
   cRingBufferLinear *ringBuffer;
   cFrameDetector *frameDetector;
   cPatPmtGenerator patPmtGenerator;
+  cNaluStreamProcessor *naluStreamProcessor;
   cFileName *fileName;
   cIndexFile *index;
   cUnbufferedFile *recordFile;
diff -Naur vdr-2.1.6/remux.c vdr-2.1.6-naludump-0.1/remux.c
--- vdr-2.1.6/remux.c	2014-03-08 16:05:35.000000000 +0100
+++ vdr-2.1.6-naludump-0.1/remux.c	2014-03-30 17:47:25.000000000 +0200
@@ -343,6 +343,42 @@
      dsyslog("WARNING: required %d video TS packets to determine frame type", numPacketsPid);
 }
 
+void TsExtendAdaptionField(unsigned char *Packet, int ToLength)
+{
+    // Hint: ExtenAdaptionField(p, TsPayloadOffset(p) - 4) is a null operation
+
+    int Offset = TsPayloadOffset(Packet); // First byte after existing adaption field
+
+    if (ToLength <= 0)
+    {
+        // Remove adaption field
+        Packet[3] = Packet[3] & ~TS_ADAPT_FIELD_EXISTS;
+        return;
+    }
+
+    // Set adaption field present
+    Packet[3] = Packet[3] | TS_ADAPT_FIELD_EXISTS;
+
+    // Set new length of adaption field:
+    Packet[4] = ToLength <= TS_SIZE-4 ? ToLength-1 : TS_SIZE-4-1;
+
+    if (Packet[4] == TS_SIZE-4-1)
+    {
+        // No more payload, remove payload flag
+        Packet[3] = Packet[3] & ~TS_PAYLOAD_EXISTS;
+    }
+
+    int NewPayload = TsPayloadOffset(Packet); // First byte after new adaption field
+
+    // Fill new adaption field
+    if (Offset == 4 && Offset < NewPayload)
+        Offset++; // skip adaptation_field_length
+    if (Offset == 5 && Offset < NewPayload)
+        Packet[Offset++] = 0; // various flags set to 0
+    while (Offset < NewPayload)
+        Packet[Offset++] = 0xff; // stuffing byte
+}
+
 // --- cPatPmtGenerator ------------------------------------------------------
 
 cPatPmtGenerator::cPatPmtGenerator(const cChannel *Channel)
@@ -1547,3 +1583,344 @@
         }
   return Processed;
 }
+
+// --- cNaluDumper ---------------------------------------------------------
+
+cNaluDumper::cNaluDumper()
+{
+    LastContinuityOutput = -1;
+    reset();
+}
+
+void cNaluDumper::reset()
+{
+    LastContinuityInput = -1;
+    ContinuityOffset = 0;
+    PesId = -1;
+    PesOffset = 0;
+    NaluFillState = NALU_NONE;
+    NaluOffset = 0;
+    History = 0xffffffff;
+    DropAllPayload = false;
+}
+
+void cNaluDumper::ProcessPayload(unsigned char *Payload, int size, bool PayloadStart, sPayloadInfo &Info)
+{
+    Info.DropPayloadStartBytes = 0;
+    Info.DropPayloadEndBytes = 0;
+    int LastKeepByte = -1;
+
+    if (PayloadStart)
+    {
+        History = 0xffffffff;
+        PesId = -1;
+        NaluFillState = NALU_NONE;
+    }
+
+    for (int i=0; i<size; i++) {
+        History = (History << 8) | Payload[i];
+
+        PesOffset++;
+        NaluOffset++;
+
+        bool DropByte = false;
+
+        if (History >= 0x00000180 && History <= 0x000001FF)
+        {
+            // Start of PES packet
+            PesId = History & 0xff;
+            PesOffset = 0;
+            NaluFillState = NALU_NONE;
+        }
+        else if (PesId >= 0xe0 && PesId <= 0xef // video stream
+                 && History >= 0x00000100 && History <= 0x0000017F) // NALU start code
+        {
+            int NaluId = History & 0xff;
+            NaluOffset = 0;
+            NaluFillState = ((NaluId & 0x1f) == 0x0c) ? NALU_FILL : NALU_NONE;
+        }
+
+        if (PesId >= 0xe0 && PesId <= 0xef // video stream
+            && PesOffset >= 1 && PesOffset <= 2)
+        {
+            Payload[i] = 0; // Zero out PES length field
+        }
+
+        if (NaluFillState == NALU_FILL && NaluOffset > 0) // Within NALU fill data
+        {
+            // We expect a series of 0xff bytes terminated by a single 0x80 byte.
+
+            if (Payload[i] == 0xFF)
+            {
+                DropByte = true;
+            }
+            else if (Payload[i] == 0x80)
+            {
+                NaluFillState = NALU_TERM; // Last byte of NALU fill, next byte sets NaluFillEnd=true
+                DropByte = true;
+            }
+            else // Invalid NALU fill
+            {
+                dsyslog("cNaluDumper: Unexpected NALU fill data: %02x", Payload[i]);
+                NaluFillState = NALU_END;
+                if (LastKeepByte == -1)
+                {
+                    // Nalu fill from beginning of packet until last byte
+                    // packet start needs to be dropped
+                    Info.DropPayloadStartBytes = i;
+                }
+            }
+        }
+        else if (NaluFillState == NALU_TERM) // Within NALU fill data
+        {
+            // We are after the terminating 0x80 byte
+            NaluFillState = NALU_END;
+            if (LastKeepByte == -1)
+            {
+                // Nalu fill from beginning of packet until last byte
+                // packet start needs to be dropped
+                Info.DropPayloadStartBytes = i;
+            }
+        }
+
+        if (!DropByte)
+            LastKeepByte = i; // Last useful byte
+    }
+
+    Info.DropAllPayloadBytes = (LastKeepByte == -1);
+    Info.DropPayloadEndBytes = size-1-LastKeepByte;
+}
+
+bool cNaluDumper::ProcessTSPacket(unsigned char *Packet)
+{
+    bool HasAdaption = TsHasAdaptationField(Packet);
+    bool HasPayload = TsHasPayload(Packet);
+
+    // Check continuity:
+    int ContinuityInput = TsContinuityCounter(Packet);
+    if (LastContinuityInput >= 0)
+    {
+        int NewContinuityInput = HasPayload ? (LastContinuityInput + 1) & TS_CONT_CNT_MASK : LastContinuityInput;
+        int Offset = (NewContinuityInput - ContinuityInput) & TS_CONT_CNT_MASK;
+        if (Offset > 0)
+            dsyslog("cNaluDumper: TS continuity offset %i", Offset);
+        if (Offset > ContinuityOffset)
+            ContinuityOffset = Offset; // max if packets get dropped, otherwise always the current one.
+    }
+    LastContinuityInput = ContinuityInput;
+
+    if (HasPayload) {
+        sPayloadInfo Info;
+        int Offset = TsPayloadOffset(Packet);
+        ProcessPayload(Packet + Offset, TS_SIZE - Offset, TsPayloadStart(Packet), Info);
+
+        if (DropAllPayload && !Info.DropAllPayloadBytes)
+        {
+            // Return from drop packet mode to normal mode
+            DropAllPayload = false;
+
+            // Does the packet start with some remaining NALU fill data?
+            if (Info.DropPayloadStartBytes > 0)
+            {
+                // Add these bytes as stuffing to the adaption field.
+
+                // Sample payload layout:
+                // FF FF FF FF FF 80 00 00 01 xx xx xx xx
+                //                   ^DropPayloadStartBytes
+
+                TsExtendAdaptionField(Packet, Offset - 4 + Info.DropPayloadStartBytes);
+            }
+        }
+
+        bool DropThisPayload = DropAllPayload;
+
+        if (!DropAllPayload && Info.DropPayloadEndBytes > 0) // Payload ends with 0xff NALU Fill
+        {
+            // Last packet of useful data
+            // Do early termination of NALU fill data
+            Packet[TS_SIZE-1] = 0x80;
+            DropAllPayload = true;
+            // Drop all packets AFTER this one
+
+            // Since we already wrote the 0x80, we have to make sure that
+            // as soon as we stop dropping packets, any beginning NALU fill of next
+            // packet gets dumped. (see DropPayloadStartBytes above)
+        }
+
+        if (DropThisPayload && HasAdaption)
+        {
+            // Drop payload data, but keep adaption field data
+            TsExtendAdaptionField(Packet, TS_SIZE-4);
+            DropThisPayload = false;
+        }
+
+        if (DropThisPayload)
+        {
+            return true; // Drop packet
+        }
+    }
+
+    // Fix Continuity Counter and reproduce incoming offsets:
+    int NewContinuityOutput = TsHasPayload(Packet) ? (LastContinuityOutput + 1) & TS_CONT_CNT_MASK : LastContinuityOutput;
+    NewContinuityOutput = (NewContinuityOutput + ContinuityOffset) & TS_CONT_CNT_MASK;
+    TsSetContinuityCounter(Packet, NewContinuityOutput);
+    LastContinuityOutput = NewContinuityOutput;
+    ContinuityOffset = 0;
+
+    return false; // Keep packet
+}
+
+// --- cNaluStreamProcessor ---------------------------------------------------------
+
+cNaluStreamProcessor::cNaluStreamProcessor()
+{
+    pPatPmtParser = NULL;
+    vpid = -1;
+    data = NULL;
+    length = 0;
+    tempLength = 0;
+    tempLengthAtEnd = false;
+    TotalPackets = 0;
+    DroppedPackets = 0;
+}
+
+void cNaluStreamProcessor::PutBuffer(uchar *Data, int Length)
+{
+    if (length > 0)
+        esyslog("cNaluStreamProcessor::PutBuffer: New data before old data was processed!");
+
+    data = Data;
+    length = Length;
+}
+
+uchar* cNaluStreamProcessor::GetBuffer(int &OutLength)
+{
+    if (length <= 0)
+    {
+        // Need more data - quick exit
+        OutLength = 0;
+        return NULL;
+    }
+    if (tempLength > 0) // Data in temp buffer?
+    {
+        if (tempLengthAtEnd) // Data is at end, copy to beginning
+        {
+            // Overlapping src and dst!
+            for (int i=0; i<tempLength; i++)
+                tempBuffer[i] = tempBuffer[TS_SIZE-tempLength+i];
+        }
+        // Normalize TempBuffer fill
+        if (tempLength < TS_SIZE && length > 0)
+        {
+            int Size = min(TS_SIZE-tempLength, length);
+            memcpy(tempBuffer+tempLength, data, Size);
+            data += Size;
+            length -= Size;
+            tempLength += Size;
+        }
+        if (tempLength < TS_SIZE)
+        {
+            // All incoming data buffered, but need more data
+            tempLengthAtEnd = false;
+            OutLength = 0;
+            return NULL;
+        }
+        // Now: TempLength==TS_SIZE
+        if (tempBuffer[0] != TS_SYNC_BYTE)
+        {
+            // Need to sync on TS within temp buffer
+            int Skipped = 1;
+            while (Skipped < TS_SIZE && (tempBuffer[Skipped] != TS_SYNC_BYTE || (Skipped < length && data[Skipped] != TS_SYNC_BYTE)))
+                Skipped++;
+            esyslog("ERROR: skipped %d bytes to sync on start of TS packet", Skipped);
+            // Pass through skipped bytes
+            tempLengthAtEnd = true;
+            tempLength = TS_SIZE - Skipped; // may be 0, thats ok
+            OutLength = Skipped;
+            return tempBuffer;
+        }
+        // Now: TempBuffer is a TS packet
+        int Pid = TsPid(tempBuffer);
+        if (pPatPmtParser)
+        {
+            if (Pid == 0)
+                pPatPmtParser->ParsePat(tempBuffer, TS_SIZE);
+            else if (pPatPmtParser->IsPmtPid(Pid))
+                pPatPmtParser->ParsePmt(tempBuffer, TS_SIZE);
+        }
+
+        TotalPackets++;
+        bool Drop = false;
+        if (Pid == vpid || (pPatPmtParser && Pid == pPatPmtParser->Vpid() && pPatPmtParser->Vtype() == 0x1B))
+            Drop = NaluDumper.ProcessTSPacket(tempBuffer);
+        if (!Drop)
+        {
+            // Keep this packet, then continue with new data
+            tempLength = 0;
+            OutLength = TS_SIZE;
+            return tempBuffer;
+        }
+        // Drop TempBuffer
+        DroppedPackets++;
+        tempLength = 0;
+    }
+    // Now: TempLength==0, just process data/length
+
+    // Pointer to processed data / length:
+    uchar *Out = data;
+    uchar *OutEnd = Out;
+
+    while (length >= TS_SIZE)
+    {
+        if (data[0] != TS_SYNC_BYTE) {
+            int Skipped = 1;
+            while (Skipped < length && (data[Skipped] != TS_SYNC_BYTE || (length - Skipped > TS_SIZE && data[Skipped + TS_SIZE] != TS_SYNC_BYTE)))
+                Skipped++;
+            esyslog("ERROR: skipped %d bytes to sync on start of TS packet", Skipped);
+
+            // Pass through skipped bytes
+            if (OutEnd != data)
+                memcpy(OutEnd, data, Skipped);
+            OutEnd += Skipped;
+            continue;
+        }
+        // Now: Data starts with complete TS packet
+
+        int Pid = TsPid(data);
+        if (pPatPmtParser)
+        {
+            if (Pid == 0)
+                pPatPmtParser->ParsePat(data, TS_SIZE);
+            else if (pPatPmtParser->IsPmtPid(Pid))
+                pPatPmtParser->ParsePmt(data, TS_SIZE);
+        }
+
+        TotalPackets++;
+        bool Drop = false;
+        if (Pid == vpid || (pPatPmtParser && Pid == pPatPmtParser->Vpid() && pPatPmtParser->Vtype() == 0x1B))
+            Drop = NaluDumper.ProcessTSPacket(data);
+        if (!Drop)
+        {
+            if (OutEnd != data)
+                memcpy(OutEnd, data, TS_SIZE);
+            OutEnd += TS_SIZE;
+        }
+        else
+        {
+            DroppedPackets++;
+        }
+        data += TS_SIZE;
+        length -= TS_SIZE;
+    }
+    // Now: Less than a packet remains.
+    if (length > 0)
+    {
+        // copy remains into temp buffer
+        memcpy(tempBuffer, data, length);
+        tempLength = length;
+        tempLengthAtEnd = false;
+        length = 0;
+    }
+    OutLength = (OutEnd - Out);
+    return OutLength > 0 ? Out : NULL;
+}
diff -Naur vdr-2.1.6/remux.h vdr-2.1.6-naludump-0.1/remux.h
--- vdr-2.1.6/remux.h	2014-02-08 13:41:50.000000000 +0100
+++ vdr-2.1.6-naludump-0.1/remux.h	2014-03-30 17:47:25.000000000 +0200
@@ -62,6 +62,11 @@
   return p[3] & TS_PAYLOAD_EXISTS;
 }
 
+inline bool TsSetPayload(const uchar *p)
+{
+  return p[3] & TS_PAYLOAD_EXISTS;
+}
+
 inline bool TsHasAdaptationField(const uchar *p)
 {
   return p[3] & TS_ADAPT_FIELD_EXISTS;
@@ -143,6 +148,7 @@
 int64_t TsGetDts(const uchar *p, int l);
 void TsSetPts(uchar *p, int l, int64_t Pts);
 void TsSetDts(uchar *p, int l, int64_t Dts);
+void TsExtendAdaptionField(unsigned char *Packet, int ToLength);
 
 // Some PES handling tools:
 // The following functions that take a pointer to PES data all assume that
@@ -518,4 +524,78 @@
       ///< available.
   };
 
+
+#define PATCH_NALUDUMP 100
+
+class cNaluDumper {
+    unsigned int History;
+
+    int LastContinuityInput;
+    int LastContinuityOutput;
+    int ContinuityOffset;
+
+    bool DropAllPayload;
+
+    int PesId;
+    int PesOffset;
+
+    int NaluOffset;
+
+    enum eNaluFillState {
+        NALU_NONE=0,    // currently not NALU fill stream
+        NALU_FILL,      // Within NALU fill stream, 0xff bytes and NALU start code in byte 0
+        NALU_TERM,      // Within NALU fill stream, read 0x80 terminating byte
+        NALU_END        // Beyond end of NALU fill stream, expecting 0x00 0x00 0x01 now
+        };
+
+    eNaluFillState NaluFillState;
+
+    struct sPayloadInfo {
+        int DropPayloadStartBytes;
+        int DropPayloadEndBytes;
+        bool DropAllPayloadBytes;
+    };
+
+public:
+    cNaluDumper();
+
+    void reset();
+
+    // Single packet interface:
+    bool ProcessTSPacket(unsigned char *Packet);
+
+private:
+    void ProcessPayload(unsigned char *Payload, int size, bool PayloadStart, sPayloadInfo &Info);
+};
+
+class cNaluStreamProcessor {
+    //Buffer stream interface:
+    int vpid;
+    uchar *data;
+    int length;
+    uchar tempBuffer[TS_SIZE];
+    int tempLength;
+    bool tempLengthAtEnd;
+    cPatPmtParser *pPatPmtParser;
+    cNaluDumper NaluDumper;
+
+    long long int TotalPackets;
+    long long int DroppedPackets;
+public:
+    cNaluStreamProcessor();
+
+    void SetPid(int VPid) { vpid = VPid; }
+    void SetPatPmtParser(cPatPmtParser *_pPatPmtParser) { pPatPmtParser = _pPatPmtParser; }
+    // Set either a PID or set a pointer to an PatPmtParser that will detect _one_ PID
+
+    void PutBuffer(uchar *Data, int Length);
+    // Add new data to be processed. Data must be valid until Get() returns NULL.
+    uchar* GetBuffer(int &OutLength);
+    // Returns filtered data, or NULL/0 to indicate that all data from Put() was processed
+    // or buffered.
+
+    long long int GetTotalPackets() { return TotalPackets; }
+    long long int GetDroppedPackets() { return DroppedPackets; }
+};
+
 #endif // __REMUX_H