2002-06-16 12:57:31 +02:00
|
|
|
/*
|
2002-12-22 11:33:08 +01:00
|
|
|
* recorder.c: The actual DVB recorder
|
2002-06-16 12:57:31 +02:00
|
|
|
*
|
|
|
|
* See the main source file 'vdr.c' for copyright information and
|
|
|
|
* how to reach the author.
|
|
|
|
*
|
2024-09-19 09:49:02 +02:00
|
|
|
* $Id: recorder.c 5.10 2024/09/19 09:49:02 kls Exp $
|
2002-06-16 12:57:31 +02:00
|
|
|
*/
|
|
|
|
|
2006-01-08 11:03:44 +01:00
|
|
|
#include "recorder.h"
|
2007-02-25 10:56:29 +01:00
|
|
|
#include "shutdown.h"
|
2002-06-16 12:57:31 +02:00
|
|
|
|
2012-09-22 11:55:26 +02:00
|
|
|
#define RECORDERBUFSIZE (MEGABYTE(20) / TS_SIZE * TS_SIZE) // multiple of TS_SIZE
|
2002-06-16 12:57:31 +02:00
|
|
|
|
2003-05-16 13:36:06 +02:00
|
|
|
// The maximum time we wait before assuming that a recorded video data stream
|
|
|
|
// is broken:
|
2013-10-12 12:08:37 +02:00
|
|
|
#define MAXBROKENTIMEOUT 30000 // milliseconds
|
2003-05-16 13:36:06 +02:00
|
|
|
|
2002-06-16 12:57:31 +02:00
|
|
|
#define MINFREEDISKSPACE (512) // MB
|
|
|
|
#define DISKCHECKINTERVAL 100 // seconds
|
|
|
|
|
2009-01-06 14:41:11 +01:00
|
|
|
// --- cRecorder -------------------------------------------------------------
|
2004-10-16 09:36:28 +02:00
|
|
|
|
2010-01-30 11:10:25 +01:00
|
|
|
cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority)
|
|
|
|
:cReceiver(Channel, Priority)
|
2009-01-06 14:41:11 +01:00
|
|
|
,cThread("recording")
|
2002-06-16 12:57:31 +02:00
|
|
|
{
|
2010-12-27 12:25:19 +01:00
|
|
|
recordingName = strdup(FileName);
|
2021-05-19 11:22:20 +02:00
|
|
|
recordingInfo = new cRecordingInfo(recordingName);
|
|
|
|
recordingInfo->Read();
|
2021-05-23 15:03:17 +02:00
|
|
|
oldErrors = max(0, recordingInfo->Errors()); // in case this is a re-started recording
|
2024-09-17 09:31:15 +02:00
|
|
|
errors = 0;
|
|
|
|
lastErrors = 0;
|
2021-05-19 11:22:20 +02:00
|
|
|
firstIframeSeen = false;
|
2010-12-27 12:25:19 +01:00
|
|
|
|
2009-01-06 14:41:11 +01:00
|
|
|
// Make sure the disk is up and running:
|
|
|
|
|
|
|
|
SpinUpDisk(FileName);
|
|
|
|
|
2011-09-04 10:13:14 +02:00
|
|
|
ringBuffer = new cRingBufferLinear(RECORDERBUFSIZE, MIN_TS_PACKETS_FOR_FRAME_DETECTOR * TS_SIZE, true, "Recorder");
|
2009-01-06 14:41:11 +01:00
|
|
|
ringBuffer->SetTimeouts(0, 100);
|
2012-09-22 11:52:33 +02:00
|
|
|
ringBuffer->SetIoThrottle();
|
2010-01-30 11:10:25 +01:00
|
|
|
|
|
|
|
int Pid = Channel->Vpid();
|
|
|
|
int Type = Channel->Vtype();
|
|
|
|
if (!Pid && Channel->Apid(0)) {
|
|
|
|
Pid = Channel->Apid(0);
|
2009-01-06 14:41:11 +01:00
|
|
|
Type = 0x04;
|
|
|
|
}
|
2010-01-30 11:10:25 +01:00
|
|
|
if (!Pid && Channel->Dpid(0)) {
|
|
|
|
Pid = Channel->Dpid(0);
|
2009-01-06 14:41:11 +01:00
|
|
|
Type = 0x06;
|
|
|
|
}
|
|
|
|
frameDetector = new cFrameDetector(Pid, Type);
|
2002-06-16 12:57:31 +02:00
|
|
|
index = NULL;
|
|
|
|
fileSize = 0;
|
|
|
|
lastDiskSpaceCheck = time(NULL);
|
2021-05-19 11:22:20 +02:00
|
|
|
lastErrorLog = 0;
|
2002-06-16 12:57:31 +02:00
|
|
|
fileName = new cFileName(FileName, true);
|
2009-05-24 15:11:28 +02:00
|
|
|
int PatVersion, PmtVersion;
|
|
|
|
if (fileName->GetLastPatPmtVersions(PatVersion, PmtVersion))
|
|
|
|
patPmtGenerator.SetVersions(PatVersion + 1, PmtVersion + 1);
|
|
|
|
patPmtGenerator.SetChannel(Channel);
|
2002-06-16 12:57:31 +02:00
|
|
|
recordFile = fileName->Open();
|
2005-10-31 13:14:26 +01:00
|
|
|
if (!recordFile)
|
2002-06-16 12:57:31 +02:00
|
|
|
return;
|
|
|
|
// Create the index file:
|
|
|
|
index = new cIndexFile(FileName, true);
|
|
|
|
if (!index)
|
|
|
|
esyslog("ERROR: can't allocate index");
|
|
|
|
// let's continue without index, so we'll at least have the recording
|
|
|
|
}
|
|
|
|
|
2009-01-06 14:41:11 +01:00
|
|
|
cRecorder::~cRecorder()
|
2002-06-16 12:57:31 +02:00
|
|
|
{
|
2009-01-06 14:41:11 +01:00
|
|
|
Detach();
|
2002-06-16 12:57:31 +02:00
|
|
|
delete index;
|
|
|
|
delete fileName;
|
2009-01-06 14:41:11 +01:00
|
|
|
delete frameDetector;
|
|
|
|
delete ringBuffer;
|
2010-12-27 12:25:19 +01:00
|
|
|
free(recordingName);
|
2002-06-16 12:57:31 +02:00
|
|
|
}
|
|
|
|
|
2021-05-19 11:22:20 +02:00
|
|
|
#define ERROR_LOG_DELTA 1 // seconds between logging errors
|
|
|
|
|
|
|
|
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) {
|
|
|
|
if (errors > lastErrors) {
|
|
|
|
int d = errors - lastErrors;
|
2024-09-17 09:31:15 +02:00
|
|
|
esyslog("%s: %d new error%s (total %d)", recordingName, d, d > 1 ? "s" : "", oldErrors + errors);
|
2021-05-19 11:22:20 +02:00
|
|
|
recordingInfo->SetErrors(oldErrors + errors);
|
|
|
|
recordingInfo->Write();
|
2021-06-19 14:21:16 +02:00
|
|
|
LOCK_RECORDINGS_WRITE;
|
|
|
|
Recordings->UpdateByName(recordingName);
|
2024-09-17 09:31:15 +02:00
|
|
|
lastErrors = errors;
|
2021-05-19 11:22:20 +02:00
|
|
|
}
|
|
|
|
lastErrorLog = time(NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-01-06 14:41:11 +01:00
|
|
|
bool cRecorder::RunningLowOnDiskSpace(void)
|
2002-06-16 12:57:31 +02:00
|
|
|
{
|
|
|
|
if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
|
|
|
|
int Free = FreeDiskSpaceMB(fileName->Name());
|
|
|
|
lastDiskSpaceCheck = time(NULL);
|
|
|
|
if (Free < MINFREEDISKSPACE) {
|
|
|
|
dsyslog("low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2009-01-06 14:41:11 +01:00
|
|
|
bool cRecorder::NextFile(void)
|
2002-06-16 12:57:31 +02:00
|
|
|
{
|
2011-09-04 10:13:14 +02:00
|
|
|
if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame
|
2009-01-06 14:41:11 +01:00
|
|
|
if (fileSize > MEGABYTE(off_t(Setup.MaxVideoFileSize)) || RunningLowOnDiskSpace()) {
|
2002-06-16 12:57:31 +02:00
|
|
|
recordFile = fileName->NextFile();
|
|
|
|
fileSize = 0;
|
|
|
|
}
|
|
|
|
}
|
2005-10-31 13:14:26 +01:00
|
|
|
return recordFile != NULL;
|
2002-06-16 12:57:31 +02:00
|
|
|
}
|
|
|
|
|
2004-10-16 09:36:28 +02:00
|
|
|
void cRecorder::Activate(bool On)
|
|
|
|
{
|
2009-01-06 14:41:11 +01:00
|
|
|
if (On)
|
2004-10-16 09:36:28 +02:00
|
|
|
Start();
|
2005-08-13 13:17:24 +02:00
|
|
|
else
|
2004-10-16 09:36:28 +02:00
|
|
|
Cancel(3);
|
|
|
|
}
|
|
|
|
|
2015-09-05 11:49:56 +02:00
|
|
|
void cRecorder::Receive(const uchar *Data, int Length)
|
2004-10-16 09:36:28 +02:00
|
|
|
{
|
2005-08-14 11:24:57 +02:00
|
|
|
if (Running()) {
|
2015-09-12 14:56:15 +02:00
|
|
|
static const uchar aff[TS_SIZE - 4] = { 0xB7, 0x00,
|
2015-09-11 11:18:40 +02:00
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF}; // Length is always TS_SIZE!
|
|
|
|
if ((Data[3] & 0b00110000) == 0b00100000 && !memcmp(Data + 4, aff, sizeof(aff)))
|
|
|
|
return; // Adaptation Field Filler found, skipping
|
2004-10-16 09:36:28 +02:00
|
|
|
int p = ringBuffer->Put(Data, Length);
|
2005-08-14 11:24:57 +02:00
|
|
|
if (p != Length && Running())
|
2004-10-16 09:36:28 +02:00
|
|
|
ringBuffer->ReportOverflow(Length - p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void cRecorder::Action(void)
|
|
|
|
{
|
2013-10-12 12:08:37 +02:00
|
|
|
cTimeMs t(MAXBROKENTIMEOUT);
|
2009-01-06 14:41:11 +01:00
|
|
|
bool InfoWritten = false;
|
2024-09-19 09:49:02 +02:00
|
|
|
bool pendIndependentFrame = false;
|
|
|
|
uint16_t pendNumber = 0;
|
|
|
|
off_t pendFileSize = 0;
|
|
|
|
bool pendErrors = false;
|
|
|
|
bool pendMissing = false;
|
|
|
|
// Check if this is a resumed recording, in which case we definitely missed frames:
|
|
|
|
NextFile();
|
|
|
|
if (fileName->Number() > 1 || oldErrors)
|
|
|
|
frameDetector->SetMissing();
|
2005-08-14 11:24:57 +02:00
|
|
|
while (Running()) {
|
2004-10-16 09:36:28 +02:00
|
|
|
int r;
|
|
|
|
uchar *b = ringBuffer->Get(r);
|
|
|
|
if (b) {
|
2009-01-06 14:41:11 +01:00
|
|
|
int Count = frameDetector->Analyze(b, r);
|
|
|
|
if (Count) {
|
|
|
|
if (!Running() && frameDetector->IndependentFrame()) // finish the recording before the next independent frame
|
|
|
|
break;
|
2009-03-27 13:38:59 +01:00
|
|
|
if (frameDetector->Synced()) {
|
2009-01-06 14:41:11 +01:00
|
|
|
if (!InfoWritten) {
|
2023-12-28 21:23:19 +01:00
|
|
|
if ((frameDetector->FramesPerSecond() > 0 && DoubleEqual(recordingInfo->FramesPerSecond(), DEFAULTFRAMESPERSECOND) && !DoubleEqual(recordingInfo->FramesPerSecond(), frameDetector->FramesPerSecond())) ||
|
|
|
|
frameDetector->FrameWidth() != recordingInfo->FrameWidth() ||
|
|
|
|
frameDetector->FrameHeight() != recordingInfo->FrameHeight() ||
|
|
|
|
frameDetector->AspectRatio() != recordingInfo->AspectRatio()) {
|
2021-05-19 11:22:20 +02:00
|
|
|
recordingInfo->SetFramesPerSecond(frameDetector->FramesPerSecond());
|
2023-12-28 21:23:19 +01:00
|
|
|
recordingInfo->SetFrameParams(frameDetector->FrameWidth(), frameDetector->FrameHeight(), frameDetector->ScanType(), frameDetector->AspectRatio());
|
2021-05-19 11:22:20 +02:00
|
|
|
recordingInfo->Write();
|
|
|
|
LOCK_RECORDINGS_WRITE;
|
|
|
|
Recordings->UpdateByName(recordingName);
|
2009-01-06 14:41:11 +01:00
|
|
|
}
|
|
|
|
InfoWritten = true;
|
2014-01-01 12:53:40 +01:00
|
|
|
cRecordingUserCommand::InvokeCommand(RUC_STARTRECORDING, recordingName);
|
2009-01-06 14:41:11 +01:00
|
|
|
}
|
2021-05-19 11:22:20 +02:00
|
|
|
if (firstIframeSeen || frameDetector->IndependentFrame()) {
|
|
|
|
firstIframeSeen = true; // start recording with the first I-frame
|
2011-09-04 10:13:14 +02:00
|
|
|
if (!NextFile())
|
2009-11-15 15:33:55 +01:00
|
|
|
break;
|
2024-09-17 09:31:15 +02:00
|
|
|
int PreviousErrors = 0;
|
2024-09-17 11:30:28 +02:00
|
|
|
int MissingFrames = 0;
|
|
|
|
if (frameDetector->NewFrame(&PreviousErrors, &MissingFrames)) {
|
2024-09-19 09:49:02 +02:00
|
|
|
if (index) {
|
|
|
|
if (pendNumber > 0)
|
|
|
|
index->Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
|
|
|
|
pendIndependentFrame = frameDetector->IndependentFrame();
|
|
|
|
pendNumber = fileName->Number();
|
|
|
|
pendFileSize = fileSize;
|
|
|
|
pendErrors = PreviousErrors;
|
|
|
|
pendMissing = MissingFrames;
|
|
|
|
}
|
2024-09-17 09:39:50 +02:00
|
|
|
if (PreviousErrors)
|
|
|
|
errors++;
|
2024-09-17 11:30:28 +02:00
|
|
|
if (MissingFrames)
|
|
|
|
errors++;
|
2021-05-19 11:22:20 +02:00
|
|
|
}
|
2009-11-15 15:33:55 +01:00
|
|
|
if (frameDetector->IndependentFrame()) {
|
|
|
|
recordFile->Write(patPmtGenerator.GetPat(), TS_SIZE);
|
|
|
|
fileSize += TS_SIZE;
|
|
|
|
int Index = 0;
|
|
|
|
while (uchar *pmt = patPmtGenerator.GetPmt(Index)) {
|
|
|
|
recordFile->Write(pmt, TS_SIZE);
|
|
|
|
fileSize += TS_SIZE;
|
|
|
|
}
|
2014-02-21 09:20:36 +01:00
|
|
|
t.Set(MAXBROKENTIMEOUT);
|
2009-11-15 15:33:55 +01:00
|
|
|
}
|
|
|
|
if (recordFile->Write(b, Count) < 0) {
|
|
|
|
LOG_ERROR_STR(fileName->Name());
|
|
|
|
break;
|
|
|
|
}
|
2021-05-19 11:22:20 +02:00
|
|
|
HandleErrors();
|
2009-11-15 15:33:55 +01:00
|
|
|
fileSize += Count;
|
2009-01-06 14:41:11 +01:00
|
|
|
}
|
|
|
|
}
|
2004-10-16 09:36:28 +02:00
|
|
|
ringBuffer->Del(Count);
|
2009-01-06 14:41:11 +01:00
|
|
|
}
|
|
|
|
}
|
2013-10-12 12:08:37 +02:00
|
|
|
if (t.TimedOut()) {
|
2024-09-19 09:49:02 +02:00
|
|
|
if (pendNumber > 0)
|
|
|
|
index->Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
|
|
|
|
frameDetector->SetMissing();
|
2024-09-17 09:31:15 +02:00
|
|
|
errors += MAXBROKENTIMEOUT / 1000 * frameDetector->FramesPerSecond();
|
2021-05-19 11:22:20 +02:00
|
|
|
HandleErrors(true);
|
2009-01-06 14:41:11 +01:00
|
|
|
esyslog("ERROR: video data stream broken");
|
|
|
|
ShutdownHandler.RequestEmergencyExit();
|
2013-10-12 12:08:37 +02:00
|
|
|
t.Set(MAXBROKENTIMEOUT);
|
2004-10-16 09:36:28 +02:00
|
|
|
}
|
2002-06-16 12:57:31 +02:00
|
|
|
}
|
2024-09-19 09:49:02 +02:00
|
|
|
if (pendNumber > 0)
|
|
|
|
index->Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
|
2021-05-19 11:22:20 +02:00
|
|
|
HandleErrors(true);
|
2002-06-16 12:57:31 +02:00
|
|
|
}
|