mirror of
				https://github.com/vdr-projects/vdr.git
				synced 2025-03-01 10:50:46 +00:00 
			
		
		
		
	The recording format is now Transport Stream
This commit is contained in:
		| @@ -284,6 +284,8 @@ Artur Skawina <skawina@geocities.com> | ||||
|  for fixing calculating the cache size in cUnbufferedFile::Read() | ||||
|  for making the /video/.update file be touched _after_ an editing process is finished | ||||
|  in order to avoid excessive disk access | ||||
|  for helping to get the IndexToHMSF() calculation right with non-integer frame | ||||
|  rates | ||||
|  | ||||
| Werner Fink <werner@suse.de> | ||||
|  for making I/O more robust by handling EINTR | ||||
|   | ||||
							
								
								
									
										49
									
								
								HISTORY
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								HISTORY
									
									
									
									
									
								
							| @@ -5849,7 +5849,7 @@ Video Disk Recorder Revision History | ||||
|   the patch from ftp://ftp.cadsoft.de/vdr/Developer/av7110_v4ldvb_api5_audiobuf_test_1.diff | ||||
|   to the driver (thanks to Oliver Endriss). | ||||
|  | ||||
| 2008-12-28: Version 1.7.3 | ||||
| 2009-01-06: Version 1.7.3 | ||||
|  | ||||
| - Updated the Russian OSD texts (thanks to Oleg Roitburd). | ||||
| - Fixed handling the 'pointer field' in generating and parsing PAT/PMT (thanks to | ||||
| @@ -5859,3 +5859,50 @@ Video Disk Recorder Revision History | ||||
| - Added a poll to cDvbDevice::PlayVideo() and cDvbDevice::PlayAudio() to avoid | ||||
|   excessive CPU load (this is just a makeshift solution until the FF DVB cards | ||||
|   can play TS directly). | ||||
| - The recording format is now Transport Stream. Existing recordings in PES format | ||||
|   can still be replayed and edited, but new recordings are done in TS. | ||||
|   All code for recording in PES has been removed. | ||||
|   The following changes were made to switch to TS recording format: | ||||
|   + The index file format has been changed to support file sizes of up to 1TB | ||||
|     (previously 2GB), and up to 65535 separate files per recording (previously | ||||
|     255). | ||||
|   + The recording file names are now of the form 00001.ts (previously 001.vdr). | ||||
|   + The frame rate is now detected by looking at two subsequent PTS values. | ||||
|     The "frame duration" (in multiples of 1/90000) is stored in the info.vdr | ||||
|     file using the new tag F (thanks to Artur Skawina for helping to get the | ||||
|     IndexToHMSF() calculation right). | ||||
|   + Several functions now have an additional parameter FramesPerSecond. | ||||
|   + Several functions now have an additional parameter IsPesRecording. | ||||
|   + The functionality of cFileWriter was moved into cRecorder, and cRemux is | ||||
|     now obsolete. This also avoids one level of data copying while recording. | ||||
|   + cRemux, cRingBufferLinearPes, cTS2PES and all c*Repacker classes have been | ||||
|     removed. | ||||
|   + A PAT/PMT is inserted before every independent frame, so that no extra | ||||
|     measures need to be taken when editing a recording. | ||||
|   + The directory name for a recording has been changed from | ||||
|     YYYY-MM-DD-hh[.:]mm.pr.lt.rec (pr=priority, lt=lifetime) to | ||||
|     YYYY-MM-DD-hh.mm.ch-ri.rec (ch=channel, ri=resumeId). | ||||
|     Priority and Lifetime are now stored in the info.vdr file with the new | ||||
|     tags P and L (if no such file exists, the maximum values are assumed by | ||||
|     default, which avoids inadvertently deleting a recording if disk space | ||||
|     is low). No longer storing Priority and Lifetime in the directory name | ||||
|     avoids starting a new recording if one of these is changed in the timer | ||||
|     and the recording is re-started for some reason. | ||||
|     Instead of Priority and Lifetime, the directory name now contains the | ||||
|     channel number from which the recording was made, and the "resume id" of | ||||
|     this instance of VDR. This avoids problems if several VDR instances record | ||||
|     the same show on different channels, or even on the same channel. | ||||
|     The '-' between channel number and resumeId prevents older versions of | ||||
|     VDR from "seeing" these recordings, which makes sure they won't even try | ||||
|     to replay them, or remove them in case the disk runs full. | ||||
|   + The semantics of PlayTs*() have been changed. These functions are now | ||||
|     required to return the given Length (which is TS_SIZE) if they have | ||||
|     processed the TS packet. | ||||
|   + The files "index", "info", "marks" and "resume" within a TS recording | ||||
|     directory are now created without the ".vdr" extension. | ||||
|   + The "resume" file is no longer a binary file, but contains tagged lines | ||||
|     to be able to store additional information, like the selected audio or | ||||
|     subtitle track. | ||||
|   + cDevice::StillPicture() will now be called with either TS or PES data. | ||||
|   + cDvbPlayer::Goto() no longer appends a "sequence end code" to the data. | ||||
|     If the output device needs this, it has to take care of it by itself. | ||||
|   | ||||
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ | ||||
| # See the main source file 'vdr.c' for copyright information and | ||||
| # how to reach the author. | ||||
| # | ||||
| # $Id: Makefile 2.2 2008/05/03 10:13:43 kls Exp $ | ||||
| # $Id: Makefile 2.3 2009/01/05 13:04:10 kls Exp $ | ||||
|  | ||||
| .DELETE_ON_ERROR: | ||||
|  | ||||
| @@ -60,6 +60,8 @@ DEFINES += -DLIRC_DEVICE=\"$(LIRC_DEVICE)\" -DRCU_DEVICE=\"$(RCU_DEVICE)\" | ||||
|  | ||||
| DEFINES += -D_GNU_SOURCE | ||||
|  | ||||
| DEFINES += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE | ||||
|  | ||||
| DEFINES += -DVIDEODIR=\"$(VIDEODIR)\" | ||||
| DEFINES += -DCONFDIR=\"$(CONFDIR)\" | ||||
| DEFINES += -DPLUGINDIR=\"$(PLUGINLIBDIR)\" | ||||
|   | ||||
							
								
								
									
										10
									
								
								config.h
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								config.h
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ | ||||
|  * See the main source file 'vdr.c' for copyright information and | ||||
|  * how to reach the author. | ||||
|  * | ||||
|  * $Id: config.h 2.4 2008/09/14 13:46:13 kls Exp $ | ||||
|  * $Id: config.h 2.5 2009/01/05 13:04:10 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #ifndef __CONFIG_H | ||||
| @@ -22,13 +22,13 @@ | ||||
|  | ||||
| // VDR's own version number: | ||||
|  | ||||
| #define VDRVERSION  "1.7.2" | ||||
| #define VDRVERSNUM   10702  // Version * 10000 + Major * 100 + Minor | ||||
| #define VDRVERSION  "1.7.3" | ||||
| #define VDRVERSNUM   10703  // Version * 10000 + Major * 100 + Minor | ||||
|  | ||||
| // The plugin API's version number: | ||||
|  | ||||
| #define APIVERSION  "1.7.0" | ||||
| #define APIVERSNUM   10700  // Version * 10000 + Major * 100 + Minor | ||||
| #define APIVERSION  "1.7.3" | ||||
| #define APIVERSNUM   10703  // Version * 10000 + Major * 100 + Minor | ||||
|  | ||||
| // When loading plugins, VDR searches them by their APIVERSION, which | ||||
| // may be smaller than VDRVERSION in case there have been no changes to | ||||
|   | ||||
							
								
								
									
										31
									
								
								cutter.c
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								cutter.c
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ | ||||
|  * See the main source file 'vdr.c' for copyright information and | ||||
|  * how to reach the author. | ||||
|  * | ||||
|  * $Id: cutter.c 1.18 2008/01/13 12:22:21 kls Exp $ | ||||
|  * $Id: cutter.c 2.1 2009/01/06 14:40:48 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #include "cutter.h" | ||||
| @@ -37,12 +37,14 @@ cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName) | ||||
|   fromFile = toFile = NULL; | ||||
|   fromFileName = toFileName = NULL; | ||||
|   fromIndex = toIndex = NULL; | ||||
|   if (fromMarks.Load(FromFileName) && fromMarks.Count()) { | ||||
|      fromFileName = new cFileName(FromFileName, false, true); | ||||
|      toFileName = new cFileName(ToFileName, true, true); | ||||
|      fromIndex = new cIndexFile(FromFileName, false); | ||||
|      toIndex = new cIndexFile(ToFileName, true); | ||||
|      toMarks.Load(ToFileName); // doesn't actually load marks, just sets the file name | ||||
|   cRecording Recording(FromFileName); | ||||
|   bool isPesRecording = Recording.IsPesRecording(); | ||||
|   if (fromMarks.Load(FromFileName, Recording.FramesPerSecond(), isPesRecording) && fromMarks.Count()) { | ||||
|      fromFileName = new cFileName(FromFileName, false, true, isPesRecording); | ||||
|      toFileName = new cFileName(ToFileName, true, true, isPesRecording); | ||||
|      fromIndex = new cIndexFile(FromFileName, false, isPesRecording); | ||||
|      toIndex = new cIndexFile(ToFileName, true, isPesRecording); | ||||
|      toMarks.Load(ToFileName, Recording.FramesPerSecond(), isPesRecording); // doesn't actually load marks, just sets the file name | ||||
|      Start(); | ||||
|      } | ||||
|   else | ||||
| @@ -69,7 +71,7 @@ void cCuttingThread::Action(void) | ||||
|      fromFile->SetReadAhead(MEGABYTE(20)); | ||||
|      int Index = Mark->position; | ||||
|      Mark = fromMarks.Next(Mark); | ||||
|      int FileSize = 0; | ||||
|      off_t FileSize = 0; | ||||
|      int CurrentFileNumber = 0; | ||||
|      int LastIFrame = 0; | ||||
|      toMarks.Add(0); | ||||
| @@ -78,9 +80,10 @@ void cCuttingThread::Action(void) | ||||
|      bool LastMark = false; | ||||
|      bool cutIn = true; | ||||
|      while (Running()) { | ||||
|            uchar FileNumber; | ||||
|            int FileOffset, Length; | ||||
|            uchar PictureType; | ||||
|            uint16_t FileNumber; | ||||
|            off_t FileOffset; | ||||
|            int Length; | ||||
|            bool Independent; | ||||
|  | ||||
|            // Make sure there is enough disk space: | ||||
|  | ||||
| @@ -88,7 +91,7 @@ void cCuttingThread::Action(void) | ||||
|  | ||||
|            // Read one frame: | ||||
|  | ||||
|            if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &PictureType, &Length)) { | ||||
|            if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &Independent, &Length)) { | ||||
|               if (FileNumber != CurrentFileNumber) { | ||||
|                  fromFile = fromFileName->SetOffset(FileNumber, FileOffset); | ||||
|                  fromFile->SetReadAhead(MEGABYTE(20)); | ||||
| @@ -119,7 +122,7 @@ void cCuttingThread::Action(void) | ||||
|  | ||||
|            // Write one frame: | ||||
|  | ||||
|            if (PictureType == I_FRAME) { // every file shall start with an I_FRAME | ||||
|            if (Independent) { // every file shall start with an independent frame | ||||
|               if (LastMark) // edited version shall end before next I-frame | ||||
|                  break; | ||||
|               if (FileSize > MEGABYTE(Setup.MaxVideoFileSize)) { | ||||
| @@ -141,7 +144,7 @@ void cCuttingThread::Action(void) | ||||
|               error = "safe_write"; | ||||
|               break; | ||||
|               } | ||||
|            if (!toIndex->Write(PictureType, toFileName->Number(), FileSize)) { | ||||
|            if (!toIndex->Write(Independent, toFileName->Number(), FileSize)) { | ||||
|               error = "toIndex"; | ||||
|               break; | ||||
|               } | ||||
|   | ||||
							
								
								
									
										50
									
								
								device.c
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								device.c
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ | ||||
|  * See the main source file 'vdr.c' for copyright information and | ||||
|  * how to reach the author. | ||||
|  * | ||||
|  * $Id: device.c 2.4 2008/12/13 14:30:28 kls Exp $ | ||||
|  * $Id: device.c 2.5 2009/01/06 09:55:13 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #include "device.h" | ||||
| @@ -1014,6 +1014,48 @@ void cDevice::Mute(void) | ||||
|  | ||||
| void cDevice::StillPicture(const uchar *Data, int Length) | ||||
| { | ||||
|   if (Data[0] == 0x47) { | ||||
|      // TS data | ||||
|      cTsToPes TsToPes; | ||||
|      uchar *buf = NULL; | ||||
|      int Size = 0; | ||||
|      while (Length >= TS_SIZE) { | ||||
|            int PayloadOffset = TsPayloadOffset(Data); | ||||
|            int Pid = TsPid(Data); | ||||
|            if (Pid == 0) | ||||
|               patPmtParser.ParsePat(Data + PayloadOffset, TS_SIZE - PayloadOffset); | ||||
|            else if (Pid == patPmtParser.PmtPid()) | ||||
|               patPmtParser.ParsePmt(Data + PayloadOffset, TS_SIZE - PayloadOffset); | ||||
|            else if (Pid == patPmtParser.Vpid()) { | ||||
|               if (TsPayloadStart(Data)) { | ||||
|                  int l; | ||||
|                  while (const uchar *p = TsToPes.GetPes(l)) { | ||||
|                        int Offset = Size; | ||||
|                        Size += l; | ||||
|                        buf = (uchar *)realloc(buf, Size); | ||||
|                        if (!buf) | ||||
|                           return; | ||||
|                        memcpy(buf + Offset, p, l); | ||||
|                        } | ||||
|                  TsToPes.Reset(); | ||||
|                  } | ||||
|               TsToPes.PutTs(Data, TS_SIZE); | ||||
|               } | ||||
|            Length -= TS_SIZE; | ||||
|            Data += TS_SIZE; | ||||
|            } | ||||
|      int l; | ||||
|      while (const uchar *p = TsToPes.GetPes(l)) { | ||||
|            int Offset = Size; | ||||
|            Size += l; | ||||
|            buf = (uchar *)realloc(buf, Size); | ||||
|            if (!buf) | ||||
|               return; | ||||
|            memcpy(buf + Offset, p, l); | ||||
|            } | ||||
|      StillPicture(buf, Size); | ||||
|      free(buf); | ||||
|      } | ||||
| } | ||||
|  | ||||
| bool cDevice::Replaying(void) const | ||||
| @@ -1301,6 +1343,11 @@ int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly) | ||||
|         return Length; | ||||
|         } | ||||
|      } | ||||
|   else if (Data == NULL) { | ||||
|      tsToPesVideo.Reset(); | ||||
|      tsToPesAudio.Reset(); | ||||
|      tsToPesSubtitle.Reset(); | ||||
|      } | ||||
|   return -1; | ||||
| } | ||||
|  | ||||
| @@ -1328,7 +1375,6 @@ bool cDevice::Receiving(bool CheckAny) const | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| #define TS_SCRAMBLING_CONTROL  0xC0 | ||||
| #define TS_SCRAMBLING_TIMEOUT     3 // seconds to wait until a TS becomes unscrambled | ||||
| #define TS_SCRAMBLING_TIME_OK    10 // seconds before a Channel/CAM combination is marked as known to decrypt | ||||
|  | ||||
|   | ||||
							
								
								
									
										19
									
								
								device.h
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								device.h
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ | ||||
|  * See the main source file 'vdr.c' for copyright information and | ||||
|  * how to reach the author. | ||||
|  * | ||||
|  * $Id: device.h 2.3 2008/09/14 13:44:54 kls Exp $ | ||||
|  * $Id: device.h 2.4 2009/01/05 16:28:06 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #ifndef __DEVICE_H | ||||
| @@ -518,8 +518,7 @@ protected: | ||||
|        ///< Data points to exactly one complete TS packet of the given Length | ||||
|        ///< (which is always TS_SIZE). | ||||
|        ///< PlayTsVideo() shall process the packet either as a whole (returning | ||||
|        ///< a positive number, which needs not necessarily be Length) or not at all | ||||
|        ///< (returning 0 or -1 and setting 'errno' to EAGAIN). | ||||
|        ///< Length) or not at all (returning 0 or -1 and setting 'errno' accordingly). | ||||
|        ///< The default implementation collects all incoming TS payload belonging | ||||
|        ///< to one PES packet and calls PlayVideo() with the resulting packet. | ||||
|   virtual int PlayTsAudio(const uchar *Data, int Length); | ||||
| @@ -527,8 +526,7 @@ protected: | ||||
|        ///< Data points to exactly one complete TS packet of the given Length | ||||
|        ///< (which is always TS_SIZE). | ||||
|        ///< PlayTsAudio() shall process the packet either as a whole (returning | ||||
|        ///< a positive number, which needs not necessarily be Length) or not at all | ||||
|        ///< (returning 0 or -1 and setting 'errno' to EAGAIN). | ||||
|        ///< Length) or not at all (returning 0 or -1 and setting 'errno' accordingly). | ||||
|        ///< The default implementation collects all incoming TS payload belonging | ||||
|        ///< to one PES packet and calls PlayAudio() with the resulting packet. | ||||
|   virtual int PlayTsSubtitle(const uchar *Data, int Length); | ||||
| @@ -536,8 +534,7 @@ protected: | ||||
|        ///< Data points to exactly one complete TS packet of the given Length | ||||
|        ///< (which is always TS_SIZE). | ||||
|        ///< PlayTsSubtitle() shall process the packet either as a whole (returning | ||||
|        ///< a positive number, which needs not necessarily be Length) or not at all | ||||
|        ///< (returning 0 or -1 and setting 'errno' to EAGAIN). | ||||
|        ///< Length) or not at all (returning 0 or -1 and setting 'errno' accordingly). | ||||
|        ///< The default implementation collects all incoming TS payload belonging | ||||
|        ///< to one PES packet and displays the resulting subtitle via the OSD. | ||||
| public: | ||||
| @@ -573,6 +570,10 @@ public: | ||||
|        ///< all registered cAudio objects are notified. | ||||
|   virtual void StillPicture(const uchar *Data, int Length); | ||||
|        ///< Displays the given I-frame as a still picture. | ||||
|        ///< Data points either to TS (first byte is 0x47) or PES (first byte | ||||
|        ///< is 0x00) data of the given Length. The default implementation | ||||
|        ///< converts TS to PES and calls itself again, allowing a derived class | ||||
|        ///< to display PES if it can't handle TS directly. | ||||
|   virtual bool Poll(cPoller &Poller, int TimeoutMs = 0); | ||||
|        ///< Returns true if the device itself or any of the file handles in | ||||
|        ///< Poller is ready for further action. | ||||
| @@ -600,12 +601,14 @@ public: | ||||
|        ///< which is necessary for trick modes like 'fast forward'. | ||||
|        ///< Data points to a single TS packet, Length is always TS_SIZE (the total | ||||
|        ///< size of a single TS packet). | ||||
|        ///< If Data is NULL any leftover data from a previous call will be | ||||
|        ///< discarded. | ||||
|        ///< A derived device can reimplement this function to handle the | ||||
|        ///< TS packets itself. Any packets the derived function can't handle | ||||
|        ///< must be sent to the base class function. This applies especially | ||||
|        ///< to the PAT/PMT packets. | ||||
|        ///< Returns -1 in case of error, otherwise the number of actually | ||||
|        ///< processed bytes is returned, which may be less than Length. | ||||
|        ///< processed bytes is returned, which must be Length. | ||||
|        ///< PlayTs() shall process the packet either as a whole (returning | ||||
|        ///< a positive number, which needs not necessarily be Length) or not at all | ||||
|        ///< (returning 0 or -1 and setting 'errno' to EAGAIN). | ||||
|   | ||||
							
								
								
									
										12
									
								
								dvbdevice.c
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								dvbdevice.c
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ | ||||
|  * See the main source file 'vdr.c' for copyright information and | ||||
|  * how to reach the author. | ||||
|  * | ||||
|  * $Id: dvbdevice.c 2.8 2008/12/28 10:59:51 kls Exp $ | ||||
|  * $Id: dvbdevice.c 2.9 2009/01/05 16:08:18 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #include "dvbdevice.h" | ||||
| @@ -1213,7 +1213,11 @@ void cDvbDevice::Mute(void) | ||||
|  | ||||
| void cDvbDevice::StillPicture(const uchar *Data, int Length) | ||||
| { | ||||
|   if (Data[0] == 0x00 && Data[1] == 0x00 && Data[2] == 0x01 && (Data[3] & 0xF0) == 0xE0) { | ||||
|   if (Data[0] == 0x47) { | ||||
|      // TS data | ||||
|      cDevice::StillPicture(Data, Length); | ||||
|      } | ||||
|   else if (Data[0] == 0x00 && Data[1] == 0x00 && Data[2] == 0x01 && (Data[3] & 0xF0) == 0xE0) { | ||||
|      // PES data | ||||
|      char *buf = MALLOC(char, Length); | ||||
|      if (!buf) | ||||
| @@ -1331,8 +1335,8 @@ int cDvbDevice::PlayTsVideo(const uchar *Data, int Length) | ||||
|  | ||||
| int cDvbDevice::PlayTsAudio(const uchar *Data, int Length) | ||||
| { | ||||
|   Length = TsGetPayload(&Data); | ||||
|   return PlayAudio(Data, Length, 0); | ||||
|   int w = PlayAudio(Data, TsGetPayload(&Data), 0); | ||||
|   return w >= 0 ? Length : w; | ||||
| } | ||||
|  | ||||
| bool cDvbDevice::OpenDvr(void) | ||||
|   | ||||
							
								
								
									
										80
									
								
								dvbplayer.c
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								dvbplayer.c
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ | ||||
|  * See the main source file 'vdr.c' for copyright information and | ||||
|  * how to reach the author. | ||||
|  * | ||||
|  * $Id: dvbplayer.c 1.48 2008/02/09 15:10:54 kls Exp $ | ||||
|  * $Id: dvbplayer.c 2.1 2009/01/05 16:52:40 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #include "dvbplayer.h" | ||||
| @@ -182,8 +182,8 @@ bool cNonBlockingFileReader::WaitForDataMs(int msToWait) | ||||
|  | ||||
| #define PLAYERBUFSIZE  MEGABYTE(1) | ||||
|  | ||||
| // The number of frames to back up when resuming an interrupted replay session: | ||||
| #define RESUMEBACKUP (10 * FRAMESPERSEC) | ||||
| // The number of seconds to back up when resuming an interrupted replay session: | ||||
| #define RESUMEBACKUP 10 | ||||
|  | ||||
| class cDvbPlayer : public cPlayer, cThread { | ||||
| private: | ||||
| @@ -196,6 +196,8 @@ private: | ||||
|   cFileName *fileName; | ||||
|   cIndexFile *index; | ||||
|   cUnbufferedFile *replayFile; | ||||
|   double framesPerSecond; | ||||
|   bool isPesRecording; | ||||
|   bool eof; | ||||
|   bool firstPacket; | ||||
|   ePlayModes playMode; | ||||
| @@ -223,6 +225,7 @@ public: | ||||
|   int SkipFrames(int Frames); | ||||
|   void SkipSeconds(int Seconds); | ||||
|   void Goto(int Position, bool Still = false); | ||||
|   virtual double FramesPerSecond(void) { return framesPerSecond; } | ||||
|   virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false); | ||||
|   virtual bool GetReplayMode(bool &Play, bool &Forward, int &Speed); | ||||
|   }; | ||||
| @@ -240,6 +243,9 @@ cDvbPlayer::cDvbPlayer(const char *FileName) | ||||
|   ringBuffer = NULL; | ||||
|   backTrace = NULL; | ||||
|   index = NULL; | ||||
|   cRecording Recording(FileName); | ||||
|   framesPerSecond = Recording.FramesPerSecond(); | ||||
|   isPesRecording = Recording.IsPesRecording(); | ||||
|   eof = false; | ||||
|   firstPacket = true; | ||||
|   playMode = pmPlay; | ||||
| @@ -249,13 +255,13 @@ cDvbPlayer::cDvbPlayer(const char *FileName) | ||||
|   readFrame = NULL; | ||||
|   playFrame = NULL; | ||||
|   isyslog("replay %s", FileName); | ||||
|   fileName = new cFileName(FileName, false); | ||||
|   fileName = new cFileName(FileName, false, false, isPesRecording); | ||||
|   replayFile = fileName->Open(); | ||||
|   if (!replayFile) | ||||
|      return; | ||||
|   ringBuffer = new cRingBufferFrame(PLAYERBUFSIZE); | ||||
|   // Create the index file: | ||||
|   index = new cIndexFile(FileName, false); | ||||
|   index = new cIndexFile(FileName, false, isPesRecording); | ||||
|   if (!index) | ||||
|      esyslog("ERROR: can't allocate index"); | ||||
|   else if (!index->Ok()) { | ||||
| @@ -327,8 +333,8 @@ int cDvbPlayer::Resume(void) | ||||
|   if (index) { | ||||
|      int Index = index->GetResume(); | ||||
|      if (Index >= 0) { | ||||
|         uchar FileNumber; | ||||
|         int FileOffset; | ||||
|         uint16_t FileNumber; | ||||
|         off_t FileOffset; | ||||
|         if (index->Get(Index, &FileNumber, &FileOffset) && NextFile(FileNumber, FileOffset)) | ||||
|            return Index; | ||||
|         } | ||||
| @@ -341,7 +347,7 @@ bool cDvbPlayer::Save(void) | ||||
|   if (index) { | ||||
|      int Index = writeIndex; | ||||
|      if (Index >= 0) { | ||||
|         Index -= RESUMEBACKUP; | ||||
|         Index -= RESUMEBACKUP * framesPerSecond; | ||||
|         if (Index > 0) | ||||
|            Index = index->GetNextIFrame(Index, false); | ||||
|         else | ||||
| @@ -371,7 +377,7 @@ void cDvbPlayer::Action(void) | ||||
|  | ||||
|   readIndex = Resume(); | ||||
|   if (readIndex >= 0) | ||||
|      isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true)); | ||||
|      isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond)); | ||||
|  | ||||
|   nonBlockingFileReader = new cNonBlockingFileReader; | ||||
|   int Length = 0; | ||||
| @@ -397,8 +403,8 @@ void cDvbPlayer::Action(void) | ||||
|               if (!readFrame && (replayFile || readIndex >= 0)) { | ||||
|                  if (!nonBlockingFileReader->Reading()) { | ||||
|                     if (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) { | ||||
|                        uchar FileNumber; | ||||
|                        int FileOffset; | ||||
|                        uint16_t FileNumber; | ||||
|                        off_t FileOffset; | ||||
|                        bool TimeShiftMode = index->IsStillRecording(); | ||||
|                        int Index = -1; | ||||
|                        if (DeviceHasIBPTrickSpeed() && playDir == pdForward) { | ||||
| @@ -435,8 +441,8 @@ void cDvbPlayer::Action(void) | ||||
|                        readIndex = Index; | ||||
|                        } | ||||
|                     else if (index) { | ||||
|                        uchar FileNumber; | ||||
|                        int FileOffset; | ||||
|                        uint16_t FileNumber; | ||||
|                        off_t FileOffset; | ||||
|                        readIndex++; | ||||
|                        if (!(index->Get(readIndex, &FileNumber, &FileOffset, NULL, &Length) && NextFile(FileNumber, FileOffset))) { | ||||
|                           readIndex = -1; | ||||
| @@ -496,14 +502,22 @@ void cDvbPlayer::Action(void) | ||||
|                  pc = playFrame->Count(); | ||||
|                  if (p) { | ||||
|                     if (firstPacket) { | ||||
|                        PlayPes(NULL, 0); | ||||
|                        cRemux::SetBrokenLink(p, pc); | ||||
|                        if (isPesRecording) { | ||||
|                           PlayPes(NULL, 0); | ||||
|                           cRemux::SetBrokenLink(p, pc); | ||||
|                           } | ||||
|                        else | ||||
|                           PlayTs(NULL, 0); | ||||
|                        firstPacket = false; | ||||
|                        } | ||||
|                     } | ||||
|                  } | ||||
|               if (p) { | ||||
|                  int w = PlayPes(p, pc, playMode != pmPlay); | ||||
|                  int w; | ||||
|                  if (isPesRecording) | ||||
|                     w = PlayPes(p, pc, playMode != pmPlay); | ||||
|                  else | ||||
|                     w = PlayTs(p, TS_SIZE, playMode != pmPlay); | ||||
|                  if (w > 0) { | ||||
|                     p += w; | ||||
|                     pc -= w; | ||||
| @@ -513,7 +527,7 @@ void cDvbPlayer::Action(void) | ||||
|                     break; | ||||
|                     } | ||||
|                  } | ||||
|               if (pc == 0) { | ||||
|               if (pc <= 0) { | ||||
|                  writeIndex = playFrame->Index(); | ||||
|                  backTrace->Add(playFrame->Index(), playFrame->Count()); | ||||
|                  ringBuffer->Drop(playFrame); | ||||
| @@ -678,7 +692,7 @@ void cDvbPlayer::SkipSeconds(int Seconds) | ||||
|      Empty(); | ||||
|      int Index = writeIndex; | ||||
|      if (Index >= 0) { | ||||
|         Index = max(Index + Seconds * FRAMESPERSEC, 0); | ||||
|         Index = max(Index + SecondsToFrames(Seconds, framesPerSecond), 0); | ||||
|         if (Index > 0) | ||||
|            Index = index->GetNextIFrame(Index, false, NULL, NULL, NULL, true); | ||||
|         if (Index >= 0) | ||||
| @@ -695,38 +709,16 @@ void cDvbPlayer::Goto(int Index, bool Still) | ||||
|      Empty(); | ||||
|      if (++Index <= 0) | ||||
|         Index = 1; // not '0', to allow GetNextIFrame() below to work! | ||||
|      uchar FileNumber; | ||||
|      int FileOffset, Length; | ||||
|      uint16_t FileNumber; | ||||
|      off_t FileOffset; | ||||
|      int Length; | ||||
|      Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset, &Length); | ||||
|      if (Index >= 0 && NextFile(FileNumber, FileOffset) && Still) { | ||||
|         uchar b[MAXFRAMESIZE + 4 + 5 + 4]; | ||||
|         uchar b[MAXFRAMESIZE]; | ||||
|         int r = ReadFrame(replayFile, b, Length, sizeof(b)); | ||||
|         if (r > 0) { | ||||
|            if (playMode == pmPause) | ||||
|               DevicePlay(); | ||||
|            // append sequence end code to get the image shown immediately with softdevices | ||||
|            if (r > 6 && (b[3] & 0xF0) == 0xE0) { // make sure to append it only to a video packet | ||||
|               b[r++] = 0x00; | ||||
|               b[r++] = 0x00; | ||||
|               b[r++] = 0x01; | ||||
|               b[r++] = b[3]; | ||||
|               if (b[6] & 0x80) { // MPEG 2 | ||||
|                  b[r++] = 0x00; | ||||
|                  b[r++] = 0x07; | ||||
|                  b[r++] = 0x80; | ||||
|                  b[r++] = 0x00; | ||||
|                  b[r++] = 0x00; | ||||
|                  } | ||||
|               else { // MPEG 1 | ||||
|                  b[r++] = 0x00; | ||||
|                  b[r++] = 0x05; | ||||
|                  b[r++] = 0x0F; | ||||
|                  } | ||||
|               b[r++] = 0x00; | ||||
|               b[r++] = 0x00; | ||||
|               b[r++] = 0x01; | ||||
|               b[r++] = 0xB7; | ||||
|               } | ||||
|            DeviceStillPicture(b, r); | ||||
|            } | ||||
|         playMode = pmStill; | ||||
|   | ||||
							
								
								
									
										27
									
								
								menu.c
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								menu.c
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ | ||||
|  * See the main source file 'vdr.c' for copyright information and | ||||
|  * how to reach the author. | ||||
|  * | ||||
|  * $Id: menu.c 2.3 2008/11/22 15:18:00 kls Exp $ | ||||
|  * $Id: menu.c 2.4 2009/01/06 14:34:17 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #include "menu.h" | ||||
| @@ -1989,10 +1989,13 @@ eOSState cMenuRecordings::Rewind(void) | ||||
|      return osContinue; | ||||
|   cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()); | ||||
|   if (ri && !ri->IsDirectory()) { | ||||
|      cDevice::PrimaryDevice()->StopReplay(); // must do this first to be able to rewind the currently replayed recording | ||||
|      cResumeFile ResumeFile(ri->FileName()); | ||||
|      ResumeFile.Delete(); | ||||
|      return Play(); | ||||
|      cRecording *recording = GetRecording(ri); | ||||
|      if (recording) { | ||||
|         cDevice::PrimaryDevice()->StopReplay(); // must do this first to be able to rewind the currently replayed recording | ||||
|         cResumeFile ResumeFile(ri->FileName(), recording->IsPesRecording()); | ||||
|         ResumeFile.Delete(); | ||||
|         return Play(); | ||||
|         } | ||||
|      } | ||||
|   return osContinue; | ||||
| } | ||||
| @@ -4014,9 +4017,9 @@ cReplayControl::cReplayControl(void) | ||||
|   lastSpeed = -2; // an invalid value | ||||
|   timeoutShow = 0; | ||||
|   timeSearchActive = false; | ||||
|   marks.Load(fileName); | ||||
|   cRecording Recording(fileName); | ||||
|   cStatus::MsgReplaying(this, Recording.Name(), Recording.FileName(), true); | ||||
|   marks.Load(fileName, Recording.FramesPerSecond(), Recording.IsPesRecording()); | ||||
|   SetTrackDescriptions(false); | ||||
| } | ||||
|  | ||||
| @@ -4126,7 +4129,7 @@ bool cReplayControl::ShowProgress(bool Initial) | ||||
|         lastCurrent = lastTotal = -1; | ||||
|         } | ||||
|      if (Total != lastTotal) { | ||||
|         displayReplay->SetTotal(IndexToHMSF(Total)); | ||||
|         displayReplay->SetTotal(IndexToHMSF(Total, false, FramesPerSecond())); | ||||
|         if (!Initial) | ||||
|            displayReplay->Flush(); | ||||
|         } | ||||
| @@ -4134,7 +4137,7 @@ bool cReplayControl::ShowProgress(bool Initial) | ||||
|         displayReplay->SetProgress(Current, Total); | ||||
|         if (!Initial) | ||||
|            displayReplay->Flush(); | ||||
|         displayReplay->SetCurrent(IndexToHMSF(Current, displayFrames)); | ||||
|         displayReplay->SetCurrent(IndexToHMSF(Current, displayFrames, FramesPerSecond())); | ||||
|         displayReplay->Flush(); | ||||
|         lastCurrent = Current; | ||||
|         } | ||||
| @@ -4167,8 +4170,8 @@ void cReplayControl::TimeSearchProcess(eKeys Key) | ||||
| { | ||||
| #define STAY_SECONDS_OFF_END 10 | ||||
|   int Seconds = (timeSearchTime >> 24) * 36000 + ((timeSearchTime & 0x00FF0000) >> 16) * 3600 + ((timeSearchTime & 0x0000FF00) >> 8) * 600 + (timeSearchTime & 0x000000FF) * 60; | ||||
|   int Current = (lastCurrent / FRAMESPERSEC); | ||||
|   int Total = (lastTotal / FRAMESPERSEC); | ||||
|   int Current = (lastCurrent / FramesPerSecond()); | ||||
|   int Total = (lastTotal / FramesPerSecond()); | ||||
|   switch (Key) { | ||||
|     case k0 ... k9: | ||||
|          if (timeSearchPos < 4) { | ||||
| @@ -4195,7 +4198,7 @@ void cReplayControl::TimeSearchProcess(eKeys Key) | ||||
|     case kDown: | ||||
|     case kOk: | ||||
|          Seconds = min(Total - STAY_SECONDS_OFF_END, Seconds); | ||||
|          Goto(Seconds * FRAMESPERSEC, Key == kDown || Key == kPause || Key == kOk); | ||||
|          Goto(SecondsToFrames(Seconds, FramesPerSecond()), Key == kDown || Key == kPause || Key == kOk); | ||||
|          timeSearchActive = false; | ||||
|          break; | ||||
|     default: | ||||
| @@ -4317,7 +4320,7 @@ void cReplayControl::EditTest(void) | ||||
|         if ((m->Index() & 0x01) != 0) | ||||
|            m = marks.Next(m); | ||||
|         if (m) { | ||||
|            Goto(m->position - SecondsToFrames(3)); | ||||
|            Goto(m->position - SecondsToFrames(3, FramesPerSecond())); | ||||
|            Play(); | ||||
|            } | ||||
|         } | ||||
|   | ||||
							
								
								
									
										5
									
								
								player.h
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								player.h
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ | ||||
|  * See the main source file 'vdr.c' for copyright information and | ||||
|  * how to reach the author. | ||||
|  * | ||||
|  * $Id: player.h 2.1 2008/08/15 14:07:48 kls Exp $ | ||||
|  * $Id: player.h 2.2 2009/01/05 13:04:10 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #ifndef __PLAYER_H | ||||
| @@ -50,6 +50,8 @@ public: | ||||
|   cPlayer(ePlayMode PlayMode = pmAudioVideo); | ||||
|   virtual ~cPlayer(); | ||||
|   bool IsAttached(void) { return device != NULL; } | ||||
|   virtual double FramesPerSecond(void) { return DEFAULTFRAMESPERSECOND; } | ||||
|        // Returns the number of frames per second of the currently played material. | ||||
|   virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false) { return false; } | ||||
|        // Returns the current and total frame index, optionally snapped to the | ||||
|        // nearest I-frame. | ||||
| @@ -82,6 +84,7 @@ public: | ||||
|   virtual ~cControl(); | ||||
|   virtual void Hide(void) = 0; | ||||
|   virtual cOsdObject *GetInfo(void); | ||||
|   double FramesPerSecond(void) { return player->FramesPerSecond(); } | ||||
|   bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false) { return player->GetIndex(Current, Total, SnapToIFrame); } | ||||
|   bool GetReplayMode(bool &Play, bool &Forward, int &Speed) { return player->GetReplayMode(Play, Forward, Speed); } | ||||
|   static void Launch(cControl *Control); | ||||
|   | ||||
							
								
								
									
										172
									
								
								recorder.c
									
									
									
									
									
								
							
							
						
						
									
										172
									
								
								recorder.c
									
									
									
									
									
								
							| @@ -4,13 +4,10 @@ | ||||
|  * See the main source file 'vdr.c' for copyright information and | ||||
|  * how to reach the author. | ||||
|  * | ||||
|  * $Id: recorder.c 1.19 2007/02/24 16:36:24 kls Exp $ | ||||
|  * $Id: recorder.c 2.1 2009/01/06 12:38:01 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #include "recorder.h" | ||||
| #include <stdarg.h> | ||||
| #include <stdio.h> | ||||
| #include <unistd.h> | ||||
| #include "shutdown.h" | ||||
|  | ||||
| #define RECORDERBUFSIZE  MEGABYTE(5) | ||||
| @@ -22,33 +19,34 @@ | ||||
| #define MINFREEDISKSPACE    (512) // MB | ||||
| #define DISKCHECKINTERVAL   100 // seconds | ||||
|  | ||||
| // --- cFileWriter ----------------------------------------------------------- | ||||
| // --- cRecorder ------------------------------------------------------------- | ||||
|  | ||||
| class cFileWriter : public cThread { | ||||
| private: | ||||
|   cRemux *remux; | ||||
|   cFileName *fileName; | ||||
|   cIndexFile *index; | ||||
|   uchar pictureType; | ||||
|   int fileSize; | ||||
|   cUnbufferedFile *recordFile; | ||||
|   time_t lastDiskSpaceCheck; | ||||
|   bool RunningLowOnDiskSpace(void); | ||||
|   bool NextFile(void); | ||||
| protected: | ||||
|   virtual void Action(void); | ||||
| public: | ||||
|   cFileWriter(const char *FileName, cRemux *Remux); | ||||
|   virtual ~cFileWriter(); | ||||
|   }; | ||||
|  | ||||
| cFileWriter::cFileWriter(const char *FileName, cRemux *Remux) | ||||
| :cThread("file writer") | ||||
| cRecorder::cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids) | ||||
| :cReceiver(ChannelID, Priority, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids) | ||||
| ,cThread("recording") | ||||
| ,recordingInfo(FileName) | ||||
| { | ||||
|   // Make sure the disk is up and running: | ||||
|  | ||||
|   SpinUpDisk(FileName); | ||||
|  | ||||
|   ringBuffer = new cRingBufferLinear(RECORDERBUFSIZE, TS_SIZE * 2, true, "Recorder"); | ||||
|   ringBuffer->SetTimeouts(0, 100); | ||||
|   cChannel *Channel = Channels.GetByChannelID(ChannelID); | ||||
|   int Pid = VPid; | ||||
|   int Type = Channel ? Channel->Vtype() : 0; | ||||
|   if (!Pid && APids) { | ||||
|      Pid = APids[0]; | ||||
|      Type = 0x04; | ||||
|      } | ||||
|   if (!Pid && DPids) { | ||||
|      Pid = DPids[0]; | ||||
|      Type = 0x06; | ||||
|      } | ||||
|   frameDetector = new cFrameDetector(Pid, Type); | ||||
|   patPmtGenerator.GeneratePmt(ChannelID); | ||||
|   fileName = NULL; | ||||
|   remux = Remux; | ||||
|   index = NULL; | ||||
|   pictureType = NO_PICTURE; | ||||
|   fileSize = 0; | ||||
|   lastDiskSpaceCheck = time(NULL); | ||||
|   fileName = new cFileName(FileName, true); | ||||
| @@ -62,14 +60,16 @@ cFileWriter::cFileWriter(const char *FileName, cRemux *Remux) | ||||
|      // let's continue without index, so we'll at least have the recording | ||||
| } | ||||
|  | ||||
| cFileWriter::~cFileWriter() | ||||
| cRecorder::~cRecorder() | ||||
| { | ||||
|   Cancel(3); | ||||
|   Detach(); | ||||
|   delete index; | ||||
|   delete fileName; | ||||
|   delete frameDetector; | ||||
|   delete ringBuffer; | ||||
| } | ||||
|  | ||||
| bool cFileWriter::RunningLowOnDiskSpace(void) | ||||
| bool cRecorder::RunningLowOnDiskSpace(void) | ||||
| { | ||||
|   if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) { | ||||
|      int Free = FreeDiskSpaceMB(fileName->Name()); | ||||
| @@ -82,10 +82,10 @@ bool cFileWriter::RunningLowOnDiskSpace(void) | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| bool cFileWriter::NextFile(void) | ||||
| bool cRecorder::NextFile(void) | ||||
| { | ||||
|   if (recordFile && pictureType == I_FRAME) { // every file shall start with an I_FRAME | ||||
|      if (fileSize > MEGABYTE(Setup.MaxVideoFileSize) || RunningLowOnDiskSpace()) { | ||||
|   if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame | ||||
|      if (fileSize > MEGABYTE(off_t(Setup.MaxVideoFileSize)) || RunningLowOnDiskSpace()) { | ||||
|         recordFile = fileName->NextFile(); | ||||
|         fileSize = 0; | ||||
|         } | ||||
| @@ -93,67 +93,10 @@ bool cFileWriter::NextFile(void) | ||||
|   return recordFile != NULL; | ||||
| } | ||||
|  | ||||
| void cFileWriter::Action(void) | ||||
| { | ||||
|   time_t t = time(NULL); | ||||
|   while (Running()) { | ||||
|         int Count; | ||||
|         uchar *p = remux->Get(Count, &pictureType); | ||||
|         if (p) { | ||||
|            if (!Running() && pictureType == I_FRAME) // finish the recording before the next 'I' frame | ||||
|               break; | ||||
|            if (NextFile()) { | ||||
|               if (index && pictureType != NO_PICTURE) | ||||
|                  index->Write(pictureType, fileName->Number(), fileSize); | ||||
|               if (recordFile->Write(p, Count) < 0) { | ||||
|                  LOG_ERROR_STR(fileName->Name()); | ||||
|                  break; | ||||
|                  } | ||||
|               fileSize += Count; | ||||
|               remux->Del(Count); | ||||
|               } | ||||
|            else | ||||
|               break; | ||||
|            t = time(NULL); | ||||
|            } | ||||
|         else if (time(NULL) - t > MAXBROKENTIMEOUT) { | ||||
|            esyslog("ERROR: video data stream broken"); | ||||
|            ShutdownHandler.RequestEmergencyExit(); | ||||
|            t = time(NULL); | ||||
|            } | ||||
|         } | ||||
| } | ||||
|  | ||||
| // --- cRecorder ------------------------------------------------------------- | ||||
|  | ||||
| cRecorder::cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids) | ||||
| :cReceiver(ChannelID, Priority, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids) | ||||
| ,cThread("recording") | ||||
| { | ||||
|   // Make sure the disk is up and running: | ||||
|  | ||||
|   SpinUpDisk(FileName); | ||||
|  | ||||
|   ringBuffer = new cRingBufferLinear(RECORDERBUFSIZE, TS_SIZE * 2, true, "Recorder"); | ||||
|   ringBuffer->SetTimeouts(0, 100); | ||||
|   remux = new cRemux(VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids, true); | ||||
|   writer = new cFileWriter(FileName, remux); | ||||
| } | ||||
|  | ||||
| cRecorder::~cRecorder() | ||||
| { | ||||
|   Detach(); | ||||
|   delete writer; | ||||
|   delete remux; | ||||
|   delete ringBuffer; | ||||
| } | ||||
|  | ||||
| void cRecorder::Activate(bool On) | ||||
| { | ||||
|   if (On) { | ||||
|      writer->Start(); | ||||
|   if (On) | ||||
|      Start(); | ||||
|      } | ||||
|   else | ||||
|      Cancel(3); | ||||
| } | ||||
| @@ -169,15 +112,54 @@ void cRecorder::Receive(uchar *Data, int Length) | ||||
|  | ||||
| void cRecorder::Action(void) | ||||
| { | ||||
|   time_t t = time(NULL); | ||||
|   bool Synced = false; | ||||
|   bool InfoWritten = false; | ||||
|   while (Running()) { | ||||
|         int r; | ||||
|         uchar *b = ringBuffer->Get(r); | ||||
|         if (b) { | ||||
|            int Count = remux->Put(b, r); | ||||
|            if (Count) | ||||
|            int Count = frameDetector->Analyze(b, r); | ||||
|            if (Count) { | ||||
|               if (!Running() && frameDetector->IndependentFrame()) // finish the recording before the next independent frame | ||||
|                  break; | ||||
|               if (Synced |= frameDetector->IndependentFrame()) { // start with first independent frame | ||||
|                  if (!InfoWritten) { | ||||
|                     if (recordingInfo.Read()) { | ||||
|                        if (frameDetector->FramesPerSecond() > 0 && recordingInfo.FramesPerSecond() != frameDetector->FramesPerSecond()) { | ||||
|                           recordingInfo.SetFramesPerSecond(frameDetector->FramesPerSecond()); | ||||
|                           recordingInfo.Write(); | ||||
|                           } | ||||
|                        } | ||||
|                     InfoWritten = true; | ||||
|                     } | ||||
|                  if (!NextFile()) | ||||
|                     break; | ||||
|                  if (index && frameDetector->NewFrame()) | ||||
|                     index->Write(frameDetector->IndependentFrame(), fileName->Number(), fileSize); | ||||
|                  if (frameDetector->IndependentFrame()) { | ||||
|                     recordFile->Write(patPmtGenerator.GetPat(), TS_SIZE); | ||||
|                     fileSize += TS_SIZE; | ||||
|                     int Index = 0; | ||||
|                     while (uchar *pmt = patPmtGenerator.GetPmt(Index)) { | ||||
|                           recordFile->Write(pmt, TS_SIZE); | ||||
|                           fileSize += TS_SIZE; | ||||
|                           } | ||||
|                     } | ||||
|                  if (recordFile->Write(b, Count) < 0) { | ||||
|                     LOG_ERROR_STR(fileName->Name()); | ||||
|                     break; | ||||
|                     } | ||||
|                  fileSize += Count; | ||||
|                  t = time(NULL); | ||||
|                  } | ||||
|               ringBuffer->Del(Count); | ||||
|            else | ||||
|               cCondWait::SleepMs(100); // avoid busy loop when resultBuffer is full in cRemux::Put() | ||||
|               } | ||||
|            } | ||||
|         if (time(NULL) - t > MAXBROKENTIMEOUT) { | ||||
|            esyslog("ERROR: video data stream broken"); | ||||
|            ShutdownHandler.RequestEmergencyExit(); | ||||
|            t = time(NULL); | ||||
|            } | ||||
|         } | ||||
| } | ||||
|   | ||||
							
								
								
									
										16
									
								
								recorder.h
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								recorder.h
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ | ||||
|  * See the main source file 'vdr.c' for copyright information and | ||||
|  * how to reach the author. | ||||
|  * | ||||
|  * $Id: recorder.h 1.5 2007/01/07 14:44:05 kls Exp $ | ||||
|  * $Id: recorder.h 2.1 2009/01/06 10:44:58 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #ifndef __RECORDER_H | ||||
| @@ -16,13 +16,19 @@ | ||||
| #include "ringbuffer.h" | ||||
| #include "thread.h" | ||||
|  | ||||
| class cFileWriter; | ||||
|  | ||||
| class cRecorder : public cReceiver, cThread { | ||||
| private: | ||||
|   cRingBufferLinear *ringBuffer; | ||||
|   cRemux *remux; | ||||
|   cFileWriter *writer; | ||||
|   cFrameDetector *frameDetector; | ||||
|   cPatPmtGenerator patPmtGenerator; | ||||
|   cFileName *fileName; | ||||
|   cIndexFile *index; | ||||
|   cUnbufferedFile *recordFile; | ||||
|   cRecordingInfo recordingInfo; | ||||
|   off_t fileSize; | ||||
|   time_t lastDiskSpaceCheck; | ||||
|   bool RunningLowOnDiskSpace(void); | ||||
|   bool NextFile(void); | ||||
| protected: | ||||
|   virtual void Activate(bool On); | ||||
|   virtual void Receive(uchar *Data, int Length); | ||||
|   | ||||
							
								
								
									
										355
									
								
								recording.c
									
									
									
									
									
								
							
							
						
						
									
										355
									
								
								recording.c
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ | ||||
|  * See the main source file 'vdr.c' for copyright information and | ||||
|  * how to reach the author. | ||||
|  * | ||||
|  * $Id: recording.c 2.3 2008/05/22 10:40:08 kls Exp $ | ||||
|  * $Id: recording.c 2.4 2009/01/06 14:41:11 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #include "recording.h" | ||||
| @@ -12,6 +12,7 @@ | ||||
| #include <dirent.h> | ||||
| #include <errno.h> | ||||
| #include <fcntl.h> | ||||
| #include <math.h> | ||||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
| #include <sys/stat.h> | ||||
| @@ -19,7 +20,6 @@ | ||||
| #include "channels.h" | ||||
| #include "i18n.h" | ||||
| #include "interface.h" | ||||
| #include "remux.h" //XXX+ I_FRAME | ||||
| #include "skins.h" | ||||
| #include "tools.h" | ||||
| #include "videodir.h" | ||||
| @@ -37,15 +37,17 @@ | ||||
| #define DATAFORMAT   "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT | ||||
| #define NAMEFORMAT   "%s/%s/" DATAFORMAT | ||||
| */ | ||||
| #define DATAFORMAT   "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT | ||||
| #define NAMEFORMAT   "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT | ||||
| #define DATAFORMATPES   "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT | ||||
| #define NAMEFORMATPES   "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT | ||||
| #define DATAFORMATTS    "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT | ||||
| #define NAMEFORMATTS    "%s/%s/" DATAFORMATTS | ||||
|  | ||||
| #define RESUMEFILESUFFIX  "/resume%s%s.vdr" | ||||
| #define RESUMEFILESUFFIX  "/resume%s%s" | ||||
| #ifdef SUMMARYFALLBACK | ||||
| #define SUMMARYFILESUFFIX "/summary.vdr" | ||||
| #endif | ||||
| #define INFOFILESUFFIX    "/info.vdr" | ||||
| #define MARKSFILESUFFIX   "/marks.vdr" | ||||
| #define INFOFILESUFFIX    "/info" | ||||
| #define MARKSFILESUFFIX   "/marks" | ||||
|  | ||||
| #define MINDISKSPACE 1024 // MB | ||||
|  | ||||
| @@ -202,12 +204,14 @@ void AssertFreeDiskSpace(int Priority, bool Force) | ||||
|  | ||||
| // --- cResumeFile ----------------------------------------------------------- | ||||
|  | ||||
| cResumeFile::cResumeFile(const char *FileName) | ||||
| cResumeFile::cResumeFile(const char *FileName, bool IsPesRecording) | ||||
| { | ||||
|   fileName = MALLOC(char, strlen(FileName) + strlen(RESUMEFILESUFFIX) + 1); | ||||
|   isPesRecording = IsPesRecording; | ||||
|   const char *Suffix = isPesRecording ? RESUMEFILESUFFIX ".vdr" : RESUMEFILESUFFIX; | ||||
|   fileName = MALLOC(char, strlen(FileName) + strlen(Suffix) + 1); | ||||
|   if (fileName) { | ||||
|      strcpy(fileName, FileName); | ||||
|      sprintf(fileName + strlen(fileName), RESUMEFILESUFFIX, Setup.ResumeID ? "." : "", Setup.ResumeID ? *itoa(Setup.ResumeID) : ""); | ||||
|      sprintf(fileName + strlen(fileName), Suffix, Setup.ResumeID ? "." : "", Setup.ResumeID ? *itoa(Setup.ResumeID) : ""); | ||||
|      } | ||||
|   else | ||||
|      esyslog("ERROR: can't allocate memory for resume file name"); | ||||
| @@ -227,16 +231,37 @@ int cResumeFile::Read(void) | ||||
|         if ((st.st_mode & S_IWUSR) == 0) // no write access, assume no resume | ||||
|            return -1; | ||||
|         } | ||||
|      int f = open(fileName, O_RDONLY); | ||||
|      if (f >= 0) { | ||||
|         if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) { | ||||
|            resume = -1; | ||||
|            LOG_ERROR_STR(fileName); | ||||
|      if (isPesRecording) { | ||||
|         int f = open(fileName, O_RDONLY); | ||||
|         if (f >= 0) { | ||||
|            if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) { | ||||
|               resume = -1; | ||||
|               LOG_ERROR_STR(fileName); | ||||
|               } | ||||
|            close(f); | ||||
|            } | ||||
|         close(f); | ||||
|         else if (errno != ENOENT) | ||||
|            LOG_ERROR_STR(fileName); | ||||
|         } | ||||
|      else { | ||||
|         FILE *f = fopen(fileName, "r"); | ||||
|         if (f) { | ||||
|            cReadLine ReadLine; | ||||
|            char *s; | ||||
|            int line = 0; | ||||
|            while ((s = ReadLine.Read(f)) != NULL) { | ||||
|                  ++line; | ||||
|                  char *t = skipspace(s + 1); | ||||
|                  switch (*s) { | ||||
|                    case 'I': resume = atoi(t); | ||||
|                              break; | ||||
|                    } | ||||
|                  } | ||||
|            fclose(f); | ||||
|            } | ||||
|         else if (errno != ENOENT) | ||||
|            LOG_ERROR_STR(fileName); | ||||
|         } | ||||
|      else if (errno != ENOENT) | ||||
|         LOG_ERROR_STR(fileName); | ||||
|      } | ||||
|   return resume; | ||||
| } | ||||
| @@ -244,12 +269,24 @@ int cResumeFile::Read(void) | ||||
| bool cResumeFile::Save(int Index) | ||||
| { | ||||
|   if (fileName) { | ||||
|      int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE); | ||||
|      if (f >= 0) { | ||||
|         if (safe_write(f, &Index, sizeof(Index)) < 0) | ||||
|      if (isPesRecording) { | ||||
|         int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE); | ||||
|         if (f >= 0) { | ||||
|            if (safe_write(f, &Index, sizeof(Index)) < 0) | ||||
|               LOG_ERROR_STR(fileName); | ||||
|            close(f); | ||||
|            Recordings.ResetResume(fileName); | ||||
|            return true; | ||||
|            } | ||||
|         } | ||||
|      else { | ||||
|         FILE *f = fopen(fileName, "w"); | ||||
|         if (f) { | ||||
|            fprintf(f, "I %d\n", Index); | ||||
|            fclose(f); | ||||
|            } | ||||
|         else | ||||
|            LOG_ERROR_STR(fileName); | ||||
|         close(f); | ||||
|         Recordings.ResetResume(fileName); | ||||
|         return true; | ||||
|         } | ||||
|      } | ||||
| @@ -274,6 +311,10 @@ cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event) | ||||
|   ownEvent = Event ? NULL : new cEvent(0); | ||||
|   event = ownEvent ? ownEvent : Event; | ||||
|   aux = NULL; | ||||
|   framesPerSecond = DEFAULTFRAMESPERSECOND; | ||||
|   priority = MAXPRIORITY; | ||||
|   lifetime = MAXLIFETIME; | ||||
|   fileName = NULL; | ||||
|   if (Channel) { | ||||
|      // Since the EPG data's component records can carry only a single | ||||
|      // language code, let's see whether the channel's PID data has | ||||
| @@ -322,11 +363,25 @@ cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event) | ||||
|      } | ||||
| } | ||||
|  | ||||
| cRecordingInfo::cRecordingInfo(const char *FileName) | ||||
| { | ||||
|   channelID = tChannelID::InvalidID; | ||||
|   channelName = NULL; | ||||
|   ownEvent = new cEvent(0); | ||||
|   event = ownEvent; | ||||
|   aux = NULL; | ||||
|   framesPerSecond = DEFAULTFRAMESPERSECOND; | ||||
|   priority = MAXPRIORITY; | ||||
|   lifetime = MAXLIFETIME; | ||||
|   fileName = strdup(cString::sprintf("%s%s", FileName, INFOFILESUFFIX)); | ||||
| } | ||||
|  | ||||
| cRecordingInfo::~cRecordingInfo() | ||||
| { | ||||
|   delete ownEvent; | ||||
|   free(aux); | ||||
|   free(channelName); | ||||
|   free(fileName); | ||||
| } | ||||
|  | ||||
| void cRecordingInfo::SetData(const char *Title, const char *ShortText, const char *Description) | ||||
| @@ -345,6 +400,11 @@ void cRecordingInfo::SetAux(const char *Aux) | ||||
|   aux = Aux ? strdup(Aux) : NULL; | ||||
| } | ||||
|  | ||||
| void cRecordingInfo::SetFramesPerSecond(double FramesPerSecond) | ||||
| { | ||||
|   framesPerSecond = FramesPerSecond; | ||||
| } | ||||
|  | ||||
| bool cRecordingInfo::Read(FILE *f) | ||||
| { | ||||
|   if (ownEvent) { | ||||
| @@ -382,6 +442,12 @@ bool cRecordingInfo::Read(FILE *f) | ||||
|                             } | ||||
|                        } | ||||
|                        break; | ||||
|              case 'F': framesPerSecond = atof(t); | ||||
|                        break; | ||||
|              case 'L': lifetime = atoi(t); | ||||
|                        break; | ||||
|              case 'P': priority = atoi(t); | ||||
|                        break; | ||||
|              case '@': free(aux); | ||||
|                        aux = strdup(t); | ||||
|                        break; | ||||
| @@ -403,11 +469,48 @@ bool cRecordingInfo::Write(FILE *f, const char *Prefix) const | ||||
|   if (channelID.Valid()) | ||||
|      fprintf(f, "%sC %s%s%s\n", Prefix, *channelID.ToString(), channelName ? " " : "", channelName ? channelName : ""); | ||||
|   event->Dump(f, Prefix, true); | ||||
|   fprintf(f, "%sF %.10g\n", Prefix, framesPerSecond); | ||||
|   fprintf(f, "%sP %d\n", Prefix, priority); | ||||
|   fprintf(f, "%sL %d\n", Prefix, lifetime); | ||||
|   if (aux) | ||||
|      fprintf(f, "%s@ %s\n", Prefix, aux); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool cRecordingInfo::Read(void) | ||||
| { | ||||
|   bool Result = false; | ||||
|   if (fileName) { | ||||
|      FILE *f = fopen(fileName, "r"); | ||||
|      if (f) { | ||||
|         if (Read(f)) | ||||
|            Result = true; | ||||
|         else | ||||
|            esyslog("ERROR: EPG data problem in file %s", fileName); | ||||
|         fclose(f); | ||||
|         } | ||||
|      else if (errno != ENOENT) | ||||
|         LOG_ERROR_STR(fileName); | ||||
|      } | ||||
|   return Result; | ||||
| } | ||||
|  | ||||
| bool cRecordingInfo::Write(void) const | ||||
| { | ||||
|   bool Result = false; | ||||
|   if (fileName) { | ||||
|      cSafeFile f(fileName); | ||||
|      if (f.Open()) { | ||||
|         if (Write(f)) | ||||
|            Result = true; | ||||
|         f.Close(); | ||||
|         } | ||||
|      else | ||||
|         LOG_ERROR_STR(fileName); | ||||
|      } | ||||
|   return Result; | ||||
| } | ||||
|  | ||||
| // --- cRecording ------------------------------------------------------------ | ||||
|  | ||||
| #define RESUME_NOT_INITIALIZED (-2) | ||||
| @@ -497,6 +600,10 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event) | ||||
|   fileName = NULL; | ||||
|   name = NULL; | ||||
|   fileSizeMB = -1; // unknown | ||||
|   channel = Timer->Channel()->Number(); | ||||
|   resumeId = Setup.ResumeID; | ||||
|   isPesRecording = false; | ||||
|   framesPerSecond = DEFAULTFRAMESPERSECOND; | ||||
|   deleted = 0; | ||||
|   // set up the actual name: | ||||
|   const char *Title = Event ? Event->Title() : NULL; | ||||
| @@ -542,12 +649,20 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event) | ||||
|   // handle info: | ||||
|   info = new cRecordingInfo(Timer->Channel(), Event); | ||||
|   info->SetAux(Timer->Aux()); | ||||
|   info->priority = priority; | ||||
|   info->lifetime = lifetime; | ||||
| } | ||||
|  | ||||
| cRecording::cRecording(const char *FileName) | ||||
| { | ||||
|   resume = RESUME_NOT_INITIALIZED; | ||||
|   fileSizeMB = -1; // unknown | ||||
|   channel = -1; | ||||
|   resumeId = -1; | ||||
|   priority = MAXPRIORITY; // assume maximum in case there is no info file | ||||
|   lifetime = MAXLIFETIME; | ||||
|   isPesRecording = false; | ||||
|   framesPerSecond = DEFAULTFRAMESPERSECOND; | ||||
|   deleted = 0; | ||||
|   titleBuffer = NULL; | ||||
|   sortBuffer = NULL; | ||||
| @@ -562,7 +677,8 @@ cRecording::cRecording(const char *FileName) | ||||
|      struct tm tm_r; | ||||
|      struct tm t = *localtime_r(&now, &tm_r); // this initializes the time zone in 't' | ||||
|      t.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting | ||||
|      if (7 == sscanf(p + 1, DATAFORMAT, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) { | ||||
|      if (7 == sscanf(p + 1, DATAFORMATTS, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &channel, &resumeId) | ||||
|       || 7 == sscanf(p + 1, DATAFORMATPES, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) { | ||||
|         t.tm_year -= 1900; | ||||
|         t.tm_mon--; | ||||
|         t.tm_sec = 0; | ||||
| @@ -571,14 +687,22 @@ cRecording::cRecording(const char *FileName) | ||||
|         strncpy(name, FileName, p - FileName); | ||||
|         name[p - FileName] = 0; | ||||
|         name = ExchangeChars(name, false); | ||||
|         isPesRecording = resumeId < 0; | ||||
|         } | ||||
|      else | ||||
|         return; | ||||
|      GetResume(); | ||||
|      // read an optional info file: | ||||
|      cString InfoFileName = cString::sprintf("%s%s", fileName, INFOFILESUFFIX); | ||||
|      cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX); | ||||
|      FILE *f = fopen(InfoFileName, "r"); | ||||
|      if (f) { | ||||
|         if (!info->Read(f)) | ||||
|            esyslog("ERROR: EPG data problem in file %s", *InfoFileName); | ||||
|         else if (!isPesRecording) { | ||||
|            priority = info->priority; | ||||
|            lifetime = info->lifetime; | ||||
|            framesPerSecond = info->framesPerSecond; | ||||
|            } | ||||
|         fclose(f); | ||||
|         } | ||||
|      else if (errno != ENOENT) | ||||
| @@ -683,7 +807,7 @@ char *cRecording::SortName(void) const | ||||
| int cRecording::GetResume(void) const | ||||
| { | ||||
|   if (resume == RESUME_NOT_INITIALIZED) { | ||||
|      cResumeFile ResumeFile(FileName()); | ||||
|      cResumeFile ResumeFile(FileName(), isPesRecording); | ||||
|      resume = ResumeFile.Read(); | ||||
|      } | ||||
|   return resume; | ||||
| @@ -700,8 +824,11 @@ const char *cRecording::FileName(void) const | ||||
|   if (!fileName) { | ||||
|      struct tm tm_r; | ||||
|      struct tm *t = localtime_r(&start, &tm_r); | ||||
|      const char *fmt = isPesRecording ? NAMEFORMATPES : NAMEFORMATTS; | ||||
|      int ch = isPesRecording ? priority : channel; | ||||
|      int ri = isPesRecording ? lifetime : resumeId; | ||||
|      name = ExchangeChars(name, true); | ||||
|      fileName = strdup(cString::sprintf(NAMEFORMAT, VideoDirectory, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, priority, lifetime)); | ||||
|      fileName = strdup(cString::sprintf(fmt, VideoDirectory, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri)); | ||||
|      name = ExchangeChars(name, false); | ||||
|      } | ||||
|   return fileName; | ||||
| @@ -789,7 +916,7 @@ bool cRecording::IsEdited(void) const | ||||
|  | ||||
| bool cRecording::WriteInfo(void) | ||||
| { | ||||
|   cString InfoFileName = cString::sprintf("%s%s", fileName, INFOFILESUFFIX); | ||||
|   cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX); | ||||
|   FILE *f = fopen(InfoFileName, "w"); | ||||
|   if (f) { | ||||
|      info->Write(f); | ||||
| @@ -1061,10 +1188,14 @@ void cRecordings::ResetResume(const char *ResumeFileName) | ||||
|  | ||||
| // --- cMark ----------------------------------------------------------------- | ||||
|  | ||||
| cMark::cMark(int Position, const char *Comment) | ||||
| double MarkFramesPerSecond = DEFAULTFRAMESPERSECOND; | ||||
| cMutex MutexMarkFramesPerSecond; | ||||
|  | ||||
| cMark::cMark(int Position, const char *Comment, double FramesPerSecond) | ||||
| { | ||||
|   position = Position; | ||||
|   comment = Comment ? strdup(Comment) : NULL; | ||||
|   framesPerSecond = FramesPerSecond; | ||||
| } | ||||
|  | ||||
| cMark::~cMark() | ||||
| @@ -1074,14 +1205,15 @@ cMark::~cMark() | ||||
|  | ||||
| cString cMark::ToText(void) | ||||
| { | ||||
|   return cString::sprintf("%s%s%s\n", *IndexToHMSF(position, true), comment ? " " : "", comment ? comment : ""); | ||||
|   return cString::sprintf("%s%s%s\n", *IndexToHMSF(position, true, framesPerSecond), comment ? " " : "", comment ? comment : ""); | ||||
| } | ||||
|  | ||||
| bool cMark::Parse(const char *s) | ||||
| { | ||||
|   free(comment); | ||||
|   comment = NULL; | ||||
|   position = HMSFToIndex(s); | ||||
|   framesPerSecond = MarkFramesPerSecond; | ||||
|   position = HMSFToIndex(s, framesPerSecond); | ||||
|   const char *p = strchr(s, ' '); | ||||
|   if (p) { | ||||
|      p = skipspace(p); | ||||
| @@ -1098,9 +1230,12 @@ bool cMark::Save(FILE *f) | ||||
|  | ||||
| // --- cMarks ---------------------------------------------------------------- | ||||
|  | ||||
| bool cMarks::Load(const char *RecordingFileName) | ||||
| bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool IsPesRecording) | ||||
| { | ||||
|   if (cConfig<cMark>::Load(AddDirectory(RecordingFileName, MARKSFILESUFFIX))) { | ||||
|   cMutexLock MutexLock(&MutexMarkFramesPerSecond); | ||||
|   framesPerSecond = FramesPerSecond; | ||||
|   MarkFramesPerSecond = framesPerSecond; | ||||
|   if (cConfig<cMark>::Load(AddDirectory(RecordingFileName, IsPesRecording ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX))) { | ||||
|      Sort(); | ||||
|      return true; | ||||
|      } | ||||
| @@ -1123,7 +1258,7 @@ cMark *cMarks::Add(int Position) | ||||
| { | ||||
|   cMark *m = Get(Position); | ||||
|   if (!m) { | ||||
|      cConfig<cMark>::Add(m = new cMark(Position)); | ||||
|      cConfig<cMark>::Add(m = new cMark(Position, NULL, framesPerSecond)); | ||||
|      Sort(); | ||||
|      } | ||||
|   return m; | ||||
| @@ -1169,12 +1304,9 @@ void cRecordingUserCommand::InvokeCommand(const char *State, const char *Recordi | ||||
|      } | ||||
| } | ||||
|  | ||||
| // --- XXX+ | ||||
|  | ||||
| //XXX+ somewhere else??? | ||||
| // --- cIndexFile ------------------------------------------------------------ | ||||
|  | ||||
| #define INDEXFILESUFFIX     "/index.vdr" | ||||
| #define INDEXFILESUFFIX     "/index" | ||||
|  | ||||
| // The number of frames to stay off the end in case of time shift: | ||||
| #define INDEXSAFETYLIMIT 150 // frames | ||||
| @@ -1185,33 +1317,56 @@ void cRecordingUserCommand::InvokeCommand(const char *State, const char *Recordi | ||||
| // The minimum age of an index file for considering it no longer to be written: | ||||
| #define MININDEXAGE    3600 // seconds | ||||
|  | ||||
| cIndexFile::cIndexFile(const char *FileName, bool Record) | ||||
| :resumeFile(FileName) | ||||
| struct tIndexPes { | ||||
|   uint32_t offset; | ||||
|   uchar type; | ||||
|   uchar number; | ||||
|   uint16_t reserved; | ||||
|   }; | ||||
|  | ||||
| struct tIndexTs { | ||||
|   uint64_t offset:40; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!) | ||||
|   int reserved:7;     // reserved for future use | ||||
|   int independent:1;  // marks frames that can be displayed by themselves (for trick modes) | ||||
|   uint16_t number:16; // up to 64K files per recording | ||||
|   tIndexTs(off_t Offset, bool Independent, uint16_t Number) | ||||
|   { | ||||
|     offset = Offset; | ||||
|     reserved = 0; | ||||
|     independent = Independent; | ||||
|     number = Number; | ||||
|   } | ||||
|   }; | ||||
|  | ||||
| cIndexFile::cIndexFile(const char *FileName, bool Record, bool IsPesRecording) | ||||
| :resumeFile(FileName, IsPesRecording) | ||||
| { | ||||
|   f = -1; | ||||
|   fileName = NULL; | ||||
|   size = 0; | ||||
|   last = -1; | ||||
|   index = NULL; | ||||
|   isPesRecording = IsPesRecording; | ||||
|   if (FileName) { | ||||
|      fileName = MALLOC(char, strlen(FileName) + strlen(INDEXFILESUFFIX) + 1); | ||||
|      const char *Suffix = isPesRecording ? INDEXFILESUFFIX ".vdr" : INDEXFILESUFFIX; | ||||
|      fileName = MALLOC(char, strlen(FileName) + strlen(Suffix) + 1); | ||||
|      if (fileName) { | ||||
|         strcpy(fileName, FileName); | ||||
|         char *pFileExt = fileName + strlen(fileName); | ||||
|         strcpy(pFileExt, INDEXFILESUFFIX); | ||||
|         strcpy(pFileExt, Suffix); | ||||
|         int delta = 0; | ||||
|         if (access(fileName, R_OK) == 0) { | ||||
|            struct stat buf; | ||||
|            if (stat(fileName, &buf) == 0) { | ||||
|               delta = buf.st_size % sizeof(tIndex); | ||||
|               delta = buf.st_size % sizeof(tIndexTs); | ||||
|               if (delta) { | ||||
|                  delta = sizeof(tIndex) - delta; | ||||
|                  esyslog("ERROR: invalid file size (%ld) in '%s'", buf.st_size, fileName); | ||||
|                  delta = sizeof(tIndexTs) - delta; | ||||
|                  esyslog("ERROR: invalid file size (%lld) in '%s'", buf.st_size, fileName); | ||||
|                  } | ||||
|               last = (buf.st_size + delta) / sizeof(tIndex) - 1; | ||||
|               last = (buf.st_size + delta) / sizeof(tIndexTs) - 1; | ||||
|               if (!Record && last >= 0) { | ||||
|                  size = last + 1; | ||||
|                  index = MALLOC(tIndex, size); | ||||
|                  index = MALLOC(tIndexTs, size); | ||||
|                  if (index) { | ||||
|                     f = open(fileName, O_RDONLY); | ||||
|                     if (f >= 0) { | ||||
| @@ -1223,12 +1378,14 @@ cIndexFile::cIndexFile(const char *FileName, bool Record) | ||||
|                           f = -1; | ||||
|                           } | ||||
|                        // we don't close f here, see CatchUp()! | ||||
|                        else if (isPesRecording) | ||||
|                           ConvertFromPes(index, size); | ||||
|                        } | ||||
|                     else | ||||
|                        LOG_ERROR_STR(fileName); | ||||
|                     } | ||||
|                  else | ||||
|                     esyslog("ERROR: can't allocate %zd bytes for index '%s'", size * sizeof(tIndex), fileName); | ||||
|                     esyslog("ERROR: can't allocate %zd bytes for index '%s'", size * sizeof(tIndexTs), fileName); | ||||
|                  } | ||||
|               } | ||||
|            else | ||||
| @@ -1261,6 +1418,18 @@ cIndexFile::~cIndexFile() | ||||
|   free(index); | ||||
| } | ||||
|  | ||||
| void cIndexFile::ConvertFromPes(tIndexTs *IndexTs, int Count) | ||||
| { | ||||
|   tIndexPes IndexPes; | ||||
|   while (Count-- > 0) { | ||||
|         memcpy(&IndexPes, IndexTs, sizeof(IndexPes)); | ||||
|         IndexTs->offset = IndexPes.offset; | ||||
|         IndexTs->independent = IndexPes.type == 1; // I_FRAME | ||||
|         IndexTs->number = IndexPes.number; | ||||
|         IndexTs++; | ||||
|         } | ||||
| } | ||||
|  | ||||
| bool cIndexFile::CatchUp(int Index) | ||||
| { | ||||
|   // returns true unless something really goes wrong, so that 'index' becomes NULL | ||||
| @@ -1275,17 +1444,17 @@ bool cIndexFile::CatchUp(int Index) | ||||
|                f = -1; | ||||
|                break; | ||||
|                } | ||||
|             int newLast = buf.st_size / sizeof(tIndex) - 1; | ||||
|             int newLast = buf.st_size / sizeof(tIndexTs) - 1; | ||||
|             if (newLast > last) { | ||||
|                if (size <= newLast) { | ||||
|                   size *= 2; | ||||
|                   if (size <= newLast) | ||||
|                      size = newLast + 1; | ||||
|                   } | ||||
|                index = (tIndex *)realloc(index, size * sizeof(tIndex)); | ||||
|                index = (tIndexTs *)realloc(index, size * sizeof(tIndexTs)); | ||||
|                if (index) { | ||||
|                   int offset = (last + 1) * sizeof(tIndex); | ||||
|                   int delta = (newLast - last) * sizeof(tIndex); | ||||
|                   int offset = (last + 1) * sizeof(tIndexTs); | ||||
|                   int delta = (newLast - last) * sizeof(tIndexTs); | ||||
|                   if (lseek(f, offset, SEEK_SET) == offset) { | ||||
|                      if (safe_read(f, &index[last + 1], delta) != delta) { | ||||
|                         esyslog("ERROR: can't read from index"); | ||||
| @@ -1295,6 +1464,8 @@ bool cIndexFile::CatchUp(int Index) | ||||
|                         f = -1; | ||||
|                         break; | ||||
|                         } | ||||
|                      if (isPesRecording) | ||||
|                         ConvertFromPes(&index[last + 1], newLast - last); | ||||
|                      last = newLast; | ||||
|                      } | ||||
|                   else | ||||
| @@ -1314,10 +1485,10 @@ bool cIndexFile::CatchUp(int Index) | ||||
|   return index != NULL; | ||||
| } | ||||
|  | ||||
| bool cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset) | ||||
| bool cIndexFile::Write(bool Independent, uint16_t FileNumber, off_t FileOffset) | ||||
| { | ||||
|   if (f >= 0) { | ||||
|      tIndex i = { FileOffset, PictureType, FileNumber, 0 }; | ||||
|      tIndexTs i(FileOffset, Independent, FileNumber); | ||||
|      if (safe_write(f, &i, sizeof(i)) < 0) { | ||||
|         LOG_ERROR_STR(fileName); | ||||
|         close(f); | ||||
| @@ -1329,14 +1500,14 @@ bool cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset) | ||||
|   return f >= 0; | ||||
| } | ||||
|  | ||||
| bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType, int *Length) | ||||
| bool cIndexFile::Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent, int *Length) | ||||
| { | ||||
|   if (CatchUp(Index)) { | ||||
|      if (Index >= 0 && Index < last) { | ||||
|         *FileNumber = index[Index].number; | ||||
|         *FileOffset = index[Index].offset; | ||||
|         if (PictureType) | ||||
|            *PictureType = index[Index].type; | ||||
|         if (Independent) | ||||
|            *Independent = index[Index].independent; | ||||
|         if (Length) { | ||||
|            int fn = index[Index + 1].number; | ||||
|            int fo = index[Index + 1].offset; | ||||
| @@ -1351,24 +1522,24 @@ bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *Pictu | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length, bool StayOffEnd) | ||||
| int cIndexFile::GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber, off_t *FileOffset, int *Length, bool StayOffEnd) | ||||
| { | ||||
|   if (CatchUp()) { | ||||
|      int d = Forward ? 1 : -1; | ||||
|      for (;;) { | ||||
|          Index += d; | ||||
|          if (Index >= 0 && Index < last - ((Forward && StayOffEnd) ? INDEXSAFETYLIMIT : 0)) { | ||||
|             if (index[Index].type == I_FRAME) { | ||||
|                if (FileNumber) | ||||
|                   *FileNumber = index[Index].number; | ||||
|                else | ||||
|                   FileNumber = &index[Index].number; | ||||
|                if (FileOffset) | ||||
|                   *FileOffset = index[Index].offset; | ||||
|                else | ||||
|                   FileOffset = &index[Index].offset; | ||||
|             if (index[Index].independent) { | ||||
|                uint16_t fn; | ||||
|                if (!FileNumber) | ||||
|                   FileNumber = &fn; | ||||
|                off_t fo; | ||||
|                if (!FileOffset) | ||||
|                   FileOffset = &fo; | ||||
|                *FileNumber = index[Index].number; | ||||
|                *FileOffset = index[Index].offset; | ||||
|                if (Length) { | ||||
|                   // all recordings end with a non-I_FRAME, so the following should be safe: | ||||
|                   // all recordings end with a non-independent frame, so the following should be safe: | ||||
|                   int fn = index[Index + 1].number; | ||||
|                   int fo = index[Index + 1].offset; | ||||
|                   if (fn == *FileNumber) | ||||
| @@ -1388,13 +1559,13 @@ int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *F | ||||
|   return -1; | ||||
| } | ||||
|  | ||||
| int cIndexFile::Get(uchar FileNumber, int FileOffset) | ||||
| int cIndexFile::Get(uint16_t FileNumber, off_t FileOffset) | ||||
| { | ||||
|   if (CatchUp()) { | ||||
|      //TODO implement binary search! | ||||
|      int i; | ||||
|      for (i = 0; i < last; i++) { | ||||
|          if (index[i].number > FileNumber || (index[i].number == FileNumber) && index[i].offset >= FileOffset) | ||||
|          if (index[i].number > FileNumber || (index[i].number == FileNumber) && off_t(index[i].offset) >= FileOffset) | ||||
|             break; | ||||
|          } | ||||
|      return i; | ||||
| @@ -1409,16 +1580,19 @@ bool cIndexFile::IsStillRecording() | ||||
|  | ||||
| // --- cFileName ------------------------------------------------------------- | ||||
|  | ||||
| #define MAXFILESPERRECORDING 255 | ||||
| #define RECORDFILESUFFIX    "/%03d.vdr" | ||||
| #define MAXFILESPERRECORDINGPES 255 | ||||
| #define RECORDFILESUFFIXPES     "/%03d.vdr" | ||||
| #define MAXFILESPERRECORDINGTS  65535 | ||||
| #define RECORDFILESUFFIXTS      "/%05d.ts" | ||||
| #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety... | ||||
|  | ||||
| cFileName::cFileName(const char *FileName, bool Record, bool Blocking) | ||||
| cFileName::cFileName(const char *FileName, bool Record, bool Blocking, bool IsPesRecording) | ||||
| { | ||||
|   file = NULL; | ||||
|   fileNumber = 0; | ||||
|   record = Record; | ||||
|   blocking = Blocking; | ||||
|   isPesRecording = IsPesRecording; | ||||
|   // Prepare the file name: | ||||
|   fileName = MALLOC(char, strlen(FileName) + RECORDFILESUFFIXLEN); | ||||
|   if (!fileName) { | ||||
| @@ -1442,14 +1616,14 @@ cUnbufferedFile *cFileName::Open(void) | ||||
|      int BlockingFlag = blocking ? 0 : O_NONBLOCK; | ||||
|      if (record) { | ||||
|         dsyslog("recording to '%s'", fileName); | ||||
|         file = OpenVideoFile(fileName, O_RDWR | O_CREAT | BlockingFlag); | ||||
|         file = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_LARGEFILE | BlockingFlag); | ||||
|         if (!file) | ||||
|            LOG_ERROR_STR(fileName); | ||||
|         } | ||||
|      else { | ||||
|         if (access(fileName, R_OK) == 0) { | ||||
|            dsyslog("playing '%s'", fileName); | ||||
|            file = cUnbufferedFile::Create(fileName, O_RDONLY | BlockingFlag); | ||||
|            file = cUnbufferedFile::Create(fileName, O_RDONLY | O_LARGEFILE | BlockingFlag); | ||||
|            if (!file) | ||||
|               LOG_ERROR_STR(fileName); | ||||
|            } | ||||
| @@ -1469,13 +1643,14 @@ void cFileName::Close(void) | ||||
|      } | ||||
| } | ||||
|  | ||||
| cUnbufferedFile *cFileName::SetOffset(int Number, int Offset) | ||||
| cUnbufferedFile *cFileName::SetOffset(int Number, off_t Offset) | ||||
| { | ||||
|   if (fileNumber != Number) | ||||
|      Close(); | ||||
|   if (0 < Number && Number <= MAXFILESPERRECORDING) { | ||||
|   int MaxFilesPerRecording = isPesRecording ? MAXFILESPERRECORDINGPES : MAXFILESPERRECORDINGTS; | ||||
|   if (0 < Number && Number <= MaxFilesPerRecording) { | ||||
|      fileNumber = Number; | ||||
|      sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber); | ||||
|      sprintf(pFileNumber, isPesRecording ? RECORDFILESUFFIXPES : RECORDFILESUFFIXTS, fileNumber); | ||||
|      if (record) { | ||||
|         if (access(fileName, F_OK) == 0) { | ||||
|            // files exists, check if it has non-zero size | ||||
| @@ -1506,7 +1681,7 @@ cUnbufferedFile *cFileName::SetOffset(int Number, int Offset) | ||||
|         } | ||||
|      return file; | ||||
|      } | ||||
|   esyslog("ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING); | ||||
|   esyslog("ERROR: max number of files (%d) exceeded", MaxFilesPerRecording); | ||||
|   return NULL; | ||||
| } | ||||
|  | ||||
| @@ -1517,11 +1692,12 @@ cUnbufferedFile *cFileName::NextFile(void) | ||||
|  | ||||
| // --- Index stuff ----------------------------------------------------------- | ||||
|  | ||||
| cString IndexToHMSF(int Index, bool WithFrame) | ||||
| cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond) | ||||
| { | ||||
|   char buffer[16]; | ||||
|   int f = (Index % FRAMESPERSEC) + 1; | ||||
|   int s = (Index / FRAMESPERSEC); | ||||
|   double Seconds; | ||||
|   int f = modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond + 1; | ||||
|   int s = int(Seconds); | ||||
|   int m = s / 60 % 60; | ||||
|   int h = s / 3600; | ||||
|   s %= 60; | ||||
| @@ -1529,17 +1705,20 @@ cString IndexToHMSF(int Index, bool WithFrame) | ||||
|   return buffer; | ||||
| } | ||||
|  | ||||
| int HMSFToIndex(const char *HMSF) | ||||
| int HMSFToIndex(const char *HMSF, double FramesPerSecond) | ||||
| { | ||||
|   int h, m, s, f = 0; | ||||
|   if (3 <= sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f)) | ||||
|      return (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1; | ||||
|   int h, m, s, f = 1; | ||||
|   int n = sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f); | ||||
|   if (n == 1) | ||||
|      return h - 1; // plain frame number | ||||
|   if (n >= 3) | ||||
|      return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f - 1; | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int SecondsToFrames(int Seconds) | ||||
| int SecondsToFrames(int Seconds, double FramesPerSecond) | ||||
| { | ||||
|   return Seconds * FRAMESPERSEC; | ||||
|   return round(Seconds * FramesPerSecond); | ||||
| } | ||||
|  | ||||
| // --- ReadFrame ------------------------------------------------------------- | ||||
|   | ||||
							
								
								
									
										61
									
								
								recording.h
									
									
									
									
									
								
							
							
						
						
									
										61
									
								
								recording.h
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ | ||||
|  * See the main source file 'vdr.c' for copyright information and | ||||
|  * how to reach the author. | ||||
|  * | ||||
|  * $Id: recording.h 1.59 2007/10/14 10:11:34 kls Exp $ | ||||
|  * $Id: recording.h 2.1 2009/01/06 10:49:59 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #ifndef __RECORDING_H | ||||
| @@ -30,8 +30,9 @@ void AssertFreeDiskSpace(int Priority = 0, bool Force = false); | ||||
| class cResumeFile { | ||||
| private: | ||||
|   char *fileName; | ||||
|   bool isPesRecording; | ||||
| public: | ||||
|   cResumeFile(const char *FileName); | ||||
|   cResumeFile(const char *FileName, bool IsPesRecording); | ||||
|   ~cResumeFile(); | ||||
|   int Read(void); | ||||
|   bool Save(int Index); | ||||
| @@ -46,10 +47,15 @@ private: | ||||
|   const cEvent *event; | ||||
|   cEvent *ownEvent; | ||||
|   char *aux; | ||||
|   double framesPerSecond; | ||||
|   int priority; | ||||
|   int lifetime; | ||||
|   char *fileName; | ||||
|   cRecordingInfo(const cChannel *Channel = NULL, const cEvent *Event = NULL); | ||||
|   void SetData(const char *Title, const char *ShortText, const char *Description); | ||||
|   void SetAux(const char *Aux); | ||||
| public: | ||||
|   cRecordingInfo(const char *FileName); | ||||
|   ~cRecordingInfo(); | ||||
|   tChannelID ChannelID(void) const { return channelID; } | ||||
|   const char *ChannelName(void) const { return channelName; } | ||||
| @@ -58,8 +64,12 @@ public: | ||||
|   const char *Description(void) const { return event->Description(); } | ||||
|   const cComponents *Components(void) const { return event->Components(); } | ||||
|   const char *Aux(void) const { return aux; } | ||||
|   double FramesPerSecond(void) const { return framesPerSecond; } | ||||
|   void SetFramesPerSecond(double FramesPerSecond); | ||||
|   bool Read(FILE *f); | ||||
|   bool Write(FILE *f, const char *Prefix = "") const; | ||||
|   bool Read(void); | ||||
|   bool Write(void) const; | ||||
|   }; | ||||
|  | ||||
| class cRecording : public cListObject { | ||||
| @@ -71,6 +81,10 @@ private: | ||||
|   mutable char *fileName; | ||||
|   mutable char *name; | ||||
|   mutable int fileSizeMB; | ||||
|   int channel; | ||||
|   int resumeId; | ||||
|   bool isPesRecording; | ||||
|   double framesPerSecond; | ||||
|   cRecordingInfo *info; | ||||
|   cRecording(const cRecording&); // can't copy cRecording | ||||
|   cRecording &operator=(const cRecording &); // can't assign cRecording | ||||
| @@ -93,8 +107,10 @@ public: | ||||
|   const char *PrefixFileName(char Prefix); | ||||
|   int HierarchyLevels(void) const; | ||||
|   void ResetResume(void) const; | ||||
|   double FramesPerSecond(void) { return framesPerSecond; } | ||||
|   bool IsNew(void) const { return GetResume() <= 0; } | ||||
|   bool IsEdited(void) const; | ||||
|   bool IsPesRecording(void) const { return isPesRecording; } | ||||
|   bool WriteInfo(void); | ||||
|   bool Delete(void); | ||||
|        // Changes the file name so that it will no longer be visible in the "Recordings" menu | ||||
| @@ -149,11 +165,15 @@ public: | ||||
| extern cRecordings Recordings; | ||||
| extern cRecordings DeletedRecordings; | ||||
|  | ||||
| #define DEFAULTFRAMESPERSECOND 25.0 | ||||
|  | ||||
| class cMark : public cListObject { | ||||
| private: | ||||
|   double framesPerSecond; | ||||
| public: | ||||
|   int position; | ||||
|   char *comment; | ||||
|   cMark(int Position = 0, const char *Comment = NULL); | ||||
|   cMark(int Position = 0, const char *Comment = NULL, double FramesPerSecond = DEFAULTFRAMESPERSECOND); | ||||
|   virtual ~cMark(); | ||||
|   cString ToText(void); | ||||
|   bool Parse(const char *s); | ||||
| @@ -161,8 +181,10 @@ public: | ||||
|   }; | ||||
|  | ||||
| class cMarks : public cConfig<cMark> { | ||||
| private: | ||||
|   double framesPerSecond; | ||||
| public: | ||||
|   bool Load(const char *RecordingFileName); | ||||
|   bool Load(const char *RecordingFileName, double FramesPerSecond = DEFAULTFRAMESPERSECOND, bool IsPesRecording = false); | ||||
|   void Sort(void); | ||||
|   cMark *Add(int Position); | ||||
|   cMark *Get(int Position); | ||||
| @@ -182,9 +204,6 @@ public: | ||||
|   static void InvokeCommand(const char *State, const char *RecordingFileName); | ||||
|   }; | ||||
|  | ||||
| //XXX+ | ||||
| #define FRAMESPERSEC 25 | ||||
|  | ||||
| // The maximum size of a single frame (up to HDTV 1920x1080): | ||||
| #define MAXFRAMESIZE  KILOBYTE(512) | ||||
|  | ||||
| @@ -197,24 +216,27 @@ public: | ||||
| #define MAXVIDEOFILESIZE 2000 // MB | ||||
| #define MINVIDEOFILESIZE  100 // MB | ||||
|  | ||||
| struct tIndexTs; | ||||
|  | ||||
| class cIndexFile { | ||||
| private: | ||||
|   struct tIndex { int offset; uchar type; uchar number; short reserved; }; | ||||
|   int f; | ||||
|   char *fileName; | ||||
|   int size, last; | ||||
|   tIndex *index; | ||||
|   tIndexTs *index; | ||||
|   bool isPesRecording; | ||||
|   cResumeFile resumeFile; | ||||
|   cMutex mutex; | ||||
|   void ConvertFromPes(tIndexTs *IndexTs, int Count); | ||||
|   bool CatchUp(int Index = -1); | ||||
| public: | ||||
|   cIndexFile(const char *FileName, bool Record); | ||||
|   cIndexFile(const char *FileName, bool Record, bool IsPesRecording = false); | ||||
|   ~cIndexFile(); | ||||
|   bool Ok(void) { return index != NULL; } | ||||
|   bool Write(uchar PictureType, uchar FileNumber, int FileOffset); | ||||
|   bool Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType = NULL, int *Length = NULL); | ||||
|   int GetNextIFrame(int Index, bool Forward, uchar *FileNumber = NULL, int *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false); | ||||
|   int Get(uchar FileNumber, int FileOffset); | ||||
|   bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset); | ||||
|   bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent = NULL, int *Length = NULL); | ||||
|   int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber = NULL, off_t *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false); | ||||
|   int Get(uint16_t FileNumber, off_t FileOffset); | ||||
|   int Last(void) { CatchUp(); return last; } | ||||
|   int GetResume(void) { return resumeFile.Read(); } | ||||
|   bool StoreResume(int Index) { return resumeFile.Save(Index); } | ||||
| @@ -228,22 +250,23 @@ private: | ||||
|   char *fileName, *pFileNumber; | ||||
|   bool record; | ||||
|   bool blocking; | ||||
|   bool isPesRecording; | ||||
| public: | ||||
|   cFileName(const char *FileName, bool Record, bool Blocking = false); | ||||
|   cFileName(const char *FileName, bool Record, bool Blocking = false, bool IsPesRecording = false); | ||||
|   ~cFileName(); | ||||
|   const char *Name(void) { return fileName; } | ||||
|   int Number(void) { return fileNumber; } | ||||
|   cUnbufferedFile *Open(void); | ||||
|   void Close(void); | ||||
|   cUnbufferedFile *SetOffset(int Number, int Offset = 0); | ||||
|   cUnbufferedFile *SetOffset(int Number, off_t Offset = 0); | ||||
|   cUnbufferedFile *NextFile(void); | ||||
|   }; | ||||
|  | ||||
| cString IndexToHMSF(int Index, bool WithFrame = false); | ||||
| cString IndexToHMSF(int Index, bool WithFrame = false, double FramesPerSecond = DEFAULTFRAMESPERSECOND); | ||||
|       // Converts the given index to a string, optionally containing the frame number. | ||||
| int HMSFToIndex(const char *HMSF); | ||||
| int HMSFToIndex(const char *HMSF, double FramesPerSecond = DEFAULTFRAMESPERSECOND); | ||||
|       // Converts the given string (format: "hh:mm:ss.ff") to an index. | ||||
| int SecondsToFrames(int Seconds); //XXX+ ->player??? | ||||
| int SecondsToFrames(int Seconds, double FramesPerSecond = DEFAULTFRAMESPERSECOND); | ||||
|       // Returns the number of frames corresponding to the given number of seconds. | ||||
|  | ||||
| int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max); | ||||
|   | ||||
							
								
								
									
										147
									
								
								remux.h
									
									
									
									
									
								
							
							
						
						
									
										147
									
								
								remux.h
									
									
									
									
									
								
							| @@ -1,17 +1,16 @@ | ||||
| /* | ||||
|  * remux.h: A streaming MPEG2 remultiplexer | ||||
|  * remux.h: Tools for detecting frames and handling PAT/PMT | ||||
|  * | ||||
|  * See the main source file 'vdr.c' for copyright information and | ||||
|  * how to reach the author. | ||||
|  * | ||||
|  * $Id: remux.h 2.3 2008/12/13 13:55:07 kls Exp $ | ||||
|  * $Id: remux.h 2.4 2009/01/06 12:40:43 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #ifndef __REMUX_H | ||||
| #define __REMUX_H | ||||
|  | ||||
| #include "channels.h" | ||||
| #include "ringbuffer.h" | ||||
| #include "tools.h" | ||||
|  | ||||
| enum ePesHeader { | ||||
| @@ -23,61 +22,9 @@ enum ePesHeader { | ||||
|  | ||||
| ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader = NULL); | ||||
|  | ||||
| // Picture types: | ||||
| #define NO_PICTURE 0 | ||||
| #define I_FRAME    1 | ||||
| #define P_FRAME    2 | ||||
| #define B_FRAME    3 | ||||
|  | ||||
| #define MAXTRACKS 64 | ||||
|  | ||||
| class cTS2PES; | ||||
|  | ||||
| class cRemux { | ||||
| private: | ||||
|   bool exitOnFailure; | ||||
|   bool noVideo; | ||||
|   int numUPTerrors; | ||||
|   bool synced; | ||||
|   int skipped; | ||||
|   cTS2PES *ts2pes[MAXTRACKS]; | ||||
|   int numTracks; | ||||
|   cRingBufferLinear *resultBuffer; | ||||
|   int resultSkipped; | ||||
|   int GetPid(const uchar *Data); | ||||
| public: | ||||
|   cRemux(int VPid, const int *APids, const int *DPids, const int *SPids, bool ExitOnFailure = false); | ||||
|        ///< Creates a new remuxer for the given PIDs. VPid is the video PID, while | ||||
|        ///< APids, DPids and SPids are pointers to zero terminated lists of audio, | ||||
|        ///< dolby and subtitle PIDs (the pointers may be NULL if there is no such | ||||
|        ///< PID). If ExitOnFailure is true, the remuxer will initiate an "emergency | ||||
|        ///< exit" in case of problems with the data stream. | ||||
|   ~cRemux(); | ||||
|   void SetTimeouts(int PutTimeout, int GetTimeout) { resultBuffer->SetTimeouts(PutTimeout, GetTimeout); } | ||||
|        ///< By default cRemux assumes that Put() and Get() are called from different | ||||
|        ///< threads, and uses a timeout in the Get() function in case there is no | ||||
|        ///< data available. SetTimeouts() can be used to modify these timeouts. | ||||
|        ///< Especially if Put() and Get() are called from the same thread, setting | ||||
|        ///< both timeouts to 0 is recommended. | ||||
|   int Put(const uchar *Data, int Count); | ||||
|        ///< Puts at most Count bytes of Data into the remuxer. | ||||
|        ///< \return Returns the number of bytes actually consumed from Data. | ||||
|   uchar *Get(int &Count, uchar *PictureType = NULL); | ||||
|        ///< Gets all currently available data from the remuxer. | ||||
|        ///< \return Count contains the number of bytes the result points to, and | ||||
|        ///< PictureType (if not NULL) will contain one of NO_PICTURE, I_FRAME, P_FRAME | ||||
|        ///< or B_FRAME. | ||||
|   void Del(int Count); | ||||
|        ///< Deletes Count bytes from the remuxer. Count must be the number returned | ||||
|        ///< from a previous call to Get(). Several calls to Del() with fractions of | ||||
|        ///< a previously returned Count may be made, but the total sum of all Count | ||||
|        ///< values must be exactly what the previous Get() has returned. | ||||
|   void Clear(void); | ||||
|        ///< Clears the remuxer of all data it might still contain, keeping the PID | ||||
|        ///< settings as they are. | ||||
|   static void SetBrokenLink(uchar *Data, int Length); | ||||
|   static int GetPacketLength(const uchar *Data, int Count, int Offset); | ||||
|   static int ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType); | ||||
|   }; | ||||
|  | ||||
| // Some TS handling tools. | ||||
| @@ -85,24 +32,39 @@ public: | ||||
|  | ||||
| #define TS_SYNC_BYTE          0x47 | ||||
| #define TS_SIZE               188 | ||||
| #define TS_ERROR              0x80 | ||||
| #define TS_PAYLOAD_START      0x40 | ||||
| #define TS_TRANSPORT_PRIORITY 0x20 | ||||
| #define TS_PID_MASK_HI        0x1F | ||||
| #define TS_SCRAMBLING_CONTROL 0xC0 | ||||
| #define TS_ADAPT_FIELD_EXISTS 0x20 | ||||
| #define TS_PAYLOAD_EXISTS     0x10 | ||||
| #define TS_CONT_CNT_MASK      0x0F | ||||
| #define TS_PAYLOAD_START      0x40 | ||||
| #define TS_ERROR              0x80 | ||||
| #define TS_PID_MASK_HI        0x1F | ||||
| #define TS_ADAPT_DISCONT      0x80 | ||||
| #define TS_ADAPT_RANDOM_ACC   0x40 // would be perfect for detecting independent frames, but unfortunately not used by all broadcasters | ||||
| #define TS_ADAPT_ELEM_PRIO    0x20 | ||||
| #define TS_ADAPT_PCR          0x10 | ||||
| #define TS_ADAPT_OPCR         0x08 | ||||
| #define TS_ADAPT_SPLICING     0x04 | ||||
| #define TS_ADAPT_TP_PRIVATE   0x02 | ||||
| #define TS_ADAPT_EXTENSION    0x01 | ||||
|  | ||||
| inline int TsHasPayload(const uchar *p) | ||||
| inline bool TsHasPayload(const uchar *p) | ||||
| { | ||||
|   return p[3] & TS_PAYLOAD_EXISTS; | ||||
| } | ||||
|  | ||||
| inline int TsPayloadStart(const uchar *p) | ||||
| inline bool TsHasAdaptationField(const uchar *p) | ||||
| { | ||||
|   return p[3] & TS_ADAPT_FIELD_EXISTS; | ||||
| } | ||||
|  | ||||
| inline bool TsPayloadStart(const uchar *p) | ||||
| { | ||||
|   return p[1] & TS_PAYLOAD_START; | ||||
| } | ||||
|  | ||||
| inline int TsError(const uchar *p) | ||||
| inline bool TsError(const uchar *p) | ||||
| { | ||||
|   return p[1] & TS_ERROR; | ||||
| } | ||||
| @@ -112,6 +74,11 @@ inline int TsPid(const uchar *p) | ||||
|   return (p[1] & TS_PID_MASK_HI) * 256 + p[2]; | ||||
| } | ||||
|  | ||||
| inline bool TsIsScrambled(const uchar *p) | ||||
| { | ||||
|   return p[3] & TS_SCRAMBLING_CONTROL; | ||||
| } | ||||
|  | ||||
| inline int TsPayloadOffset(const uchar *p) | ||||
| { | ||||
|   return (p[3] & TS_ADAPT_FIELD_EXISTS) ? p[4] + 5 : 4; | ||||
| @@ -129,6 +96,11 @@ inline int TsContinuityCounter(const uchar *p) | ||||
|   return p[3] & TS_CONT_CNT_MASK; | ||||
| } | ||||
|  | ||||
| inline int TsGetAdaptationField(const uchar *p) | ||||
| { | ||||
|   return TsHasAdaptationField(p) ? p[5] : 0x00; | ||||
| } | ||||
|  | ||||
| // Some PES handling tools: | ||||
| // The following functions that take a pointer to PES data all assume that | ||||
| // there is enough data so that PesLongEnough() returns true. | ||||
| @@ -153,16 +125,18 @@ inline int PesPayloadOffset(const uchar *p) | ||||
|   return 9 + p[8]; | ||||
| } | ||||
|  | ||||
| inline bool PesHasPts(const uchar *p) | ||||
| { | ||||
|   return (p[7] & 0x80) && p[8] >= 5; | ||||
| } | ||||
|  | ||||
| inline int64_t PesGetPts(const uchar *p) | ||||
| { | ||||
|   if ((p[7] & 0x80) && p[8] >= 5) { | ||||
|      return ((((int64_t)p[ 9]) & 0x0E) << 29) | | ||||
|             (( (int64_t)p[10])         << 22) | | ||||
|             ((((int64_t)p[11]) & 0xFE) << 14) | | ||||
|             (( (int64_t)p[12])         <<  7) | | ||||
|             ((((int64_t)p[13]) & 0xFE) >>  1); | ||||
|      } | ||||
|   return 0; | ||||
|   return ((((int64_t)p[ 9]) & 0x0E) << 29) | | ||||
|          (( (int64_t)p[10])         << 22) | | ||||
|          ((((int64_t)p[11]) & 0xFE) << 14) | | ||||
|          (( (int64_t)p[12])         <<  7) | | ||||
|          ((((int64_t)p[13]) & 0xFE) >>  1); | ||||
| } | ||||
|  | ||||
| // PAT/PMT Generator: | ||||
| @@ -274,4 +248,39 @@ void BlockDump(const char *Name, const u_char *Data, int Length); | ||||
| void TsDump(const char *Name, const u_char *Data, int Length); | ||||
| void PesDump(const char *Name, const u_char *Data, int Length); | ||||
|  | ||||
| // Frame detector: | ||||
|  | ||||
| class cFrameDetector { | ||||
| private: | ||||
|   int pid; | ||||
|   int type; | ||||
|   bool newFrame; | ||||
|   bool independentFrame; | ||||
|   int64_t lastPts; | ||||
|   bool isVideo; | ||||
|   int frameDuration; | ||||
|   int framesPerPayloadUnit; | ||||
|   bool scanning; | ||||
|   uint32_t scanner; | ||||
| public: | ||||
|   cFrameDetector(int Pid, int Type); | ||||
|   int Analyze(const uchar *Data, int Length); | ||||
|       ///< Analyzes the TS packets pointed to by Data. Length is the number of | ||||
|       ///< bytes Data points to, and must be a multiple of 188. | ||||
|       ///< Returns the number of bytes that have been analyzed and may be written | ||||
|       ///< to the recording file. If the return value is 0, the data was not | ||||
|       ///< sufficient for analyzing and Analyze() needs to be called again with | ||||
|       ///< more actual data. | ||||
|   bool NewFrame(void) { return newFrame; } | ||||
|       ///< Returns true if the data given to the last call to Analyze() started a | ||||
|       ///< new frame. | ||||
|   bool IndependentFrame(void) { return independentFrame; } | ||||
|       ///< Returns true if a new frame was detected and this is an independent frame | ||||
|       ///< (i.e. one that can be displayed by itself, without using data from any | ||||
|       ///< other frames). | ||||
|   double FramesPerSecond(void) { return frameDuration ? 90000.0 / frameDuration : 0; } | ||||
|       ///< Returns the number of frames per second, or 0 if this information is not | ||||
|       ///< available. | ||||
|   }; | ||||
|  | ||||
| #endif // __REMUX_H | ||||
|   | ||||
							
								
								
									
										16
									
								
								svdrp.c
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								svdrp.c
									
									
									
									
									
								
							| @@ -10,7 +10,7 @@ | ||||
|  * and interact with the Video Disk Recorder - or write a full featured | ||||
|  * graphical interface that sits on top of an SVDRP connection. | ||||
|  * | ||||
|  * $Id: svdrp.c 2.1 2008/05/02 14:15:38 kls Exp $ | ||||
|  * $Id: svdrp.c 2.2 2009/01/06 14:35:45 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #include "svdrp.h" | ||||
| @@ -701,7 +701,7 @@ void cSVDRP::CmdEDIT(const char *Option) | ||||
|         cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); | ||||
|         if (recording) { | ||||
|            cMarks Marks; | ||||
|            if (Marks.Load(recording->FileName()) && Marks.Count()) { | ||||
|            if (Marks.Load(recording->FileName(), recording->FramesPerSecond(), recording->IsPesRecording()) && Marks.Count()) { | ||||
|               if (!cCutter::Active()) { | ||||
|                  if (cCutter::Start(recording->FileName())) | ||||
|                     Reply(250, "Editing recording \"%s\" [%s]", Option, recording->Title()); | ||||
| @@ -1338,15 +1338,9 @@ void cSVDRP::CmdPLAY(const char *Option) | ||||
|            cControl::Shutdown(); | ||||
|            if (*option) { | ||||
|               int pos = 0; | ||||
|               if (strcasecmp(option, "BEGIN") != 0) { | ||||
|                  int h, m = 0, s = 0, f = 1; | ||||
|                  int x = sscanf(option, "%d:%d:%d.%d", &h, &m, &s, &f); | ||||
|                  if (x == 1) | ||||
|                     pos = h; | ||||
|                  else if (x >= 3) | ||||
|                     pos = (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1; | ||||
|                  } | ||||
|               cResumeFile resume(recording->FileName()); | ||||
|               if (strcasecmp(option, "BEGIN") != 0) | ||||
|                  pos = HMSFToIndex(option, recording->FramesPerSecond()); | ||||
|               cResumeFile resume(recording->FileName(), recording->IsPesRecording()); | ||||
|               if (pos <= 0) | ||||
|                  resume.Delete(); | ||||
|               else | ||||
|   | ||||
							
								
								
									
										6
									
								
								tools.c
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								tools.c
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ | ||||
|  * See the main source file 'vdr.c' for copyright information and | ||||
|  * how to reach the author. | ||||
|  * | ||||
|  * $Id: tools.c 1.145 2008/03/05 17:23:47 kls Exp $ | ||||
|  * $Id: tools.c 2.1 2009/01/05 13:04:10 kls Exp $ | ||||
|  */ | ||||
|  | ||||
| #include "tools.h" | ||||
| @@ -1589,7 +1589,7 @@ ssize_t cUnbufferedFile::Write(const void *Data, size_t Size) | ||||
|               //    last (partial) page might be skipped, writeback will start only after | ||||
|               //    second call; the third call will still include this page and finally | ||||
|               //    drop it from cache. | ||||
|               off_t headdrop = min(begin, WRITE_BUFFER * 2L); | ||||
|               off_t headdrop = min(begin, off_t(WRITE_BUFFER * 2)); | ||||
|               posix_fadvise(fd, begin - headdrop, lastpos - begin + headdrop, POSIX_FADV_DONTNEED); | ||||
|               } | ||||
|            begin = lastpos = curpos; | ||||
| @@ -1608,7 +1608,7 @@ ssize_t cUnbufferedFile::Write(const void *Data, size_t Size) | ||||
|               // kind of write gathering enabled), but the syncs cause (io) load.. | ||||
|               // Uncomment the next line if you think you need them. | ||||
|               //fdatasync(fd); | ||||
|               off_t headdrop = min(curpos - totwritten, totwritten * 2L); | ||||
|               off_t headdrop = min(curpos - totwritten, off_t(totwritten * 2)); | ||||
|               posix_fadvise(fd, curpos - totwritten - headdrop, totwritten + headdrop, POSIX_FADV_DONTNEED); | ||||
|               totwritten = 0; | ||||
|               } | ||||
|   | ||||
							
								
								
									
										54
									
								
								vdr.5
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								vdr.5
									
									
									
									
									
								
							| @@ -8,7 +8,7 @@ | ||||
| .\" License as specified in the file COPYING that comes with the | ||||
| .\" vdr distribution. | ||||
| .\" | ||||
| .\" $Id: vdr.5 2.5 2008/11/22 15:23:10 kls Exp $ | ||||
| .\" $Id: vdr.5 2.6 2009/01/06 12:37:35 kls Exp $ | ||||
| .\" | ||||
| .TH vdr 5 "10 Feb 2008" "1.6" "Video Disk Recorder Files" | ||||
| .SH NAME | ||||
| @@ -334,7 +334,7 @@ of these cannot be determined, \fBTITLE\fR will default to the channel name, and | ||||
| An arbitrary string that can be used by external applications to store any | ||||
| kind of data related to this timer. The string must not contain any newline | ||||
| characters. If this field is not empty, its contents will be written into the | ||||
| \fIinfo.vdr\fR file of the recording with the '@' tag. | ||||
| \fIinfo\fR file of the recording with the '@' tag. | ||||
| .SS SOURCES | ||||
| The file \fIsources.conf\fR defines the codes to be used in the \fBSource\fR field | ||||
| of channels in \fIchannels.conf\fR and assigns descriptive texts to them. | ||||
| @@ -558,41 +558,55 @@ messages file will be actually used. | ||||
| If a theme file doesn't contain a Description, the name of the theme (as | ||||
| given in the theme's file name) will be used. | ||||
| .SS AUDIO/VIDEO DATA | ||||
| The files \fI001.vdr\fR...\fI255.vdr\fR are the actual recorded MPEG data | ||||
| The files \fI00001.ts\fR...\fI65535.ts\fR are the actual recorded data | ||||
| files. In order to keep the size of an individual file below a given limit, | ||||
| a recording is split into several files. The contents of these files is | ||||
| \fBPacketized Elementary Stream\fR (PES) and contains ES packets with ids | ||||
| 0xE0...0xEF for video (only one of these may actually occur in a file), | ||||
| 0xC0...0xDF for audio 1...32 (up to 32 audio tracks may occur). | ||||
| Dolby Digital data is stored in packets with ids 0xBD ("Private Stream 1") | ||||
| and substream ids 0x80...0x87. | ||||
| DVB subtitle data is stored in packets with ids 0xBD ("Private Stream 1") | ||||
| and substream ids 0x20...0x27. | ||||
| a recording may be split into several files. The contents of these files is | ||||
| \fBTransport Stream\fR (TS) and contains data packets that are each 188 byte | ||||
| long and start with 0x47. Data is stored exactly as it is broadcast, with | ||||
| a generated PAT/PMT inserted right before every independent frame. | ||||
| .SS INDEX | ||||
| The file \fIindex.vdr\fR (if present in a recording directory) contains | ||||
| The file \fIindex\fR (if present in a recording directory) contains | ||||
| the (binary) index data into each of the the recording files | ||||
| \fI001.vdr\fR...\fI255.vdr\fR. It is used during replay to determine | ||||
| \fI00001.ts\fR...\fI65535.ts\fR. It is used during replay to determine | ||||
| the current position within the recording, and to implement skipping | ||||
| and fast forward/back functions. | ||||
| See the definition of the \fBcIndexFile\fR class for details about the | ||||
| actual contents of this file. | ||||
| .SS INFO | ||||
| The file \fIinfo.vdr\fR (if present in a recording directory) contains | ||||
| The file \fIinfo\fR (if present in a recording directory) contains | ||||
| a description of the recording, derived from the EPG data at recording time | ||||
| (if such data was available). The \fBAux\fR field of the corresponding | ||||
| timer (if given) is copied into this file, using the '@' tag. | ||||
| This is a plain ASCII file and contains tagged lines like the \fBEPG DATA\fR | ||||
| file (see the description of the \fIepg.data\fR file). Note that the lowercase | ||||
| tags ('c' and 'e') will not appear in an \fIinfo.vdr\fR file. | ||||
| tags ('c' and 'e') will not appear in an \fIinfo\fR file. | ||||
| Lines tagged with '#' are ignored and can be used by external tools to | ||||
| store arbitrary information. | ||||
|  | ||||
| In addition to the tags used in the \fIepg.data\fR file, the following tag | ||||
| characters are defined: | ||||
| .TS | ||||
| tab (|); | ||||
| l l. | ||||
| \fBF\fR|<frame duration> | ||||
| \fBL\fR|<lifetime> | ||||
| \fBP\fR|<priority> | ||||
| \fB@\fR|<auxiliary data> | ||||
| .TE | ||||
| .SS RESUME | ||||
| The file \fIresume.vdr\fR (if present in a recording directory) contains | ||||
| The file \fIresume\fR (if present in a recording directory) contains | ||||
| the position within the recording where the last replay session left off. | ||||
| The data is a four byte (binary) integer value and defines an offset into | ||||
| the file \fIindex.vdr\fR. | ||||
| The file consists of tagged lines that describe the various parameters | ||||
| necessary to pick up replay where it left off. | ||||
|  | ||||
| The following tag characters are defined: | ||||
| .TS | ||||
| tab (@); | ||||
| l l. | ||||
| \fBI\fR@<offset into the file \fIindex\fR> | ||||
| .TE | ||||
| .SS MARKS | ||||
| The file \fImarks.vdr\fR (if present in a recording directory) contains | ||||
| The file \fImarks\fR (if present in a recording directory) contains | ||||
| the editing marks defined for this recording. | ||||
| Each line contains the definition of one mark in the following format: | ||||
|  | ||||
| @@ -641,8 +655,6 @@ All other tags are optional (although every event | ||||
| should at least have a \fBT\fR entry). | ||||
| There may be several \fBX\fR tags, depending on the number of tracks (video, audio etc.) | ||||
| the event provides. | ||||
| The special tag character \fB@\fR is used to mark the \fBauxiliary data\fR from | ||||
| a timer definition in the \fIinfo.vdr\fR file. | ||||
|  | ||||
| .TS | ||||
| tab (@); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user