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