From 6dd5854b7ab929d09bc83005bb6c64b7f3065a95 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Tue, 17 Sep 2024 09:31:15 +0200 Subject: [PATCH] Moved error checking from recorder.c to remux.c --- HISTORY | 3 +- recorder.c | 168 +++----------------------------------------------- recorder.h | 9 +-- remux.c | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++- remux.h | 13 +++- 5 files changed, 198 insertions(+), 171 deletions(-) diff --git a/HISTORY b/HISTORY index 50c7b3db..775c4d4c 100644 --- a/HISTORY +++ b/HISTORY @@ -9984,7 +9984,7 @@ Video Disk Recorder Revision History version numbering. Version numbers are simply counted upwards, with each of the three parts ("version", "major", "minor") always being a single digit, and '0' being skipped. -2024-09-16: +2024-09-17: - Fix for compilers that don't like non-constant format strings (thanks to Stefan Hofmann). - Deprecated code is now marked with [[deprecated]] to issue a compile time warning when @@ -9997,3 +9997,4 @@ Video Disk Recorder Revision History with RunningStatus() >= SI::RunningStatusPausing (reported by Markus Ehrnsperger). - Silenced a compiler warning with gcc 14.1.0 (reported by Winfried Köhler). - Updated the Italian OSD texts (thanks to Diego Pierotto). +- Moved error checking from recorder.c to remux.c. diff --git a/recorder.c b/recorder.c index 6c366a78..4c3e960f 100644 --- a/recorder.c +++ b/recorder.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recorder.c 5.6 2024/01/20 20:04:03 kls Exp $ + * $Id: recorder.c 5.7 2024/09/17 09:28:03 kls Exp $ */ #include "recorder.h" @@ -19,160 +19,18 @@ #define MINFREEDISKSPACE (512) // MB #define DISKCHECKINTERVAL 100 // seconds -static bool DebugChecks = false; - -// cTsChecker and cFrameChecker are used to detect errors in the recorded data stream. -// While cTsChecker checks the continuity counter of the incoming TS packets, cFrameChecker -// works on entire frames, checking their PTS (Presentation Time Stamps) to see whether -// all expected frames arrive. The resulting number of errors is not a precise value. -// If it is zero, the recording can be safely considered error free. The higher the value, -// the more damaged the recording is. - -// --- cTsChecker ------------------------------------------------------------ - -#define TS_CC_UNKNOWN 0xFF - -class cTsChecker { -private: - uchar counter[MAXPID]; - int errors; - void Report(int Pid, const char *Message); -public: - cTsChecker(void); - void CheckTs(const uchar *Data, int Length); - int Errors(void) { return errors; } - }; - -cTsChecker::cTsChecker(void) -{ - memset(counter, TS_CC_UNKNOWN, sizeof(counter)); - errors = 0; -} - -void cTsChecker::Report(int Pid, const char *Message) -{ - errors++; - if (DebugChecks) - fprintf(stderr, "%s: TS error #%d on PID %d (%s)\n", *TimeToString(time(NULL)), errors, Pid, Message); -} - -void cTsChecker::CheckTs(const uchar *Data, int Length) -{ - int Pid = TsPid(Data); - uchar Cc = TsContinuityCounter(Data); - if (TsHasPayload(Data)) { - if (TsError(Data)) - Report(Pid, "tei"); - else if (TsIsScrambled(Data)) - Report(Pid, "scrambled"); - else { - uchar OldCc = counter[Pid]; - if (OldCc != TS_CC_UNKNOWN) { - uchar NewCc = (OldCc + 1) & TS_CONT_CNT_MASK; - if (Cc != NewCc) - Report(Pid, "continuity"); - } - } - } - counter[Pid] = Cc; -} - -// --- cFrameChecker --------------------------------------------------------- - -#define MAX_BACK_REFS 32 - -class cFrameChecker { -private: - int frameDelta; - int64_t lastPts; - uint32_t backRefs; - int lastFwdRef; - int errors; - void Report(const char *Message, int NumErrors = 1); -public: - cFrameChecker(void); - void SetFrameDelta(int FrameDelta) { frameDelta = FrameDelta; } - void CheckFrame(const uchar *Data, int Length); - void ReportBroken(void); - int Errors(void) { return errors; } - }; - -cFrameChecker::cFrameChecker(void) -{ - frameDelta = PTSTICKS / DEFAULTFRAMESPERSECOND; - lastPts = -1; - backRefs = 0; - lastFwdRef = 0; - errors = 0; -} - -void cFrameChecker::Report(const char *Message, int NumErrors) -{ - errors += NumErrors; - if (DebugChecks) - fprintf(stderr, "%s: frame error #%d (%s)\n", *TimeToString(time(NULL)), errors, Message); -} - -void cFrameChecker::CheckFrame(const uchar *Data, int Length) -{ - int64_t Pts = TsGetPts(Data, Length); - if (Pts >= 0) { - if (lastPts >= 0) { - int Diff = int(round((PtsDiff(lastPts, Pts) / double(frameDelta)))); - if (Diff > 0) { - if (Diff <= MAX_BACK_REFS) { - if (lastFwdRef > 1) { - if (backRefs != uint32_t((1 << (lastFwdRef - 1)) - 1)) - Report("missing backref"); - } - } - else - Report("missed", Diff); - backRefs = 0; - lastFwdRef = Diff; - lastPts = Pts; - } - else if (Diff < 0) { - Diff = -Diff; - if (Diff <= MAX_BACK_REFS) { - int b = 1 << (Diff - 1); - if ((backRefs & b) != 0) - Report("duplicate backref"); - backRefs |= b; - } - else - Report("rev diff too big"); - } - else - Report("zero diff"); - } - else - lastPts = Pts; - } - else - Report("no PTS"); -} - -void cFrameChecker::ReportBroken(void) -{ - int MissedFrames = MAXBROKENTIMEOUT / 1000 * PTSTICKS / frameDelta; - Report("missed", MissedFrames); -} - // --- cRecorder ------------------------------------------------------------- cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority) :cReceiver(Channel, Priority) ,cThread("recording") { - tsChecker = new cTsChecker; - frameChecker = new cFrameChecker; recordingName = strdup(FileName); recordingInfo = new cRecordingInfo(recordingName); recordingInfo->Read(); oldErrors = max(0, recordingInfo->Errors()); // in case this is a re-started recording - errors = oldErrors; - lastErrors = errors; + errors = 0; + lastErrors = 0; firstIframeSeen = false; // Make sure the disk is up and running: @@ -220,8 +78,6 @@ cRecorder::~cRecorder() delete fileName; delete frameDetector; delete ringBuffer; - delete frameChecker; - delete tsChecker; free(recordingName); } @@ -231,18 +87,15 @@ void cRecorder::HandleErrors(bool Force) { // We don't log every single error separately, to avoid spamming the log file: if (Force || time(NULL) - lastErrorLog >= ERROR_LOG_DELTA) { - errors = tsChecker->Errors() + frameChecker->Errors(); if (errors > lastErrors) { int d = errors - lastErrors; - if (DebugChecks) - fprintf(stderr, "%s: %s: %d new error%s (total %d)\n", *TimeToString(time(NULL)), recordingName, d, d > 1 ? "s" : "", errors); - esyslog("%s: %d new error%s (total %d)", recordingName, d, d > 1 ? "s" : "", errors); + esyslog("%s: %d new error%s (total %d)", recordingName, d, d > 1 ? "s" : "", oldErrors + errors); recordingInfo->SetErrors(oldErrors + errors); recordingInfo->Write(); LOCK_RECORDINGS_WRITE; Recordings->UpdateByName(recordingName); + lastErrors = errors; } - lastErrors = errors; lastErrorLog = time(NULL); } } @@ -307,8 +160,6 @@ void cRecorder::Receive(const uchar *Data, int Length) int p = ringBuffer->Put(Data, Length); if (p != Length && Running()) ringBuffer->ReportOverflow(Length - p); - else if (firstIframeSeen) // we ignore any garbage before the first I-frame - tsChecker->CheckTs(Data, Length); } } @@ -338,17 +189,16 @@ void cRecorder::Action(void) } InfoWritten = true; cRecordingUserCommand::InvokeCommand(RUC_STARTRECORDING, recordingName); - frameChecker->SetFrameDelta(PTSTICKS / frameDetector->FramesPerSecond()); } if (firstIframeSeen || frameDetector->IndependentFrame()) { firstIframeSeen = true; // start recording with the first I-frame if (!NextFile()) break; - if (frameDetector->NewFrame()) { + int PreviousErrors = 0; + if (frameDetector->NewFrame(&PreviousErrors)) { if (index) index->Write(frameDetector->IndependentFrame(), fileName->Number(), fileSize); - if (frameChecker) - frameChecker->CheckFrame(b, Count); + errors += PreviousErrors; } if (frameDetector->IndependentFrame()) { recordFile->Write(patPmtGenerator.GetPat(), TS_SIZE); @@ -372,7 +222,7 @@ void cRecorder::Action(void) } } if (t.TimedOut()) { - frameChecker->ReportBroken(); + errors += MAXBROKENTIMEOUT / 1000 * frameDetector->FramesPerSecond(); HandleErrors(true); esyslog("ERROR: video data stream broken"); ShutdownHandler.RequestEmergencyExit(); diff --git a/recorder.h b/recorder.h index 825f4af3..b32eb294 100644 --- a/recorder.h +++ b/recorder.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recorder.h 5.1 2021/05/19 11:22:20 kls Exp $ + * $Id: recorder.h 5.2 2024/09/16 19:56:37 kls Exp $ */ #ifndef __RECORDER_H @@ -16,13 +16,8 @@ #include "ringbuffer.h" #include "thread.h" -class cTsChecker; -class cFrameChecker; - class cRecorder : public cReceiver, cThread { private: - cTsChecker *tsChecker; - cFrameChecker *frameChecker; cRingBufferLinear *ringBuffer; cFrameDetector *frameDetector; cPatPmtGenerator patPmtGenerator; @@ -56,6 +51,8 @@ public: virtual ~cRecorder(); int Errors(void) { return oldErrors + errors; }; ///< Returns the number of errors that were detected during recording. + ///< If this is a resumed recording, this includes errors that occurred + ///< in the previous parts. }; #endif //__RECORDER_H diff --git a/remux.c b/remux.c index ae435668..09e0b71d 100644 --- a/remux.c +++ b/remux.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remux.c 5.8 2024/09/16 09:07:12 kls Exp $ + * $Id: remux.c 5.9 2024/09/16 19:56:37 kls Exp $ */ #include "remux.h" @@ -1931,6 +1931,156 @@ void cH265Parser::ParseSequenceParameterSet(void) } } +static bool DebugChecks = false; + +// cTsChecker and cFrameChecker are used to detect errors in the recorded data stream. +// While cTsChecker checks the continuity counter of the incoming TS packets, cFrameChecker +// works on entire frames, checking their PTS (Presentation Time Stamps) to see whether +// all expected frames arrive. The resulting number of errors is not a precise value. +// If it is zero, the recording can be safely considered error free. The higher the value, +// the more damaged the recording is. + +// --- cTsChecker ------------------------------------------------------------ + +#define TS_CC_UNKNOWN 0xFF + +class cTsChecker { +private: + uchar counter[MAXPID]; + int errors; + void Report(int Pid, const char *Message); +public: + cTsChecker(void); + void CheckTs(const uchar *Data, int Length); + int Errors(void) { return errors; } + void Clear(void) { errors = 0; } + }; + +cTsChecker::cTsChecker(void) +{ + memset(counter, TS_CC_UNKNOWN, sizeof(counter)); + errors = 0; +} + +void cTsChecker::Report(int Pid, const char *Message) +{ + errors++; + if (DebugChecks) + fprintf(stderr, "%s: TS error #%d on PID %d (%s)\n", *TimeToString(time(NULL)), errors, Pid, Message); +} + +void cTsChecker::CheckTs(const uchar *Data, int Length) +{ + while (Length >= TS_SIZE) { + int Pid = TsPid(Data); + uchar Cc = TsContinuityCounter(Data); + if (TsHasPayload(Data)) { + if (TsError(Data)) + Report(Pid, "tei"); + else if (TsIsScrambled(Data)) + Report(Pid, "scrambled"); + else { + uchar OldCc = counter[Pid]; + if (OldCc != TS_CC_UNKNOWN) { + uchar NewCc = (OldCc + 1) & TS_CONT_CNT_MASK; + if (Cc != NewCc) + Report(Pid, "continuity"); + } + } + } + counter[Pid] = Cc; + Data += TS_SIZE; + Length -= TS_SIZE; + } +} + +// --- cFrameChecker --------------------------------------------------------- + +#define MAX_BACK_REFS 32 + +class cFrameChecker { +private: + cTsChecker tsChecker; + int frameDelta; + int64_t lastPts; + uint32_t backRefs; + int lastFwdRef; + int errors; + int previousErrors; + void Report(const char *Message, int NumErrors = 1); +public: + cFrameChecker(void); + void SetFrameDelta(int FrameDelta) { frameDelta = FrameDelta; } + void CheckTs(const uchar *Data, int Length); + void CheckFrame(const uchar *Data, int Length); + int PreviousErrors(void) { return previousErrors; } + }; + +cFrameChecker::cFrameChecker(void) +{ + frameDelta = PTSTICKS / DEFAULTFRAMESPERSECOND; + lastPts = -1; + backRefs = 0; + lastFwdRef = 0; + errors = 0; + previousErrors = 0; +} + +void cFrameChecker::Report(const char *Message, int NumErrors) +{ + errors += NumErrors; + if (DebugChecks) + fprintf(stderr, "%s: frame error #%d (%s)\n", *TimeToString(time(NULL)), errors, Message); +} + +void cFrameChecker::CheckTs(const uchar *Data, int Length) +{ + tsChecker.CheckTs(Data, Length); +} + +void cFrameChecker::CheckFrame(const uchar *Data, int Length) +{ + previousErrors = tsChecker.Errors() + errors; + errors = 0; + tsChecker.Clear(); + int64_t Pts = TsGetPts(Data, Length); + if (Pts >= 0) { + if (lastPts >= 0) { + int Diff = int(round((PtsDiff(lastPts, Pts) / double(frameDelta)))); + if (Diff > 0) { + if (Diff <= MAX_BACK_REFS) { + if (lastFwdRef > 1) { + if (backRefs != uint32_t((1 << (lastFwdRef - 1)) - 1)) + Report("missing backref"); + } + } + else + Report("missed", Diff); + backRefs = 0; + lastFwdRef = Diff; + lastPts = Pts; + } + else if (Diff < 0) { + Diff = -Diff; + if (Diff <= MAX_BACK_REFS) { + int b = 1 << (Diff - 1); + if ((backRefs & b) != 0) + Report("duplicate backref"); + backRefs |= b; + } + else + Report("rev diff too big"); + } + else + Report("zero diff"); + } + else + lastPts = Pts; + } + else + Report("no PTS"); +} + // --- cFrameDetector -------------------------------------------------------- const char *ScanTypeChars = "-pi"; // index is eScanType @@ -1958,6 +2108,13 @@ cFrameDetector::cFrameDetector(int Pid, int Type) aspectRatio = arUnknown; framesInPayloadUnit = framesPerPayloadUnit = 0; scanning = false; + firstIframeSeen = false; + frameChecker = new cFrameChecker; +} + +cFrameDetector::~cFrameDetector() +{ + delete frameChecker; } static int CmpUint32(const void *p1, const void *p2) @@ -1986,6 +2143,15 @@ void cFrameDetector::SetPid(int Pid, int Type) esyslog("ERROR: unknown stream type %d (PID %d) in frame detector", type, pid); } +bool cFrameDetector::NewFrame(int *PreviousErrors) +{ + if (newFrame) { + if (PreviousErrors) + *PreviousErrors = frameChecker->PreviousErrors(); + } + return newFrame; +} + int cFrameDetector::Analyze(const uchar *Data, int Length) { if (!parser) @@ -2016,7 +2182,10 @@ int cFrameDetector::Analyze(const uchar *Data, int Length) if (parser->NewFrame()) { newFrame = true; independentFrame = parser->IndependentFrame(); + firstIframeSeen |= independentFrame; if (synced) { + if (firstIframeSeen) + frameChecker->CheckFrame(Data, n); if (framesPerPayloadUnit <= 1) scanning = false; } @@ -2027,6 +2196,7 @@ int cFrameDetector::Analyze(const uchar *Data, int Length) frameHeight = parser->FrameHeight(); scanType = parser->ScanType(); aspectRatio = parser->AspectRatio(); + frameChecker->SetFrameDelta(PTSTICKS / framesPerSecond); synced = true; parser->SetDebug(false); } @@ -2043,7 +2213,7 @@ int cFrameDetector::Analyze(const uchar *Data, int Length) if (framesPerSecond <= 0.0) { // frame rate unknown, so collect a sequence of PTS values: if (numPtsValues < 2 || numPtsValues < MaxPtsValues && numIFrames < 2) { // collect a sequence containing at least two I-frames - if (newFrame) { // only take PTS values at the beginning of a frame (in case if fields!) + if (newFrame) { // only take PTS values at the beginning of a frame (in case of fields!) const uchar *Pes = Data + TsPayloadOffset(Data); if (numIFrames && PesHasPts(Pes)) { ptsValues[numPtsValues] = PesGetPts(Pes); @@ -2100,6 +2270,8 @@ int cFrameDetector::Analyze(const uchar *Data, int Length) else if (Pid == PATPID && synced && Processed) return Processed; // allow the caller to see any PAT packets } + if (firstIframeSeen) + frameChecker->CheckTs(Data, Handled); Data += Handled; Length -= Handled; Processed += Handled; diff --git a/remux.h b/remux.h index e28fb40a..ecc61a49 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 5.3 2023/12/27 09:30:42 kls Exp $ + * $Id: remux.h 5.4 2024/09/16 19:56:37 kls Exp $ */ #ifndef __REMUX_H @@ -523,6 +523,8 @@ enum eAspectRatio { extern const char *ScanTypeChars; extern const char *AspectRatioTexts[]; +class cFrameChecker; + class cFrameDetector { private: enum { MaxPtsValues = 150 }; @@ -544,12 +546,15 @@ private: int framesPerPayloadUnit; // Some broadcasters send one frame per payload unit (== 1), // while others put an entire GOP into one payload unit (> 1). bool scanning; + bool firstIframeSeen; cFrameParser *parser; public: + cFrameChecker *frameChecker; cFrameDetector(int Pid = 0, int Type = 0); ///< Sets up a frame detector for the given Pid and stream Type. ///< If no Pid and Type is given, they need to be set by a separate ///< call to SetPid(). + ~cFrameDetector(); void SetPid(int Pid, int Type); ///< Sets the Pid and stream Type to detect frames for. int Analyze(const uchar *Data, int Length); @@ -560,9 +565,11 @@ public: ///< Analyze() needs to be called again with more actual data. bool Synced(void) { return synced; } ///< Returns true if the frame detector has synced on the data stream. - bool NewFrame(void) { return newFrame; } + bool NewFrame(int *PreviousErrors = NULL); ///< Returns true if the data given to the last call to Analyze() started a - ///< new frame. + ///< new frame. If PreviousErrors is given, it will be set to the number of errors in + ///< the previous frame. + ///< The result returned in PreviousErrors is only valid if the function returns true. bool IndependentFrame(void) { return independentFrame; } ///< Returns true if a new frame was detected and this is an independent frame ///< (i.e. one that can be displayed by itself, without using data from any