mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
Moved error checking from recorder.c to remux.c
This commit is contained in:
parent
83425df0b6
commit
6dd5854b7a
3
HISTORY
3
HISTORY
@ -9984,7 +9984,7 @@ Video Disk Recorder Revision History
|
|||||||
version numbering. Version numbers are simply counted upwards, with each of the three
|
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.
|
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).
|
- 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
|
- 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).
|
with RunningStatus() >= SI::RunningStatusPausing (reported by Markus Ehrnsperger).
|
||||||
- Silenced a compiler warning with gcc 14.1.0 (reported by Winfried Köhler).
|
- Silenced a compiler warning with gcc 14.1.0 (reported by Winfried Köhler).
|
||||||
- Updated the Italian OSD texts (thanks to Diego Pierotto).
|
- Updated the Italian OSD texts (thanks to Diego Pierotto).
|
||||||
|
- Moved error checking from recorder.c to remux.c.
|
||||||
|
168
recorder.c
168
recorder.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* 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"
|
#include "recorder.h"
|
||||||
@ -19,160 +19,18 @@
|
|||||||
#define MINFREEDISKSPACE (512) // MB
|
#define MINFREEDISKSPACE (512) // MB
|
||||||
#define DISKCHECKINTERVAL 100 // seconds
|
#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::cRecorder(const char *FileName, const cChannel *Channel, int Priority)
|
cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority)
|
||||||
:cReceiver(Channel, Priority)
|
:cReceiver(Channel, Priority)
|
||||||
,cThread("recording")
|
,cThread("recording")
|
||||||
{
|
{
|
||||||
tsChecker = new cTsChecker;
|
|
||||||
frameChecker = new cFrameChecker;
|
|
||||||
recordingName = strdup(FileName);
|
recordingName = strdup(FileName);
|
||||||
recordingInfo = new cRecordingInfo(recordingName);
|
recordingInfo = new cRecordingInfo(recordingName);
|
||||||
recordingInfo->Read();
|
recordingInfo->Read();
|
||||||
oldErrors = max(0, recordingInfo->Errors()); // in case this is a re-started recording
|
oldErrors = max(0, recordingInfo->Errors()); // in case this is a re-started recording
|
||||||
errors = oldErrors;
|
errors = 0;
|
||||||
lastErrors = errors;
|
lastErrors = 0;
|
||||||
firstIframeSeen = false;
|
firstIframeSeen = false;
|
||||||
|
|
||||||
// Make sure the disk is up and running:
|
// Make sure the disk is up and running:
|
||||||
@ -220,8 +78,6 @@ cRecorder::~cRecorder()
|
|||||||
delete fileName;
|
delete fileName;
|
||||||
delete frameDetector;
|
delete frameDetector;
|
||||||
delete ringBuffer;
|
delete ringBuffer;
|
||||||
delete frameChecker;
|
|
||||||
delete tsChecker;
|
|
||||||
free(recordingName);
|
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:
|
// We don't log every single error separately, to avoid spamming the log file:
|
||||||
if (Force || time(NULL) - lastErrorLog >= ERROR_LOG_DELTA) {
|
if (Force || time(NULL) - lastErrorLog >= ERROR_LOG_DELTA) {
|
||||||
errors = tsChecker->Errors() + frameChecker->Errors();
|
|
||||||
if (errors > lastErrors) {
|
if (errors > lastErrors) {
|
||||||
int d = errors - lastErrors;
|
int d = errors - lastErrors;
|
||||||
if (DebugChecks)
|
esyslog("%s: %d new error%s (total %d)", recordingName, d, d > 1 ? "s" : "", oldErrors + errors);
|
||||||
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);
|
|
||||||
recordingInfo->SetErrors(oldErrors + errors);
|
recordingInfo->SetErrors(oldErrors + errors);
|
||||||
recordingInfo->Write();
|
recordingInfo->Write();
|
||||||
LOCK_RECORDINGS_WRITE;
|
LOCK_RECORDINGS_WRITE;
|
||||||
Recordings->UpdateByName(recordingName);
|
Recordings->UpdateByName(recordingName);
|
||||||
|
lastErrors = errors;
|
||||||
}
|
}
|
||||||
lastErrors = errors;
|
|
||||||
lastErrorLog = time(NULL);
|
lastErrorLog = time(NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -307,8 +160,6 @@ void cRecorder::Receive(const uchar *Data, int Length)
|
|||||||
int p = ringBuffer->Put(Data, Length);
|
int p = ringBuffer->Put(Data, Length);
|
||||||
if (p != Length && Running())
|
if (p != Length && Running())
|
||||||
ringBuffer->ReportOverflow(Length - p);
|
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;
|
InfoWritten = true;
|
||||||
cRecordingUserCommand::InvokeCommand(RUC_STARTRECORDING, recordingName);
|
cRecordingUserCommand::InvokeCommand(RUC_STARTRECORDING, recordingName);
|
||||||
frameChecker->SetFrameDelta(PTSTICKS / frameDetector->FramesPerSecond());
|
|
||||||
}
|
}
|
||||||
if (firstIframeSeen || frameDetector->IndependentFrame()) {
|
if (firstIframeSeen || frameDetector->IndependentFrame()) {
|
||||||
firstIframeSeen = true; // start recording with the first I-frame
|
firstIframeSeen = true; // start recording with the first I-frame
|
||||||
if (!NextFile())
|
if (!NextFile())
|
||||||
break;
|
break;
|
||||||
if (frameDetector->NewFrame()) {
|
int PreviousErrors = 0;
|
||||||
|
if (frameDetector->NewFrame(&PreviousErrors)) {
|
||||||
if (index)
|
if (index)
|
||||||
index->Write(frameDetector->IndependentFrame(), fileName->Number(), fileSize);
|
index->Write(frameDetector->IndependentFrame(), fileName->Number(), fileSize);
|
||||||
if (frameChecker)
|
errors += PreviousErrors;
|
||||||
frameChecker->CheckFrame(b, Count);
|
|
||||||
}
|
}
|
||||||
if (frameDetector->IndependentFrame()) {
|
if (frameDetector->IndependentFrame()) {
|
||||||
recordFile->Write(patPmtGenerator.GetPat(), TS_SIZE);
|
recordFile->Write(patPmtGenerator.GetPat(), TS_SIZE);
|
||||||
@ -372,7 +222,7 @@ void cRecorder::Action(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (t.TimedOut()) {
|
if (t.TimedOut()) {
|
||||||
frameChecker->ReportBroken();
|
errors += MAXBROKENTIMEOUT / 1000 * frameDetector->FramesPerSecond();
|
||||||
HandleErrors(true);
|
HandleErrors(true);
|
||||||
esyslog("ERROR: video data stream broken");
|
esyslog("ERROR: video data stream broken");
|
||||||
ShutdownHandler.RequestEmergencyExit();
|
ShutdownHandler.RequestEmergencyExit();
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* 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
|
#ifndef __RECORDER_H
|
||||||
@ -16,13 +16,8 @@
|
|||||||
#include "ringbuffer.h"
|
#include "ringbuffer.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
|
|
||||||
class cTsChecker;
|
|
||||||
class cFrameChecker;
|
|
||||||
|
|
||||||
class cRecorder : public cReceiver, cThread {
|
class cRecorder : public cReceiver, cThread {
|
||||||
private:
|
private:
|
||||||
cTsChecker *tsChecker;
|
|
||||||
cFrameChecker *frameChecker;
|
|
||||||
cRingBufferLinear *ringBuffer;
|
cRingBufferLinear *ringBuffer;
|
||||||
cFrameDetector *frameDetector;
|
cFrameDetector *frameDetector;
|
||||||
cPatPmtGenerator patPmtGenerator;
|
cPatPmtGenerator patPmtGenerator;
|
||||||
@ -56,6 +51,8 @@ public:
|
|||||||
virtual ~cRecorder();
|
virtual ~cRecorder();
|
||||||
int Errors(void) { return oldErrors + errors; };
|
int Errors(void) { return oldErrors + errors; };
|
||||||
///< Returns the number of errors that were detected during recording.
|
///< 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
|
#endif //__RECORDER_H
|
||||||
|
176
remux.c
176
remux.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* 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"
|
#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 --------------------------------------------------------
|
// --- cFrameDetector --------------------------------------------------------
|
||||||
|
|
||||||
const char *ScanTypeChars = "-pi"; // index is eScanType
|
const char *ScanTypeChars = "-pi"; // index is eScanType
|
||||||
@ -1958,6 +2108,13 @@ cFrameDetector::cFrameDetector(int Pid, int Type)
|
|||||||
aspectRatio = arUnknown;
|
aspectRatio = arUnknown;
|
||||||
framesInPayloadUnit = framesPerPayloadUnit = 0;
|
framesInPayloadUnit = framesPerPayloadUnit = 0;
|
||||||
scanning = false;
|
scanning = false;
|
||||||
|
firstIframeSeen = false;
|
||||||
|
frameChecker = new cFrameChecker;
|
||||||
|
}
|
||||||
|
|
||||||
|
cFrameDetector::~cFrameDetector()
|
||||||
|
{
|
||||||
|
delete frameChecker;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int CmpUint32(const void *p1, const void *p2)
|
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);
|
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)
|
int cFrameDetector::Analyze(const uchar *Data, int Length)
|
||||||
{
|
{
|
||||||
if (!parser)
|
if (!parser)
|
||||||
@ -2016,7 +2182,10 @@ int cFrameDetector::Analyze(const uchar *Data, int Length)
|
|||||||
if (parser->NewFrame()) {
|
if (parser->NewFrame()) {
|
||||||
newFrame = true;
|
newFrame = true;
|
||||||
independentFrame = parser->IndependentFrame();
|
independentFrame = parser->IndependentFrame();
|
||||||
|
firstIframeSeen |= independentFrame;
|
||||||
if (synced) {
|
if (synced) {
|
||||||
|
if (firstIframeSeen)
|
||||||
|
frameChecker->CheckFrame(Data, n);
|
||||||
if (framesPerPayloadUnit <= 1)
|
if (framesPerPayloadUnit <= 1)
|
||||||
scanning = false;
|
scanning = false;
|
||||||
}
|
}
|
||||||
@ -2027,6 +2196,7 @@ int cFrameDetector::Analyze(const uchar *Data, int Length)
|
|||||||
frameHeight = parser->FrameHeight();
|
frameHeight = parser->FrameHeight();
|
||||||
scanType = parser->ScanType();
|
scanType = parser->ScanType();
|
||||||
aspectRatio = parser->AspectRatio();
|
aspectRatio = parser->AspectRatio();
|
||||||
|
frameChecker->SetFrameDelta(PTSTICKS / framesPerSecond);
|
||||||
synced = true;
|
synced = true;
|
||||||
parser->SetDebug(false);
|
parser->SetDebug(false);
|
||||||
}
|
}
|
||||||
@ -2043,7 +2213,7 @@ int cFrameDetector::Analyze(const uchar *Data, int Length)
|
|||||||
if (framesPerSecond <= 0.0) {
|
if (framesPerSecond <= 0.0) {
|
||||||
// frame rate unknown, so collect a sequence of PTS values:
|
// 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 (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);
|
const uchar *Pes = Data + TsPayloadOffset(Data);
|
||||||
if (numIFrames && PesHasPts(Pes)) {
|
if (numIFrames && PesHasPts(Pes)) {
|
||||||
ptsValues[numPtsValues] = PesGetPts(Pes);
|
ptsValues[numPtsValues] = PesGetPts(Pes);
|
||||||
@ -2100,6 +2270,8 @@ int cFrameDetector::Analyze(const uchar *Data, int Length)
|
|||||||
else if (Pid == PATPID && synced && Processed)
|
else if (Pid == PATPID && synced && Processed)
|
||||||
return Processed; // allow the caller to see any PAT packets
|
return Processed; // allow the caller to see any PAT packets
|
||||||
}
|
}
|
||||||
|
if (firstIframeSeen)
|
||||||
|
frameChecker->CheckTs(Data, Handled);
|
||||||
Data += Handled;
|
Data += Handled;
|
||||||
Length -= Handled;
|
Length -= Handled;
|
||||||
Processed += Handled;
|
Processed += Handled;
|
||||||
|
13
remux.h
13
remux.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* 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
|
#ifndef __REMUX_H
|
||||||
@ -523,6 +523,8 @@ enum eAspectRatio {
|
|||||||
extern const char *ScanTypeChars;
|
extern const char *ScanTypeChars;
|
||||||
extern const char *AspectRatioTexts[];
|
extern const char *AspectRatioTexts[];
|
||||||
|
|
||||||
|
class cFrameChecker;
|
||||||
|
|
||||||
class cFrameDetector {
|
class cFrameDetector {
|
||||||
private:
|
private:
|
||||||
enum { MaxPtsValues = 150 };
|
enum { MaxPtsValues = 150 };
|
||||||
@ -544,12 +546,15 @@ private:
|
|||||||
int framesPerPayloadUnit; // Some broadcasters send one frame per payload unit (== 1),
|
int framesPerPayloadUnit; // Some broadcasters send one frame per payload unit (== 1),
|
||||||
// while others put an entire GOP into one payload unit (> 1).
|
// while others put an entire GOP into one payload unit (> 1).
|
||||||
bool scanning;
|
bool scanning;
|
||||||
|
bool firstIframeSeen;
|
||||||
cFrameParser *parser;
|
cFrameParser *parser;
|
||||||
public:
|
public:
|
||||||
|
cFrameChecker *frameChecker;
|
||||||
cFrameDetector(int Pid = 0, int Type = 0);
|
cFrameDetector(int Pid = 0, int Type = 0);
|
||||||
///< Sets up a frame detector for the given Pid and stream Type.
|
///< 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
|
///< If no Pid and Type is given, they need to be set by a separate
|
||||||
///< call to SetPid().
|
///< call to SetPid().
|
||||||
|
~cFrameDetector();
|
||||||
void SetPid(int Pid, int Type);
|
void SetPid(int Pid, int Type);
|
||||||
///< Sets the Pid and stream Type to detect frames for.
|
///< Sets the Pid and stream Type to detect frames for.
|
||||||
int Analyze(const uchar *Data, int Length);
|
int Analyze(const uchar *Data, int Length);
|
||||||
@ -560,9 +565,11 @@ public:
|
|||||||
///< Analyze() needs to be called again with more actual data.
|
///< Analyze() needs to be called again with more actual data.
|
||||||
bool Synced(void) { return synced; }
|
bool Synced(void) { return synced; }
|
||||||
///< Returns true if the frame detector has synced on the data stream.
|
///< 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
|
///< 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; }
|
bool IndependentFrame(void) { return independentFrame; }
|
||||||
///< Returns true if a new frame was detected and this is an independent frame
|
///< 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
|
///< (i.e. one that can be displayed by itself, without using data from any
|
||||||
|
Loading…
x
Reference in New Issue
Block a user