diff -Nru vdr-1.5.10/dvbplayer.c vdr-1.5.10-dvbplayer/dvbplayer.c --- vdr-1.5.10/dvbplayer.c 2007-10-19 14:31:50.000000000 +0200 +++ vdr-1.5.10-dvbplayer/dvbplayer.c 2007-10-19 22:02:17.000000000 +0200 @@ -390,6 +390,202 @@ Cancel(9); } +// --- BEGIN fix for I frames ------------------------------------------- +// +// Prior to the introduction of cVideoRepacker, VDR didn't start a new +// PES packet when a new frame started. So, it was likely that the tail +// of an I frame was at the beginning of the packet which started the +// following B frame. Due to the organisation of VDR's index file, VDR +// typically didn't read the tail of the I frame and therefore caused +// softdevice plugins to not render such a frame as it was incomplete, +// e. g. when moving cutting marks. +// +// The following code tries to fix incomplete I frames for recordings +// made prior to the introdcution of cVideoRepacker, to be able to +// edit cutting marks for example with softdevice plugins like vdr-xine. +// + +#if VDRVERSNUM < 10331 + +enum ePesHeader { + phNeedMoreData = -1, + phInvalid = 0, + phMPEG1 = 1, + phMPEG2 = 2 + }; + +static ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader = NULL) +{ + if (Count < 7) + return phNeedMoreData; // too short + + if ((Data[6] & 0xC0) == 0x80) { // MPEG 2 + if (Count < 9) + return phNeedMoreData; // too short + + PesPayloadOffset = 6 + 3 + Data[8]; + if (Count < PesPayloadOffset) + return phNeedMoreData; // too short + + if (ContinuationHeader) + *ContinuationHeader = ((Data[6] == 0x80) && !Data[7] && !Data[8]); + + return phMPEG2; // MPEG 2 + } + + // check for MPEG 1 ... + PesPayloadOffset = 6; + + // skip up to 16 stuffing bytes + for (int i = 0; i < 16; i++) { + if (Data[PesPayloadOffset] != 0xFF) + break; + + if (Count <= ++PesPayloadOffset) + return phNeedMoreData; // too short + } + + // skip STD_buffer_scale/size + if ((Data[PesPayloadOffset] & 0xC0) == 0x40) { + PesPayloadOffset += 2; + + if (Count <= PesPayloadOffset) + return phNeedMoreData; // too short + } + + if (ContinuationHeader) + *ContinuationHeader = false; + + if ((Data[PesPayloadOffset] & 0xF0) == 0x20) { + // skip PTS only + PesPayloadOffset += 5; + } + else if ((Data[PesPayloadOffset] & 0xF0) == 0x30) { + // skip PTS and DTS + PesPayloadOffset += 10; + } + else if (Data[PesPayloadOffset] == 0x0F) { + // continuation header + PesPayloadOffset++; + + if (ContinuationHeader) + *ContinuationHeader = true; + } + else + return phInvalid; // unknown + + if (Count < PesPayloadOffset) + return phNeedMoreData; // too short + + return phMPEG1; // MPEG 1 +} + +#endif + +static uchar *findStartCode(uchar *Data, int Length, int &PesPayloadOffset) +{ + uchar *limit = Data + Length; + if (AnalyzePesHeader(Data, Length, PesPayloadOffset) <= phInvalid) + return 0; // neither MPEG1 nor MPEG2 + + Data += PesPayloadOffset + 3; // move to video payload and skip 00 00 01 + while (Data < limit) { + // possible start codes that appear before/after picture data + // 00 00 01 B3: sequence header code + // 00 00 01 B8: group start code + // 00 00 01 00: picture start code + // 00 00 01 B7: sequence end code + if (0x01 == Data[-1] && (0xB3 == Data[0] || 0xB8 == Data[0] || 0x00 == Data[0] || 0xB7 == Data[0]) && 0x00 == Data[-2] && 0x00 == Data[-3]) + return Data - 3; + Data++; + } + + return 0; +} + +static void fixIFrameHead(uchar *Data, int Length) +{ + int pesPayloadOffset = 0; + uchar *p = findStartCode(Data, Length, pesPayloadOffset); + if (!p) { + esyslog("fixIframeHead: start code not found!\n"); + return; + } + + Data += pesPayloadOffset; // move to video payload + if (Data < p) + memset(Data, 0, p - Data); // zero preceeding bytes +} + +static int fixIFrameTail(uchar *Data, int Length) +{ + int pesPayloadOffset = 0; + uchar *p = findStartCode(Data, Length, pesPayloadOffset); + if (!p) { + esyslog("fixIframeTail: start code not found!\n"); + return Length; + } + + // is this PES packet required? + uchar *videoPayload = Data + pesPayloadOffset; + if (videoPayload >= p) + return 0; // no + + // adjust PES length + int lenPES = (p - Data); + Data[4] = (lenPES - 6) >> 8; + Data[5] = (lenPES - 6) & 0xFF; + + return lenPES; +} + +#define IPACKS 2048 // originally defined in remux.c + +static void fixIFrame(uchar *Data, int &Length, const int OriginalLength) +{ + int done = 0; + + while (done < Length) { + if (0x00 != Data[0] || 0x00 != Data[1] || 0x01 != Data[2]) { + esyslog("fixIFrame: PES start code not found at offset %d (data length: %d, original length: %d)!", done, Length, OriginalLength); + if (Length > OriginalLength) // roll back additional data + Length = OriginalLength; + return; + } + + int lenPES = 6 + Data[4] * 256 + Data[5]; + if (0xBA == Data[3]) { // pack header has fixed length + if (0x00 == (0xC0 & Data[4])) + lenPES = 12; // MPEG1 + else + lenPES = 14 + (Data[13] & 0x07); // MPEG2 + } + else if (0xB9 == Data[3]) // stream end has fixed length + lenPES = 4; + else if (0xE0 == (0xF0 & Data[3])) { // video packet + int todo = Length - done; + int bite = (lenPES < todo) ? lenPES : todo; + if (0 == done) // first packet + fixIFrameHead(Data, bite); + else if (done >= OriginalLength) { // last packet + Length = done + fixIFrameTail(Data, bite); + return; + } + } + else if (0 == done && 0xC0 == (0xE0 & Data[3])) { + // if the first I frame packet is an audio packet then this is a radio recording: don't touch it! + if (Length > OriginalLength) // roll back additional data + Length = OriginalLength; + return; + } + + done += lenPES; + Data += lenPES; + } +} + +// --- END fix for I frames --------------------------------------------- + void cDvbPlayer::Action(void) { uchar *b = NULL; @@ -439,6 +635,7 @@ readIndex = Index; continue; } + Length += IPACKS; // fixIFrame needs next video packet } else { if (!TimeShiftMode && playDir == pdForward) { @@ -483,6 +680,8 @@ } int r = nonBlockingFileReader->Read(replayFile, b, Length); if (r > 0) { + if (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) + fixIFrame(b, r, Length - IPACKS); WaitingForData = false; readFrame = new cFrame(b, -r, ftUnknown, readIndex); // hands over b to the ringBuffer b = NULL; @@ -722,9 +921,11 @@ int FileOffset, Length; Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset, &Length); if (Index >= 0 && NextFile(FileNumber, FileOffset) && Still) { + Length += IPACKS; // fixIFrame needs next video packet uchar b[MAXFRAMESIZE + 4 + 5 + 4]; int r = ReadFrame(replayFile, b, Length, sizeof(b)); if (r > 0) { + fixIFrame(b, r, Length - IPACKS); if (playMode == pmPause) DevicePlay(); // append sequence end code to get the image shown immediately with softdevices