diff options
author | Klaus Schmidinger <Klaus (dot) Schmidinger (at) tvdr (dot) de> | 2012-11-18 14:58:00 +0100 |
---|---|---|
committer | Dieter Hametner <dh (plus) vdr (at) gekrumbel (dot) de> | 2012-11-19 00:55:28 +0100 |
commit | beffcabc81cff46d620b1662d03e4da4dbd6b678 (patch) | |
tree | 72dcb1b1e60a8b600d168696fecedf220fe8198d /cutter.c | |
parent | fa6845328cec89c24a6d491d38072746769766a8 (diff) | |
download | vdr-patches-beffcabc81cff46d620b1662d03e4da4dbd6b678.tar.gz vdr-patches-beffcabc81cff46d620b1662d03e4da4dbd6b678.tar.bz2 |
Version 1.7.32
VDR developer version 1.7.32 is now available at
ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.32.tar.bz2
A 'diff' against the previous version is available at
ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.31-1.7.32.diff
MD5 checksums:
068ba78fd427694dcc480fe3b2d07148 vdr-1.7.32.tar.bz2
222f1e9b4d4edaa6fe57286409614cc7 vdr-1.7.31-1.7.32.diff
WARNING:
========
This is a developer version. Even though I use it in my productive
environment. I strongly recommend that you only use it under controlled
conditions and for testing and debugging.
The main focus of this version is on an improved frame detection code,
and improvements to the cutting process. When cutting a recording, VDR
now removes any "dangling" TS packets from the beginning of an editing
sequence and pulls in any "pending" TS packets at the end of a sequence.
It also fixes all timestamps and continuity counters.
However, while the results look much better now in, for instance, Kaffeine,
the TT S2-6400 still shows some video artifacts at the editing points, and
the Mac video player sometimes totally chokes on edited material.
I did spend a lot of time trying to find out what could still be wrong here,
but couldn't come up with any new ideas. So I think it's now time to invite
others to test this new cutting code, read the source code and try to find
out what's still going wrong here. Maybe (hopefully ;-) it's just some stupid
little error... ;-)
From the HISTORY file:
- Pressing the Play key during normal live viewing mode now opens the Recordings menu
if there is no "last viewed" recording (thanks to Alexander Wenzel).
The same behavior has been implemented for the Blue key in the main menu.
- cIoThrottle::Engaged() is now also checked in cRemoveDeletedRecordingsThread::Action(),
to suspend removing deleted recordings in case this is necessary to make room for
new, ongoing recordings (suggested by Udo Richter).
- The cThread constructor now has an additional boolean parameter that can be set to
true to have this thread run at a lower priority. Plugin authors that use low
priority threads may want to use this instead of the calls to SetPriority(19) and
SetIOPriority(7). The priority of a thread ("low" or "high") is now logged when the
thread starts.
- Changed DTV_DVBT2_PLP_ID to DTV_STREAM_ID in dvbdevice.c to adapt to an incompatible
change in DVB API 5.8 (reported by Derek Kelly).
Removed the meanwhile obsolete definition of FE_CAN_TURBO_FEC.
- Fixed some compiler warnings under gcc version 4.7.1.
- Fixed setting the video format in the dvbhdffdevice (thanks to Torsten Lang).
- Fixed 'make install' to not overwrite existing configuration files (thanks to Peter
Münster).
- Added including the Make.global and Make.config files to the dvbdhffdevice's
libhdffcmd/Makefile.
- Added options to build a 32-bit version of VDR on a 64-bit machine to
Make.config.template.
- Fixed handling VPS timers in case the running status of an event goes to '1' (not
running) and later goes to '4' (running).
- If a frame position in the 'marks' file of a recording doesn't point to an I-frame,
it will now be shifted towards the next I-frame, either up or down, whichever is
closer (suggested by Udo Richter).
- Fixed a possible memory leak in SI::StructureLoop::getNextAsPointer() (reported by
Sundararaj Reel).
- Fixed handling timers in case an event is modified and "phased out" while the timer
is recording.
- Improved frame detection by parsing just far enough into the MPEG-4 NAL units to get
the necessary information about frames and slices.
- The initial syncing of the frame detector is now done immediately after the first
complete GOP has been seen. This makes recordings and especially pausing live video
start up to twice as fast as before.
- Updated the Romanian OSD texts (thanks to Lucian Muresan).
- Fixed handling the very last entry in a recording index.
- The return type of cMarks::Add() has been changed to void, since due to the sorting
of the list of marks the returned pointer might have pointed to a totally different
mark. Besides, the return value was never actually used.
- Improved editing TS recordings by
+ stripping dangling TS packets from the beginning of a sequence
+ including pending TS packets at the end of a sequence
+ fixing all timestamps and continuity counters
+ generating editing marks for the edited version in such a way that each cutting
point is marked by an "end" and "begin" mark with the same offset
+ no longer generating an editing mark at the "end" of the edited recording (this
was actually generated at the beginning of the last GOP, so that a subsequent
edit would have cut off the last GOP)
+ no longer generating any editing marks if the edited recording results on just
one single sequence
+ ignoring pairs of editing marks that are placed at exactly the same position of
a recording when actually cutting the recording
+ not doing anything if the editing marks in place would result in the edited
version being the same as the original recording
- Editing marks can now be placed directly on top of each other, in which case they
simply mark a position, but have no effect on the actual cutting process.
- When positioned at an offset where two (or more) editing marks are placed on top
of each other, the '4' key moves the first one of them to the left, while the '6'
key moves the last one of them to the right. The '7' and '9' key handle multiple
marks at the same place as if it were one single mark.
- Modified editing marks are now written to disk whenever the replay progress display
gets hidden (thanks to Christoph Haubrich).
Diffstat (limited to 'cutter.c')
-rw-r--r-- | cutter.c | 654 |
1 files changed, 488 insertions, 166 deletions
@@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: cutter.c 2.14 2012/09/20 09:12:47 kls Exp $ + * $Id: cutter.c 2.16 2012/11/18 12:09:00 kls Exp $ */ #include "cutter.h" @@ -13,17 +13,261 @@ #include "remux.h" #include "videodir.h" +// --- cPacketBuffer --------------------------------------------------------- + +class cPacketBuffer { +private: + uchar *data; + int size; + int length; +public: + cPacketBuffer(void); + ~cPacketBuffer(); + void Append(uchar *Data, int Length); + ///< Appends Length bytes of Data to this packet buffer. + void Flush(uchar *Data, int &Length, int MaxLength); + ///< Flushes the content of this packet buffer into the given Data, starting + ///< at position Length, and clears the buffer afterwards. Length will be + ///< incremented accordingly. If Length plus the total length of the stored + ///< packets would exceed MaxLength, nothing is copied. + }; + +cPacketBuffer::cPacketBuffer(void) +{ + data = NULL; + size = length = 0; +} + +cPacketBuffer::~cPacketBuffer() +{ + free(data); +} + +void cPacketBuffer::Append(uchar *Data, int Length) +{ + if (length + Length >= size) { + int NewSize = (length + Length) * 3 / 2; + if (uchar *p = (uchar *)realloc(data, NewSize)) { + data = p; + size = NewSize; + } + else + return; // out of memory + } + memcpy(data + length, Data, Length); + length += Length; +} + +void cPacketBuffer::Flush(uchar *Data, int &Length, int MaxLength) +{ + if (Data && length > 0 && Length + length <= MaxLength) { + memcpy(Data + Length, data, length); + Length += length; + } + length = 0; +} + +// --- cPacketStorage -------------------------------------------------------- + +class cPacketStorage { +private: + cPacketBuffer *buffers[MAXPID]; +public: + cPacketStorage(void); + ~cPacketStorage(); + void Append(int Pid, uchar *Data, int Length); + void Flush(int Pid, uchar *Data, int &Length, int MaxLength); + }; + +cPacketStorage::cPacketStorage(void) +{ + for (int i = 0; i < MAXPID; i++) + buffers[i] = NULL; +} + +cPacketStorage::~cPacketStorage() +{ + for (int i = 0; i < MAXPID; i++) + delete buffers[i]; +} + +void cPacketStorage::Append(int Pid, uchar *Data, int Length) +{ + if (!buffers[Pid]) + buffers[Pid] = new cPacketBuffer; + buffers[Pid]->Append(Data, Length); +} + +void cPacketStorage::Flush(int Pid, uchar *Data, int &Length, int MaxLength) +{ + if (buffers[Pid]) + buffers[Pid]->Flush(Data, Length, MaxLength); +} + +// --- cDanglingPacketStripper ----------------------------------------------- + +class cDanglingPacketStripper { +private: + bool processed[MAXPID]; + cPatPmtParser patPmtParser; +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. + }; + +cDanglingPacketStripper::cDanglingPacketStripper(void) +{ + memset(processed, 0x00, sizeof(processed)); +} + +bool cDanglingPacketStripper::Process(uchar *Data, int Length, int64_t FirstPts) +{ + 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 (Pid == patPmtParser.PmtPid()) + 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; +} + +// --- cPtsFixer ------------------------------------------------------------- + +class cPtsFixer { +private: + int delta; // time between two frames + int64_t last; // the last (i.e. highest) video PTS value seen + int64_t offset; // offset to add to PTS values + 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) +{ + delta = 0; + last = -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; + } + // Determine the PTS offset at the beginning of each sequence (except the first one): + if (CutIn && last >= 0) { + int64_t Pts = TsGetPts(Data, Length); + if (Pts >= 0) { + // offset is calculated so that Pts + offset results in last + delta: + offset = Pts - PtsAdd(last, delta); + if (offset <= 0) + offset = -offset; + else + offset = MAX33BIT + 1 - offset; + } + fixCounters = true; + } + // Keep track of the highest video PTS: + uchar *p = Data; + int len = Length; + while (len >= TS_SIZE && *p == TS_SYNC_BYTE) { + int Pid = TsPid(p); + if (Pid == patPmtParser.Vpid()) { + int64_t Pts = PtsAdd(TsGetPts(p, TS_SIZE), offset); // offset is taken into account here, to make last have the "new" value already! + if (Pts >= 0 && (last < 0 || PtsDiff(last, Pts) > 0)) + last = Pts; + } + // Adjust the TS continuity counter: + if (fixCounters) { + 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) + 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; + } + } +} + // --- cCuttingThread -------------------------------------------------------- class cCuttingThread : public cThread { private: const char *error; bool isPesRecording; + double framesPerSecond; cUnbufferedFile *fromFile, *toFile; cFileName *fromFileName, *toFileName; cIndexFile *fromIndex, *toIndex; cMarks fromMarks, toMarks; + int numSequences; off_t maxVideoFileSize; + off_t fileSize; + cPtsFixer ptsFixer; + bool suspensionLogged; + 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); + // 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, + // and add them to the end of the given Data. + bool ProcessSequence(int LastEndIndex, int BeginIndex, int EndIndex, int NextBeginIndex); protected: virtual void Action(void); public: @@ -33,7 +277,7 @@ public: }; cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName) -:cThread("video cutting") +:cThread("video cutting", true) { error = NULL; fromFile = toFile = NULL; @@ -41,16 +285,25 @@ cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName) fromIndex = toIndex = NULL; cRecording Recording(FromFileName); isPesRecording = Recording.IsPesRecording(); - if (fromMarks.Load(FromFileName, Recording.FramesPerSecond(), isPesRecording) && fromMarks.Count()) { - fromFileName = new cFileName(FromFileName, false, true, isPesRecording); - toFileName = new cFileName(ToFileName, true, true, isPesRecording); - fromIndex = new cIndexFile(FromFileName, false, isPesRecording); - toIndex = new cIndexFile(ToFileName, true, isPesRecording); - toMarks.Load(ToFileName, Recording.FramesPerSecond(), isPesRecording); // doesn't actually load marks, just sets the file name - maxVideoFileSize = MEGABYTE(Setup.MaxVideoFileSize); - if (isPesRecording && maxVideoFileSize > MEGABYTE(MAXVIDEOFILESIZEPES)) - maxVideoFileSize = MEGABYTE(MAXVIDEOFILESIZEPES); - Start(); + framesPerSecond = Recording.FramesPerSecond(); + suspensionLogged = false; + fileSize = 0; + ptsFixer.Setup(framesPerSecond); + if (fromMarks.Load(FromFileName, framesPerSecond, isPesRecording) && fromMarks.Count()) { + numSequences = fromMarks.GetNumSequences(); + if (numSequences > 0) { + fromFileName = new cFileName(FromFileName, false, true, isPesRecording); + toFileName = new cFileName(ToFileName, true, true, isPesRecording); + fromIndex = new cIndexFile(FromFileName, false, isPesRecording); + toIndex = new cIndexFile(ToFileName, true, isPesRecording); + toMarks.Load(ToFileName, framesPerSecond, isPesRecording); // doesn't actually load marks, just sets the file name + maxVideoFileSize = MEGABYTE(Setup.MaxVideoFileSize); + if (isPesRecording && maxVideoFileSize > MEGABYTE(MAXVIDEOFILESIZEPES)) + maxVideoFileSize = MEGABYTE(MAXVIDEOFILESIZEPES); + Start(); + } + else + esyslog("no editing sequences found for %s", FromFileName); } else esyslog("no editing marks found for %s", FromFileName); @@ -65,171 +318,236 @@ cCuttingThread::~cCuttingThread() delete toIndex; } +bool cCuttingThread::Throttled(void) +{ + if (cIoThrottle::Engaged()) { + if (!suspensionLogged) { + dsyslog("suspending cutter thread"); + suspensionLogged = true; + } + return true; + } + else if (suspensionLogged) { + dsyslog("resuming cutter thread"); + suspensionLogged = false; + } + return false; +} + +bool cCuttingThread::LoadFrame(int Index, uchar *Buffer, bool &Independent, int &Length) +{ + uint16_t FileNumber; + off_t FileOffset; + if (fromIndex->Get(Index, &FileNumber, &FileOffset, &Independent, &Length)) { + fromFile = fromFileName->SetOffset(FileNumber, FileOffset); + if (fromFile) { + fromFile->SetReadAhead(MEGABYTE(20)); + int len = ReadFrame(fromFile, Buffer, Length, MAXFRAMESIZE); + if (len < 0) + error = "ReadFrame"; + else if (len != Length) + Length = len; + return error == NULL; + } + else + error = "fromFile"; + } + return false; +} + +bool cCuttingThread::SwitchFile(bool Force) +{ + if (fileSize > maxVideoFileSize || Force) { + toFile = toFileName->NextFile(); + if (!toFile) { + error = "toFile"; + return false; + } + fileSize = 0; + } + return true; +} + +bool cCuttingThread::FramesAreEqual(int Index1, int Index2) +{ + bool Independent; + uchar Buffer1[MAXFRAMESIZE]; + uchar Buffer2[MAXFRAMESIZE]; + int Length1; + int Length2; + if (LoadFrame(Index1, Buffer1, Independent, Length1) && LoadFrame(Index2, Buffer2, Independent, Length2)) { + if (Length1 == Length2) { + int Diffs = 0; + for (int i = 0; i < Length1; i++) { + if (Buffer1[i] != Buffer2[i]) { + if (Diffs++ > 10) // the continuity counters of the PAT/PMT packets may differ + return false; + } + } + return true; + } + } + return false; +} + +void cCuttingThread::GetPendingPackets(uchar *Data, int &Length, int Index, int64_t LastPts) +{ + bool Processed[MAXPID] = { false }; + int NumIndependentFrames = 0; + cPatPmtParser PatPmtParser; + cPacketStorage PacketStorage; + for (; 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 (Pid == PatPmtParser.PmtPid()) + PatPmtParser.ParsePmt(p, TS_SIZE); + 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; + } + } + else + break; + } +} + +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)) { + 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); + } + // Every file shall start with an independent frame: + if (Independent) { + NumIndependentFrames++; + if (!SwitchFile()) + return false; + } + // Write index: + if (!toIndex->Write(Independent, toFileName->Number(), fileSize)) { + error = "toIndex"; + return false; + } + // Write data: + if (toFile->Write(Buffer, Length) < 0) { + error = "safe_write"; + return false; + } + fileSize += Length; + // Generate marks at the editing points in the edited recording: + if (numSequences > 0 && Index == BeginIndex) { + if (toMarks.Count() > 0) + toMarks.Add(toIndex->Last()); + toMarks.Add(toIndex->Last()); + toMarks.Save(); + } + } + else + return false; + } + return true; +} + void cCuttingThread::Action(void) { - cMark *Mark = fromMarks.First(); - if (Mark) { - SetPriority(19); - SetIOPriority(7); + if (cMark *BeginMark = fromMarks.GetNextBegin()) { fromFile = fromFileName->Open(); toFile = toFileName->Open(); if (!fromFile || !toFile) return; - fromFile->SetReadAhead(MEGABYTE(20)); - int Index = Mark->Position(); - Mark = fromMarks.Next(Mark); - off_t FileSize = 0; - int CurrentFileNumber = 0; - int LastIFrame = 0; - toMarks.Add(0); - toMarks.Save(); - uchar buffer[MAXFRAMESIZE], buffer2[MAXFRAMESIZE]; - int Length2; - bool CheckForSeamlessStream = false; - bool LastMark = false; - bool cutIn = true; - bool suspensionLogged = false; - while (Running()) { - uint16_t FileNumber; - off_t FileOffset; - int Length; - bool Independent; - + int LastEndIndex = -1; + while (BeginMark && Running()) { // Suspend cutting if we have severe throughput problems: - - if (cIoThrottle::Engaged()) { - if (!suspensionLogged) { - dsyslog("suspending cutter thread"); - suspensionLogged = true; - } + if (Throttled()) { cCondWait::SleepMs(100); continue; } - else if (suspensionLogged) { - dsyslog("resuming cutter thread"); - suspensionLogged = false; - } - // Make sure there is enough disk space: - AssertFreeDiskSpace(-1); - - // Read one frame: - - if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &Independent, &Length)) { - if (FileNumber != CurrentFileNumber) { - fromFile = fromFileName->SetOffset(FileNumber, FileOffset); - if (fromFile) - fromFile->SetReadAhead(MEGABYTE(20)); - CurrentFileNumber = FileNumber; - } - if (fromFile) { - int len = ReadFrame(fromFile, buffer, Length, sizeof(buffer)); - if (len < 0) { - error = "ReadFrame"; - break; - } - if (len != Length) { - CurrentFileNumber = 0; // this re-syncs in case the frame was larger than the buffer - Length = len; - } - } - else { - error = "fromFile"; - break; - } + // Determine the actual begin and end marks, skipping any marks at the same position: + cMark *EndMark = fromMarks.GetNextEnd(BeginMark); + // Process the current sequence: + int EndIndex = EndMark ? EndMark->Position() : fromIndex->Last() + 1; + int NextBeginIndex = -1; + if (EndMark) { + if (cMark *NextBeginMark = fromMarks.GetNextBegin(EndMark)) + NextBeginIndex = NextBeginMark->Position(); } - else { - // Error, unless we're past the last cut-in and there's no cut-out - if (Mark || LastMark) - error = "index"; + if (!ProcessSequence(LastEndIndex, BeginMark->Position(), EndIndex, NextBeginIndex)) break; - } - - // Write one frame: - - if (Independent) { // every file shall start with an independent frame - if (LastMark) // edited version shall end before next I-frame - break; - if (FileSize > maxVideoFileSize) { - toFile = toFileName->NextFile(); - if (!toFile) { - error = "toFile 1"; + if (!EndMark) + break; // reached EOF + LastEndIndex = EndIndex; + // Switch to the next sequence: + BeginMark = fromMarks.GetNextBegin(EndMark); + if (BeginMark) { + // Split edited files: + if (Setup.SplitEditedFiles) { + if (!SwitchFile(true)) break; - } - FileSize = 0; - } - LastIFrame = 0; - // Compare the current frame with the previously stored one, to see if this is a seamlessly merged recording of the same stream: - if (CheckForSeamlessStream) { - if (Length == Length2) { - int diffs = 0; - for (int i = 0; i < Length; i++) { - if (buffer[i] != buffer2[i]) { - if (diffs++ > 10) - break; - } - } - if (diffs < 10) // the continuity counters of the PAT/PMT packets may differ - cutIn = false; // it's apparently a seamless stream, so no need for "broken" handling - } - CheckForSeamlessStream = false; - } - if (cutIn) { - if (isPesRecording) - cRemux::SetBrokenLink(buffer, Length); - else - TsSetTeiOnBrokenPackets(buffer, Length); - cutIn = false; } } - if (toFile->Write(buffer, Length) < 0) { - error = "safe_write"; - break; - } - if (!toIndex->Write(Independent, toFileName->Number(), FileSize)) { - error = "toIndex"; - break; - } - FileSize += Length; - if (!LastIFrame) - LastIFrame = toIndex->Last(); - - // Check editing marks: - - if (Mark && Index >= Mark->Position()) { - Mark = fromMarks.Next(Mark); - toMarks.Add(LastIFrame); - if (Mark) - toMarks.Add(toIndex->Last() + 1); - toMarks.Save(); - if (Mark) { - // Read the next frame, for later comparison with the first frame at this mark: - if (fromIndex->Get(Index, &FileNumber, &FileOffset, &Independent, &Length2)) { - if (FileNumber != CurrentFileNumber) - fromFile = fromFileName->SetOffset(FileNumber, FileOffset); - if (fromFile) { - int len = ReadFrame(fromFile, buffer2, Length2, sizeof(buffer2)); - if (len >= 0 && len == Length2) - CheckForSeamlessStream = true; - } - } - Index = Mark->Position(); - Mark = fromMarks.Next(Mark); - CurrentFileNumber = 0; // triggers SetOffset before reading next frame - cutIn = true; - if (Setup.SplitEditedFiles) { - toFile = toFileName->NextFile(); - if (!toFile) { - error = "toFile 2"; - break; - } - FileSize = 0; - } - } - else - LastMark = true; - } } Recordings.TouchUpdate(); } @@ -257,7 +575,7 @@ bool cCutter::Start(const char *FileName) cMarks FromMarks; FromMarks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording()); - if (cMark *First = FromMarks.First()) + if (cMark *First = FromMarks.GetNextBegin()) Recording.SetStartTime(Recording.Start() + (int(First->Position() / Recording.FramesPerSecond() + 30) / 60) * 60); const char *evn = Recording.PrefixFileName('%'); @@ -345,13 +663,17 @@ bool CutRecording(const char *FileName) if (Recording.Name()) { cMarks Marks; if (Marks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording()) && Marks.Count()) { - if (cCutter::Start(FileName)) { - while (cCutter::Active()) - cCondWait::SleepMs(CUTTINGCHECKINTERVAL); - return true; + if (Marks.GetNumSequences()) { + if (cCutter::Start(FileName)) { + while (cCutter::Active()) + cCondWait::SleepMs(CUTTINGCHECKINTERVAL); + return true; + } + else + fprintf(stderr, "can't start editing process\n"); } else - fprintf(stderr, "can't start editing process\n"); + fprintf(stderr, "'%s' has no editing sequences\n", FileName); } else fprintf(stderr, "'%s' has no editing marks\n", FileName); |