summaryrefslogtreecommitdiff
path: root/cutter.c
diff options
context:
space:
mode:
Diffstat (limited to 'cutter.c')
-rw-r--r--cutter.c463
1 files changed, 254 insertions, 209 deletions
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);
- };
+void cMpeg2Fixer::SetBrokenLink(void)
+{
+ if (!FindHeader(0x000001B8, "GOP"))
+ return;
+ SkipBytes(3);
+ uchar b = GetByte();
+ if (!(b & 0x40)) { // GOP not closed
+ b |= 0x20;
+ SetByte(b, GetLastIndex());
+ }
+}
-cPtsFixer::cPtsFixer(void)
+void cMpeg2Fixer::SetClosedGop(void)
{
- delta = 0;
- deltaDts = 0;
- lastPts = -1;
- lastDts = -1;
- offset = -1;
- fixCounters = false;
- memset(counter, 0x00, sizeof(counter));
+ if (!FindHeader(0x000001B8, "GOP"))
+ return;
+ SkipBytes(3);
+ uchar b = GetByte();
+ b |= 0x40;
+ SetByte(b, GetLastIndex());
}
-void cPtsFixer::Setup(double FramesPerSecond)
+int cMpeg2Fixer::GetTref(void)
{
- delta = int(round(PTSTICKS / FramesPerSecond));
+ if (!FindHeader(0x00000100, "picture"))
+ return 0;
+ int Tref = GetByte() << 2;
+ Tref |= GetByte() >> 6;
+ return Tref;
}
-void cPtsFixer::Fix(uchar *Data, int Length, bool CutIn)
+void cMpeg2Fixer::AdjGopTime(int Offset, int FramesPerSecond)
{
- if (!patPmtParser.Vpid()) {
- if (!patPmtParser.ParsePatPmt(Data, Length))
- return;
+ 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);
}
- // 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;
- }
- // 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;
}