From abde1d04aeb7beb29c2638389c29ff92ff87a75a Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sun, 20 Jan 2013 12:19:42 +0100 Subject: [PATCH] Improved cutting MPEG-2 video --- CONTRIBUTORS | 1 + HISTORY | 1 + cutter.c | 475 ++++++++++++++++++++++++++++----------------------- remux.h | 4 +- 4 files changed, 265 insertions(+), 216 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 82f5cd0d..39bc0d38 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -3024,6 +3024,7 @@ S for dropped B-frames for simplifying calculating the PTS offset in cPtsFixer::Fix() and fixing the overflow handling of PCR values + for improving cutting MPEG-2 video Peter Münster for fixing 'make install' to not overwrite existing configuration files diff --git a/HISTORY b/HISTORY index 4c337d90..a36f1af5 100644 --- a/HISTORY +++ b/HISTORY @@ -7530,3 +7530,4 @@ Video Disk Recorder Revision History - The SVDRP command NEWT no longer checks whether a timer with the given data already exists (suggested by Malte Forkel). - Implemented scaling of SPU bitmaps (thanks to Johann Friedrichs). +- Improved cutting MPEG-2 video (thanks to Sören Moch). diff --git a/cutter.c b/cutter.c index 300eef43..8a6efc54 100644 --- a/cutter.c +++ b/cutter.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: cutter.c 2.21 2012/12/02 14:30:55 kls Exp $ + * $Id: cutter.c 2.22 2013/01/20 12:04:07 kls Exp $ */ #include "cutter.h" @@ -104,167 +104,121 @@ void cPacketStorage::Flush(int Pid, uchar *Data, int &Length, int MaxLength) buffers[Pid]->Flush(Data, Length, MaxLength); } -// --- cDanglingPacketStripper ----------------------------------------------- +// --- cMpeg2Fixer -------------------------------------------------------- -class cDanglingPacketStripper { +class cMpeg2Fixer : private cTsPayload { private: - bool processed[MAXPID]; - cPatPmtParser patPmtParser; + bool FindHeader(uint32_t Code, const char *Header); public: - cDanglingPacketStripper(void); - bool Process(uchar *Data, int Length, int64_t FirstPts); - ///< Scans the frame given in Data and hides the payloads of any TS packets - ///< that either didn't start within this frame, or have a PTS that is - ///< before FirstPts. The TS packets in question are not physically removed - ///< from Data in order to keep any frame counts and PCR timestamps intact. - ///< Returns true if any dangling packets have been found. + cMpeg2Fixer(uchar *Data, int Length, int Vpid); + void SetBrokenLink(void); + void SetClosedGop(void); + int GetTref(void); + void AdjGopTime(int Offset, int FramesPerSecond); + void AdjTref(int TrefOffset); }; -cDanglingPacketStripper::cDanglingPacketStripper(void) +cMpeg2Fixer::cMpeg2Fixer(uchar *Data, int Length, int Vpid) { - memset(processed, 0x00, sizeof(processed)); + // Go to first video packet: + for (; Length > 0; Data += TS_SIZE, Length -= TS_SIZE) { + if (TsPid(Data) == Vpid) { + Setup(Data, Length, Vpid); + break; + } + } } -bool cDanglingPacketStripper::Process(uchar *Data, int Length, int64_t FirstPts) +bool cMpeg2Fixer::FindHeader(uint32_t Code, const char *Header) { - bool Found = false; - while (Length >= TS_SIZE && *Data == TS_SYNC_BYTE) { - int Pid = TsPid(Data); - if (Pid == PATPID) - patPmtParser.ParsePat(Data, TS_SIZE); - else if (patPmtParser.IsPmtPid(Pid)) - patPmtParser.ParsePmt(Data, TS_SIZE); - else { - int64_t Pts = TsGetPts(Data, TS_SIZE); - if (Pts >= 0) - processed[Pid] = PtsDiff(FirstPts, Pts) >= 0; // Pts is at or after FirstPts - if (!processed[Pid]) { - TsHidePayload(Data); - Found = true; - } - } - Length -= TS_SIZE; - Data += TS_SIZE; - } - return Found; + Reset(); + if (Find(Code)) + return true; + esyslog("ERROR: %s header not found!", Header); + return false; } -// --- cPtsFixer ------------------------------------------------------------- - -class cPtsFixer { -private: - int delta; // time between two frames - int64_t deltaDts; // the difference between two consecutive DTS values (may differ from 'delta' in case of multiple fields per frame) - int64_t lastPts; // the video PTS of the last frame (in display order) - int64_t lastDts; // the last video DTS value seen - int64_t offset; // offset to add to all timestamps - bool fixCounters; // controls fixing the TS continuity counters (only from the second CutIn up) - uchar counter[MAXPID]; // the TS continuity counter for each PID - cPatPmtParser patPmtParser; -public: - cPtsFixer(void); - void Setup(double FramesPerSecond); - void Fix(uchar *Data, int Length, bool CutIn); - }; - -cPtsFixer::cPtsFixer(void) +void cMpeg2Fixer::SetBrokenLink(void) { - delta = 0; - deltaDts = 0; - lastPts = -1; - lastDts = -1; - offset = -1; - fixCounters = false; - memset(counter, 0x00, sizeof(counter)); -} - -void cPtsFixer::Setup(double FramesPerSecond) -{ - delta = int(round(PTSTICKS / FramesPerSecond)); -} - -void cPtsFixer::Fix(uchar *Data, int Length, bool CutIn) -{ - if (!patPmtParser.Vpid()) { - if (!patPmtParser.ParsePatPmt(Data, Length)) - return; + if (!FindHeader(0x000001B8, "GOP")) + return; + SkipBytes(3); + uchar b = GetByte(); + if (!(b & 0x40)) { // GOP not closed + b |= 0x20; + SetByte(b, GetLastIndex()); } - // Determine the PTS offset at the beginning of each sequence (except the first one): - if (CutIn && lastPts >= 0) { - int64_t Pts = TsGetPts(Data, Length); - if (Pts >= 0) - offset = (lastPts + delta - Pts) & MAX33BIT; // offset is calculated so that Pts + offset results in lastPts + delta - fixCounters = true; +} + +void cMpeg2Fixer::SetClosedGop(void) +{ + if (!FindHeader(0x000001B8, "GOP")) + return; + SkipBytes(3); + uchar b = GetByte(); + b |= 0x40; + SetByte(b, GetLastIndex()); +} + +int cMpeg2Fixer::GetTref(void) +{ + if (!FindHeader(0x00000100, "picture")) + return 0; + int Tref = GetByte() << 2; + Tref |= GetByte() >> 6; + return Tref; +} + +void cMpeg2Fixer::AdjGopTime(int Offset, int FramesPerSecond) +{ + if (!FindHeader(0x000001B8, "GOP")) + return; + uchar Byte1 = GetByte(); + int Index1 = GetLastIndex(); + uchar Byte2 = GetByte(); + int Index2 = GetLastIndex(); + uchar Byte3 = GetByte(); + int Index3 = GetLastIndex(); + uchar Byte4 = GetByte(); + int Index4 = GetLastIndex(); + uchar Frame = ((Byte3 & 0x1F) << 1) | (Byte4 >> 7); + uchar Sec = ((Byte2 & 0x07) << 3) | (Byte3 >> 5); + uchar Min = ((Byte1 & 0x03) << 4) | (Byte2 >> 4); + uchar Hour = ((Byte1 & 0x7C) >> 2); + int GopTime = ((Hour * 60 + Min) * 60 + Sec) * FramesPerSecond + Frame; + if (GopTime) { // do not fix when zero + GopTime += Offset; + if (GopTime < 0) + GopTime += 24 * 60 * 60 * FramesPerSecond; + Frame = GopTime % FramesPerSecond; + GopTime = GopTime / FramesPerSecond; + Sec = GopTime % 60; + GopTime = GopTime / 60; + Min = GopTime % 60; + GopTime = GopTime / 60; + Hour = GopTime % 24; + SetByte((Byte1 & 0x80) | (Hour << 2) | (Min >> 4), Index1); + SetByte(((Min & 0x0F) << 4) | 0x08 | (Sec >> 3), Index2); + SetByte(((Sec & 0x07) << 3) | (Frame >> 1), Index3); + SetByte((Byte4 & 0x7F) | ((Frame & 0x01) << 7), Index4); } - // Keep track of the highest video PTS: - bool GotPts = false; - int64_t PrevDts = lastDts; - uchar *p = Data; - int len = Length; - while (len >= TS_SIZE && *p == TS_SYNC_BYTE) { - int Pid = TsPid(p); - if (Pid == patPmtParser.Vpid()) { - if (!GotPts) { // in case of multiple fields per frame, the offset is calculated only with the first one - int64_t Pts = TsGetPts(p, TS_SIZE); - if (Pts >= 0) { - if (offset >= 0) - Pts = PtsAdd(Pts, offset); // offset is taken into account here, to make lastPts have the "new" value already! - if (lastPts < 0 || PtsDiff(lastPts, Pts) > 0) - lastPts = Pts; - } - GotPts = true; - } - if (!CutIn) { - int64_t Dts = TsGetDts(p, TS_SIZE); - if (Dts >= 0) { - if (offset >= 0) - Dts = PtsAdd(Dts, offset); // offset is taken into account here, to make lastDts have the "new" value already! - deltaDts = PtsDiff(PrevDts, Dts); - PrevDts = Dts; - } - } - } - // Adjust the TS continuity counter: - if (fixCounters) { - if (TsHasPayload(p)) - counter[Pid] = (counter[Pid] + 1) & TS_CONT_CNT_MASK; - TsSetContinuityCounter(p, counter[Pid]); - } - else - counter[Pid] = TsGetContinuityCounter(p); // collect initial counters - p += TS_SIZE; - len -= TS_SIZE; - } - // Apply the PTS offset: - if (offset > 0) { - uchar *p = Data; - int len = Length; - while (len >= TS_SIZE && *p == TS_SYNC_BYTE) { - // Adjust the various timestamps: - int64_t Pts = TsGetPts(p, TS_SIZE); - if (Pts >= 0) - TsSetPts(p, TS_SIZE, PtsAdd(Pts, offset)); - int64_t Dts = TsGetDts(p, TS_SIZE); - if (Dts >= 0) { - if (CutIn) { - lastDts = PtsAdd(lastDts, deltaDts); - TsSetDts(p, TS_SIZE, lastDts); - } - else - TsSetDts(p, TS_SIZE, PtsAdd(Dts, offset)); - } - int64_t Pcr = TsGetPcr(p); - if (Pcr >= 0) { - int64_t NewPcr = Pcr + offset * PCRFACTOR; - if (NewPcr > MAX27MHZ) - NewPcr -= MAX27MHZ + 1; - TsSetPcr(p, NewPcr); - } - p += TS_SIZE; - len -= TS_SIZE; - } +} + +void cMpeg2Fixer::AdjTref(int TrefOffset) +{ + Reset(); + if (!Find(0x00000100)) { + esyslog("ERROR: Picture header not found!"); + return; } - lastDts = PrevDts; + int Tref = GetByte() << 2; + int Index1 = GetLastIndex(); + uchar Byte2 = GetByte(); + int Index2 = GetLastIndex(); + Tref |= Byte2 >> 6; + Tref -= TrefOffset; + SetByte(Tref >> 2, Index1); + SetByte((Tref << 6) | (Byte2 & 0x3F), Index2); } // --- cCuttingThread -------------------------------------------------------- @@ -281,16 +235,27 @@ private: int numSequences; off_t maxVideoFileSize; off_t fileSize; - cPtsFixer ptsFixer; bool suspensionLogged; + int sequence; // cutting sequence + int delta; // time between two frames (PTS ticks) + int64_t lastVidPts; // the video PTS of the last frame (in display order) + bool multiFramePkt; // multiple frames within one PES packet + int64_t firstPts; // first valid PTS, for dangling packet stripping + int64_t offset; // offset to add to all timestamps + int tRefOffset; // number of stripped frames in GOP + uchar counter[MAXPID]; // the TS continuity counter for each PID + bool keepPkt[MAXPID]; // flag for each PID to keep packets, for dangling packet stripping + int numIFrames; // number of I-frames without pending packets + cPatPmtParser patPmtParser; bool Throttled(void); bool SwitchFile(bool Force = false); bool LoadFrame(int Index, uchar *Buffer, bool &Independent, int &Length); bool FramesAreEqual(int Index1, int Index2); - void GetPendingPackets(uchar *Buffer, int &Length, int Index, int64_t LastPts); + void GetPendingPackets(uchar *Buffer, int &Length, int Index); // Gather all non-video TS packets from Index upward that either belong to - // payloads that started before Index, or have a PTS that is before LastPts, + // payloads that started before Index, or have a PTS that is before lastVidPts, // and add them to the end of the given Data. + bool FixFrame(uchar *Data, int &Length, bool Independent, int Index, bool CutIn, bool CutOut); bool ProcessSequence(int LastEndIndex, int BeginIndex, int EndIndex, int NextBeginIndex); protected: virtual void Action(void); @@ -312,7 +277,15 @@ cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName) framesPerSecond = Recording.FramesPerSecond(); suspensionLogged = false; fileSize = 0; - ptsFixer.Setup(framesPerSecond); + sequence = 0; + delta = int(round(PTSTICKS / framesPerSecond)); + lastVidPts = -1; + multiFramePkt = false; + firstPts = -1; + offset = 0; + tRefOffset = 0; + memset(counter, 0x00, sizeof(counter)); + numIFrames = 0; if (fromMarks.Load(FromFileName, framesPerSecond, isPesRecording) && fromMarks.Count()) { numSequences = fromMarks.GetNumSequences(); if (numSequences > 0) { @@ -414,104 +387,176 @@ bool cCuttingThread::FramesAreEqual(int Index1, int Index2) return false; } -void cCuttingThread::GetPendingPackets(uchar *Data, int &Length, int Index, int64_t LastPts) +void cCuttingThread::GetPendingPackets(uchar *Data, int &Length, int Index) { bool Processed[MAXPID] = { false }; - int NumIndependentFrames = 0; - cPatPmtParser PatPmtParser; cPacketStorage PacketStorage; - for (; NumIndependentFrames < 2; Index++) { + int64_t LastPts = lastVidPts + delta;// adding one frame length to fully cover the very last frame + Processed[patPmtParser.Vpid()] = true; // we only want non-video packets + for (int NumIndependentFrames = 0; NumIndependentFrames < 2; Index++) { uchar Buffer[MAXFRAMESIZE]; bool Independent; int len; if (LoadFrame(Index, Buffer, Independent, len)) { if (Independent) NumIndependentFrames++; - uchar *p = Buffer; - while (len >= TS_SIZE && *p == TS_SYNC_BYTE) { - int Pid = TsPid(p); - if (Pid == PATPID) - PatPmtParser.ParsePat(p, TS_SIZE); - else if (PatPmtParser.IsPmtPid(Pid)) { - PatPmtParser.ParsePmt(p, TS_SIZE); - Processed[PatPmtParser.Vpid()] = true; // we only want non-video packets - } - else if (!Processed[Pid]) { - int64_t Pts = TsGetPts(p, TS_SIZE); - if (Pts >= 0) { - int64_t d = PtsDiff(LastPts, Pts); - if (d <= 0) // Pts is before or at LastPts - PacketStorage.Flush(Pid, Data, Length, MAXFRAMESIZE); - if (d >= 0) { // Pts is at or after LastPts - NumIndependentFrames = 0; // we search until we find two consecutive I-frames without any more pending packets - Processed[Pid] = true; - } - } - if (!Processed[Pid]) - PacketStorage.Append(Pid, p, TS_SIZE); - } - len -= TS_SIZE; - p += TS_SIZE; - } + for (uchar *p = Buffer; len >= TS_SIZE && *p == TS_SYNC_BYTE; len -= TS_SIZE, p += TS_SIZE) { + int Pid = TsPid(p); + if (Pid != PATPID && !patPmtParser.IsPmtPid(Pid) && !Processed[Pid]) { + int64_t Pts = TsGetPts(p, TS_SIZE); + if (Pts >= 0) { + int64_t d = PtsDiff(LastPts, Pts); + if (d < 0) // Pts is before LastPts + PacketStorage.Flush(Pid, Data, Length, MAXFRAMESIZE); + else { // Pts is at or after LastPts + NumIndependentFrames = 0; // we search until we find two consecutive I-frames without any more pending packets + Processed[Pid] = true; + } + } + if (!Processed[Pid]) + PacketStorage.Append(Pid, p, TS_SIZE); + } + } } else break; } } +bool cCuttingThread::FixFrame(uchar *Data, int &Length, bool Independent, int Index, bool CutIn, bool CutOut) +{ + if (!patPmtParser.Vpid()) { + if (!patPmtParser.ParsePatPmt(Data, Length)) + return false; + } + if (CutIn) { + sequence++; + memset(keepPkt, false, sizeof(keepPkt)); + numIFrames = 0; + firstPts = TsGetPts(Data, Length); + // Determine the PTS offset at the beginning of each sequence (except the first one): + if (sequence != 1) { + if (firstPts >= 0) + offset = (lastVidPts + delta - firstPts) & MAX33BIT; + } + } + if (CutOut) + GetPendingPackets(Data, Length, Index + 1); + if (Independent) { + numIFrames++; + tRefOffset = 0; + } + // Fix MPEG-2: + if (patPmtParser.Vtype() == 2) { + cMpeg2Fixer Mpeg2fixer(Data, Length, patPmtParser.Vpid()); + if (CutIn) { + if (sequence == 1 || multiFramePkt) + Mpeg2fixer.SetBrokenLink(); + else { + Mpeg2fixer.SetClosedGop(); + tRefOffset = Mpeg2fixer.GetTref(); + } + } + if (tRefOffset) + Mpeg2fixer.AdjTref(tRefOffset); + if (sequence > 1 && Independent) + Mpeg2fixer.AdjGopTime((offset - MAX33BIT) / delta, round(framesPerSecond)); + } + bool DeletedFrame = false; + bool GotVidPts = false; + bool StripVideo = sequence > 1 && tRefOffset; + uchar *p; + int len; + for (p = Data, len = Length; len >= TS_SIZE && *p == TS_SYNC_BYTE; p += TS_SIZE, len -= TS_SIZE) { + int Pid = TsPid(p); + // Strip dangling packets: + if (numIFrames < 2 && Pid != PATPID && !patPmtParser.IsPmtPid(Pid)) { + if (Pid != patPmtParser.Vpid() || StripVideo) { + int64_t Pts = TsGetPts(p, TS_SIZE); + if (Pts >= 0) + keepPkt[Pid] = PtsDiff(firstPts, Pts) >= 0; // Pts is at or after FirstPts + if (!keepPkt[Pid]) { + TsHidePayload(p); + numIFrames = 0; // we search until we find two consecutive I-frames without any more dangling packets + if (Pid == patPmtParser.Vpid()) + DeletedFrame = true; + } + } + } + // Adjust the TS continuity counter: + if (sequence > 1) { + if (TsHasPayload(p)) + counter[Pid] = (counter[Pid] + 1) & TS_CONT_CNT_MASK; + TsSetContinuityCounter(p, counter[Pid]); + } + else + counter[Pid] = TsGetContinuityCounter(p); // collect initial counters + // Adjust PTS: + int64_t Pts = TsGetPts(p, TS_SIZE); + if (Pts >= 0) { + if (sequence > 1) { + Pts = PtsAdd(Pts, offset); + TsSetPts(p, TS_SIZE, Pts); + } + // Keep track of the highest video PTS - in case of multiple fields per frame, take the first one + if (!GotVidPts && Pid == patPmtParser.Vpid()) { + GotVidPts = true; + if (lastVidPts < 0 || PtsDiff(lastVidPts, Pts) > 0) + lastVidPts = Pts; + } + } + // Adjust DTS: + if (sequence > 1) { + int64_t Dts = TsGetDts(p, TS_SIZE); + if (Dts >= 0) { + Dts = PtsAdd(Dts, offset); + if (CutIn) + Dts = PtsAdd(Dts, tRefOffset * delta); + TsSetDts(p, TS_SIZE, Dts); + } + int64_t Pcr = TsGetPcr(p); + if (Pcr >= 0) { + Pcr = Pcr + offset * PCRFACTOR; + if (Pcr > MAX27MHZ) + Pcr -= MAX27MHZ + 1; + TsSetPcr(p, Pcr); + } + } + } + if (!DeletedFrame && !GotVidPts) { + // Adjust PTS for multiple frames within a single PES packet: + lastVidPts = (lastVidPts + delta) & MAX33BIT; + multiFramePkt = true; + } + return DeletedFrame; +} + bool cCuttingThread::ProcessSequence(int LastEndIndex, int BeginIndex, int EndIndex, int NextBeginIndex) { // Check for seamless connections: bool SeamlessBegin = LastEndIndex >= 0 && FramesAreEqual(LastEndIndex, BeginIndex); bool SeamlessEnd = NextBeginIndex >= 0 && FramesAreEqual(EndIndex, NextBeginIndex); // Process all frames from BeginIndex (included) to EndIndex (excluded): - cDanglingPacketStripper DanglingPacketStripper; - int NumIndependentFrames = 0; - int64_t FirstPts = -1; - int64_t LastPts = -1; for (int Index = BeginIndex; Running() && Index < EndIndex; Index++) { uchar Buffer[MAXFRAMESIZE]; bool Independent; int Length; if (LoadFrame(Index, Buffer, Independent, Length)) { + bool CutIn = !SeamlessBegin && Index == BeginIndex; + bool CutOut = !SeamlessEnd && Index == EndIndex - 1; + bool DeletedFrame = false; if (!isPesRecording) { - int64_t Pts = TsGetPts(Buffer, Length); - if (FirstPts < 0) - FirstPts = Pts; // the PTS of the first frame in the sequence - else if (LastPts < 0 || PtsDiff(LastPts, Pts) > 0) - LastPts = Pts; // the PTS of the frame that is displayed as the very last one of the sequence - } - // Fixup data at the beginning of the sequence: - if (!SeamlessBegin) { - if (isPesRecording) { - if (Index == BeginIndex) - cRemux::SetBrokenLink(Buffer, Length); - } - else if (NumIndependentFrames < 2) { - if (DanglingPacketStripper.Process(Buffer, Length, FirstPts)) - NumIndependentFrames = 0; // we search until we find two consecutive I-frames without any more dangling packets - } - } - // Fixup data at the end of the sequence: - if (!SeamlessEnd) { - if (Index == EndIndex - 1) { - if (!isPesRecording) - GetPendingPackets(Buffer, Length, EndIndex, LastPts + int(round(PTSTICKS / framesPerSecond))); // adding one frame length to fully cover the very last frame - } - } - // Fixup timestamps and continuity counters: - if (!isPesRecording) { - if (numSequences > 1) - ptsFixer.Fix(Buffer, Length, !SeamlessBegin && Index == BeginIndex); + DeletedFrame = FixFrame(Buffer, Length, Independent, Index, CutIn, CutOut); } + else if (CutIn) + cRemux::SetBrokenLink(Buffer, Length); // Every file shall start with an independent frame: if (Independent) { - NumIndependentFrames++; if (!SwitchFile()) return false; } // Write index: - if (!toIndex->Write(Independent, toFileName->Number(), fileSize)) { + if (!DeletedFrame && !toIndex->Write(Independent, toFileName->Number(), fileSize)) { error = "toIndex"; return false; } diff --git a/remux.h b/remux.h index 19f94797..67eda4c6 100644 --- a/remux.h +++ b/remux.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remux.h 2.36 2012/11/19 10:22:28 kls Exp $ + * $Id: remux.h 2.37 2013/01/20 11:43:59 kls Exp $ */ #ifndef __REMUX_H @@ -217,6 +217,8 @@ private: int length; int pid; int index; // points to the next byte to process +protected: + void Reset(void) { index = 0; } public: cTsPayload(void); cTsPayload(uchar *Data, int Length, int Pid = -1);