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:
parent
7470253c60
commit
7de7ede26f
@ -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
49
HISTORY
@ -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.
|
||||
|
4
Makefile
4
Makefile
@ -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)\"
|
||||
|
10
config.h
10
config.h
@ -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
|
||||
|
31
cutter.c
31
cutter.c
@ -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;
|
||||
}
|
||||
|
50
device.c
50
device.c
@ -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
|
||||
|
||||
|
19
device.h
19
device.h
@ -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).
|
||||
|
12
dvbdevice.c
12
dvbdevice.c
@ -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)
|
||||
|
80
dvbplayer.c
80
dvbplayer.c
@ -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) {
|
||||
PlayPes(NULL, 0);
|
||||
cRemux::SetBrokenLink(p, pc);
|
||||
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;
|
||||
|
27
menu.c
27
menu.c
@ -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,10 +1989,13 @@ eOSState cMenuRecordings::Rewind(void)
|
||||
return osContinue;
|
||||
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
|
||||
if (ri && !ri->IsDirectory()) {
|
||||
cDevice::PrimaryDevice()->StopReplay(); // must do this first to be able to rewind the currently replayed recording
|
||||
cResumeFile ResumeFile(ri->FileName());
|
||||
ResumeFile.Delete();
|
||||
return Play();
|
||||
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(), 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();
|
||||
}
|
||||
}
|
||||
|
5
player.h
5
player.h
@ -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);
|
||||
|
172
recorder.c
172
recorder.c
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
16
recorder.h
16
recorder.h
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: recorder.h 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);
|
||||
|
355
recording.c
355
recording.c
@ -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,16 +231,37 @@ int cResumeFile::Read(void)
|
||||
if ((st.st_mode & S_IWUSR) == 0) // no write access, assume no resume
|
||||
return -1;
|
||||
}
|
||||
int f = open(fileName, O_RDONLY);
|
||||
if (f >= 0) {
|
||||
if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) {
|
||||
resume = -1;
|
||||
LOG_ERROR_STR(fileName);
|
||||
if (isPesRecording) {
|
||||
int f = open(fileName, O_RDONLY);
|
||||
if (f >= 0) {
|
||||
if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) {
|
||||
resume = -1;
|
||||
LOG_ERROR_STR(fileName);
|
||||
}
|
||||
close(f);
|
||||
}
|
||||
close(f);
|
||||
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);
|
||||
}
|
||||
else if (errno != ENOENT)
|
||||
LOG_ERROR_STR(fileName);
|
||||
}
|
||||
return resume;
|
||||
}
|
||||
@ -244,12 +269,24 @@ int cResumeFile::Read(void)
|
||||
bool cResumeFile::Save(int Index)
|
||||
{
|
||||
if (fileName) {
|
||||
int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
|
||||
if (f >= 0) {
|
||||
if (safe_write(f, &Index, sizeof(Index)) < 0)
|
||||
if (isPesRecording) {
|
||||
int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
|
||||
if (f >= 0) {
|
||||
if (safe_write(f, &Index, sizeof(Index)) < 0)
|
||||
LOG_ERROR_STR(fileName);
|
||||
close(f);
|
||||
Recordings.ResetResume(fileName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
FILE *f = fopen(fileName, "w");
|
||||
if (f) {
|
||||
fprintf(f, "I %d\n", Index);
|
||||
fclose(f);
|
||||
}
|
||||
else
|
||||
LOG_ERROR_STR(fileName);
|
||||
close(f);
|
||||
Recordings.ResetResume(fileName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
*FileNumber = index[Index].number;
|
||||
else
|
||||
FileNumber = &index[Index].number;
|
||||
if (FileOffset)
|
||||
*FileOffset = index[Index].offset;
|
||||
else
|
||||
FileOffset = &index[Index].offset;
|
||||
if (index[Index].independent) {
|
||||
uint16_t fn;
|
||||
if (!FileNumber)
|
||||
FileNumber = &fn;
|
||||
off_t fo;
|
||||
if (!FileOffset)
|
||||
FileOffset = &fo;
|
||||
*FileNumber = index[Index].number;
|
||||
*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 -------------------------------------------------------------
|
||||
|
61
recording.h
61
recording.h
@ -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);
|
||||
|
147
remux.h
147
remux.h
@ -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;
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
16
svdrp.c
@ -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
|
||||
|
6
tools.c
6
tools.c
@ -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
54
vdr.5
@ -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 (@);
|
||||
|
Loading…
Reference in New Issue
Block a user