1
0
mirror of https://github.com/VDR4Arch/vdr.git synced 2023-10-10 13:36:52 +02:00

The recording format is now Transport Stream

This commit is contained in:
Klaus Schmidinger 2009-01-06 14:41:11 +01:00
parent 7470253c60
commit 7de7ede26f
20 changed files with 832 additions and 2488 deletions

View File

@ -284,6 +284,8 @@ Artur Skawina <skawina@geocities.com>
for fixing calculating the cache size in cUnbufferedFile::Read()
for making the /video/.update file be touched _after_ an editing process is finished
in order to avoid excessive disk access
for helping to get the IndexToHMSF() calculation right with non-integer frame
rates
Werner Fink <werner@suse.de>
for making I/O more robust by handling EINTR

49
HISTORY
View File

@ -5849,7 +5849,7 @@ Video Disk Recorder Revision History
the patch from ftp://ftp.cadsoft.de/vdr/Developer/av7110_v4ldvb_api5_audiobuf_test_1.diff
to the driver (thanks to Oliver Endriss).
2008-12-28: Version 1.7.3
2009-01-06: Version 1.7.3
- Updated the Russian OSD texts (thanks to Oleg Roitburd).
- Fixed handling the 'pointer field' in generating and parsing PAT/PMT (thanks to
@ -5859,3 +5859,50 @@ Video Disk Recorder Revision History
- Added a poll to cDvbDevice::PlayVideo() and cDvbDevice::PlayAudio() to avoid
excessive CPU load (this is just a makeshift solution until the FF DVB cards
can play TS directly).
- The recording format is now Transport Stream. Existing recordings in PES format
can still be replayed and edited, but new recordings are done in TS.
All code for recording in PES has been removed.
The following changes were made to switch to TS recording format:
+ The index file format has been changed to support file sizes of up to 1TB
(previously 2GB), and up to 65535 separate files per recording (previously
255).
+ The recording file names are now of the form 00001.ts (previously 001.vdr).
+ The frame rate is now detected by looking at two subsequent PTS values.
The "frame duration" (in multiples of 1/90000) is stored in the info.vdr
file using the new tag F (thanks to Artur Skawina for helping to get the
IndexToHMSF() calculation right).
+ Several functions now have an additional parameter FramesPerSecond.
+ Several functions now have an additional parameter IsPesRecording.
+ The functionality of cFileWriter was moved into cRecorder, and cRemux is
now obsolete. This also avoids one level of data copying while recording.
+ cRemux, cRingBufferLinearPes, cTS2PES and all c*Repacker classes have been
removed.
+ A PAT/PMT is inserted before every independent frame, so that no extra
measures need to be taken when editing a recording.
+ The directory name for a recording has been changed from
YYYY-MM-DD-hh[.:]mm.pr.lt.rec (pr=priority, lt=lifetime) to
YYYY-MM-DD-hh.mm.ch-ri.rec (ch=channel, ri=resumeId).
Priority and Lifetime are now stored in the info.vdr file with the new
tags P and L (if no such file exists, the maximum values are assumed by
default, which avoids inadvertently deleting a recording if disk space
is low). No longer storing Priority and Lifetime in the directory name
avoids starting a new recording if one of these is changed in the timer
and the recording is re-started for some reason.
Instead of Priority and Lifetime, the directory name now contains the
channel number from which the recording was made, and the "resume id" of
this instance of VDR. This avoids problems if several VDR instances record
the same show on different channels, or even on the same channel.
The '-' between channel number and resumeId prevents older versions of
VDR from "seeing" these recordings, which makes sure they won't even try
to replay them, or remove them in case the disk runs full.
+ The semantics of PlayTs*() have been changed. These functions are now
required to return the given Length (which is TS_SIZE) if they have
processed the TS packet.
+ The files "index", "info", "marks" and "resume" within a TS recording
directory are now created without the ".vdr" extension.
+ The "resume" file is no longer a binary file, but contains tagged lines
to be able to store additional information, like the selected audio or
subtitle track.
+ cDevice::StillPicture() will now be called with either TS or PES data.
+ cDvbPlayer::Goto() no longer appends a "sequence end code" to the data.
If the output device needs this, it has to take care of it by itself.

View File

@ -4,7 +4,7 @@
# See the main source file 'vdr.c' for copyright information and
# how to reach the author.
#
# $Id: Makefile 2.2 2008/05/03 10:13:43 kls Exp $
# $Id: Makefile 2.3 2009/01/05 13:04:10 kls Exp $
.DELETE_ON_ERROR:
@ -60,6 +60,8 @@ DEFINES += -DLIRC_DEVICE=\"$(LIRC_DEVICE)\" -DRCU_DEVICE=\"$(RCU_DEVICE)\"
DEFINES += -D_GNU_SOURCE
DEFINES += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE
DEFINES += -DVIDEODIR=\"$(VIDEODIR)\"
DEFINES += -DCONFDIR=\"$(CONFDIR)\"
DEFINES += -DPLUGINDIR=\"$(PLUGINLIBDIR)\"

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: config.h 2.4 2008/09/14 13:46:13 kls Exp $
* $Id: config.h 2.5 2009/01/05 13:04:10 kls Exp $
*/
#ifndef __CONFIG_H
@ -22,13 +22,13 @@
// VDR's own version number:
#define VDRVERSION "1.7.2"
#define VDRVERSNUM 10702 // Version * 10000 + Major * 100 + Minor
#define VDRVERSION "1.7.3"
#define VDRVERSNUM 10703 // Version * 10000 + Major * 100 + Minor
// The plugin API's version number:
#define APIVERSION "1.7.0"
#define APIVERSNUM 10700 // Version * 10000 + Major * 100 + Minor
#define APIVERSION "1.7.3"
#define APIVERSNUM 10703 // Version * 10000 + Major * 100 + Minor
// When loading plugins, VDR searches them by their APIVERSION, which
// may be smaller than VDRVERSION in case there have been no changes to

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: cutter.c 1.18 2008/01/13 12:22:21 kls Exp $
* $Id: cutter.c 2.1 2009/01/06 14:40:48 kls Exp $
*/
#include "cutter.h"
@ -37,12 +37,14 @@ cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName)
fromFile = toFile = NULL;
fromFileName = toFileName = NULL;
fromIndex = toIndex = NULL;
if (fromMarks.Load(FromFileName) && fromMarks.Count()) {
fromFileName = new cFileName(FromFileName, false, true);
toFileName = new cFileName(ToFileName, true, true);
fromIndex = new cIndexFile(FromFileName, false);
toIndex = new cIndexFile(ToFileName, true);
toMarks.Load(ToFileName); // doesn't actually load marks, just sets the file name
cRecording Recording(FromFileName);
bool 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
Start();
}
else
@ -69,7 +71,7 @@ void cCuttingThread::Action(void)
fromFile->SetReadAhead(MEGABYTE(20));
int Index = Mark->position;
Mark = fromMarks.Next(Mark);
int FileSize = 0;
off_t FileSize = 0;
int CurrentFileNumber = 0;
int LastIFrame = 0;
toMarks.Add(0);
@ -78,9 +80,10 @@ void cCuttingThread::Action(void)
bool LastMark = false;
bool cutIn = true;
while (Running()) {
uchar FileNumber;
int FileOffset, Length;
uchar PictureType;
uint16_t FileNumber;
off_t FileOffset;
int Length;
bool Independent;
// Make sure there is enough disk space:
@ -88,7 +91,7 @@ void cCuttingThread::Action(void)
// Read one frame:
if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &PictureType, &Length)) {
if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &Independent, &Length)) {
if (FileNumber != CurrentFileNumber) {
fromFile = fromFileName->SetOffset(FileNumber, FileOffset);
fromFile->SetReadAhead(MEGABYTE(20));
@ -119,7 +122,7 @@ void cCuttingThread::Action(void)
// Write one frame:
if (PictureType == I_FRAME) { // every file shall start with an I_FRAME
if (Independent) { // every file shall start with an independent frame
if (LastMark) // edited version shall end before next I-frame
break;
if (FileSize > MEGABYTE(Setup.MaxVideoFileSize)) {
@ -141,7 +144,7 @@ void cCuttingThread::Action(void)
error = "safe_write";
break;
}
if (!toIndex->Write(PictureType, toFileName->Number(), FileSize)) {
if (!toIndex->Write(Independent, toFileName->Number(), FileSize)) {
error = "toIndex";
break;
}

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: device.c 2.4 2008/12/13 14:30:28 kls Exp $
* $Id: device.c 2.5 2009/01/06 09:55:13 kls Exp $
*/
#include "device.h"
@ -1014,6 +1014,48 @@ void cDevice::Mute(void)
void cDevice::StillPicture(const uchar *Data, int Length)
{
if (Data[0] == 0x47) {
// TS data
cTsToPes TsToPes;
uchar *buf = NULL;
int Size = 0;
while (Length >= TS_SIZE) {
int PayloadOffset = TsPayloadOffset(Data);
int Pid = TsPid(Data);
if (Pid == 0)
patPmtParser.ParsePat(Data + PayloadOffset, TS_SIZE - PayloadOffset);
else if (Pid == patPmtParser.PmtPid())
patPmtParser.ParsePmt(Data + PayloadOffset, TS_SIZE - PayloadOffset);
else if (Pid == patPmtParser.Vpid()) {
if (TsPayloadStart(Data)) {
int l;
while (const uchar *p = TsToPes.GetPes(l)) {
int Offset = Size;
Size += l;
buf = (uchar *)realloc(buf, Size);
if (!buf)
return;
memcpy(buf + Offset, p, l);
}
TsToPes.Reset();
}
TsToPes.PutTs(Data, TS_SIZE);
}
Length -= TS_SIZE;
Data += TS_SIZE;
}
int l;
while (const uchar *p = TsToPes.GetPes(l)) {
int Offset = Size;
Size += l;
buf = (uchar *)realloc(buf, Size);
if (!buf)
return;
memcpy(buf + Offset, p, l);
}
StillPicture(buf, Size);
free(buf);
}
}
bool cDevice::Replaying(void) const
@ -1301,6 +1343,11 @@ int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly)
return Length;
}
}
else if (Data == NULL) {
tsToPesVideo.Reset();
tsToPesAudio.Reset();
tsToPesSubtitle.Reset();
}
return -1;
}
@ -1328,7 +1375,6 @@ bool cDevice::Receiving(bool CheckAny) const
return false;
}
#define TS_SCRAMBLING_CONTROL 0xC0
#define TS_SCRAMBLING_TIMEOUT 3 // seconds to wait until a TS becomes unscrambled
#define TS_SCRAMBLING_TIME_OK 10 // seconds before a Channel/CAM combination is marked as known to decrypt

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: device.h 2.3 2008/09/14 13:44:54 kls Exp $
* $Id: device.h 2.4 2009/01/05 16:28:06 kls Exp $
*/
#ifndef __DEVICE_H
@ -518,8 +518,7 @@ protected:
///< Data points to exactly one complete TS packet of the given Length
///< (which is always TS_SIZE).
///< PlayTsVideo() shall process the packet either as a whole (returning
///< a positive number, which needs not necessarily be Length) or not at all
///< (returning 0 or -1 and setting 'errno' to EAGAIN).
///< Length) or not at all (returning 0 or -1 and setting 'errno' accordingly).
///< The default implementation collects all incoming TS payload belonging
///< to one PES packet and calls PlayVideo() with the resulting packet.
virtual int PlayTsAudio(const uchar *Data, int Length);
@ -527,8 +526,7 @@ protected:
///< Data points to exactly one complete TS packet of the given Length
///< (which is always TS_SIZE).
///< PlayTsAudio() shall process the packet either as a whole (returning
///< a positive number, which needs not necessarily be Length) or not at all
///< (returning 0 or -1 and setting 'errno' to EAGAIN).
///< Length) or not at all (returning 0 or -1 and setting 'errno' accordingly).
///< The default implementation collects all incoming TS payload belonging
///< to one PES packet and calls PlayAudio() with the resulting packet.
virtual int PlayTsSubtitle(const uchar *Data, int Length);
@ -536,8 +534,7 @@ protected:
///< Data points to exactly one complete TS packet of the given Length
///< (which is always TS_SIZE).
///< PlayTsSubtitle() shall process the packet either as a whole (returning
///< a positive number, which needs not necessarily be Length) or not at all
///< (returning 0 or -1 and setting 'errno' to EAGAIN).
///< Length) or not at all (returning 0 or -1 and setting 'errno' accordingly).
///< The default implementation collects all incoming TS payload belonging
///< to one PES packet and displays the resulting subtitle via the OSD.
public:
@ -573,6 +570,10 @@ public:
///< all registered cAudio objects are notified.
virtual void StillPicture(const uchar *Data, int Length);
///< Displays the given I-frame as a still picture.
///< Data points either to TS (first byte is 0x47) or PES (first byte
///< is 0x00) data of the given Length. The default implementation
///< converts TS to PES and calls itself again, allowing a derived class
///< to display PES if it can't handle TS directly.
virtual bool Poll(cPoller &Poller, int TimeoutMs = 0);
///< Returns true if the device itself or any of the file handles in
///< Poller is ready for further action.
@ -600,12 +601,14 @@ public:
///< which is necessary for trick modes like 'fast forward'.
///< Data points to a single TS packet, Length is always TS_SIZE (the total
///< size of a single TS packet).
///< If Data is NULL any leftover data from a previous call will be
///< discarded.
///< A derived device can reimplement this function to handle the
///< TS packets itself. Any packets the derived function can't handle
///< must be sent to the base class function. This applies especially
///< to the PAT/PMT packets.
///< Returns -1 in case of error, otherwise the number of actually
///< processed bytes is returned, which may be less than Length.
///< processed bytes is returned, which must be Length.
///< PlayTs() shall process the packet either as a whole (returning
///< a positive number, which needs not necessarily be Length) or not at all
///< (returning 0 or -1 and setting 'errno' to EAGAIN).

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: dvbdevice.c 2.8 2008/12/28 10:59:51 kls Exp $
* $Id: dvbdevice.c 2.9 2009/01/05 16:08:18 kls Exp $
*/
#include "dvbdevice.h"
@ -1213,7 +1213,11 @@ void cDvbDevice::Mute(void)
void cDvbDevice::StillPicture(const uchar *Data, int Length)
{
if (Data[0] == 0x00 && Data[1] == 0x00 && Data[2] == 0x01 && (Data[3] & 0xF0) == 0xE0) {
if (Data[0] == 0x47) {
// TS data
cDevice::StillPicture(Data, Length);
}
else if (Data[0] == 0x00 && Data[1] == 0x00 && Data[2] == 0x01 && (Data[3] & 0xF0) == 0xE0) {
// PES data
char *buf = MALLOC(char, Length);
if (!buf)
@ -1331,8 +1335,8 @@ int cDvbDevice::PlayTsVideo(const uchar *Data, int Length)
int cDvbDevice::PlayTsAudio(const uchar *Data, int Length)
{
Length = TsGetPayload(&Data);
return PlayAudio(Data, Length, 0);
int w = PlayAudio(Data, TsGetPayload(&Data), 0);
return w >= 0 ? Length : w;
}
bool cDvbDevice::OpenDvr(void)

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: dvbplayer.c 1.48 2008/02/09 15:10:54 kls Exp $
* $Id: dvbplayer.c 2.1 2009/01/05 16:52:40 kls Exp $
*/
#include "dvbplayer.h"
@ -182,8 +182,8 @@ bool cNonBlockingFileReader::WaitForDataMs(int msToWait)
#define PLAYERBUFSIZE MEGABYTE(1)
// The number of frames to back up when resuming an interrupted replay session:
#define RESUMEBACKUP (10 * FRAMESPERSEC)
// The number of seconds to back up when resuming an interrupted replay session:
#define RESUMEBACKUP 10
class cDvbPlayer : public cPlayer, cThread {
private:
@ -196,6 +196,8 @@ private:
cFileName *fileName;
cIndexFile *index;
cUnbufferedFile *replayFile;
double framesPerSecond;
bool isPesRecording;
bool eof;
bool firstPacket;
ePlayModes playMode;
@ -223,6 +225,7 @@ public:
int SkipFrames(int Frames);
void SkipSeconds(int Seconds);
void Goto(int Position, bool Still = false);
virtual double FramesPerSecond(void) { return framesPerSecond; }
virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false);
virtual bool GetReplayMode(bool &Play, bool &Forward, int &Speed);
};
@ -240,6 +243,9 @@ cDvbPlayer::cDvbPlayer(const char *FileName)
ringBuffer = NULL;
backTrace = NULL;
index = NULL;
cRecording Recording(FileName);
framesPerSecond = Recording.FramesPerSecond();
isPesRecording = Recording.IsPesRecording();
eof = false;
firstPacket = true;
playMode = pmPlay;
@ -249,13 +255,13 @@ cDvbPlayer::cDvbPlayer(const char *FileName)
readFrame = NULL;
playFrame = NULL;
isyslog("replay %s", FileName);
fileName = new cFileName(FileName, false);
fileName = new cFileName(FileName, false, false, isPesRecording);
replayFile = fileName->Open();
if (!replayFile)
return;
ringBuffer = new cRingBufferFrame(PLAYERBUFSIZE);
// Create the index file:
index = new cIndexFile(FileName, false);
index = new cIndexFile(FileName, false, isPesRecording);
if (!index)
esyslog("ERROR: can't allocate index");
else if (!index->Ok()) {
@ -327,8 +333,8 @@ int cDvbPlayer::Resume(void)
if (index) {
int Index = index->GetResume();
if (Index >= 0) {
uchar FileNumber;
int FileOffset;
uint16_t FileNumber;
off_t FileOffset;
if (index->Get(Index, &FileNumber, &FileOffset) && NextFile(FileNumber, FileOffset))
return Index;
}
@ -341,7 +347,7 @@ bool cDvbPlayer::Save(void)
if (index) {
int Index = writeIndex;
if (Index >= 0) {
Index -= RESUMEBACKUP;
Index -= RESUMEBACKUP * framesPerSecond;
if (Index > 0)
Index = index->GetNextIFrame(Index, false);
else
@ -371,7 +377,7 @@ void cDvbPlayer::Action(void)
readIndex = Resume();
if (readIndex >= 0)
isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true));
isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond));
nonBlockingFileReader = new cNonBlockingFileReader;
int Length = 0;
@ -397,8 +403,8 @@ void cDvbPlayer::Action(void)
if (!readFrame && (replayFile || readIndex >= 0)) {
if (!nonBlockingFileReader->Reading()) {
if (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) {
uchar FileNumber;
int FileOffset;
uint16_t FileNumber;
off_t FileOffset;
bool TimeShiftMode = index->IsStillRecording();
int Index = -1;
if (DeviceHasIBPTrickSpeed() && playDir == pdForward) {
@ -435,8 +441,8 @@ void cDvbPlayer::Action(void)
readIndex = Index;
}
else if (index) {
uchar FileNumber;
int FileOffset;
uint16_t FileNumber;
off_t FileOffset;
readIndex++;
if (!(index->Get(readIndex, &FileNumber, &FileOffset, NULL, &Length) && NextFile(FileNumber, FileOffset))) {
readIndex = -1;
@ -496,14 +502,22 @@ void cDvbPlayer::Action(void)
pc = playFrame->Count();
if (p) {
if (firstPacket) {
if (isPesRecording) {
PlayPes(NULL, 0);
cRemux::SetBrokenLink(p, pc);
}
else
PlayTs(NULL, 0);
firstPacket = false;
}
}
}
if (p) {
int w = PlayPes(p, pc, playMode != pmPlay);
int w;
if (isPesRecording)
w = PlayPes(p, pc, playMode != pmPlay);
else
w = PlayTs(p, TS_SIZE, playMode != pmPlay);
if (w > 0) {
p += w;
pc -= w;
@ -513,7 +527,7 @@ void cDvbPlayer::Action(void)
break;
}
}
if (pc == 0) {
if (pc <= 0) {
writeIndex = playFrame->Index();
backTrace->Add(playFrame->Index(), playFrame->Count());
ringBuffer->Drop(playFrame);
@ -678,7 +692,7 @@ void cDvbPlayer::SkipSeconds(int Seconds)
Empty();
int Index = writeIndex;
if (Index >= 0) {
Index = max(Index + Seconds * FRAMESPERSEC, 0);
Index = max(Index + SecondsToFrames(Seconds, framesPerSecond), 0);
if (Index > 0)
Index = index->GetNextIFrame(Index, false, NULL, NULL, NULL, true);
if (Index >= 0)
@ -695,38 +709,16 @@ void cDvbPlayer::Goto(int Index, bool Still)
Empty();
if (++Index <= 0)
Index = 1; // not '0', to allow GetNextIFrame() below to work!
uchar FileNumber;
int FileOffset, Length;
uint16_t FileNumber;
off_t FileOffset;
int Length;
Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset, &Length);
if (Index >= 0 && NextFile(FileNumber, FileOffset) && Still) {
uchar b[MAXFRAMESIZE + 4 + 5 + 4];
uchar b[MAXFRAMESIZE];
int r = ReadFrame(replayFile, b, Length, sizeof(b));
if (r > 0) {
if (playMode == pmPause)
DevicePlay();
// append sequence end code to get the image shown immediately with softdevices
if (r > 6 && (b[3] & 0xF0) == 0xE0) { // make sure to append it only to a video packet
b[r++] = 0x00;
b[r++] = 0x00;
b[r++] = 0x01;
b[r++] = b[3];
if (b[6] & 0x80) { // MPEG 2
b[r++] = 0x00;
b[r++] = 0x07;
b[r++] = 0x80;
b[r++] = 0x00;
b[r++] = 0x00;
}
else { // MPEG 1
b[r++] = 0x00;
b[r++] = 0x05;
b[r++] = 0x0F;
}
b[r++] = 0x00;
b[r++] = 0x00;
b[r++] = 0x01;
b[r++] = 0xB7;
}
DeviceStillPicture(b, r);
}
playMode = pmStill;

21
menu.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: menu.c 2.3 2008/11/22 15:18:00 kls Exp $
* $Id: menu.c 2.4 2009/01/06 14:34:17 kls Exp $
*/
#include "menu.h"
@ -1989,11 +1989,14 @@ eOSState cMenuRecordings::Rewind(void)
return osContinue;
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
if (ri && !ri->IsDirectory()) {
cRecording *recording = GetRecording(ri);
if (recording) {
cDevice::PrimaryDevice()->StopReplay(); // must do this first to be able to rewind the currently replayed recording
cResumeFile ResumeFile(ri->FileName());
cResumeFile ResumeFile(ri->FileName(), recording->IsPesRecording());
ResumeFile.Delete();
return Play();
}
}
return osContinue;
}
@ -4014,9 +4017,9 @@ cReplayControl::cReplayControl(void)
lastSpeed = -2; // an invalid value
timeoutShow = 0;
timeSearchActive = false;
marks.Load(fileName);
cRecording Recording(fileName);
cStatus::MsgReplaying(this, Recording.Name(), Recording.FileName(), true);
marks.Load(fileName, Recording.FramesPerSecond(), Recording.IsPesRecording());
SetTrackDescriptions(false);
}
@ -4126,7 +4129,7 @@ bool cReplayControl::ShowProgress(bool Initial)
lastCurrent = lastTotal = -1;
}
if (Total != lastTotal) {
displayReplay->SetTotal(IndexToHMSF(Total));
displayReplay->SetTotal(IndexToHMSF(Total, false, FramesPerSecond()));
if (!Initial)
displayReplay->Flush();
}
@ -4134,7 +4137,7 @@ bool cReplayControl::ShowProgress(bool Initial)
displayReplay->SetProgress(Current, Total);
if (!Initial)
displayReplay->Flush();
displayReplay->SetCurrent(IndexToHMSF(Current, displayFrames));
displayReplay->SetCurrent(IndexToHMSF(Current, displayFrames, FramesPerSecond()));
displayReplay->Flush();
lastCurrent = Current;
}
@ -4167,8 +4170,8 @@ void cReplayControl::TimeSearchProcess(eKeys Key)
{
#define STAY_SECONDS_OFF_END 10
int Seconds = (timeSearchTime >> 24) * 36000 + ((timeSearchTime & 0x00FF0000) >> 16) * 3600 + ((timeSearchTime & 0x0000FF00) >> 8) * 600 + (timeSearchTime & 0x000000FF) * 60;
int Current = (lastCurrent / FRAMESPERSEC);
int Total = (lastTotal / FRAMESPERSEC);
int Current = (lastCurrent / FramesPerSecond());
int Total = (lastTotal / FramesPerSecond());
switch (Key) {
case k0 ... k9:
if (timeSearchPos < 4) {
@ -4195,7 +4198,7 @@ void cReplayControl::TimeSearchProcess(eKeys Key)
case kDown:
case kOk:
Seconds = min(Total - STAY_SECONDS_OFF_END, Seconds);
Goto(Seconds * FRAMESPERSEC, Key == kDown || Key == kPause || Key == kOk);
Goto(SecondsToFrames(Seconds, FramesPerSecond()), Key == kDown || Key == kPause || Key == kOk);
timeSearchActive = false;
break;
default:
@ -4317,7 +4320,7 @@ void cReplayControl::EditTest(void)
if ((m->Index() & 0x01) != 0)
m = marks.Next(m);
if (m) {
Goto(m->position - SecondsToFrames(3));
Goto(m->position - SecondsToFrames(3, FramesPerSecond()));
Play();
}
}

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: player.h 2.1 2008/08/15 14:07:48 kls Exp $
* $Id: player.h 2.2 2009/01/05 13:04:10 kls Exp $
*/
#ifndef __PLAYER_H
@ -50,6 +50,8 @@ public:
cPlayer(ePlayMode PlayMode = pmAudioVideo);
virtual ~cPlayer();
bool IsAttached(void) { return device != NULL; }
virtual double FramesPerSecond(void) { return DEFAULTFRAMESPERSECOND; }
// Returns the number of frames per second of the currently played material.
virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false) { return false; }
// Returns the current and total frame index, optionally snapped to the
// nearest I-frame.
@ -82,6 +84,7 @@ public:
virtual ~cControl();
virtual void Hide(void) = 0;
virtual cOsdObject *GetInfo(void);
double FramesPerSecond(void) { return player->FramesPerSecond(); }
bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false) { return player->GetIndex(Current, Total, SnapToIFrame); }
bool GetReplayMode(bool &Play, bool &Forward, int &Speed) { return player->GetReplayMode(Play, Forward, Speed); }
static void Launch(cControl *Control);

View File

@ -4,13 +4,10 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: recorder.c 1.19 2007/02/24 16:36:24 kls Exp $
* $Id: recorder.c 2.1 2009/01/06 12:38:01 kls Exp $
*/
#include "recorder.h"
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include "shutdown.h"
#define RECORDERBUFSIZE MEGABYTE(5)
@ -22,33 +19,34 @@
#define MINFREEDISKSPACE (512) // MB
#define DISKCHECKINTERVAL 100 // seconds
// --- cFileWriter -----------------------------------------------------------
// --- cRecorder -------------------------------------------------------------
class cFileWriter : public cThread {
private:
cRemux *remux;
cFileName *fileName;
cIndexFile *index;
uchar pictureType;
int fileSize;
cUnbufferedFile *recordFile;
time_t lastDiskSpaceCheck;
bool RunningLowOnDiskSpace(void);
bool NextFile(void);
protected:
virtual void Action(void);
public:
cFileWriter(const char *FileName, cRemux *Remux);
virtual ~cFileWriter();
};
cFileWriter::cFileWriter(const char *FileName, cRemux *Remux)
:cThread("file writer")
cRecorder::cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids)
:cReceiver(ChannelID, Priority, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids)
,cThread("recording")
,recordingInfo(FileName)
{
// Make sure the disk is up and running:
SpinUpDisk(FileName);
ringBuffer = new cRingBufferLinear(RECORDERBUFSIZE, TS_SIZE * 2, true, "Recorder");
ringBuffer->SetTimeouts(0, 100);
cChannel *Channel = Channels.GetByChannelID(ChannelID);
int Pid = VPid;
int Type = Channel ? Channel->Vtype() : 0;
if (!Pid && APids) {
Pid = APids[0];
Type = 0x04;
}
if (!Pid && DPids) {
Pid = DPids[0];
Type = 0x06;
}
frameDetector = new cFrameDetector(Pid, Type);
patPmtGenerator.GeneratePmt(ChannelID);
fileName = NULL;
remux = Remux;
index = NULL;
pictureType = NO_PICTURE;
fileSize = 0;
lastDiskSpaceCheck = time(NULL);
fileName = new cFileName(FileName, true);
@ -62,14 +60,16 @@ cFileWriter::cFileWriter(const char *FileName, cRemux *Remux)
// let's continue without index, so we'll at least have the recording
}
cFileWriter::~cFileWriter()
cRecorder::~cRecorder()
{
Cancel(3);
Detach();
delete index;
delete fileName;
delete frameDetector;
delete ringBuffer;
}
bool cFileWriter::RunningLowOnDiskSpace(void)
bool cRecorder::RunningLowOnDiskSpace(void)
{
if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
int Free = FreeDiskSpaceMB(fileName->Name());
@ -82,10 +82,10 @@ bool cFileWriter::RunningLowOnDiskSpace(void)
return false;
}
bool cFileWriter::NextFile(void)
bool cRecorder::NextFile(void)
{
if (recordFile && pictureType == I_FRAME) { // every file shall start with an I_FRAME
if (fileSize > MEGABYTE(Setup.MaxVideoFileSize) || RunningLowOnDiskSpace()) {
if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame
if (fileSize > MEGABYTE(off_t(Setup.MaxVideoFileSize)) || RunningLowOnDiskSpace()) {
recordFile = fileName->NextFile();
fileSize = 0;
}
@ -93,67 +93,10 @@ bool cFileWriter::NextFile(void)
return recordFile != NULL;
}
void cFileWriter::Action(void)
{
time_t t = time(NULL);
while (Running()) {
int Count;
uchar *p = remux->Get(Count, &pictureType);
if (p) {
if (!Running() && pictureType == I_FRAME) // finish the recording before the next 'I' frame
break;
if (NextFile()) {
if (index && pictureType != NO_PICTURE)
index->Write(pictureType, fileName->Number(), fileSize);
if (recordFile->Write(p, Count) < 0) {
LOG_ERROR_STR(fileName->Name());
break;
}
fileSize += Count;
remux->Del(Count);
}
else
break;
t = time(NULL);
}
else if (time(NULL) - t > MAXBROKENTIMEOUT) {
esyslog("ERROR: video data stream broken");
ShutdownHandler.RequestEmergencyExit();
t = time(NULL);
}
}
}
// --- cRecorder -------------------------------------------------------------
cRecorder::cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids)
:cReceiver(ChannelID, Priority, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids)
,cThread("recording")
{
// Make sure the disk is up and running:
SpinUpDisk(FileName);
ringBuffer = new cRingBufferLinear(RECORDERBUFSIZE, TS_SIZE * 2, true, "Recorder");
ringBuffer->SetTimeouts(0, 100);
remux = new cRemux(VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids, true);
writer = new cFileWriter(FileName, remux);
}
cRecorder::~cRecorder()
{
Detach();
delete writer;
delete remux;
delete ringBuffer;
}
void cRecorder::Activate(bool On)
{
if (On) {
writer->Start();
if (On)
Start();
}
else
Cancel(3);
}
@ -169,15 +112,54 @@ void cRecorder::Receive(uchar *Data, int Length)
void cRecorder::Action(void)
{
time_t t = time(NULL);
bool Synced = false;
bool InfoWritten = false;
while (Running()) {
int r;
uchar *b = ringBuffer->Get(r);
if (b) {
int Count = remux->Put(b, r);
if (Count)
int Count = frameDetector->Analyze(b, r);
if (Count) {
if (!Running() && frameDetector->IndependentFrame()) // finish the recording before the next independent frame
break;
if (Synced |= frameDetector->IndependentFrame()) { // start with first independent frame
if (!InfoWritten) {
if (recordingInfo.Read()) {
if (frameDetector->FramesPerSecond() > 0 && recordingInfo.FramesPerSecond() != frameDetector->FramesPerSecond()) {
recordingInfo.SetFramesPerSecond(frameDetector->FramesPerSecond());
recordingInfo.Write();
}
}
InfoWritten = true;
}
if (!NextFile())
break;
if (index && frameDetector->NewFrame())
index->Write(frameDetector->IndependentFrame(), fileName->Number(), fileSize);
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;
}
}
if (recordFile->Write(b, Count) < 0) {
LOG_ERROR_STR(fileName->Name());
break;
}
fileSize += Count;
t = time(NULL);
}
ringBuffer->Del(Count);
else
cCondWait::SleepMs(100); // avoid busy loop when resultBuffer is full in cRemux::Put()
}
}
if (time(NULL) - t > MAXBROKENTIMEOUT) {
esyslog("ERROR: video data stream broken");
ShutdownHandler.RequestEmergencyExit();
t = time(NULL);
}
}
}

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: recorder.h 1.5 2007/01/07 14:44:05 kls Exp $
* $Id: recorder.h 2.1 2009/01/06 10:44:58 kls Exp $
*/
#ifndef __RECORDER_H
@ -16,13 +16,19 @@
#include "ringbuffer.h"
#include "thread.h"
class cFileWriter;
class cRecorder : public cReceiver, cThread {
private:
cRingBufferLinear *ringBuffer;
cRemux *remux;
cFileWriter *writer;
cFrameDetector *frameDetector;
cPatPmtGenerator patPmtGenerator;
cFileName *fileName;
cIndexFile *index;
cUnbufferedFile *recordFile;
cRecordingInfo recordingInfo;
off_t fileSize;
time_t lastDiskSpaceCheck;
bool RunningLowOnDiskSpace(void);
bool NextFile(void);
protected:
virtual void Activate(bool On);
virtual void Receive(uchar *Data, int Length);

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: recording.c 2.3 2008/05/22 10:40:08 kls Exp $
* $Id: recording.c 2.4 2009/01/06 14:41:11 kls Exp $
*/
#include "recording.h"
@ -12,6 +12,7 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
@ -19,7 +20,6 @@
#include "channels.h"
#include "i18n.h"
#include "interface.h"
#include "remux.h" //XXX+ I_FRAME
#include "skins.h"
#include "tools.h"
#include "videodir.h"
@ -37,15 +37,17 @@
#define DATAFORMAT "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT
#define NAMEFORMAT "%s/%s/" DATAFORMAT
*/
#define DATAFORMAT "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT
#define NAMEFORMAT "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT
#define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT
#define NAMEFORMATPES "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT
#define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT
#define NAMEFORMATTS "%s/%s/" DATAFORMATTS
#define RESUMEFILESUFFIX "/resume%s%s.vdr"
#define RESUMEFILESUFFIX "/resume%s%s"
#ifdef SUMMARYFALLBACK
#define SUMMARYFILESUFFIX "/summary.vdr"
#endif
#define INFOFILESUFFIX "/info.vdr"
#define MARKSFILESUFFIX "/marks.vdr"
#define INFOFILESUFFIX "/info"
#define MARKSFILESUFFIX "/marks"
#define MINDISKSPACE 1024 // MB
@ -202,12 +204,14 @@ void AssertFreeDiskSpace(int Priority, bool Force)
// --- cResumeFile -----------------------------------------------------------
cResumeFile::cResumeFile(const char *FileName)
cResumeFile::cResumeFile(const char *FileName, bool IsPesRecording)
{
fileName = MALLOC(char, strlen(FileName) + strlen(RESUMEFILESUFFIX) + 1);
isPesRecording = IsPesRecording;
const char *Suffix = isPesRecording ? RESUMEFILESUFFIX ".vdr" : RESUMEFILESUFFIX;
fileName = MALLOC(char, strlen(FileName) + strlen(Suffix) + 1);
if (fileName) {
strcpy(fileName, FileName);
sprintf(fileName + strlen(fileName), RESUMEFILESUFFIX, Setup.ResumeID ? "." : "", Setup.ResumeID ? *itoa(Setup.ResumeID) : "");
sprintf(fileName + strlen(fileName), Suffix, Setup.ResumeID ? "." : "", Setup.ResumeID ? *itoa(Setup.ResumeID) : "");
}
else
esyslog("ERROR: can't allocate memory for resume file name");
@ -227,6 +231,7 @@ int cResumeFile::Read(void)
if ((st.st_mode & S_IWUSR) == 0) // no write access, assume no resume
return -1;
}
if (isPesRecording) {
int f = open(fileName, O_RDONLY);
if (f >= 0) {
if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) {
@ -238,12 +243,33 @@ int cResumeFile::Read(void)
else if (errno != ENOENT)
LOG_ERROR_STR(fileName);
}
else {
FILE *f = fopen(fileName, "r");
if (f) {
cReadLine ReadLine;
char *s;
int line = 0;
while ((s = ReadLine.Read(f)) != NULL) {
++line;
char *t = skipspace(s + 1);
switch (*s) {
case 'I': resume = atoi(t);
break;
}
}
fclose(f);
}
else if (errno != ENOENT)
LOG_ERROR_STR(fileName);
}
}
return resume;
}
bool cResumeFile::Save(int Index)
{
if (fileName) {
if (isPesRecording) {
int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
if (f >= 0) {
if (safe_write(f, &Index, sizeof(Index)) < 0)
@ -253,6 +279,17 @@ bool cResumeFile::Save(int Index)
return true;
}
}
else {
FILE *f = fopen(fileName, "w");
if (f) {
fprintf(f, "I %d\n", Index);
fclose(f);
}
else
LOG_ERROR_STR(fileName);
return true;
}
}
return false;
}
@ -274,6 +311,10 @@ cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event)
ownEvent = Event ? NULL : new cEvent(0);
event = ownEvent ? ownEvent : Event;
aux = NULL;
framesPerSecond = DEFAULTFRAMESPERSECOND;
priority = MAXPRIORITY;
lifetime = MAXLIFETIME;
fileName = NULL;
if (Channel) {
// Since the EPG data's component records can carry only a single
// language code, let's see whether the channel's PID data has
@ -322,11 +363,25 @@ cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event)
}
}
cRecordingInfo::cRecordingInfo(const char *FileName)
{
channelID = tChannelID::InvalidID;
channelName = NULL;
ownEvent = new cEvent(0);
event = ownEvent;
aux = NULL;
framesPerSecond = DEFAULTFRAMESPERSECOND;
priority = MAXPRIORITY;
lifetime = MAXLIFETIME;
fileName = strdup(cString::sprintf("%s%s", FileName, INFOFILESUFFIX));
}
cRecordingInfo::~cRecordingInfo()
{
delete ownEvent;
free(aux);
free(channelName);
free(fileName);
}
void cRecordingInfo::SetData(const char *Title, const char *ShortText, const char *Description)
@ -345,6 +400,11 @@ void cRecordingInfo::SetAux(const char *Aux)
aux = Aux ? strdup(Aux) : NULL;
}
void cRecordingInfo::SetFramesPerSecond(double FramesPerSecond)
{
framesPerSecond = FramesPerSecond;
}
bool cRecordingInfo::Read(FILE *f)
{
if (ownEvent) {
@ -382,6 +442,12 @@ bool cRecordingInfo::Read(FILE *f)
}
}
break;
case 'F': framesPerSecond = atof(t);
break;
case 'L': lifetime = atoi(t);
break;
case 'P': priority = atoi(t);
break;
case '@': free(aux);
aux = strdup(t);
break;
@ -403,11 +469,48 @@ bool cRecordingInfo::Write(FILE *f, const char *Prefix) const
if (channelID.Valid())
fprintf(f, "%sC %s%s%s\n", Prefix, *channelID.ToString(), channelName ? " " : "", channelName ? channelName : "");
event->Dump(f, Prefix, true);
fprintf(f, "%sF %.10g\n", Prefix, framesPerSecond);
fprintf(f, "%sP %d\n", Prefix, priority);
fprintf(f, "%sL %d\n", Prefix, lifetime);
if (aux)
fprintf(f, "%s@ %s\n", Prefix, aux);
return true;
}
bool cRecordingInfo::Read(void)
{
bool Result = false;
if (fileName) {
FILE *f = fopen(fileName, "r");
if (f) {
if (Read(f))
Result = true;
else
esyslog("ERROR: EPG data problem in file %s", fileName);
fclose(f);
}
else if (errno != ENOENT)
LOG_ERROR_STR(fileName);
}
return Result;
}
bool cRecordingInfo::Write(void) const
{
bool Result = false;
if (fileName) {
cSafeFile f(fileName);
if (f.Open()) {
if (Write(f))
Result = true;
f.Close();
}
else
LOG_ERROR_STR(fileName);
}
return Result;
}
// --- cRecording ------------------------------------------------------------
#define RESUME_NOT_INITIALIZED (-2)
@ -497,6 +600,10 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event)
fileName = NULL;
name = NULL;
fileSizeMB = -1; // unknown
channel = Timer->Channel()->Number();
resumeId = Setup.ResumeID;
isPesRecording = false;
framesPerSecond = DEFAULTFRAMESPERSECOND;
deleted = 0;
// set up the actual name:
const char *Title = Event ? Event->Title() : NULL;
@ -542,12 +649,20 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event)
// handle info:
info = new cRecordingInfo(Timer->Channel(), Event);
info->SetAux(Timer->Aux());
info->priority = priority;
info->lifetime = lifetime;
}
cRecording::cRecording(const char *FileName)
{
resume = RESUME_NOT_INITIALIZED;
fileSizeMB = -1; // unknown
channel = -1;
resumeId = -1;
priority = MAXPRIORITY; // assume maximum in case there is no info file
lifetime = MAXLIFETIME;
isPesRecording = false;
framesPerSecond = DEFAULTFRAMESPERSECOND;
deleted = 0;
titleBuffer = NULL;
sortBuffer = NULL;
@ -562,7 +677,8 @@ cRecording::cRecording(const char *FileName)
struct tm tm_r;
struct tm t = *localtime_r(&now, &tm_r); // this initializes the time zone in 't'
t.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
if (7 == sscanf(p + 1, DATAFORMAT, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) {
if (7 == sscanf(p + 1, DATAFORMATTS, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &channel, &resumeId)
|| 7 == sscanf(p + 1, DATAFORMATPES, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) {
t.tm_year -= 1900;
t.tm_mon--;
t.tm_sec = 0;
@ -571,14 +687,22 @@ cRecording::cRecording(const char *FileName)
strncpy(name, FileName, p - FileName);
name[p - FileName] = 0;
name = ExchangeChars(name, false);
isPesRecording = resumeId < 0;
}
else
return;
GetResume();
// read an optional info file:
cString InfoFileName = cString::sprintf("%s%s", fileName, INFOFILESUFFIX);
cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX);
FILE *f = fopen(InfoFileName, "r");
if (f) {
if (!info->Read(f))
esyslog("ERROR: EPG data problem in file %s", *InfoFileName);
else if (!isPesRecording) {
priority = info->priority;
lifetime = info->lifetime;
framesPerSecond = info->framesPerSecond;
}
fclose(f);
}
else if (errno != ENOENT)
@ -683,7 +807,7 @@ char *cRecording::SortName(void) const
int cRecording::GetResume(void) const
{
if (resume == RESUME_NOT_INITIALIZED) {
cResumeFile ResumeFile(FileName());
cResumeFile ResumeFile(FileName(), isPesRecording);
resume = ResumeFile.Read();
}
return resume;
@ -700,8 +824,11 @@ const char *cRecording::FileName(void) const
if (!fileName) {
struct tm tm_r;
struct tm *t = localtime_r(&start, &tm_r);
const char *fmt = isPesRecording ? NAMEFORMATPES : NAMEFORMATTS;
int ch = isPesRecording ? priority : channel;
int ri = isPesRecording ? lifetime : resumeId;
name = ExchangeChars(name, true);
fileName = strdup(cString::sprintf(NAMEFORMAT, VideoDirectory, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, priority, lifetime));
fileName = strdup(cString::sprintf(fmt, VideoDirectory, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri));
name = ExchangeChars(name, false);
}
return fileName;
@ -789,7 +916,7 @@ bool cRecording::IsEdited(void) const
bool cRecording::WriteInfo(void)
{
cString InfoFileName = cString::sprintf("%s%s", fileName, INFOFILESUFFIX);
cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX);
FILE *f = fopen(InfoFileName, "w");
if (f) {
info->Write(f);
@ -1061,10 +1188,14 @@ void cRecordings::ResetResume(const char *ResumeFileName)
// --- cMark -----------------------------------------------------------------
cMark::cMark(int Position, const char *Comment)
double MarkFramesPerSecond = DEFAULTFRAMESPERSECOND;
cMutex MutexMarkFramesPerSecond;
cMark::cMark(int Position, const char *Comment, double FramesPerSecond)
{
position = Position;
comment = Comment ? strdup(Comment) : NULL;
framesPerSecond = FramesPerSecond;
}
cMark::~cMark()
@ -1074,14 +1205,15 @@ cMark::~cMark()
cString cMark::ToText(void)
{
return cString::sprintf("%s%s%s\n", *IndexToHMSF(position, true), comment ? " " : "", comment ? comment : "");
return cString::sprintf("%s%s%s\n", *IndexToHMSF(position, true, framesPerSecond), comment ? " " : "", comment ? comment : "");
}
bool cMark::Parse(const char *s)
{
free(comment);
comment = NULL;
position = HMSFToIndex(s);
framesPerSecond = MarkFramesPerSecond;
position = HMSFToIndex(s, framesPerSecond);
const char *p = strchr(s, ' ');
if (p) {
p = skipspace(p);
@ -1098,9 +1230,12 @@ bool cMark::Save(FILE *f)
// --- cMarks ----------------------------------------------------------------
bool cMarks::Load(const char *RecordingFileName)
bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool IsPesRecording)
{
if (cConfig<cMark>::Load(AddDirectory(RecordingFileName, MARKSFILESUFFIX))) {
cMutexLock MutexLock(&MutexMarkFramesPerSecond);
framesPerSecond = FramesPerSecond;
MarkFramesPerSecond = framesPerSecond;
if (cConfig<cMark>::Load(AddDirectory(RecordingFileName, IsPesRecording ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX))) {
Sort();
return true;
}
@ -1123,7 +1258,7 @@ cMark *cMarks::Add(int Position)
{
cMark *m = Get(Position);
if (!m) {
cConfig<cMark>::Add(m = new cMark(Position));
cConfig<cMark>::Add(m = new cMark(Position, NULL, framesPerSecond));
Sort();
}
return m;
@ -1169,12 +1304,9 @@ void cRecordingUserCommand::InvokeCommand(const char *State, const char *Recordi
}
}
// --- XXX+
//XXX+ somewhere else???
// --- cIndexFile ------------------------------------------------------------
#define INDEXFILESUFFIX "/index.vdr"
#define INDEXFILESUFFIX "/index"
// The number of frames to stay off the end in case of time shift:
#define INDEXSAFETYLIMIT 150 // frames
@ -1185,33 +1317,56 @@ void cRecordingUserCommand::InvokeCommand(const char *State, const char *Recordi
// The minimum age of an index file for considering it no longer to be written:
#define MININDEXAGE 3600 // seconds
cIndexFile::cIndexFile(const char *FileName, bool Record)
:resumeFile(FileName)
struct tIndexPes {
uint32_t offset;
uchar type;
uchar number;
uint16_t reserved;
};
struct tIndexTs {
uint64_t offset:40; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!)
int reserved:7; // reserved for future use
int independent:1; // marks frames that can be displayed by themselves (for trick modes)
uint16_t number:16; // up to 64K files per recording
tIndexTs(off_t Offset, bool Independent, uint16_t Number)
{
offset = Offset;
reserved = 0;
independent = Independent;
number = Number;
}
};
cIndexFile::cIndexFile(const char *FileName, bool Record, bool IsPesRecording)
:resumeFile(FileName, IsPesRecording)
{
f = -1;
fileName = NULL;
size = 0;
last = -1;
index = NULL;
isPesRecording = IsPesRecording;
if (FileName) {
fileName = MALLOC(char, strlen(FileName) + strlen(INDEXFILESUFFIX) + 1);
const char *Suffix = isPesRecording ? INDEXFILESUFFIX ".vdr" : INDEXFILESUFFIX;
fileName = MALLOC(char, strlen(FileName) + strlen(Suffix) + 1);
if (fileName) {
strcpy(fileName, FileName);
char *pFileExt = fileName + strlen(fileName);
strcpy(pFileExt, INDEXFILESUFFIX);
strcpy(pFileExt, Suffix);
int delta = 0;
if (access(fileName, R_OK) == 0) {
struct stat buf;
if (stat(fileName, &buf) == 0) {
delta = buf.st_size % sizeof(tIndex);
delta = buf.st_size % sizeof(tIndexTs);
if (delta) {
delta = sizeof(tIndex) - delta;
esyslog("ERROR: invalid file size (%ld) in '%s'", buf.st_size, fileName);
delta = sizeof(tIndexTs) - delta;
esyslog("ERROR: invalid file size (%lld) in '%s'", buf.st_size, fileName);
}
last = (buf.st_size + delta) / sizeof(tIndex) - 1;
last = (buf.st_size + delta) / sizeof(tIndexTs) - 1;
if (!Record && last >= 0) {
size = last + 1;
index = MALLOC(tIndex, size);
index = MALLOC(tIndexTs, size);
if (index) {
f = open(fileName, O_RDONLY);
if (f >= 0) {
@ -1223,12 +1378,14 @@ cIndexFile::cIndexFile(const char *FileName, bool Record)
f = -1;
}
// we don't close f here, see CatchUp()!
else if (isPesRecording)
ConvertFromPes(index, size);
}
else
LOG_ERROR_STR(fileName);
}
else
esyslog("ERROR: can't allocate %zd bytes for index '%s'", size * sizeof(tIndex), fileName);
esyslog("ERROR: can't allocate %zd bytes for index '%s'", size * sizeof(tIndexTs), fileName);
}
}
else
@ -1261,6 +1418,18 @@ cIndexFile::~cIndexFile()
free(index);
}
void cIndexFile::ConvertFromPes(tIndexTs *IndexTs, int Count)
{
tIndexPes IndexPes;
while (Count-- > 0) {
memcpy(&IndexPes, IndexTs, sizeof(IndexPes));
IndexTs->offset = IndexPes.offset;
IndexTs->independent = IndexPes.type == 1; // I_FRAME
IndexTs->number = IndexPes.number;
IndexTs++;
}
}
bool cIndexFile::CatchUp(int Index)
{
// returns true unless something really goes wrong, so that 'index' becomes NULL
@ -1275,17 +1444,17 @@ bool cIndexFile::CatchUp(int Index)
f = -1;
break;
}
int newLast = buf.st_size / sizeof(tIndex) - 1;
int newLast = buf.st_size / sizeof(tIndexTs) - 1;
if (newLast > last) {
if (size <= newLast) {
size *= 2;
if (size <= newLast)
size = newLast + 1;
}
index = (tIndex *)realloc(index, size * sizeof(tIndex));
index = (tIndexTs *)realloc(index, size * sizeof(tIndexTs));
if (index) {
int offset = (last + 1) * sizeof(tIndex);
int delta = (newLast - last) * sizeof(tIndex);
int offset = (last + 1) * sizeof(tIndexTs);
int delta = (newLast - last) * sizeof(tIndexTs);
if (lseek(f, offset, SEEK_SET) == offset) {
if (safe_read(f, &index[last + 1], delta) != delta) {
esyslog("ERROR: can't read from index");
@ -1295,6 +1464,8 @@ bool cIndexFile::CatchUp(int Index)
f = -1;
break;
}
if (isPesRecording)
ConvertFromPes(&index[last + 1], newLast - last);
last = newLast;
}
else
@ -1314,10 +1485,10 @@ bool cIndexFile::CatchUp(int Index)
return index != NULL;
}
bool cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset)
bool cIndexFile::Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
{
if (f >= 0) {
tIndex i = { FileOffset, PictureType, FileNumber, 0 };
tIndexTs i(FileOffset, Independent, FileNumber);
if (safe_write(f, &i, sizeof(i)) < 0) {
LOG_ERROR_STR(fileName);
close(f);
@ -1329,14 +1500,14 @@ bool cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset)
return f >= 0;
}
bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType, int *Length)
bool cIndexFile::Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent, int *Length)
{
if (CatchUp(Index)) {
if (Index >= 0 && Index < last) {
*FileNumber = index[Index].number;
*FileOffset = index[Index].offset;
if (PictureType)
*PictureType = index[Index].type;
if (Independent)
*Independent = index[Index].independent;
if (Length) {
int fn = index[Index + 1].number;
int fo = index[Index + 1].offset;
@ -1351,24 +1522,24 @@ bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *Pictu
return false;
}
int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length, bool StayOffEnd)
int cIndexFile::GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber, off_t *FileOffset, int *Length, bool StayOffEnd)
{
if (CatchUp()) {
int d = Forward ? 1 : -1;
for (;;) {
Index += d;
if (Index >= 0 && Index < last - ((Forward && StayOffEnd) ? INDEXSAFETYLIMIT : 0)) {
if (index[Index].type == I_FRAME) {
if (FileNumber)
if (index[Index].independent) {
uint16_t fn;
if (!FileNumber)
FileNumber = &fn;
off_t fo;
if (!FileOffset)
FileOffset = &fo;
*FileNumber = index[Index].number;
else
FileNumber = &index[Index].number;
if (FileOffset)
*FileOffset = index[Index].offset;
else
FileOffset = &index[Index].offset;
if (Length) {
// all recordings end with a non-I_FRAME, so the following should be safe:
// all recordings end with a non-independent frame, so the following should be safe:
int fn = index[Index + 1].number;
int fo = index[Index + 1].offset;
if (fn == *FileNumber)
@ -1388,13 +1559,13 @@ int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *F
return -1;
}
int cIndexFile::Get(uchar FileNumber, int FileOffset)
int cIndexFile::Get(uint16_t FileNumber, off_t FileOffset)
{
if (CatchUp()) {
//TODO implement binary search!
int i;
for (i = 0; i < last; i++) {
if (index[i].number > FileNumber || (index[i].number == FileNumber) && index[i].offset >= FileOffset)
if (index[i].number > FileNumber || (index[i].number == FileNumber) && off_t(index[i].offset) >= FileOffset)
break;
}
return i;
@ -1409,16 +1580,19 @@ bool cIndexFile::IsStillRecording()
// --- cFileName -------------------------------------------------------------
#define MAXFILESPERRECORDING 255
#define RECORDFILESUFFIX "/%03d.vdr"
#define MAXFILESPERRECORDINGPES 255
#define RECORDFILESUFFIXPES "/%03d.vdr"
#define MAXFILESPERRECORDINGTS 65535
#define RECORDFILESUFFIXTS "/%05d.ts"
#define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
cFileName::cFileName(const char *FileName, bool Record, bool Blocking)
cFileName::cFileName(const char *FileName, bool Record, bool Blocking, bool IsPesRecording)
{
file = NULL;
fileNumber = 0;
record = Record;
blocking = Blocking;
isPesRecording = IsPesRecording;
// Prepare the file name:
fileName = MALLOC(char, strlen(FileName) + RECORDFILESUFFIXLEN);
if (!fileName) {
@ -1442,14 +1616,14 @@ cUnbufferedFile *cFileName::Open(void)
int BlockingFlag = blocking ? 0 : O_NONBLOCK;
if (record) {
dsyslog("recording to '%s'", fileName);
file = OpenVideoFile(fileName, O_RDWR | O_CREAT | BlockingFlag);
file = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_LARGEFILE | BlockingFlag);
if (!file)
LOG_ERROR_STR(fileName);
}
else {
if (access(fileName, R_OK) == 0) {
dsyslog("playing '%s'", fileName);
file = cUnbufferedFile::Create(fileName, O_RDONLY | BlockingFlag);
file = cUnbufferedFile::Create(fileName, O_RDONLY | O_LARGEFILE | BlockingFlag);
if (!file)
LOG_ERROR_STR(fileName);
}
@ -1469,13 +1643,14 @@ void cFileName::Close(void)
}
}
cUnbufferedFile *cFileName::SetOffset(int Number, int Offset)
cUnbufferedFile *cFileName::SetOffset(int Number, off_t Offset)
{
if (fileNumber != Number)
Close();
if (0 < Number && Number <= MAXFILESPERRECORDING) {
int MaxFilesPerRecording = isPesRecording ? MAXFILESPERRECORDINGPES : MAXFILESPERRECORDINGTS;
if (0 < Number && Number <= MaxFilesPerRecording) {
fileNumber = Number;
sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber);
sprintf(pFileNumber, isPesRecording ? RECORDFILESUFFIXPES : RECORDFILESUFFIXTS, fileNumber);
if (record) {
if (access(fileName, F_OK) == 0) {
// files exists, check if it has non-zero size
@ -1506,7 +1681,7 @@ cUnbufferedFile *cFileName::SetOffset(int Number, int Offset)
}
return file;
}
esyslog("ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING);
esyslog("ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
return NULL;
}
@ -1517,11 +1692,12 @@ cUnbufferedFile *cFileName::NextFile(void)
// --- Index stuff -----------------------------------------------------------
cString IndexToHMSF(int Index, bool WithFrame)
cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
{
char buffer[16];
int f = (Index % FRAMESPERSEC) + 1;
int s = (Index / FRAMESPERSEC);
double Seconds;
int f = modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond + 1;
int s = int(Seconds);
int m = s / 60 % 60;
int h = s / 3600;
s %= 60;
@ -1529,17 +1705,20 @@ cString IndexToHMSF(int Index, bool WithFrame)
return buffer;
}
int HMSFToIndex(const char *HMSF)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
{
int h, m, s, f = 0;
if (3 <= sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f))
return (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1;
int h, m, s, f = 1;
int n = sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f);
if (n == 1)
return h - 1; // plain frame number
if (n >= 3)
return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f - 1;
return 0;
}
int SecondsToFrames(int Seconds)
int SecondsToFrames(int Seconds, double FramesPerSecond)
{
return Seconds * FRAMESPERSEC;
return round(Seconds * FramesPerSecond);
}
// --- ReadFrame -------------------------------------------------------------

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: recording.h 1.59 2007/10/14 10:11:34 kls Exp $
* $Id: recording.h 2.1 2009/01/06 10:49:59 kls Exp $
*/
#ifndef __RECORDING_H
@ -30,8 +30,9 @@ void AssertFreeDiskSpace(int Priority = 0, bool Force = false);
class cResumeFile {
private:
char *fileName;
bool isPesRecording;
public:
cResumeFile(const char *FileName);
cResumeFile(const char *FileName, bool IsPesRecording);
~cResumeFile();
int Read(void);
bool Save(int Index);
@ -46,10 +47,15 @@ private:
const cEvent *event;
cEvent *ownEvent;
char *aux;
double framesPerSecond;
int priority;
int lifetime;
char *fileName;
cRecordingInfo(const cChannel *Channel = NULL, const cEvent *Event = NULL);
void SetData(const char *Title, const char *ShortText, const char *Description);
void SetAux(const char *Aux);
public:
cRecordingInfo(const char *FileName);
~cRecordingInfo();
tChannelID ChannelID(void) const { return channelID; }
const char *ChannelName(void) const { return channelName; }
@ -58,8 +64,12 @@ public:
const char *Description(void) const { return event->Description(); }
const cComponents *Components(void) const { return event->Components(); }
const char *Aux(void) const { return aux; }
double FramesPerSecond(void) const { return framesPerSecond; }
void SetFramesPerSecond(double FramesPerSecond);
bool Read(FILE *f);
bool Write(FILE *f, const char *Prefix = "") const;
bool Read(void);
bool Write(void) const;
};
class cRecording : public cListObject {
@ -71,6 +81,10 @@ private:
mutable char *fileName;
mutable char *name;
mutable int fileSizeMB;
int channel;
int resumeId;
bool isPesRecording;
double framesPerSecond;
cRecordingInfo *info;
cRecording(const cRecording&); // can't copy cRecording
cRecording &operator=(const cRecording &); // can't assign cRecording
@ -93,8 +107,10 @@ public:
const char *PrefixFileName(char Prefix);
int HierarchyLevels(void) const;
void ResetResume(void) const;
double FramesPerSecond(void) { return framesPerSecond; }
bool IsNew(void) const { return GetResume() <= 0; }
bool IsEdited(void) const;
bool IsPesRecording(void) const { return isPesRecording; }
bool WriteInfo(void);
bool Delete(void);
// Changes the file name so that it will no longer be visible in the "Recordings" menu
@ -149,11 +165,15 @@ public:
extern cRecordings Recordings;
extern cRecordings DeletedRecordings;
#define DEFAULTFRAMESPERSECOND 25.0
class cMark : public cListObject {
private:
double framesPerSecond;
public:
int position;
char *comment;
cMark(int Position = 0, const char *Comment = NULL);
cMark(int Position = 0, const char *Comment = NULL, double FramesPerSecond = DEFAULTFRAMESPERSECOND);
virtual ~cMark();
cString ToText(void);
bool Parse(const char *s);
@ -161,8 +181,10 @@ public:
};
class cMarks : public cConfig<cMark> {
private:
double framesPerSecond;
public:
bool Load(const char *RecordingFileName);
bool Load(const char *RecordingFileName, double FramesPerSecond = DEFAULTFRAMESPERSECOND, bool IsPesRecording = false);
void Sort(void);
cMark *Add(int Position);
cMark *Get(int Position);
@ -182,9 +204,6 @@ public:
static void InvokeCommand(const char *State, const char *RecordingFileName);
};
//XXX+
#define FRAMESPERSEC 25
// The maximum size of a single frame (up to HDTV 1920x1080):
#define MAXFRAMESIZE KILOBYTE(512)
@ -197,24 +216,27 @@ public:
#define MAXVIDEOFILESIZE 2000 // MB
#define MINVIDEOFILESIZE 100 // MB
struct tIndexTs;
class cIndexFile {
private:
struct tIndex { int offset; uchar type; uchar number; short reserved; };
int f;
char *fileName;
int size, last;
tIndex *index;
tIndexTs *index;
bool isPesRecording;
cResumeFile resumeFile;
cMutex mutex;
void ConvertFromPes(tIndexTs *IndexTs, int Count);
bool CatchUp(int Index = -1);
public:
cIndexFile(const char *FileName, bool Record);
cIndexFile(const char *FileName, bool Record, bool IsPesRecording = false);
~cIndexFile();
bool Ok(void) { return index != NULL; }
bool Write(uchar PictureType, uchar FileNumber, int FileOffset);
bool Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType = NULL, int *Length = NULL);
int GetNextIFrame(int Index, bool Forward, uchar *FileNumber = NULL, int *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false);
int Get(uchar FileNumber, int FileOffset);
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset);
bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent = NULL, int *Length = NULL);
int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber = NULL, off_t *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false);
int Get(uint16_t FileNumber, off_t FileOffset);
int Last(void) { CatchUp(); return last; }
int GetResume(void) { return resumeFile.Read(); }
bool StoreResume(int Index) { return resumeFile.Save(Index); }
@ -228,22 +250,23 @@ private:
char *fileName, *pFileNumber;
bool record;
bool blocking;
bool isPesRecording;
public:
cFileName(const char *FileName, bool Record, bool Blocking = false);
cFileName(const char *FileName, bool Record, bool Blocking = false, bool IsPesRecording = false);
~cFileName();
const char *Name(void) { return fileName; }
int Number(void) { return fileNumber; }
cUnbufferedFile *Open(void);
void Close(void);
cUnbufferedFile *SetOffset(int Number, int Offset = 0);
cUnbufferedFile *SetOffset(int Number, off_t Offset = 0);
cUnbufferedFile *NextFile(void);
};
cString IndexToHMSF(int Index, bool WithFrame = false);
cString IndexToHMSF(int Index, bool WithFrame = false, double FramesPerSecond = DEFAULTFRAMESPERSECOND);
// Converts the given index to a string, optionally containing the frame number.
int HMSFToIndex(const char *HMSF);
int HMSFToIndex(const char *HMSF, double FramesPerSecond = DEFAULTFRAMESPERSECOND);
// Converts the given string (format: "hh:mm:ss.ff") to an index.
int SecondsToFrames(int Seconds); //XXX+ ->player???
int SecondsToFrames(int Seconds, double FramesPerSecond = DEFAULTFRAMESPERSECOND);
// Returns the number of frames corresponding to the given number of seconds.
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max);

2204
remux.c

File diff suppressed because it is too large Load Diff

137
remux.h
View File

@ -1,17 +1,16 @@
/*
* remux.h: A streaming MPEG2 remultiplexer
* remux.h: Tools for detecting frames and handling PAT/PMT
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: remux.h 2.3 2008/12/13 13:55:07 kls Exp $
* $Id: remux.h 2.4 2009/01/06 12:40:43 kls Exp $
*/
#ifndef __REMUX_H
#define __REMUX_H
#include "channels.h"
#include "ringbuffer.h"
#include "tools.h"
enum ePesHeader {
@ -23,61 +22,9 @@ enum ePesHeader {
ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader = NULL);
// Picture types:
#define NO_PICTURE 0
#define I_FRAME 1
#define P_FRAME 2
#define B_FRAME 3
#define MAXTRACKS 64
class cTS2PES;
class cRemux {
private:
bool exitOnFailure;
bool noVideo;
int numUPTerrors;
bool synced;
int skipped;
cTS2PES *ts2pes[MAXTRACKS];
int numTracks;
cRingBufferLinear *resultBuffer;
int resultSkipped;
int GetPid(const uchar *Data);
public:
cRemux(int VPid, const int *APids, const int *DPids, const int *SPids, bool ExitOnFailure = false);
///< Creates a new remuxer for the given PIDs. VPid is the video PID, while
///< APids, DPids and SPids are pointers to zero terminated lists of audio,
///< dolby and subtitle PIDs (the pointers may be NULL if there is no such
///< PID). If ExitOnFailure is true, the remuxer will initiate an "emergency
///< exit" in case of problems with the data stream.
~cRemux();
void SetTimeouts(int PutTimeout, int GetTimeout) { resultBuffer->SetTimeouts(PutTimeout, GetTimeout); }
///< By default cRemux assumes that Put() and Get() are called from different
///< threads, and uses a timeout in the Get() function in case there is no
///< data available. SetTimeouts() can be used to modify these timeouts.
///< Especially if Put() and Get() are called from the same thread, setting
///< both timeouts to 0 is recommended.
int Put(const uchar *Data, int Count);
///< Puts at most Count bytes of Data into the remuxer.
///< \return Returns the number of bytes actually consumed from Data.
uchar *Get(int &Count, uchar *PictureType = NULL);
///< Gets all currently available data from the remuxer.
///< \return Count contains the number of bytes the result points to, and
///< PictureType (if not NULL) will contain one of NO_PICTURE, I_FRAME, P_FRAME
///< or B_FRAME.
void Del(int Count);
///< Deletes Count bytes from the remuxer. Count must be the number returned
///< from a previous call to Get(). Several calls to Del() with fractions of
///< a previously returned Count may be made, but the total sum of all Count
///< values must be exactly what the previous Get() has returned.
void Clear(void);
///< Clears the remuxer of all data it might still contain, keeping the PID
///< settings as they are.
static void SetBrokenLink(uchar *Data, int Length);
static int GetPacketLength(const uchar *Data, int Count, int Offset);
static int ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType);
};
// Some TS handling tools.
@ -85,24 +32,39 @@ public:
#define TS_SYNC_BYTE 0x47
#define TS_SIZE 188
#define TS_ERROR 0x80
#define TS_PAYLOAD_START 0x40
#define TS_TRANSPORT_PRIORITY 0x20
#define TS_PID_MASK_HI 0x1F
#define TS_SCRAMBLING_CONTROL 0xC0
#define TS_ADAPT_FIELD_EXISTS 0x20
#define TS_PAYLOAD_EXISTS 0x10
#define TS_CONT_CNT_MASK 0x0F
#define TS_PAYLOAD_START 0x40
#define TS_ERROR 0x80
#define TS_PID_MASK_HI 0x1F
#define TS_ADAPT_DISCONT 0x80
#define TS_ADAPT_RANDOM_ACC 0x40 // would be perfect for detecting independent frames, but unfortunately not used by all broadcasters
#define TS_ADAPT_ELEM_PRIO 0x20
#define TS_ADAPT_PCR 0x10
#define TS_ADAPT_OPCR 0x08
#define TS_ADAPT_SPLICING 0x04
#define TS_ADAPT_TP_PRIVATE 0x02
#define TS_ADAPT_EXTENSION 0x01
inline int TsHasPayload(const uchar *p)
inline bool TsHasPayload(const uchar *p)
{
return p[3] & TS_PAYLOAD_EXISTS;
}
inline int TsPayloadStart(const uchar *p)
inline bool TsHasAdaptationField(const uchar *p)
{
return p[3] & TS_ADAPT_FIELD_EXISTS;
}
inline bool TsPayloadStart(const uchar *p)
{
return p[1] & TS_PAYLOAD_START;
}
inline int TsError(const uchar *p)
inline bool TsError(const uchar *p)
{
return p[1] & TS_ERROR;
}
@ -112,6 +74,11 @@ inline int TsPid(const uchar *p)
return (p[1] & TS_PID_MASK_HI) * 256 + p[2];
}
inline bool TsIsScrambled(const uchar *p)
{
return p[3] & TS_SCRAMBLING_CONTROL;
}
inline int TsPayloadOffset(const uchar *p)
{
return (p[3] & TS_ADAPT_FIELD_EXISTS) ? p[4] + 5 : 4;
@ -129,6 +96,11 @@ inline int TsContinuityCounter(const uchar *p)
return p[3] & TS_CONT_CNT_MASK;
}
inline int TsGetAdaptationField(const uchar *p)
{
return TsHasAdaptationField(p) ? p[5] : 0x00;
}
// Some PES handling tools:
// The following functions that take a pointer to PES data all assume that
// there is enough data so that PesLongEnough() returns true.
@ -153,16 +125,18 @@ inline int PesPayloadOffset(const uchar *p)
return 9 + p[8];
}
inline bool PesHasPts(const uchar *p)
{
return (p[7] & 0x80) && p[8] >= 5;
}
inline int64_t PesGetPts(const uchar *p)
{
if ((p[7] & 0x80) && p[8] >= 5) {
return ((((int64_t)p[ 9]) & 0x0E) << 29) |
(( (int64_t)p[10]) << 22) |
((((int64_t)p[11]) & 0xFE) << 14) |
(( (int64_t)p[12]) << 7) |
((((int64_t)p[13]) & 0xFE) >> 1);
}
return 0;
}
// PAT/PMT Generator:
@ -274,4 +248,39 @@ void BlockDump(const char *Name, const u_char *Data, int Length);
void TsDump(const char *Name, const u_char *Data, int Length);
void PesDump(const char *Name, const u_char *Data, int Length);
// Frame detector:
class cFrameDetector {
private:
int pid;
int type;
bool newFrame;
bool independentFrame;
int64_t lastPts;
bool isVideo;
int frameDuration;
int framesPerPayloadUnit;
bool scanning;
uint32_t scanner;
public:
cFrameDetector(int Pid, int Type);
int Analyze(const uchar *Data, int Length);
///< Analyzes the TS packets pointed to by Data. Length is the number of
///< bytes Data points to, and must be a multiple of 188.
///< Returns the number of bytes that have been analyzed and may be written
///< to the recording file. If the return value is 0, the data was not
///< sufficient for analyzing and Analyze() needs to be called again with
///< more actual data.
bool NewFrame(void) { return newFrame; }
///< Returns true if the data given to the last call to Analyze() started a
///< new frame.
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
///< other frames).
double FramesPerSecond(void) { return frameDuration ? 90000.0 / frameDuration : 0; }
///< Returns the number of frames per second, or 0 if this information is not
///< available.
};
#endif // __REMUX_H

16
svdrp.c
View File

@ -10,7 +10,7 @@
* and interact with the Video Disk Recorder - or write a full featured
* graphical interface that sits on top of an SVDRP connection.
*
* $Id: svdrp.c 2.1 2008/05/02 14:15:38 kls Exp $
* $Id: svdrp.c 2.2 2009/01/06 14:35:45 kls Exp $
*/
#include "svdrp.h"
@ -701,7 +701,7 @@ void cSVDRP::CmdEDIT(const char *Option)
cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
if (recording) {
cMarks Marks;
if (Marks.Load(recording->FileName()) && Marks.Count()) {
if (Marks.Load(recording->FileName(), recording->FramesPerSecond(), recording->IsPesRecording()) && Marks.Count()) {
if (!cCutter::Active()) {
if (cCutter::Start(recording->FileName()))
Reply(250, "Editing recording \"%s\" [%s]", Option, recording->Title());
@ -1338,15 +1338,9 @@ void cSVDRP::CmdPLAY(const char *Option)
cControl::Shutdown();
if (*option) {
int pos = 0;
if (strcasecmp(option, "BEGIN") != 0) {
int h, m = 0, s = 0, f = 1;
int x = sscanf(option, "%d:%d:%d.%d", &h, &m, &s, &f);
if (x == 1)
pos = h;
else if (x >= 3)
pos = (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1;
}
cResumeFile resume(recording->FileName());
if (strcasecmp(option, "BEGIN") != 0)
pos = HMSFToIndex(option, recording->FramesPerSecond());
cResumeFile resume(recording->FileName(), recording->IsPesRecording());
if (pos <= 0)
resume.Delete();
else

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: tools.c 1.145 2008/03/05 17:23:47 kls Exp $
* $Id: tools.c 2.1 2009/01/05 13:04:10 kls Exp $
*/
#include "tools.h"
@ -1589,7 +1589,7 @@ ssize_t cUnbufferedFile::Write(const void *Data, size_t Size)
// last (partial) page might be skipped, writeback will start only after
// second call; the third call will still include this page and finally
// drop it from cache.
off_t headdrop = min(begin, WRITE_BUFFER * 2L);
off_t headdrop = min(begin, off_t(WRITE_BUFFER * 2));
posix_fadvise(fd, begin - headdrop, lastpos - begin + headdrop, POSIX_FADV_DONTNEED);
}
begin = lastpos = curpos;
@ -1608,7 +1608,7 @@ ssize_t cUnbufferedFile::Write(const void *Data, size_t Size)
// kind of write gathering enabled), but the syncs cause (io) load..
// Uncomment the next line if you think you need them.
//fdatasync(fd);
off_t headdrop = min(curpos - totwritten, totwritten * 2L);
off_t headdrop = min(curpos - totwritten, off_t(totwritten * 2));
posix_fadvise(fd, curpos - totwritten - headdrop, totwritten + headdrop, POSIX_FADV_DONTNEED);
totwritten = 0;
}

54
vdr.5
View File

@ -8,7 +8,7 @@
.\" License as specified in the file COPYING that comes with the
.\" vdr distribution.
.\"
.\" $Id: vdr.5 2.5 2008/11/22 15:23:10 kls Exp $
.\" $Id: vdr.5 2.6 2009/01/06 12:37:35 kls Exp $
.\"
.TH vdr 5 "10 Feb 2008" "1.6" "Video Disk Recorder Files"
.SH NAME
@ -334,7 +334,7 @@ of these cannot be determined, \fBTITLE\fR will default to the channel name, and
An arbitrary string that can be used by external applications to store any
kind of data related to this timer. The string must not contain any newline
characters. If this field is not empty, its contents will be written into the
\fIinfo.vdr\fR file of the recording with the '@' tag.
\fIinfo\fR file of the recording with the '@' tag.
.SS SOURCES
The file \fIsources.conf\fR defines the codes to be used in the \fBSource\fR field
of channels in \fIchannels.conf\fR and assigns descriptive texts to them.
@ -558,41 +558,55 @@ messages file will be actually used.
If a theme file doesn't contain a Description, the name of the theme (as
given in the theme's file name) will be used.
.SS AUDIO/VIDEO DATA
The files \fI001.vdr\fR...\fI255.vdr\fR are the actual recorded MPEG data
The files \fI00001.ts\fR...\fI65535.ts\fR are the actual recorded data
files. In order to keep the size of an individual file below a given limit,
a recording is split into several files. The contents of these files is
\fBPacketized Elementary Stream\fR (PES) and contains ES packets with ids
0xE0...0xEF for video (only one of these may actually occur in a file),
0xC0...0xDF for audio 1...32 (up to 32 audio tracks may occur).
Dolby Digital data is stored in packets with ids 0xBD ("Private Stream 1")
and substream ids 0x80...0x87.
DVB subtitle data is stored in packets with ids 0xBD ("Private Stream 1")
and substream ids 0x20...0x27.
a recording may be split into several files. The contents of these files is
\fBTransport Stream\fR (TS) and contains data packets that are each 188 byte
long and start with 0x47. Data is stored exactly as it is broadcast, with
a generated PAT/PMT inserted right before every independent frame.
.SS INDEX
The file \fIindex.vdr\fR (if present in a recording directory) contains
The file \fIindex\fR (if present in a recording directory) contains
the (binary) index data into each of the the recording files
\fI001.vdr\fR...\fI255.vdr\fR. It is used during replay to determine
\fI00001.ts\fR...\fI65535.ts\fR. It is used during replay to determine
the current position within the recording, and to implement skipping
and fast forward/back functions.
See the definition of the \fBcIndexFile\fR class for details about the
actual contents of this file.
.SS INFO
The file \fIinfo.vdr\fR (if present in a recording directory) contains
The file \fIinfo\fR (if present in a recording directory) contains
a description of the recording, derived from the EPG data at recording time
(if such data was available). The \fBAux\fR field of the corresponding
timer (if given) is copied into this file, using the '@' tag.
This is a plain ASCII file and contains tagged lines like the \fBEPG DATA\fR
file (see the description of the \fIepg.data\fR file). Note that the lowercase
tags ('c' and 'e') will not appear in an \fIinfo.vdr\fR file.
tags ('c' and 'e') will not appear in an \fIinfo\fR file.
Lines tagged with '#' are ignored and can be used by external tools to
store arbitrary information.
In addition to the tags used in the \fIepg.data\fR file, the following tag
characters are defined:
.TS
tab (|);
l l.
\fBF\fR|<frame duration>
\fBL\fR|<lifetime>
\fBP\fR|<priority>
\fB@\fR|<auxiliary data>
.TE
.SS RESUME
The file \fIresume.vdr\fR (if present in a recording directory) contains
The file \fIresume\fR (if present in a recording directory) contains
the position within the recording where the last replay session left off.
The data is a four byte (binary) integer value and defines an offset into
the file \fIindex.vdr\fR.
The file consists of tagged lines that describe the various parameters
necessary to pick up replay where it left off.
The following tag characters are defined:
.TS
tab (@);
l l.
\fBI\fR@<offset into the file \fIindex\fR>
.TE
.SS MARKS
The file \fImarks.vdr\fR (if present in a recording directory) contains
The file \fImarks\fR (if present in a recording directory) contains
the editing marks defined for this recording.
Each line contains the definition of one mark in the following format:
@ -641,8 +655,6 @@ All other tags are optional (although every event
should at least have a \fBT\fR entry).
There may be several \fBX\fR tags, depending on the number of tracks (video, audio etc.)
the event provides.
The special tag character \fB@\fR is used to mark the \fBauxiliary data\fR from
a timer definition in the \fIinfo.vdr\fR file.
.TS
tab (@);