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 fixing calculating the cache size in cUnbufferedFile::Read()
|
||||||
for making the /video/.update file be touched _after_ an editing process is finished
|
for making the /video/.update file be touched _after_ an editing process is finished
|
||||||
in order to avoid excessive disk access
|
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>
|
Werner Fink <werner@suse.de>
|
||||||
for making I/O more robust by handling EINTR
|
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
|
the patch from ftp://ftp.cadsoft.de/vdr/Developer/av7110_v4ldvb_api5_audiobuf_test_1.diff
|
||||||
to the driver (thanks to Oliver Endriss).
|
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).
|
- Updated the Russian OSD texts (thanks to Oleg Roitburd).
|
||||||
- Fixed handling the 'pointer field' in generating and parsing PAT/PMT (thanks to
|
- 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
|
- 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
|
excessive CPU load (this is just a makeshift solution until the FF DVB cards
|
||||||
can play TS directly).
|
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
|
# See the main source file 'vdr.c' for copyright information and
|
||||||
# how to reach the author.
|
# 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:
|
.DELETE_ON_ERROR:
|
||||||
|
|
||||||
@ -60,6 +60,8 @@ DEFINES += -DLIRC_DEVICE=\"$(LIRC_DEVICE)\" -DRCU_DEVICE=\"$(RCU_DEVICE)\"
|
|||||||
|
|
||||||
DEFINES += -D_GNU_SOURCE
|
DEFINES += -D_GNU_SOURCE
|
||||||
|
|
||||||
|
DEFINES += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE
|
||||||
|
|
||||||
DEFINES += -DVIDEODIR=\"$(VIDEODIR)\"
|
DEFINES += -DVIDEODIR=\"$(VIDEODIR)\"
|
||||||
DEFINES += -DCONFDIR=\"$(CONFDIR)\"
|
DEFINES += -DCONFDIR=\"$(CONFDIR)\"
|
||||||
DEFINES += -DPLUGINDIR=\"$(PLUGINLIBDIR)\"
|
DEFINES += -DPLUGINDIR=\"$(PLUGINLIBDIR)\"
|
||||||
|
10
config.h
10
config.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: 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
|
#ifndef __CONFIG_H
|
||||||
@ -22,13 +22,13 @@
|
|||||||
|
|
||||||
// VDR's own version number:
|
// VDR's own version number:
|
||||||
|
|
||||||
#define VDRVERSION "1.7.2"
|
#define VDRVERSION "1.7.3"
|
||||||
#define VDRVERSNUM 10702 // Version * 10000 + Major * 100 + Minor
|
#define VDRVERSNUM 10703 // Version * 10000 + Major * 100 + Minor
|
||||||
|
|
||||||
// The plugin API's version number:
|
// The plugin API's version number:
|
||||||
|
|
||||||
#define APIVERSION "1.7.0"
|
#define APIVERSION "1.7.3"
|
||||||
#define APIVERSNUM 10700 // Version * 10000 + Major * 100 + Minor
|
#define APIVERSNUM 10703 // Version * 10000 + Major * 100 + Minor
|
||||||
|
|
||||||
// When loading plugins, VDR searches them by their APIVERSION, which
|
// When loading plugins, VDR searches them by their APIVERSION, which
|
||||||
// may be smaller than VDRVERSION in case there have been no changes to
|
// 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
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* 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"
|
#include "cutter.h"
|
||||||
@ -37,12 +37,14 @@ cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName)
|
|||||||
fromFile = toFile = NULL;
|
fromFile = toFile = NULL;
|
||||||
fromFileName = toFileName = NULL;
|
fromFileName = toFileName = NULL;
|
||||||
fromIndex = toIndex = NULL;
|
fromIndex = toIndex = NULL;
|
||||||
if (fromMarks.Load(FromFileName) && fromMarks.Count()) {
|
cRecording Recording(FromFileName);
|
||||||
fromFileName = new cFileName(FromFileName, false, true);
|
bool isPesRecording = Recording.IsPesRecording();
|
||||||
toFileName = new cFileName(ToFileName, true, true);
|
if (fromMarks.Load(FromFileName, Recording.FramesPerSecond(), isPesRecording) && fromMarks.Count()) {
|
||||||
fromIndex = new cIndexFile(FromFileName, false);
|
fromFileName = new cFileName(FromFileName, false, true, isPesRecording);
|
||||||
toIndex = new cIndexFile(ToFileName, true);
|
toFileName = new cFileName(ToFileName, true, true, isPesRecording);
|
||||||
toMarks.Load(ToFileName); // doesn't actually load marks, just sets the file name
|
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();
|
Start();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -69,7 +71,7 @@ void cCuttingThread::Action(void)
|
|||||||
fromFile->SetReadAhead(MEGABYTE(20));
|
fromFile->SetReadAhead(MEGABYTE(20));
|
||||||
int Index = Mark->position;
|
int Index = Mark->position;
|
||||||
Mark = fromMarks.Next(Mark);
|
Mark = fromMarks.Next(Mark);
|
||||||
int FileSize = 0;
|
off_t FileSize = 0;
|
||||||
int CurrentFileNumber = 0;
|
int CurrentFileNumber = 0;
|
||||||
int LastIFrame = 0;
|
int LastIFrame = 0;
|
||||||
toMarks.Add(0);
|
toMarks.Add(0);
|
||||||
@ -78,9 +80,10 @@ void cCuttingThread::Action(void)
|
|||||||
bool LastMark = false;
|
bool LastMark = false;
|
||||||
bool cutIn = true;
|
bool cutIn = true;
|
||||||
while (Running()) {
|
while (Running()) {
|
||||||
uchar FileNumber;
|
uint16_t FileNumber;
|
||||||
int FileOffset, Length;
|
off_t FileOffset;
|
||||||
uchar PictureType;
|
int Length;
|
||||||
|
bool Independent;
|
||||||
|
|
||||||
// Make sure there is enough disk space:
|
// Make sure there is enough disk space:
|
||||||
|
|
||||||
@ -88,7 +91,7 @@ void cCuttingThread::Action(void)
|
|||||||
|
|
||||||
// Read one frame:
|
// Read one frame:
|
||||||
|
|
||||||
if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &PictureType, &Length)) {
|
if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &Independent, &Length)) {
|
||||||
if (FileNumber != CurrentFileNumber) {
|
if (FileNumber != CurrentFileNumber) {
|
||||||
fromFile = fromFileName->SetOffset(FileNumber, FileOffset);
|
fromFile = fromFileName->SetOffset(FileNumber, FileOffset);
|
||||||
fromFile->SetReadAhead(MEGABYTE(20));
|
fromFile->SetReadAhead(MEGABYTE(20));
|
||||||
@ -119,7 +122,7 @@ void cCuttingThread::Action(void)
|
|||||||
|
|
||||||
// Write one frame:
|
// 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
|
if (LastMark) // edited version shall end before next I-frame
|
||||||
break;
|
break;
|
||||||
if (FileSize > MEGABYTE(Setup.MaxVideoFileSize)) {
|
if (FileSize > MEGABYTE(Setup.MaxVideoFileSize)) {
|
||||||
@ -141,7 +144,7 @@ void cCuttingThread::Action(void)
|
|||||||
error = "safe_write";
|
error = "safe_write";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!toIndex->Write(PictureType, toFileName->Number(), FileSize)) {
|
if (!toIndex->Write(Independent, toFileName->Number(), FileSize)) {
|
||||||
error = "toIndex";
|
error = "toIndex";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
50
device.c
50
device.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: 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"
|
#include "device.h"
|
||||||
@ -1014,6 +1014,48 @@ void cDevice::Mute(void)
|
|||||||
|
|
||||||
void cDevice::StillPicture(const uchar *Data, int Length)
|
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
|
bool cDevice::Replaying(void) const
|
||||||
@ -1301,6 +1343,11 @@ int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly)
|
|||||||
return Length;
|
return Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (Data == NULL) {
|
||||||
|
tsToPesVideo.Reset();
|
||||||
|
tsToPesAudio.Reset();
|
||||||
|
tsToPesSubtitle.Reset();
|
||||||
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1328,7 +1375,6 @@ bool cDevice::Receiving(bool CheckAny) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define TS_SCRAMBLING_CONTROL 0xC0
|
|
||||||
#define TS_SCRAMBLING_TIMEOUT 3 // seconds to wait until a TS becomes unscrambled
|
#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
|
#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
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* 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
|
#ifndef __DEVICE_H
|
||||||
@ -518,8 +518,7 @@ protected:
|
|||||||
///< Data points to exactly one complete TS packet of the given Length
|
///< Data points to exactly one complete TS packet of the given Length
|
||||||
///< (which is always TS_SIZE).
|
///< (which is always TS_SIZE).
|
||||||
///< PlayTsVideo() shall process the packet either as a whole (returning
|
///< PlayTsVideo() shall process the packet either as a whole (returning
|
||||||
///< a positive number, which needs not necessarily be Length) or not at all
|
///< Length) or not at all (returning 0 or -1 and setting 'errno' accordingly).
|
||||||
///< (returning 0 or -1 and setting 'errno' to EAGAIN).
|
|
||||||
///< The default implementation collects all incoming TS payload belonging
|
///< The default implementation collects all incoming TS payload belonging
|
||||||
///< to one PES packet and calls PlayVideo() with the resulting packet.
|
///< to one PES packet and calls PlayVideo() with the resulting packet.
|
||||||
virtual int PlayTsAudio(const uchar *Data, int Length);
|
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
|
///< Data points to exactly one complete TS packet of the given Length
|
||||||
///< (which is always TS_SIZE).
|
///< (which is always TS_SIZE).
|
||||||
///< PlayTsAudio() shall process the packet either as a whole (returning
|
///< PlayTsAudio() shall process the packet either as a whole (returning
|
||||||
///< a positive number, which needs not necessarily be Length) or not at all
|
///< Length) or not at all (returning 0 or -1 and setting 'errno' accordingly).
|
||||||
///< (returning 0 or -1 and setting 'errno' to EAGAIN).
|
|
||||||
///< The default implementation collects all incoming TS payload belonging
|
///< The default implementation collects all incoming TS payload belonging
|
||||||
///< to one PES packet and calls PlayAudio() with the resulting packet.
|
///< to one PES packet and calls PlayAudio() with the resulting packet.
|
||||||
virtual int PlayTsSubtitle(const uchar *Data, int Length);
|
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
|
///< Data points to exactly one complete TS packet of the given Length
|
||||||
///< (which is always TS_SIZE).
|
///< (which is always TS_SIZE).
|
||||||
///< PlayTsSubtitle() shall process the packet either as a whole (returning
|
///< PlayTsSubtitle() shall process the packet either as a whole (returning
|
||||||
///< a positive number, which needs not necessarily be Length) or not at all
|
///< Length) or not at all (returning 0 or -1 and setting 'errno' accordingly).
|
||||||
///< (returning 0 or -1 and setting 'errno' to EAGAIN).
|
|
||||||
///< The default implementation collects all incoming TS payload belonging
|
///< The default implementation collects all incoming TS payload belonging
|
||||||
///< to one PES packet and displays the resulting subtitle via the OSD.
|
///< to one PES packet and displays the resulting subtitle via the OSD.
|
||||||
public:
|
public:
|
||||||
@ -573,6 +570,10 @@ public:
|
|||||||
///< all registered cAudio objects are notified.
|
///< all registered cAudio objects are notified.
|
||||||
virtual void StillPicture(const uchar *Data, int Length);
|
virtual void StillPicture(const uchar *Data, int Length);
|
||||||
///< Displays the given I-frame as a still picture.
|
///< 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);
|
virtual bool Poll(cPoller &Poller, int TimeoutMs = 0);
|
||||||
///< Returns true if the device itself or any of the file handles in
|
///< Returns true if the device itself or any of the file handles in
|
||||||
///< Poller is ready for further action.
|
///< Poller is ready for further action.
|
||||||
@ -600,12 +601,14 @@ public:
|
|||||||
///< which is necessary for trick modes like 'fast forward'.
|
///< which is necessary for trick modes like 'fast forward'.
|
||||||
///< Data points to a single TS packet, Length is always TS_SIZE (the total
|
///< Data points to a single TS packet, Length is always TS_SIZE (the total
|
||||||
///< size of a single TS packet).
|
///< 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
|
///< A derived device can reimplement this function to handle the
|
||||||
///< TS packets itself. Any packets the derived function can't handle
|
///< TS packets itself. Any packets the derived function can't handle
|
||||||
///< must be sent to the base class function. This applies especially
|
///< must be sent to the base class function. This applies especially
|
||||||
///< to the PAT/PMT packets.
|
///< to the PAT/PMT packets.
|
||||||
///< Returns -1 in case of error, otherwise the number of actually
|
///< 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
|
///< PlayTs() shall process the packet either as a whole (returning
|
||||||
///< a positive number, which needs not necessarily be Length) or not at all
|
///< a positive number, which needs not necessarily be Length) or not at all
|
||||||
///< (returning 0 or -1 and setting 'errno' to EAGAIN).
|
///< (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
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* 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"
|
#include "dvbdevice.h"
|
||||||
@ -1213,7 +1213,11 @@ void cDvbDevice::Mute(void)
|
|||||||
|
|
||||||
void cDvbDevice::StillPicture(const uchar *Data, int Length)
|
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
|
// PES data
|
||||||
char *buf = MALLOC(char, Length);
|
char *buf = MALLOC(char, Length);
|
||||||
if (!buf)
|
if (!buf)
|
||||||
@ -1331,8 +1335,8 @@ int cDvbDevice::PlayTsVideo(const uchar *Data, int Length)
|
|||||||
|
|
||||||
int cDvbDevice::PlayTsAudio(const uchar *Data, int Length)
|
int cDvbDevice::PlayTsAudio(const uchar *Data, int Length)
|
||||||
{
|
{
|
||||||
Length = TsGetPayload(&Data);
|
int w = PlayAudio(Data, TsGetPayload(&Data), 0);
|
||||||
return PlayAudio(Data, Length, 0);
|
return w >= 0 ? Length : w;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cDvbDevice::OpenDvr(void)
|
bool cDvbDevice::OpenDvr(void)
|
||||||
|
76
dvbplayer.c
76
dvbplayer.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: 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"
|
#include "dvbplayer.h"
|
||||||
@ -182,8 +182,8 @@ bool cNonBlockingFileReader::WaitForDataMs(int msToWait)
|
|||||||
|
|
||||||
#define PLAYERBUFSIZE MEGABYTE(1)
|
#define PLAYERBUFSIZE MEGABYTE(1)
|
||||||
|
|
||||||
// The number of frames to back up when resuming an interrupted replay session:
|
// The number of seconds to back up when resuming an interrupted replay session:
|
||||||
#define RESUMEBACKUP (10 * FRAMESPERSEC)
|
#define RESUMEBACKUP 10
|
||||||
|
|
||||||
class cDvbPlayer : public cPlayer, cThread {
|
class cDvbPlayer : public cPlayer, cThread {
|
||||||
private:
|
private:
|
||||||
@ -196,6 +196,8 @@ private:
|
|||||||
cFileName *fileName;
|
cFileName *fileName;
|
||||||
cIndexFile *index;
|
cIndexFile *index;
|
||||||
cUnbufferedFile *replayFile;
|
cUnbufferedFile *replayFile;
|
||||||
|
double framesPerSecond;
|
||||||
|
bool isPesRecording;
|
||||||
bool eof;
|
bool eof;
|
||||||
bool firstPacket;
|
bool firstPacket;
|
||||||
ePlayModes playMode;
|
ePlayModes playMode;
|
||||||
@ -223,6 +225,7 @@ public:
|
|||||||
int SkipFrames(int Frames);
|
int SkipFrames(int Frames);
|
||||||
void SkipSeconds(int Seconds);
|
void SkipSeconds(int Seconds);
|
||||||
void Goto(int Position, bool Still = false);
|
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 GetIndex(int &Current, int &Total, bool SnapToIFrame = false);
|
||||||
virtual bool GetReplayMode(bool &Play, bool &Forward, int &Speed);
|
virtual bool GetReplayMode(bool &Play, bool &Forward, int &Speed);
|
||||||
};
|
};
|
||||||
@ -240,6 +243,9 @@ cDvbPlayer::cDvbPlayer(const char *FileName)
|
|||||||
ringBuffer = NULL;
|
ringBuffer = NULL;
|
||||||
backTrace = NULL;
|
backTrace = NULL;
|
||||||
index = NULL;
|
index = NULL;
|
||||||
|
cRecording Recording(FileName);
|
||||||
|
framesPerSecond = Recording.FramesPerSecond();
|
||||||
|
isPesRecording = Recording.IsPesRecording();
|
||||||
eof = false;
|
eof = false;
|
||||||
firstPacket = true;
|
firstPacket = true;
|
||||||
playMode = pmPlay;
|
playMode = pmPlay;
|
||||||
@ -249,13 +255,13 @@ cDvbPlayer::cDvbPlayer(const char *FileName)
|
|||||||
readFrame = NULL;
|
readFrame = NULL;
|
||||||
playFrame = NULL;
|
playFrame = NULL;
|
||||||
isyslog("replay %s", FileName);
|
isyslog("replay %s", FileName);
|
||||||
fileName = new cFileName(FileName, false);
|
fileName = new cFileName(FileName, false, false, isPesRecording);
|
||||||
replayFile = fileName->Open();
|
replayFile = fileName->Open();
|
||||||
if (!replayFile)
|
if (!replayFile)
|
||||||
return;
|
return;
|
||||||
ringBuffer = new cRingBufferFrame(PLAYERBUFSIZE);
|
ringBuffer = new cRingBufferFrame(PLAYERBUFSIZE);
|
||||||
// Create the index file:
|
// Create the index file:
|
||||||
index = new cIndexFile(FileName, false);
|
index = new cIndexFile(FileName, false, isPesRecording);
|
||||||
if (!index)
|
if (!index)
|
||||||
esyslog("ERROR: can't allocate index");
|
esyslog("ERROR: can't allocate index");
|
||||||
else if (!index->Ok()) {
|
else if (!index->Ok()) {
|
||||||
@ -327,8 +333,8 @@ int cDvbPlayer::Resume(void)
|
|||||||
if (index) {
|
if (index) {
|
||||||
int Index = index->GetResume();
|
int Index = index->GetResume();
|
||||||
if (Index >= 0) {
|
if (Index >= 0) {
|
||||||
uchar FileNumber;
|
uint16_t FileNumber;
|
||||||
int FileOffset;
|
off_t FileOffset;
|
||||||
if (index->Get(Index, &FileNumber, &FileOffset) && NextFile(FileNumber, FileOffset))
|
if (index->Get(Index, &FileNumber, &FileOffset) && NextFile(FileNumber, FileOffset))
|
||||||
return Index;
|
return Index;
|
||||||
}
|
}
|
||||||
@ -341,7 +347,7 @@ bool cDvbPlayer::Save(void)
|
|||||||
if (index) {
|
if (index) {
|
||||||
int Index = writeIndex;
|
int Index = writeIndex;
|
||||||
if (Index >= 0) {
|
if (Index >= 0) {
|
||||||
Index -= RESUMEBACKUP;
|
Index -= RESUMEBACKUP * framesPerSecond;
|
||||||
if (Index > 0)
|
if (Index > 0)
|
||||||
Index = index->GetNextIFrame(Index, false);
|
Index = index->GetNextIFrame(Index, false);
|
||||||
else
|
else
|
||||||
@ -371,7 +377,7 @@ void cDvbPlayer::Action(void)
|
|||||||
|
|
||||||
readIndex = Resume();
|
readIndex = Resume();
|
||||||
if (readIndex >= 0)
|
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;
|
nonBlockingFileReader = new cNonBlockingFileReader;
|
||||||
int Length = 0;
|
int Length = 0;
|
||||||
@ -397,8 +403,8 @@ void cDvbPlayer::Action(void)
|
|||||||
if (!readFrame && (replayFile || readIndex >= 0)) {
|
if (!readFrame && (replayFile || readIndex >= 0)) {
|
||||||
if (!nonBlockingFileReader->Reading()) {
|
if (!nonBlockingFileReader->Reading()) {
|
||||||
if (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) {
|
if (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) {
|
||||||
uchar FileNumber;
|
uint16_t FileNumber;
|
||||||
int FileOffset;
|
off_t FileOffset;
|
||||||
bool TimeShiftMode = index->IsStillRecording();
|
bool TimeShiftMode = index->IsStillRecording();
|
||||||
int Index = -1;
|
int Index = -1;
|
||||||
if (DeviceHasIBPTrickSpeed() && playDir == pdForward) {
|
if (DeviceHasIBPTrickSpeed() && playDir == pdForward) {
|
||||||
@ -435,8 +441,8 @@ void cDvbPlayer::Action(void)
|
|||||||
readIndex = Index;
|
readIndex = Index;
|
||||||
}
|
}
|
||||||
else if (index) {
|
else if (index) {
|
||||||
uchar FileNumber;
|
uint16_t FileNumber;
|
||||||
int FileOffset;
|
off_t FileOffset;
|
||||||
readIndex++;
|
readIndex++;
|
||||||
if (!(index->Get(readIndex, &FileNumber, &FileOffset, NULL, &Length) && NextFile(FileNumber, FileOffset))) {
|
if (!(index->Get(readIndex, &FileNumber, &FileOffset, NULL, &Length) && NextFile(FileNumber, FileOffset))) {
|
||||||
readIndex = -1;
|
readIndex = -1;
|
||||||
@ -496,14 +502,22 @@ void cDvbPlayer::Action(void)
|
|||||||
pc = playFrame->Count();
|
pc = playFrame->Count();
|
||||||
if (p) {
|
if (p) {
|
||||||
if (firstPacket) {
|
if (firstPacket) {
|
||||||
|
if (isPesRecording) {
|
||||||
PlayPes(NULL, 0);
|
PlayPes(NULL, 0);
|
||||||
cRemux::SetBrokenLink(p, pc);
|
cRemux::SetBrokenLink(p, pc);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
PlayTs(NULL, 0);
|
||||||
firstPacket = false;
|
firstPacket = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (p) {
|
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) {
|
if (w > 0) {
|
||||||
p += w;
|
p += w;
|
||||||
pc -= w;
|
pc -= w;
|
||||||
@ -513,7 +527,7 @@ void cDvbPlayer::Action(void)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pc == 0) {
|
if (pc <= 0) {
|
||||||
writeIndex = playFrame->Index();
|
writeIndex = playFrame->Index();
|
||||||
backTrace->Add(playFrame->Index(), playFrame->Count());
|
backTrace->Add(playFrame->Index(), playFrame->Count());
|
||||||
ringBuffer->Drop(playFrame);
|
ringBuffer->Drop(playFrame);
|
||||||
@ -678,7 +692,7 @@ void cDvbPlayer::SkipSeconds(int Seconds)
|
|||||||
Empty();
|
Empty();
|
||||||
int Index = writeIndex;
|
int Index = writeIndex;
|
||||||
if (Index >= 0) {
|
if (Index >= 0) {
|
||||||
Index = max(Index + Seconds * FRAMESPERSEC, 0);
|
Index = max(Index + SecondsToFrames(Seconds, framesPerSecond), 0);
|
||||||
if (Index > 0)
|
if (Index > 0)
|
||||||
Index = index->GetNextIFrame(Index, false, NULL, NULL, NULL, true);
|
Index = index->GetNextIFrame(Index, false, NULL, NULL, NULL, true);
|
||||||
if (Index >= 0)
|
if (Index >= 0)
|
||||||
@ -695,38 +709,16 @@ void cDvbPlayer::Goto(int Index, bool Still)
|
|||||||
Empty();
|
Empty();
|
||||||
if (++Index <= 0)
|
if (++Index <= 0)
|
||||||
Index = 1; // not '0', to allow GetNextIFrame() below to work!
|
Index = 1; // not '0', to allow GetNextIFrame() below to work!
|
||||||
uchar FileNumber;
|
uint16_t FileNumber;
|
||||||
int FileOffset, Length;
|
off_t FileOffset;
|
||||||
|
int Length;
|
||||||
Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset, &Length);
|
Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset, &Length);
|
||||||
if (Index >= 0 && NextFile(FileNumber, FileOffset) && Still) {
|
if (Index >= 0 && NextFile(FileNumber, FileOffset) && Still) {
|
||||||
uchar b[MAXFRAMESIZE + 4 + 5 + 4];
|
uchar b[MAXFRAMESIZE];
|
||||||
int r = ReadFrame(replayFile, b, Length, sizeof(b));
|
int r = ReadFrame(replayFile, b, Length, sizeof(b));
|
||||||
if (r > 0) {
|
if (r > 0) {
|
||||||
if (playMode == pmPause)
|
if (playMode == pmPause)
|
||||||
DevicePlay();
|
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);
|
DeviceStillPicture(b, r);
|
||||||
}
|
}
|
||||||
playMode = pmStill;
|
playMode = pmStill;
|
||||||
|
21
menu.c
21
menu.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: 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"
|
#include "menu.h"
|
||||||
@ -1989,11 +1989,14 @@ eOSState cMenuRecordings::Rewind(void)
|
|||||||
return osContinue;
|
return osContinue;
|
||||||
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
|
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
|
||||||
if (ri && !ri->IsDirectory()) {
|
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
|
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();
|
ResumeFile.Delete();
|
||||||
return Play();
|
return Play();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return osContinue;
|
return osContinue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4014,9 +4017,9 @@ cReplayControl::cReplayControl(void)
|
|||||||
lastSpeed = -2; // an invalid value
|
lastSpeed = -2; // an invalid value
|
||||||
timeoutShow = 0;
|
timeoutShow = 0;
|
||||||
timeSearchActive = false;
|
timeSearchActive = false;
|
||||||
marks.Load(fileName);
|
|
||||||
cRecording Recording(fileName);
|
cRecording Recording(fileName);
|
||||||
cStatus::MsgReplaying(this, Recording.Name(), Recording.FileName(), true);
|
cStatus::MsgReplaying(this, Recording.Name(), Recording.FileName(), true);
|
||||||
|
marks.Load(fileName, Recording.FramesPerSecond(), Recording.IsPesRecording());
|
||||||
SetTrackDescriptions(false);
|
SetTrackDescriptions(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4126,7 +4129,7 @@ bool cReplayControl::ShowProgress(bool Initial)
|
|||||||
lastCurrent = lastTotal = -1;
|
lastCurrent = lastTotal = -1;
|
||||||
}
|
}
|
||||||
if (Total != lastTotal) {
|
if (Total != lastTotal) {
|
||||||
displayReplay->SetTotal(IndexToHMSF(Total));
|
displayReplay->SetTotal(IndexToHMSF(Total, false, FramesPerSecond()));
|
||||||
if (!Initial)
|
if (!Initial)
|
||||||
displayReplay->Flush();
|
displayReplay->Flush();
|
||||||
}
|
}
|
||||||
@ -4134,7 +4137,7 @@ bool cReplayControl::ShowProgress(bool Initial)
|
|||||||
displayReplay->SetProgress(Current, Total);
|
displayReplay->SetProgress(Current, Total);
|
||||||
if (!Initial)
|
if (!Initial)
|
||||||
displayReplay->Flush();
|
displayReplay->Flush();
|
||||||
displayReplay->SetCurrent(IndexToHMSF(Current, displayFrames));
|
displayReplay->SetCurrent(IndexToHMSF(Current, displayFrames, FramesPerSecond()));
|
||||||
displayReplay->Flush();
|
displayReplay->Flush();
|
||||||
lastCurrent = Current;
|
lastCurrent = Current;
|
||||||
}
|
}
|
||||||
@ -4167,8 +4170,8 @@ void cReplayControl::TimeSearchProcess(eKeys Key)
|
|||||||
{
|
{
|
||||||
#define STAY_SECONDS_OFF_END 10
|
#define STAY_SECONDS_OFF_END 10
|
||||||
int Seconds = (timeSearchTime >> 24) * 36000 + ((timeSearchTime & 0x00FF0000) >> 16) * 3600 + ((timeSearchTime & 0x0000FF00) >> 8) * 600 + (timeSearchTime & 0x000000FF) * 60;
|
int Seconds = (timeSearchTime >> 24) * 36000 + ((timeSearchTime & 0x00FF0000) >> 16) * 3600 + ((timeSearchTime & 0x0000FF00) >> 8) * 600 + (timeSearchTime & 0x000000FF) * 60;
|
||||||
int Current = (lastCurrent / FRAMESPERSEC);
|
int Current = (lastCurrent / FramesPerSecond());
|
||||||
int Total = (lastTotal / FRAMESPERSEC);
|
int Total = (lastTotal / FramesPerSecond());
|
||||||
switch (Key) {
|
switch (Key) {
|
||||||
case k0 ... k9:
|
case k0 ... k9:
|
||||||
if (timeSearchPos < 4) {
|
if (timeSearchPos < 4) {
|
||||||
@ -4195,7 +4198,7 @@ void cReplayControl::TimeSearchProcess(eKeys Key)
|
|||||||
case kDown:
|
case kDown:
|
||||||
case kOk:
|
case kOk:
|
||||||
Seconds = min(Total - STAY_SECONDS_OFF_END, Seconds);
|
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;
|
timeSearchActive = false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -4317,7 +4320,7 @@ void cReplayControl::EditTest(void)
|
|||||||
if ((m->Index() & 0x01) != 0)
|
if ((m->Index() & 0x01) != 0)
|
||||||
m = marks.Next(m);
|
m = marks.Next(m);
|
||||||
if (m) {
|
if (m) {
|
||||||
Goto(m->position - SecondsToFrames(3));
|
Goto(m->position - SecondsToFrames(3, FramesPerSecond()));
|
||||||
Play();
|
Play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
player.h
5
player.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: 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
|
#ifndef __PLAYER_H
|
||||||
@ -50,6 +50,8 @@ public:
|
|||||||
cPlayer(ePlayMode PlayMode = pmAudioVideo);
|
cPlayer(ePlayMode PlayMode = pmAudioVideo);
|
||||||
virtual ~cPlayer();
|
virtual ~cPlayer();
|
||||||
bool IsAttached(void) { return device != NULL; }
|
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; }
|
virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false) { return false; }
|
||||||
// Returns the current and total frame index, optionally snapped to the
|
// Returns the current and total frame index, optionally snapped to the
|
||||||
// nearest I-frame.
|
// nearest I-frame.
|
||||||
@ -82,6 +84,7 @@ public:
|
|||||||
virtual ~cControl();
|
virtual ~cControl();
|
||||||
virtual void Hide(void) = 0;
|
virtual void Hide(void) = 0;
|
||||||
virtual cOsdObject *GetInfo(void);
|
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 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); }
|
bool GetReplayMode(bool &Play, bool &Forward, int &Speed) { return player->GetReplayMode(Play, Forward, Speed); }
|
||||||
static void Launch(cControl *Control);
|
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
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* 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 "recorder.h"
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include "shutdown.h"
|
#include "shutdown.h"
|
||||||
|
|
||||||
#define RECORDERBUFSIZE MEGABYTE(5)
|
#define RECORDERBUFSIZE MEGABYTE(5)
|
||||||
@ -22,33 +19,34 @@
|
|||||||
#define MINFREEDISKSPACE (512) // MB
|
#define MINFREEDISKSPACE (512) // MB
|
||||||
#define DISKCHECKINTERVAL 100 // seconds
|
#define DISKCHECKINTERVAL 100 // seconds
|
||||||
|
|
||||||
// --- cFileWriter -----------------------------------------------------------
|
// --- cRecorder -------------------------------------------------------------
|
||||||
|
|
||||||
class cFileWriter : public cThread {
|
cRecorder::cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids)
|
||||||
private:
|
:cReceiver(ChannelID, Priority, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids)
|
||||||
cRemux *remux;
|
,cThread("recording")
|
||||||
cFileName *fileName;
|
,recordingInfo(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")
|
|
||||||
{
|
{
|
||||||
|
// 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;
|
fileName = NULL;
|
||||||
remux = Remux;
|
|
||||||
index = NULL;
|
index = NULL;
|
||||||
pictureType = NO_PICTURE;
|
|
||||||
fileSize = 0;
|
fileSize = 0;
|
||||||
lastDiskSpaceCheck = time(NULL);
|
lastDiskSpaceCheck = time(NULL);
|
||||||
fileName = new cFileName(FileName, true);
|
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
|
// let's continue without index, so we'll at least have the recording
|
||||||
}
|
}
|
||||||
|
|
||||||
cFileWriter::~cFileWriter()
|
cRecorder::~cRecorder()
|
||||||
{
|
{
|
||||||
Cancel(3);
|
Detach();
|
||||||
delete index;
|
delete index;
|
||||||
delete fileName;
|
delete fileName;
|
||||||
|
delete frameDetector;
|
||||||
|
delete ringBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cFileWriter::RunningLowOnDiskSpace(void)
|
bool cRecorder::RunningLowOnDiskSpace(void)
|
||||||
{
|
{
|
||||||
if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
|
if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
|
||||||
int Free = FreeDiskSpaceMB(fileName->Name());
|
int Free = FreeDiskSpaceMB(fileName->Name());
|
||||||
@ -82,10 +82,10 @@ bool cFileWriter::RunningLowOnDiskSpace(void)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cFileWriter::NextFile(void)
|
bool cRecorder::NextFile(void)
|
||||||
{
|
{
|
||||||
if (recordFile && pictureType == I_FRAME) { // every file shall start with an I_FRAME
|
if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame
|
||||||
if (fileSize > MEGABYTE(Setup.MaxVideoFileSize) || RunningLowOnDiskSpace()) {
|
if (fileSize > MEGABYTE(off_t(Setup.MaxVideoFileSize)) || RunningLowOnDiskSpace()) {
|
||||||
recordFile = fileName->NextFile();
|
recordFile = fileName->NextFile();
|
||||||
fileSize = 0;
|
fileSize = 0;
|
||||||
}
|
}
|
||||||
@ -93,67 +93,10 @@ bool cFileWriter::NextFile(void)
|
|||||||
return recordFile != NULL;
|
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)
|
void cRecorder::Activate(bool On)
|
||||||
{
|
{
|
||||||
if (On) {
|
if (On)
|
||||||
writer->Start();
|
|
||||||
Start();
|
Start();
|
||||||
}
|
|
||||||
else
|
else
|
||||||
Cancel(3);
|
Cancel(3);
|
||||||
}
|
}
|
||||||
@ -169,15 +112,54 @@ void cRecorder::Receive(uchar *Data, int Length)
|
|||||||
|
|
||||||
void cRecorder::Action(void)
|
void cRecorder::Action(void)
|
||||||
{
|
{
|
||||||
|
time_t t = time(NULL);
|
||||||
|
bool Synced = false;
|
||||||
|
bool InfoWritten = false;
|
||||||
while (Running()) {
|
while (Running()) {
|
||||||
int r;
|
int r;
|
||||||
uchar *b = ringBuffer->Get(r);
|
uchar *b = ringBuffer->Get(r);
|
||||||
if (b) {
|
if (b) {
|
||||||
int Count = remux->Put(b, r);
|
int Count = frameDetector->Analyze(b, r);
|
||||||
if (Count)
|
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);
|
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
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* 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
|
#ifndef __RECORDER_H
|
||||||
@ -16,13 +16,19 @@
|
|||||||
#include "ringbuffer.h"
|
#include "ringbuffer.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
|
|
||||||
class cFileWriter;
|
|
||||||
|
|
||||||
class cRecorder : public cReceiver, cThread {
|
class cRecorder : public cReceiver, cThread {
|
||||||
private:
|
private:
|
||||||
cRingBufferLinear *ringBuffer;
|
cRingBufferLinear *ringBuffer;
|
||||||
cRemux *remux;
|
cFrameDetector *frameDetector;
|
||||||
cFileWriter *writer;
|
cPatPmtGenerator patPmtGenerator;
|
||||||
|
cFileName *fileName;
|
||||||
|
cIndexFile *index;
|
||||||
|
cUnbufferedFile *recordFile;
|
||||||
|
cRecordingInfo recordingInfo;
|
||||||
|
off_t fileSize;
|
||||||
|
time_t lastDiskSpaceCheck;
|
||||||
|
bool RunningLowOnDiskSpace(void);
|
||||||
|
bool NextFile(void);
|
||||||
protected:
|
protected:
|
||||||
virtual void Activate(bool On);
|
virtual void Activate(bool On);
|
||||||
virtual void Receive(uchar *Data, int Length);
|
virtual void Receive(uchar *Data, int Length);
|
||||||
|
325
recording.c
325
recording.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: 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"
|
#include "recording.h"
|
||||||
@ -12,6 +12,7 @@
|
|||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <math.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@ -19,7 +20,6 @@
|
|||||||
#include "channels.h"
|
#include "channels.h"
|
||||||
#include "i18n.h"
|
#include "i18n.h"
|
||||||
#include "interface.h"
|
#include "interface.h"
|
||||||
#include "remux.h" //XXX+ I_FRAME
|
|
||||||
#include "skins.h"
|
#include "skins.h"
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
#include "videodir.h"
|
#include "videodir.h"
|
||||||
@ -37,15 +37,17 @@
|
|||||||
#define DATAFORMAT "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT
|
#define DATAFORMAT "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT
|
||||||
#define NAMEFORMAT "%s/%s/" DATAFORMAT
|
#define NAMEFORMAT "%s/%s/" DATAFORMAT
|
||||||
*/
|
*/
|
||||||
#define DATAFORMAT "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT
|
#define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT
|
||||||
#define NAMEFORMAT "%s/%s/" "%4d-%02d-%02d.%02d.%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
|
#ifdef SUMMARYFALLBACK
|
||||||
#define SUMMARYFILESUFFIX "/summary.vdr"
|
#define SUMMARYFILESUFFIX "/summary.vdr"
|
||||||
#endif
|
#endif
|
||||||
#define INFOFILESUFFIX "/info.vdr"
|
#define INFOFILESUFFIX "/info"
|
||||||
#define MARKSFILESUFFIX "/marks.vdr"
|
#define MARKSFILESUFFIX "/marks"
|
||||||
|
|
||||||
#define MINDISKSPACE 1024 // MB
|
#define MINDISKSPACE 1024 // MB
|
||||||
|
|
||||||
@ -202,12 +204,14 @@ void AssertFreeDiskSpace(int Priority, bool Force)
|
|||||||
|
|
||||||
// --- cResumeFile -----------------------------------------------------------
|
// --- 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) {
|
if (fileName) {
|
||||||
strcpy(fileName, 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
|
else
|
||||||
esyslog("ERROR: can't allocate memory for resume file name");
|
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
|
if ((st.st_mode & S_IWUSR) == 0) // no write access, assume no resume
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
if (isPesRecording) {
|
||||||
int f = open(fileName, O_RDONLY);
|
int f = open(fileName, O_RDONLY);
|
||||||
if (f >= 0) {
|
if (f >= 0) {
|
||||||
if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) {
|
if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) {
|
||||||
@ -238,12 +243,33 @@ int cResumeFile::Read(void)
|
|||||||
else if (errno != ENOENT)
|
else if (errno != ENOENT)
|
||||||
LOG_ERROR_STR(fileName);
|
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;
|
return resume;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cResumeFile::Save(int Index)
|
bool cResumeFile::Save(int Index)
|
||||||
{
|
{
|
||||||
if (fileName) {
|
if (fileName) {
|
||||||
|
if (isPesRecording) {
|
||||||
int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
|
int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
|
||||||
if (f >= 0) {
|
if (f >= 0) {
|
||||||
if (safe_write(f, &Index, sizeof(Index)) < 0)
|
if (safe_write(f, &Index, sizeof(Index)) < 0)
|
||||||
@ -253,6 +279,17 @@ bool cResumeFile::Save(int Index)
|
|||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,6 +311,10 @@ cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event)
|
|||||||
ownEvent = Event ? NULL : new cEvent(0);
|
ownEvent = Event ? NULL : new cEvent(0);
|
||||||
event = ownEvent ? ownEvent : Event;
|
event = ownEvent ? ownEvent : Event;
|
||||||
aux = NULL;
|
aux = NULL;
|
||||||
|
framesPerSecond = DEFAULTFRAMESPERSECOND;
|
||||||
|
priority = MAXPRIORITY;
|
||||||
|
lifetime = MAXLIFETIME;
|
||||||
|
fileName = NULL;
|
||||||
if (Channel) {
|
if (Channel) {
|
||||||
// Since the EPG data's component records can carry only a single
|
// Since the EPG data's component records can carry only a single
|
||||||
// language code, let's see whether the channel's PID data has
|
// 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()
|
cRecordingInfo::~cRecordingInfo()
|
||||||
{
|
{
|
||||||
delete ownEvent;
|
delete ownEvent;
|
||||||
free(aux);
|
free(aux);
|
||||||
free(channelName);
|
free(channelName);
|
||||||
|
free(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cRecordingInfo::SetData(const char *Title, const char *ShortText, const char *Description)
|
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;
|
aux = Aux ? strdup(Aux) : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cRecordingInfo::SetFramesPerSecond(double FramesPerSecond)
|
||||||
|
{
|
||||||
|
framesPerSecond = FramesPerSecond;
|
||||||
|
}
|
||||||
|
|
||||||
bool cRecordingInfo::Read(FILE *f)
|
bool cRecordingInfo::Read(FILE *f)
|
||||||
{
|
{
|
||||||
if (ownEvent) {
|
if (ownEvent) {
|
||||||
@ -382,6 +442,12 @@ bool cRecordingInfo::Read(FILE *f)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'F': framesPerSecond = atof(t);
|
||||||
|
break;
|
||||||
|
case 'L': lifetime = atoi(t);
|
||||||
|
break;
|
||||||
|
case 'P': priority = atoi(t);
|
||||||
|
break;
|
||||||
case '@': free(aux);
|
case '@': free(aux);
|
||||||
aux = strdup(t);
|
aux = strdup(t);
|
||||||
break;
|
break;
|
||||||
@ -403,11 +469,48 @@ bool cRecordingInfo::Write(FILE *f, const char *Prefix) const
|
|||||||
if (channelID.Valid())
|
if (channelID.Valid())
|
||||||
fprintf(f, "%sC %s%s%s\n", Prefix, *channelID.ToString(), channelName ? " " : "", channelName ? channelName : "");
|
fprintf(f, "%sC %s%s%s\n", Prefix, *channelID.ToString(), channelName ? " " : "", channelName ? channelName : "");
|
||||||
event->Dump(f, Prefix, true);
|
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)
|
if (aux)
|
||||||
fprintf(f, "%s@ %s\n", Prefix, aux);
|
fprintf(f, "%s@ %s\n", Prefix, aux);
|
||||||
return true;
|
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 ------------------------------------------------------------
|
// --- cRecording ------------------------------------------------------------
|
||||||
|
|
||||||
#define RESUME_NOT_INITIALIZED (-2)
|
#define RESUME_NOT_INITIALIZED (-2)
|
||||||
@ -497,6 +600,10 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event)
|
|||||||
fileName = NULL;
|
fileName = NULL;
|
||||||
name = NULL;
|
name = NULL;
|
||||||
fileSizeMB = -1; // unknown
|
fileSizeMB = -1; // unknown
|
||||||
|
channel = Timer->Channel()->Number();
|
||||||
|
resumeId = Setup.ResumeID;
|
||||||
|
isPesRecording = false;
|
||||||
|
framesPerSecond = DEFAULTFRAMESPERSECOND;
|
||||||
deleted = 0;
|
deleted = 0;
|
||||||
// set up the actual name:
|
// set up the actual name:
|
||||||
const char *Title = Event ? Event->Title() : NULL;
|
const char *Title = Event ? Event->Title() : NULL;
|
||||||
@ -542,12 +649,20 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event)
|
|||||||
// handle info:
|
// handle info:
|
||||||
info = new cRecordingInfo(Timer->Channel(), Event);
|
info = new cRecordingInfo(Timer->Channel(), Event);
|
||||||
info->SetAux(Timer->Aux());
|
info->SetAux(Timer->Aux());
|
||||||
|
info->priority = priority;
|
||||||
|
info->lifetime = lifetime;
|
||||||
}
|
}
|
||||||
|
|
||||||
cRecording::cRecording(const char *FileName)
|
cRecording::cRecording(const char *FileName)
|
||||||
{
|
{
|
||||||
resume = RESUME_NOT_INITIALIZED;
|
resume = RESUME_NOT_INITIALIZED;
|
||||||
fileSizeMB = -1; // unknown
|
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;
|
deleted = 0;
|
||||||
titleBuffer = NULL;
|
titleBuffer = NULL;
|
||||||
sortBuffer = NULL;
|
sortBuffer = NULL;
|
||||||
@ -562,7 +677,8 @@ cRecording::cRecording(const char *FileName)
|
|||||||
struct tm tm_r;
|
struct tm tm_r;
|
||||||
struct tm t = *localtime_r(&now, &tm_r); // this initializes the time zone in 't'
|
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
|
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_year -= 1900;
|
||||||
t.tm_mon--;
|
t.tm_mon--;
|
||||||
t.tm_sec = 0;
|
t.tm_sec = 0;
|
||||||
@ -571,14 +687,22 @@ cRecording::cRecording(const char *FileName)
|
|||||||
strncpy(name, FileName, p - FileName);
|
strncpy(name, FileName, p - FileName);
|
||||||
name[p - FileName] = 0;
|
name[p - FileName] = 0;
|
||||||
name = ExchangeChars(name, false);
|
name = ExchangeChars(name, false);
|
||||||
|
isPesRecording = resumeId < 0;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
return;
|
||||||
GetResume();
|
GetResume();
|
||||||
// read an optional info file:
|
// 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");
|
FILE *f = fopen(InfoFileName, "r");
|
||||||
if (f) {
|
if (f) {
|
||||||
if (!info->Read(f))
|
if (!info->Read(f))
|
||||||
esyslog("ERROR: EPG data problem in file %s", *InfoFileName);
|
esyslog("ERROR: EPG data problem in file %s", *InfoFileName);
|
||||||
|
else if (!isPesRecording) {
|
||||||
|
priority = info->priority;
|
||||||
|
lifetime = info->lifetime;
|
||||||
|
framesPerSecond = info->framesPerSecond;
|
||||||
|
}
|
||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
else if (errno != ENOENT)
|
else if (errno != ENOENT)
|
||||||
@ -683,7 +807,7 @@ char *cRecording::SortName(void) const
|
|||||||
int cRecording::GetResume(void) const
|
int cRecording::GetResume(void) const
|
||||||
{
|
{
|
||||||
if (resume == RESUME_NOT_INITIALIZED) {
|
if (resume == RESUME_NOT_INITIALIZED) {
|
||||||
cResumeFile ResumeFile(FileName());
|
cResumeFile ResumeFile(FileName(), isPesRecording);
|
||||||
resume = ResumeFile.Read();
|
resume = ResumeFile.Read();
|
||||||
}
|
}
|
||||||
return resume;
|
return resume;
|
||||||
@ -700,8 +824,11 @@ const char *cRecording::FileName(void) const
|
|||||||
if (!fileName) {
|
if (!fileName) {
|
||||||
struct tm tm_r;
|
struct tm tm_r;
|
||||||
struct tm *t = localtime_r(&start, &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);
|
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);
|
name = ExchangeChars(name, false);
|
||||||
}
|
}
|
||||||
return fileName;
|
return fileName;
|
||||||
@ -789,7 +916,7 @@ bool cRecording::IsEdited(void) const
|
|||||||
|
|
||||||
bool cRecording::WriteInfo(void)
|
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");
|
FILE *f = fopen(InfoFileName, "w");
|
||||||
if (f) {
|
if (f) {
|
||||||
info->Write(f);
|
info->Write(f);
|
||||||
@ -1061,10 +1188,14 @@ void cRecordings::ResetResume(const char *ResumeFileName)
|
|||||||
|
|
||||||
// --- cMark -----------------------------------------------------------------
|
// --- cMark -----------------------------------------------------------------
|
||||||
|
|
||||||
cMark::cMark(int Position, const char *Comment)
|
double MarkFramesPerSecond = DEFAULTFRAMESPERSECOND;
|
||||||
|
cMutex MutexMarkFramesPerSecond;
|
||||||
|
|
||||||
|
cMark::cMark(int Position, const char *Comment, double FramesPerSecond)
|
||||||
{
|
{
|
||||||
position = Position;
|
position = Position;
|
||||||
comment = Comment ? strdup(Comment) : NULL;
|
comment = Comment ? strdup(Comment) : NULL;
|
||||||
|
framesPerSecond = FramesPerSecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
cMark::~cMark()
|
cMark::~cMark()
|
||||||
@ -1074,14 +1205,15 @@ cMark::~cMark()
|
|||||||
|
|
||||||
cString cMark::ToText(void)
|
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)
|
bool cMark::Parse(const char *s)
|
||||||
{
|
{
|
||||||
free(comment);
|
free(comment);
|
||||||
comment = NULL;
|
comment = NULL;
|
||||||
position = HMSFToIndex(s);
|
framesPerSecond = MarkFramesPerSecond;
|
||||||
|
position = HMSFToIndex(s, framesPerSecond);
|
||||||
const char *p = strchr(s, ' ');
|
const char *p = strchr(s, ' ');
|
||||||
if (p) {
|
if (p) {
|
||||||
p = skipspace(p);
|
p = skipspace(p);
|
||||||
@ -1098,9 +1230,12 @@ bool cMark::Save(FILE *f)
|
|||||||
|
|
||||||
// --- cMarks ----------------------------------------------------------------
|
// --- 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();
|
Sort();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1123,7 +1258,7 @@ cMark *cMarks::Add(int Position)
|
|||||||
{
|
{
|
||||||
cMark *m = Get(Position);
|
cMark *m = Get(Position);
|
||||||
if (!m) {
|
if (!m) {
|
||||||
cConfig<cMark>::Add(m = new cMark(Position));
|
cConfig<cMark>::Add(m = new cMark(Position, NULL, framesPerSecond));
|
||||||
Sort();
|
Sort();
|
||||||
}
|
}
|
||||||
return m;
|
return m;
|
||||||
@ -1169,12 +1304,9 @@ void cRecordingUserCommand::InvokeCommand(const char *State, const char *Recordi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- XXX+
|
|
||||||
|
|
||||||
//XXX+ somewhere else???
|
|
||||||
// --- cIndexFile ------------------------------------------------------------
|
// --- cIndexFile ------------------------------------------------------------
|
||||||
|
|
||||||
#define INDEXFILESUFFIX "/index.vdr"
|
#define INDEXFILESUFFIX "/index"
|
||||||
|
|
||||||
// The number of frames to stay off the end in case of time shift:
|
// The number of frames to stay off the end in case of time shift:
|
||||||
#define INDEXSAFETYLIMIT 150 // frames
|
#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:
|
// The minimum age of an index file for considering it no longer to be written:
|
||||||
#define MININDEXAGE 3600 // seconds
|
#define MININDEXAGE 3600 // seconds
|
||||||
|
|
||||||
cIndexFile::cIndexFile(const char *FileName, bool Record)
|
struct tIndexPes {
|
||||||
:resumeFile(FileName)
|
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;
|
f = -1;
|
||||||
fileName = NULL;
|
fileName = NULL;
|
||||||
size = 0;
|
size = 0;
|
||||||
last = -1;
|
last = -1;
|
||||||
index = NULL;
|
index = NULL;
|
||||||
|
isPesRecording = IsPesRecording;
|
||||||
if (FileName) {
|
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) {
|
if (fileName) {
|
||||||
strcpy(fileName, FileName);
|
strcpy(fileName, FileName);
|
||||||
char *pFileExt = fileName + strlen(fileName);
|
char *pFileExt = fileName + strlen(fileName);
|
||||||
strcpy(pFileExt, INDEXFILESUFFIX);
|
strcpy(pFileExt, Suffix);
|
||||||
int delta = 0;
|
int delta = 0;
|
||||||
if (access(fileName, R_OK) == 0) {
|
if (access(fileName, R_OK) == 0) {
|
||||||
struct stat buf;
|
struct stat buf;
|
||||||
if (stat(fileName, &buf) == 0) {
|
if (stat(fileName, &buf) == 0) {
|
||||||
delta = buf.st_size % sizeof(tIndex);
|
delta = buf.st_size % sizeof(tIndexTs);
|
||||||
if (delta) {
|
if (delta) {
|
||||||
delta = sizeof(tIndex) - delta;
|
delta = sizeof(tIndexTs) - delta;
|
||||||
esyslog("ERROR: invalid file size (%ld) in '%s'", buf.st_size, fileName);
|
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) {
|
if (!Record && last >= 0) {
|
||||||
size = last + 1;
|
size = last + 1;
|
||||||
index = MALLOC(tIndex, size);
|
index = MALLOC(tIndexTs, size);
|
||||||
if (index) {
|
if (index) {
|
||||||
f = open(fileName, O_RDONLY);
|
f = open(fileName, O_RDONLY);
|
||||||
if (f >= 0) {
|
if (f >= 0) {
|
||||||
@ -1223,12 +1378,14 @@ cIndexFile::cIndexFile(const char *FileName, bool Record)
|
|||||||
f = -1;
|
f = -1;
|
||||||
}
|
}
|
||||||
// we don't close f here, see CatchUp()!
|
// we don't close f here, see CatchUp()!
|
||||||
|
else if (isPesRecording)
|
||||||
|
ConvertFromPes(index, size);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
LOG_ERROR_STR(fileName);
|
LOG_ERROR_STR(fileName);
|
||||||
}
|
}
|
||||||
else
|
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
|
else
|
||||||
@ -1261,6 +1418,18 @@ cIndexFile::~cIndexFile()
|
|||||||
free(index);
|
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)
|
bool cIndexFile::CatchUp(int Index)
|
||||||
{
|
{
|
||||||
// returns true unless something really goes wrong, so that 'index' becomes NULL
|
// returns true unless something really goes wrong, so that 'index' becomes NULL
|
||||||
@ -1275,17 +1444,17 @@ bool cIndexFile::CatchUp(int Index)
|
|||||||
f = -1;
|
f = -1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
int newLast = buf.st_size / sizeof(tIndex) - 1;
|
int newLast = buf.st_size / sizeof(tIndexTs) - 1;
|
||||||
if (newLast > last) {
|
if (newLast > last) {
|
||||||
if (size <= newLast) {
|
if (size <= newLast) {
|
||||||
size *= 2;
|
size *= 2;
|
||||||
if (size <= newLast)
|
if (size <= newLast)
|
||||||
size = newLast + 1;
|
size = newLast + 1;
|
||||||
}
|
}
|
||||||
index = (tIndex *)realloc(index, size * sizeof(tIndex));
|
index = (tIndexTs *)realloc(index, size * sizeof(tIndexTs));
|
||||||
if (index) {
|
if (index) {
|
||||||
int offset = (last + 1) * sizeof(tIndex);
|
int offset = (last + 1) * sizeof(tIndexTs);
|
||||||
int delta = (newLast - last) * sizeof(tIndex);
|
int delta = (newLast - last) * sizeof(tIndexTs);
|
||||||
if (lseek(f, offset, SEEK_SET) == offset) {
|
if (lseek(f, offset, SEEK_SET) == offset) {
|
||||||
if (safe_read(f, &index[last + 1], delta) != delta) {
|
if (safe_read(f, &index[last + 1], delta) != delta) {
|
||||||
esyslog("ERROR: can't read from index");
|
esyslog("ERROR: can't read from index");
|
||||||
@ -1295,6 +1464,8 @@ bool cIndexFile::CatchUp(int Index)
|
|||||||
f = -1;
|
f = -1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (isPesRecording)
|
||||||
|
ConvertFromPes(&index[last + 1], newLast - last);
|
||||||
last = newLast;
|
last = newLast;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1314,10 +1485,10 @@ bool cIndexFile::CatchUp(int Index)
|
|||||||
return index != NULL;
|
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) {
|
if (f >= 0) {
|
||||||
tIndex i = { FileOffset, PictureType, FileNumber, 0 };
|
tIndexTs i(FileOffset, Independent, FileNumber);
|
||||||
if (safe_write(f, &i, sizeof(i)) < 0) {
|
if (safe_write(f, &i, sizeof(i)) < 0) {
|
||||||
LOG_ERROR_STR(fileName);
|
LOG_ERROR_STR(fileName);
|
||||||
close(f);
|
close(f);
|
||||||
@ -1329,14 +1500,14 @@ bool cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset)
|
|||||||
return f >= 0;
|
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 (CatchUp(Index)) {
|
||||||
if (Index >= 0 && Index < last) {
|
if (Index >= 0 && Index < last) {
|
||||||
*FileNumber = index[Index].number;
|
*FileNumber = index[Index].number;
|
||||||
*FileOffset = index[Index].offset;
|
*FileOffset = index[Index].offset;
|
||||||
if (PictureType)
|
if (Independent)
|
||||||
*PictureType = index[Index].type;
|
*Independent = index[Index].independent;
|
||||||
if (Length) {
|
if (Length) {
|
||||||
int fn = index[Index + 1].number;
|
int fn = index[Index + 1].number;
|
||||||
int fo = index[Index + 1].offset;
|
int fo = index[Index + 1].offset;
|
||||||
@ -1351,24 +1522,24 @@ bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *Pictu
|
|||||||
return false;
|
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()) {
|
if (CatchUp()) {
|
||||||
int d = Forward ? 1 : -1;
|
int d = Forward ? 1 : -1;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
Index += d;
|
Index += d;
|
||||||
if (Index >= 0 && Index < last - ((Forward && StayOffEnd) ? INDEXSAFETYLIMIT : 0)) {
|
if (Index >= 0 && Index < last - ((Forward && StayOffEnd) ? INDEXSAFETYLIMIT : 0)) {
|
||||||
if (index[Index].type == I_FRAME) {
|
if (index[Index].independent) {
|
||||||
if (FileNumber)
|
uint16_t fn;
|
||||||
|
if (!FileNumber)
|
||||||
|
FileNumber = &fn;
|
||||||
|
off_t fo;
|
||||||
|
if (!FileOffset)
|
||||||
|
FileOffset = &fo;
|
||||||
*FileNumber = index[Index].number;
|
*FileNumber = index[Index].number;
|
||||||
else
|
|
||||||
FileNumber = &index[Index].number;
|
|
||||||
if (FileOffset)
|
|
||||||
*FileOffset = index[Index].offset;
|
*FileOffset = index[Index].offset;
|
||||||
else
|
|
||||||
FileOffset = &index[Index].offset;
|
|
||||||
if (Length) {
|
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 fn = index[Index + 1].number;
|
||||||
int fo = index[Index + 1].offset;
|
int fo = index[Index + 1].offset;
|
||||||
if (fn == *FileNumber)
|
if (fn == *FileNumber)
|
||||||
@ -1388,13 +1559,13 @@ int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *F
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cIndexFile::Get(uchar FileNumber, int FileOffset)
|
int cIndexFile::Get(uint16_t FileNumber, off_t FileOffset)
|
||||||
{
|
{
|
||||||
if (CatchUp()) {
|
if (CatchUp()) {
|
||||||
//TODO implement binary search!
|
//TODO implement binary search!
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < last; 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;
|
break;
|
||||||
}
|
}
|
||||||
return i;
|
return i;
|
||||||
@ -1409,16 +1580,19 @@ bool cIndexFile::IsStillRecording()
|
|||||||
|
|
||||||
// --- cFileName -------------------------------------------------------------
|
// --- cFileName -------------------------------------------------------------
|
||||||
|
|
||||||
#define MAXFILESPERRECORDING 255
|
#define MAXFILESPERRECORDINGPES 255
|
||||||
#define RECORDFILESUFFIX "/%03d.vdr"
|
#define RECORDFILESUFFIXPES "/%03d.vdr"
|
||||||
|
#define MAXFILESPERRECORDINGTS 65535
|
||||||
|
#define RECORDFILESUFFIXTS "/%05d.ts"
|
||||||
#define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
|
#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;
|
file = NULL;
|
||||||
fileNumber = 0;
|
fileNumber = 0;
|
||||||
record = Record;
|
record = Record;
|
||||||
blocking = Blocking;
|
blocking = Blocking;
|
||||||
|
isPesRecording = IsPesRecording;
|
||||||
// Prepare the file name:
|
// Prepare the file name:
|
||||||
fileName = MALLOC(char, strlen(FileName) + RECORDFILESUFFIXLEN);
|
fileName = MALLOC(char, strlen(FileName) + RECORDFILESUFFIXLEN);
|
||||||
if (!fileName) {
|
if (!fileName) {
|
||||||
@ -1442,14 +1616,14 @@ cUnbufferedFile *cFileName::Open(void)
|
|||||||
int BlockingFlag = blocking ? 0 : O_NONBLOCK;
|
int BlockingFlag = blocking ? 0 : O_NONBLOCK;
|
||||||
if (record) {
|
if (record) {
|
||||||
dsyslog("recording to '%s'", fileName);
|
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)
|
if (!file)
|
||||||
LOG_ERROR_STR(fileName);
|
LOG_ERROR_STR(fileName);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (access(fileName, R_OK) == 0) {
|
if (access(fileName, R_OK) == 0) {
|
||||||
dsyslog("playing '%s'", fileName);
|
dsyslog("playing '%s'", fileName);
|
||||||
file = cUnbufferedFile::Create(fileName, O_RDONLY | BlockingFlag);
|
file = cUnbufferedFile::Create(fileName, O_RDONLY | O_LARGEFILE | BlockingFlag);
|
||||||
if (!file)
|
if (!file)
|
||||||
LOG_ERROR_STR(fileName);
|
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)
|
if (fileNumber != Number)
|
||||||
Close();
|
Close();
|
||||||
if (0 < Number && Number <= MAXFILESPERRECORDING) {
|
int MaxFilesPerRecording = isPesRecording ? MAXFILESPERRECORDINGPES : MAXFILESPERRECORDINGTS;
|
||||||
|
if (0 < Number && Number <= MaxFilesPerRecording) {
|
||||||
fileNumber = Number;
|
fileNumber = Number;
|
||||||
sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber);
|
sprintf(pFileNumber, isPesRecording ? RECORDFILESUFFIXPES : RECORDFILESUFFIXTS, fileNumber);
|
||||||
if (record) {
|
if (record) {
|
||||||
if (access(fileName, F_OK) == 0) {
|
if (access(fileName, F_OK) == 0) {
|
||||||
// files exists, check if it has non-zero size
|
// files exists, check if it has non-zero size
|
||||||
@ -1506,7 +1681,7 @@ cUnbufferedFile *cFileName::SetOffset(int Number, int Offset)
|
|||||||
}
|
}
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
esyslog("ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING);
|
esyslog("ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1517,11 +1692,12 @@ cUnbufferedFile *cFileName::NextFile(void)
|
|||||||
|
|
||||||
// --- Index stuff -----------------------------------------------------------
|
// --- Index stuff -----------------------------------------------------------
|
||||||
|
|
||||||
cString IndexToHMSF(int Index, bool WithFrame)
|
cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
|
||||||
{
|
{
|
||||||
char buffer[16];
|
char buffer[16];
|
||||||
int f = (Index % FRAMESPERSEC) + 1;
|
double Seconds;
|
||||||
int s = (Index / FRAMESPERSEC);
|
int f = modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond + 1;
|
||||||
|
int s = int(Seconds);
|
||||||
int m = s / 60 % 60;
|
int m = s / 60 % 60;
|
||||||
int h = s / 3600;
|
int h = s / 3600;
|
||||||
s %= 60;
|
s %= 60;
|
||||||
@ -1529,17 +1705,20 @@ cString IndexToHMSF(int Index, bool WithFrame)
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
int HMSFToIndex(const char *HMSF)
|
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
|
||||||
{
|
{
|
||||||
int h, m, s, f = 0;
|
int h, m, s, f = 1;
|
||||||
if (3 <= sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f))
|
int n = sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f);
|
||||||
return (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SecondsToFrames(int Seconds)
|
int SecondsToFrames(int Seconds, double FramesPerSecond)
|
||||||
{
|
{
|
||||||
return Seconds * FRAMESPERSEC;
|
return round(Seconds * FramesPerSecond);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ReadFrame -------------------------------------------------------------
|
// --- ReadFrame -------------------------------------------------------------
|
||||||
|
61
recording.h
61
recording.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: 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
|
#ifndef __RECORDING_H
|
||||||
@ -30,8 +30,9 @@ void AssertFreeDiskSpace(int Priority = 0, bool Force = false);
|
|||||||
class cResumeFile {
|
class cResumeFile {
|
||||||
private:
|
private:
|
||||||
char *fileName;
|
char *fileName;
|
||||||
|
bool isPesRecording;
|
||||||
public:
|
public:
|
||||||
cResumeFile(const char *FileName);
|
cResumeFile(const char *FileName, bool IsPesRecording);
|
||||||
~cResumeFile();
|
~cResumeFile();
|
||||||
int Read(void);
|
int Read(void);
|
||||||
bool Save(int Index);
|
bool Save(int Index);
|
||||||
@ -46,10 +47,15 @@ private:
|
|||||||
const cEvent *event;
|
const cEvent *event;
|
||||||
cEvent *ownEvent;
|
cEvent *ownEvent;
|
||||||
char *aux;
|
char *aux;
|
||||||
|
double framesPerSecond;
|
||||||
|
int priority;
|
||||||
|
int lifetime;
|
||||||
|
char *fileName;
|
||||||
cRecordingInfo(const cChannel *Channel = NULL, const cEvent *Event = NULL);
|
cRecordingInfo(const cChannel *Channel = NULL, const cEvent *Event = NULL);
|
||||||
void SetData(const char *Title, const char *ShortText, const char *Description);
|
void SetData(const char *Title, const char *ShortText, const char *Description);
|
||||||
void SetAux(const char *Aux);
|
void SetAux(const char *Aux);
|
||||||
public:
|
public:
|
||||||
|
cRecordingInfo(const char *FileName);
|
||||||
~cRecordingInfo();
|
~cRecordingInfo();
|
||||||
tChannelID ChannelID(void) const { return channelID; }
|
tChannelID ChannelID(void) const { return channelID; }
|
||||||
const char *ChannelName(void) const { return channelName; }
|
const char *ChannelName(void) const { return channelName; }
|
||||||
@ -58,8 +64,12 @@ public:
|
|||||||
const char *Description(void) const { return event->Description(); }
|
const char *Description(void) const { return event->Description(); }
|
||||||
const cComponents *Components(void) const { return event->Components(); }
|
const cComponents *Components(void) const { return event->Components(); }
|
||||||
const char *Aux(void) const { return aux; }
|
const char *Aux(void) const { return aux; }
|
||||||
|
double FramesPerSecond(void) const { return framesPerSecond; }
|
||||||
|
void SetFramesPerSecond(double FramesPerSecond);
|
||||||
bool Read(FILE *f);
|
bool Read(FILE *f);
|
||||||
bool Write(FILE *f, const char *Prefix = "") const;
|
bool Write(FILE *f, const char *Prefix = "") const;
|
||||||
|
bool Read(void);
|
||||||
|
bool Write(void) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class cRecording : public cListObject {
|
class cRecording : public cListObject {
|
||||||
@ -71,6 +81,10 @@ private:
|
|||||||
mutable char *fileName;
|
mutable char *fileName;
|
||||||
mutable char *name;
|
mutable char *name;
|
||||||
mutable int fileSizeMB;
|
mutable int fileSizeMB;
|
||||||
|
int channel;
|
||||||
|
int resumeId;
|
||||||
|
bool isPesRecording;
|
||||||
|
double framesPerSecond;
|
||||||
cRecordingInfo *info;
|
cRecordingInfo *info;
|
||||||
cRecording(const cRecording&); // can't copy cRecording
|
cRecording(const cRecording&); // can't copy cRecording
|
||||||
cRecording &operator=(const cRecording &); // can't assign cRecording
|
cRecording &operator=(const cRecording &); // can't assign cRecording
|
||||||
@ -93,8 +107,10 @@ public:
|
|||||||
const char *PrefixFileName(char Prefix);
|
const char *PrefixFileName(char Prefix);
|
||||||
int HierarchyLevels(void) const;
|
int HierarchyLevels(void) const;
|
||||||
void ResetResume(void) const;
|
void ResetResume(void) const;
|
||||||
|
double FramesPerSecond(void) { return framesPerSecond; }
|
||||||
bool IsNew(void) const { return GetResume() <= 0; }
|
bool IsNew(void) const { return GetResume() <= 0; }
|
||||||
bool IsEdited(void) const;
|
bool IsEdited(void) const;
|
||||||
|
bool IsPesRecording(void) const { return isPesRecording; }
|
||||||
bool WriteInfo(void);
|
bool WriteInfo(void);
|
||||||
bool Delete(void);
|
bool Delete(void);
|
||||||
// Changes the file name so that it will no longer be visible in the "Recordings" menu
|
// 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 Recordings;
|
||||||
extern cRecordings DeletedRecordings;
|
extern cRecordings DeletedRecordings;
|
||||||
|
|
||||||
|
#define DEFAULTFRAMESPERSECOND 25.0
|
||||||
|
|
||||||
class cMark : public cListObject {
|
class cMark : public cListObject {
|
||||||
|
private:
|
||||||
|
double framesPerSecond;
|
||||||
public:
|
public:
|
||||||
int position;
|
int position;
|
||||||
char *comment;
|
char *comment;
|
||||||
cMark(int Position = 0, const char *Comment = NULL);
|
cMark(int Position = 0, const char *Comment = NULL, double FramesPerSecond = DEFAULTFRAMESPERSECOND);
|
||||||
virtual ~cMark();
|
virtual ~cMark();
|
||||||
cString ToText(void);
|
cString ToText(void);
|
||||||
bool Parse(const char *s);
|
bool Parse(const char *s);
|
||||||
@ -161,8 +181,10 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class cMarks : public cConfig<cMark> {
|
class cMarks : public cConfig<cMark> {
|
||||||
|
private:
|
||||||
|
double framesPerSecond;
|
||||||
public:
|
public:
|
||||||
bool Load(const char *RecordingFileName);
|
bool Load(const char *RecordingFileName, double FramesPerSecond = DEFAULTFRAMESPERSECOND, bool IsPesRecording = false);
|
||||||
void Sort(void);
|
void Sort(void);
|
||||||
cMark *Add(int Position);
|
cMark *Add(int Position);
|
||||||
cMark *Get(int Position);
|
cMark *Get(int Position);
|
||||||
@ -182,9 +204,6 @@ public:
|
|||||||
static void InvokeCommand(const char *State, const char *RecordingFileName);
|
static void InvokeCommand(const char *State, const char *RecordingFileName);
|
||||||
};
|
};
|
||||||
|
|
||||||
//XXX+
|
|
||||||
#define FRAMESPERSEC 25
|
|
||||||
|
|
||||||
// The maximum size of a single frame (up to HDTV 1920x1080):
|
// The maximum size of a single frame (up to HDTV 1920x1080):
|
||||||
#define MAXFRAMESIZE KILOBYTE(512)
|
#define MAXFRAMESIZE KILOBYTE(512)
|
||||||
|
|
||||||
@ -197,24 +216,27 @@ public:
|
|||||||
#define MAXVIDEOFILESIZE 2000 // MB
|
#define MAXVIDEOFILESIZE 2000 // MB
|
||||||
#define MINVIDEOFILESIZE 100 // MB
|
#define MINVIDEOFILESIZE 100 // MB
|
||||||
|
|
||||||
|
struct tIndexTs;
|
||||||
|
|
||||||
class cIndexFile {
|
class cIndexFile {
|
||||||
private:
|
private:
|
||||||
struct tIndex { int offset; uchar type; uchar number; short reserved; };
|
|
||||||
int f;
|
int f;
|
||||||
char *fileName;
|
char *fileName;
|
||||||
int size, last;
|
int size, last;
|
||||||
tIndex *index;
|
tIndexTs *index;
|
||||||
|
bool isPesRecording;
|
||||||
cResumeFile resumeFile;
|
cResumeFile resumeFile;
|
||||||
cMutex mutex;
|
cMutex mutex;
|
||||||
|
void ConvertFromPes(tIndexTs *IndexTs, int Count);
|
||||||
bool CatchUp(int Index = -1);
|
bool CatchUp(int Index = -1);
|
||||||
public:
|
public:
|
||||||
cIndexFile(const char *FileName, bool Record);
|
cIndexFile(const char *FileName, bool Record, bool IsPesRecording = false);
|
||||||
~cIndexFile();
|
~cIndexFile();
|
||||||
bool Ok(void) { return index != NULL; }
|
bool Ok(void) { return index != NULL; }
|
||||||
bool Write(uchar PictureType, uchar FileNumber, int FileOffset);
|
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset);
|
||||||
bool Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType = NULL, int *Length = NULL);
|
bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent = NULL, int *Length = NULL);
|
||||||
int GetNextIFrame(int Index, bool Forward, uchar *FileNumber = NULL, int *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false);
|
int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber = NULL, off_t *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false);
|
||||||
int Get(uchar FileNumber, int FileOffset);
|
int Get(uint16_t FileNumber, off_t FileOffset);
|
||||||
int Last(void) { CatchUp(); return last; }
|
int Last(void) { CatchUp(); return last; }
|
||||||
int GetResume(void) { return resumeFile.Read(); }
|
int GetResume(void) { return resumeFile.Read(); }
|
||||||
bool StoreResume(int Index) { return resumeFile.Save(Index); }
|
bool StoreResume(int Index) { return resumeFile.Save(Index); }
|
||||||
@ -228,22 +250,23 @@ private:
|
|||||||
char *fileName, *pFileNumber;
|
char *fileName, *pFileNumber;
|
||||||
bool record;
|
bool record;
|
||||||
bool blocking;
|
bool blocking;
|
||||||
|
bool isPesRecording;
|
||||||
public:
|
public:
|
||||||
cFileName(const char *FileName, bool Record, bool Blocking = false);
|
cFileName(const char *FileName, bool Record, bool Blocking = false, bool IsPesRecording = false);
|
||||||
~cFileName();
|
~cFileName();
|
||||||
const char *Name(void) { return fileName; }
|
const char *Name(void) { return fileName; }
|
||||||
int Number(void) { return fileNumber; }
|
int Number(void) { return fileNumber; }
|
||||||
cUnbufferedFile *Open(void);
|
cUnbufferedFile *Open(void);
|
||||||
void Close(void);
|
void Close(void);
|
||||||
cUnbufferedFile *SetOffset(int Number, int Offset = 0);
|
cUnbufferedFile *SetOffset(int Number, off_t Offset = 0);
|
||||||
cUnbufferedFile *NextFile(void);
|
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.
|
// 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.
|
// 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.
|
// Returns the number of frames corresponding to the given number of seconds.
|
||||||
|
|
||||||
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max);
|
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max);
|
||||||
|
137
remux.h
137
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
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* 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
|
#ifndef __REMUX_H
|
||||||
#define __REMUX_H
|
#define __REMUX_H
|
||||||
|
|
||||||
#include "channels.h"
|
#include "channels.h"
|
||||||
#include "ringbuffer.h"
|
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
|
|
||||||
enum ePesHeader {
|
enum ePesHeader {
|
||||||
@ -23,61 +22,9 @@ enum ePesHeader {
|
|||||||
|
|
||||||
ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader = NULL);
|
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 {
|
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:
|
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 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.
|
// Some TS handling tools.
|
||||||
@ -85,24 +32,39 @@ public:
|
|||||||
|
|
||||||
#define TS_SYNC_BYTE 0x47
|
#define TS_SYNC_BYTE 0x47
|
||||||
#define TS_SIZE 188
|
#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_ADAPT_FIELD_EXISTS 0x20
|
||||||
#define TS_PAYLOAD_EXISTS 0x10
|
#define TS_PAYLOAD_EXISTS 0x10
|
||||||
#define TS_CONT_CNT_MASK 0x0F
|
#define TS_CONT_CNT_MASK 0x0F
|
||||||
#define TS_PAYLOAD_START 0x40
|
#define TS_ADAPT_DISCONT 0x80
|
||||||
#define TS_ERROR 0x80
|
#define TS_ADAPT_RANDOM_ACC 0x40 // would be perfect for detecting independent frames, but unfortunately not used by all broadcasters
|
||||||
#define TS_PID_MASK_HI 0x1F
|
#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;
|
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;
|
return p[1] & TS_PAYLOAD_START;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int TsError(const uchar *p)
|
inline bool TsError(const uchar *p)
|
||||||
{
|
{
|
||||||
return p[1] & TS_ERROR;
|
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];
|
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)
|
inline int TsPayloadOffset(const uchar *p)
|
||||||
{
|
{
|
||||||
return (p[3] & TS_ADAPT_FIELD_EXISTS) ? p[4] + 5 : 4;
|
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;
|
return p[3] & TS_CONT_CNT_MASK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline int TsGetAdaptationField(const uchar *p)
|
||||||
|
{
|
||||||
|
return TsHasAdaptationField(p) ? p[5] : 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
// Some PES handling tools:
|
// Some PES handling tools:
|
||||||
// The following functions that take a pointer to PES data all assume that
|
// The following functions that take a pointer to PES data all assume that
|
||||||
// there is enough data so that PesLongEnough() returns true.
|
// there is enough data so that PesLongEnough() returns true.
|
||||||
@ -153,17 +125,19 @@ inline int PesPayloadOffset(const uchar *p)
|
|||||||
return 9 + p[8];
|
return 9 + p[8];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool PesHasPts(const uchar *p)
|
||||||
|
{
|
||||||
|
return (p[7] & 0x80) && p[8] >= 5;
|
||||||
|
}
|
||||||
|
|
||||||
inline int64_t PesGetPts(const uchar *p)
|
inline int64_t PesGetPts(const uchar *p)
|
||||||
{
|
{
|
||||||
if ((p[7] & 0x80) && p[8] >= 5) {
|
|
||||||
return ((((int64_t)p[ 9]) & 0x0E) << 29) |
|
return ((((int64_t)p[ 9]) & 0x0E) << 29) |
|
||||||
(( (int64_t)p[10]) << 22) |
|
(( (int64_t)p[10]) << 22) |
|
||||||
((((int64_t)p[11]) & 0xFE) << 14) |
|
((((int64_t)p[11]) & 0xFE) << 14) |
|
||||||
(( (int64_t)p[12]) << 7) |
|
(( (int64_t)p[12]) << 7) |
|
||||||
((((int64_t)p[13]) & 0xFE) >> 1);
|
((((int64_t)p[13]) & 0xFE) >> 1);
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PAT/PMT Generator:
|
// 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 TsDump(const char *Name, const u_char *Data, int Length);
|
||||||
void PesDump(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
|
#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
|
* and interact with the Video Disk Recorder - or write a full featured
|
||||||
* graphical interface that sits on top of an SVDRP connection.
|
* 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"
|
#include "svdrp.h"
|
||||||
@ -701,7 +701,7 @@ void cSVDRP::CmdEDIT(const char *Option)
|
|||||||
cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
|
cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
|
||||||
if (recording) {
|
if (recording) {
|
||||||
cMarks Marks;
|
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::Active()) {
|
||||||
if (cCutter::Start(recording->FileName()))
|
if (cCutter::Start(recording->FileName()))
|
||||||
Reply(250, "Editing recording \"%s\" [%s]", Option, recording->Title());
|
Reply(250, "Editing recording \"%s\" [%s]", Option, recording->Title());
|
||||||
@ -1338,15 +1338,9 @@ void cSVDRP::CmdPLAY(const char *Option)
|
|||||||
cControl::Shutdown();
|
cControl::Shutdown();
|
||||||
if (*option) {
|
if (*option) {
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
if (strcasecmp(option, "BEGIN") != 0) {
|
if (strcasecmp(option, "BEGIN") != 0)
|
||||||
int h, m = 0, s = 0, f = 1;
|
pos = HMSFToIndex(option, recording->FramesPerSecond());
|
||||||
int x = sscanf(option, "%d:%d:%d.%d", &h, &m, &s, &f);
|
cResumeFile resume(recording->FileName(), recording->IsPesRecording());
|
||||||
if (x == 1)
|
|
||||||
pos = h;
|
|
||||||
else if (x >= 3)
|
|
||||||
pos = (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1;
|
|
||||||
}
|
|
||||||
cResumeFile resume(recording->FileName());
|
|
||||||
if (pos <= 0)
|
if (pos <= 0)
|
||||||
resume.Delete();
|
resume.Delete();
|
||||||
else
|
else
|
||||||
|
6
tools.c
6
tools.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: 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"
|
#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
|
// last (partial) page might be skipped, writeback will start only after
|
||||||
// second call; the third call will still include this page and finally
|
// second call; the third call will still include this page and finally
|
||||||
// drop it from cache.
|
// 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);
|
posix_fadvise(fd, begin - headdrop, lastpos - begin + headdrop, POSIX_FADV_DONTNEED);
|
||||||
}
|
}
|
||||||
begin = lastpos = curpos;
|
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..
|
// kind of write gathering enabled), but the syncs cause (io) load..
|
||||||
// Uncomment the next line if you think you need them.
|
// Uncomment the next line if you think you need them.
|
||||||
//fdatasync(fd);
|
//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);
|
posix_fadvise(fd, curpos - totwritten - headdrop, totwritten + headdrop, POSIX_FADV_DONTNEED);
|
||||||
totwritten = 0;
|
totwritten = 0;
|
||||||
}
|
}
|
||||||
|
54
vdr.5
54
vdr.5
@ -8,7 +8,7 @@
|
|||||||
.\" License as specified in the file COPYING that comes with the
|
.\" License as specified in the file COPYING that comes with the
|
||||||
.\" vdr distribution.
|
.\" 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"
|
.TH vdr 5 "10 Feb 2008" "1.6" "Video Disk Recorder Files"
|
||||||
.SH NAME
|
.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
|
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
|
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
|
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
|
.SS SOURCES
|
||||||
The file \fIsources.conf\fR defines the codes to be used in the \fBSource\fR field
|
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.
|
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
|
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.
|
given in the theme's file name) will be used.
|
||||||
.SS AUDIO/VIDEO DATA
|
.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,
|
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
|
a recording may be split into several files. The contents of these files is
|
||||||
\fBPacketized Elementary Stream\fR (PES) and contains ES packets with ids
|
\fBTransport Stream\fR (TS) and contains data packets that are each 188 byte
|
||||||
0xE0...0xEF for video (only one of these may actually occur in a file),
|
long and start with 0x47. Data is stored exactly as it is broadcast, with
|
||||||
0xC0...0xDF for audio 1...32 (up to 32 audio tracks may occur).
|
a generated PAT/PMT inserted right before every independent frame.
|
||||||
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.
|
|
||||||
.SS INDEX
|
.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
|
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
|
the current position within the recording, and to implement skipping
|
||||||
and fast forward/back functions.
|
and fast forward/back functions.
|
||||||
See the definition of the \fBcIndexFile\fR class for details about the
|
See the definition of the \fBcIndexFile\fR class for details about the
|
||||||
actual contents of this file.
|
actual contents of this file.
|
||||||
.SS INFO
|
.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
|
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
|
(if such data was available). The \fBAux\fR field of the corresponding
|
||||||
timer (if given) is copied into this file, using the '@' tag.
|
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
|
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
|
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
|
Lines tagged with '#' are ignored and can be used by external tools to
|
||||||
store arbitrary information.
|
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
|
.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 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 consists of tagged lines that describe the various parameters
|
||||||
the file \fIindex.vdr\fR.
|
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
|
.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.
|
the editing marks defined for this recording.
|
||||||
Each line contains the definition of one mark in the following format:
|
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).
|
should at least have a \fBT\fR entry).
|
||||||
There may be several \fBX\fR tags, depending on the number of tracks (video, audio etc.)
|
There may be several \fBX\fR tags, depending on the number of tracks (video, audio etc.)
|
||||||
the event provides.
|
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
|
.TS
|
||||||
tab (@);
|
tab (@);
|
||||||
|
Loading…
Reference in New Issue
Block a user