diff options
-rw-r--r-- | CONTRIBUTORS | 2 | ||||
-rw-r--r-- | HISTORY | 49 | ||||
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | config.h | 10 | ||||
-rw-r--r-- | cutter.c | 31 | ||||
-rw-r--r-- | device.c | 50 | ||||
-rw-r--r-- | device.h | 19 | ||||
-rw-r--r-- | dvbdevice.c | 12 | ||||
-rw-r--r-- | dvbplayer.c | 80 | ||||
-rw-r--r-- | menu.c | 27 | ||||
-rw-r--r-- | player.h | 5 | ||||
-rw-r--r-- | recorder.c | 172 | ||||
-rw-r--r-- | recorder.h | 16 | ||||
-rw-r--r-- | recording.c | 353 | ||||
-rw-r--r-- | recording.h | 61 | ||||
-rw-r--r-- | remux.c | 2204 | ||||
-rw-r--r-- | remux.h | 149 | ||||
-rw-r--r-- | svdrp.c | 16 | ||||
-rw-r--r-- | tools.c | 6 | ||||
-rw-r--r-- | vdr.5 | 54 |
20 files changed, 832 insertions, 2488 deletions
diff --git a/CONTRIBUTORS b/CONTRIBUTORS index ff452acf..28c0438d 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -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 @@ -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,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)\" @@ -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 @@ -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; } @@ -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 @@ -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). diff --git a/dvbdevice.c b/dvbdevice.c index 5be20db2..8c632c3d 100644 --- a/dvbdevice.c +++ b/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) diff --git a/dvbplayer.c b/dvbplayer.c index 64fa559b..9de7e486 100644 --- a/dvbplayer.c +++ b/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; @@ -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(); } } @@ -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); @@ -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 ----------------------------------------------------------- - -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(); - }; +// --- cRecorder ------------------------------------------------------------- -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); } } } @@ -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); diff --git a/recording.c b/recording.c index 23024286..2948343c 100644 --- a/recording.c +++ b/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; + 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); + } + 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); } - close(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 ------------------------------------------------------------- diff --git a/recording.h b/recording.h index 4bdcd1b2..a1d497d7 100644 --- a/recording.h +++ b/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); @@ -1,22 +1,13 @@ /* - * remux.c: 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. * - * The parts of this code that implement cTS2PES have been taken from - * the Linux DVB driver's 'tuxplayer' example and were rewritten to suit - * VDR's needs. - * - * The cRepacker family's code was originally written by Reinhard Nissl <rnissl@gmx.de>, - * and adapted to the VDR coding style by Klaus.Schmidinger@cadsoft.de. - * - * $Id: remux.c 2.3 2008/12/20 10:38:47 kls Exp $ + * $Id: remux.c 2.4 2009/01/06 12:39:34 kls Exp $ */ #include "remux.h" -#include <stdlib.h> -#include "channels.h" #include "device.h" #include "libsi/si.h" #include "libsi/section.h" @@ -24,10 +15,12 @@ #include "shutdown.h" #include "tools.h" -// Set this to 'true' for debug output: +// Set these to 'true' for debug output: static bool DebugPatPmt = false; +static bool DebugFrames = false; #define dbgpatpmt(a...) if (DebugPatPmt) fprintf(stderr, a) +#define dbgframes(a...) if (DebugFrames) fprintf(stderr, a) ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader) { @@ -95,2083 +88,10 @@ ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, return phMPEG1; // MPEG 1 } -// --- cRepacker ------------------------------------------------------------- - -#define MIN_LOG_INTERVAL 10 // min. # of seconds between two consecutive log messages of a cRepacker -#define LOG(a...) (LogAllowed() && (esyslog(a), true)) - -class cRepacker { -protected: - bool initiallySyncing; - int maxPacketSize; - uint8_t subStreamId; - time_t lastLog; - int suppressedLogMessages; - bool LogAllowed(void); - void DroppedData(const char *Reason, int Count) { LOG("%s (dropped %d bytes)", Reason, Count); } -public: - static int Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded); - cRepacker(void); - virtual ~cRepacker() {} - virtual void Reset(void) { initiallySyncing = true; } - virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) = 0; - virtual int BreakAt(const uchar *Data, int Count) = 0; - virtual int QuerySnoopSize(void) { return 0; } - void SetMaxPacketSize(int MaxPacketSize) { maxPacketSize = MaxPacketSize; } - void SetSubStreamId(uint8_t SubStreamId) { subStreamId = SubStreamId; } - }; - -cRepacker::cRepacker(void) -{ - initiallySyncing = true; - maxPacketSize = 6 + 65535; - subStreamId = 0; - suppressedLogMessages = 0;; - lastLog = 0; -} - -bool cRepacker::LogAllowed(void) -{ - bool Allowed = time(NULL) - lastLog >= MIN_LOG_INTERVAL; - lastLog = time(NULL); - if (Allowed) { - if (suppressedLogMessages) { - esyslog("%d cRepacker messages suppressed", suppressedLogMessages); - suppressedLogMessages = 0; - } - } - else - suppressedLogMessages++; - return Allowed; -} - -int cRepacker::Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded) -{ - if (CapacityNeeded >= Count && ResultBuffer->Free() < CapacityNeeded) { - esyslog("ERROR: possible result buffer overflow, dropped %d out of %d byte", CapacityNeeded, CapacityNeeded); - return 0; - } - int n = ResultBuffer->Put(Data, Count); - if (n != Count) - esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Count - n, Count); - return n; -} - -// --- cCommonRepacker ------------------------------------------------------- - -class cCommonRepacker : public cRepacker { -protected: - int skippedBytes; - int packetTodo; - uchar fragmentData[6 + 65535 + 3]; - int fragmentLen; - uchar pesHeader[6 + 3 + 255 + 3]; - int pesHeaderLen; - uchar pesHeaderBackup[6 + 3 + 255]; - int pesHeaderBackupLen; - uint32_t scanner; - uint32_t localScanner; - int localStart; - bool PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); - virtual int QuerySnoopSize() { return 4; } - virtual void Reset(void); - }; - -void cCommonRepacker::Reset(void) -{ - cRepacker::Reset(); - skippedBytes = 0; - packetTodo = 0; - fragmentLen = 0; - pesHeaderLen = 0; - pesHeaderBackupLen = 0; - localStart = -1; -} - -bool cCommonRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) -{ - // enter packet length into PES header ... - if (fragmentLen > 0) { // ... which is contained in the fragment buffer - // determine PES packet payload - int PacketLen = fragmentLen + Count - 6; - fragmentData[ 4 ] = PacketLen >> 8; - fragmentData[ 5 ] = PacketLen & 0xFF; - // just skip packets with no payload - int PesPayloadOffset = 0; - if (AnalyzePesHeader(fragmentData, fragmentLen, PesPayloadOffset) <= phInvalid) - LOG("cCommonRepacker: invalid PES packet encountered in fragment buffer!"); - else if (6 + PacketLen <= PesPayloadOffset) { - fragmentLen = 0; - return true; // skip empty packet - } - // amount of data to put into result buffer: a negative Count value means - // to strip off any partially contained start code. - int Bite = fragmentLen + (Count >= 0 ? 0 : Count); - // put data into result buffer - int n = Put(ResultBuffer, fragmentData, Bite, 6 + PacketLen); - fragmentLen = 0; - if (n != Bite) - return false; - } - else if (pesHeaderLen > 0) { // ... which is contained in the PES header buffer - int PacketLen = pesHeaderLen + Count - 6; - pesHeader[ 4 ] = PacketLen >> 8; - pesHeader[ 5 ] = PacketLen & 0xFF; - // just skip packets with no payload - int PesPayloadOffset = 0; - if (AnalyzePesHeader(pesHeader, pesHeaderLen, PesPayloadOffset) <= phInvalid) - LOG("cCommonRepacker: invalid PES packet encountered in header buffer!"); - else if (6 + PacketLen <= PesPayloadOffset) { - pesHeaderLen = 0; - return true; // skip empty packet - } - // amount of data to put into result buffer: a negative Count value means - // to strip off any partially contained start code. - int Bite = pesHeaderLen + (Count >= 0 ? 0 : Count); - // put data into result buffer - int n = Put(ResultBuffer, pesHeader, Bite, 6 + PacketLen); - pesHeaderLen = 0; - if (n != Bite) - return false; - } - // append further payload - if (Count > 0) { - // amount of data to put into result buffer - int Bite = Count; - // put data into result buffer - int n = Put(ResultBuffer, Data, Bite, Bite); - if (n != Bite) - return false; - } - // we did it ;-) - return true; -} - -// --- cVideoRepacker -------------------------------------------------------- - -class cVideoRepacker : public cCommonRepacker { -private: - enum eState { - syncing, - findPicture, - scanPicture - }; - int state; - void HandleStartCode(const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel); - inline bool ScanDataForStartCodeSlow(const uchar *const Data); - inline bool ScanDataForStartCodeFast(const uchar *&Data, const uchar *Limit); - inline bool ScanDataForStartCode(const uchar *&Data, int &Done, int &Todo); - inline void AdjustCounters(const int Delta, int &Done, int &Todo); - inline bool ScanForEndOfPictureSlow(const uchar *&Data); - inline bool ScanForEndOfPictureFast(const uchar *&Data, const uchar *Limit); - inline bool ScanForEndOfPicture(const uchar *&Data, const uchar *Limit); -public: - cVideoRepacker(void); - virtual void Reset(void); - virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); - virtual int BreakAt(const uchar *Data, int Count); - }; - -cVideoRepacker::cVideoRepacker(void) -{ - Reset(); -} - -void cVideoRepacker::Reset(void) -{ - cCommonRepacker::Reset(); - scanner = 0xFFFFFFFF; - state = syncing; -} - -void cVideoRepacker::HandleStartCode(const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel) -{ - // synchronisation is detected some bytes after frame start. - const int SkippedBytesLimit = 4; - - // which kind of start code have we got? - switch (*Data) { - case 0xB9 ... 0xFF: // system start codes - LOG("cVideoRepacker: found system start code: stream seems to be scrambled or not demultiplexed"); - break; - case 0xB0 ... 0xB1: // reserved start codes - case 0xB6: - LOG("cVideoRepacker: found reserved start code: stream seems to be scrambled"); - break; - case 0xB4: // sequence error code - LOG("cVideoRepacker: found sequence error code: stream seems to be damaged"); - case 0xB2: // user data start code - case 0xB5: // extension start code - break; - case 0xB7: // sequence end code - case 0xB3: // sequence header code - case 0xB8: // group start code - case 0x00: // picture start code - if (state == scanPicture) { - // the above start codes indicate that the current picture is done. So - // push out the packet to start a new packet for the next picuture. If - // the byte count get's negative then the current buffer ends in a - // partitial start code that must be stripped off, as it shall be put - // in the next packet. - PushOutPacket(ResultBuffer, Payload, Data - 3 - Payload); - // go on with syncing to the next picture - state = syncing; - } - if (state == syncing) { - if (initiallySyncing) // omit report for the typical initial case - initiallySyncing = false; - else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes - LOG("cVideoRepacker: skipped %d bytes to sync on next picture", skippedBytes - SkippedBytesLimit); - skippedBytes = 0; - // if there is a PES header available, then use it ... - if (pesHeaderBackupLen > 0) { - // ISO 13818-1 says: - // In the case of video, if a PTS is present in a PES packet header - // it shall refer to the access unit containing the first picture start - // code that commences in this PES packet. A picture start code commences - // in PES packet if the first byte of the picture start code is present - // in the PES packet. - memcpy(pesHeader, pesHeaderBackup, pesHeaderBackupLen); - pesHeaderLen = pesHeaderBackupLen; - pesHeaderBackupLen = 0; - } - else { - // ... otherwise create a continuation PES header - pesHeaderLen = 0; - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = 0x01; - pesHeader[pesHeaderLen++] = StreamID; // video stream ID - pesHeader[pesHeaderLen++] = 0x00; // length still unknown - pesHeader[pesHeaderLen++] = 0x00; // length still unknown - - if (MpegLevel == phMPEG2) { - pesHeader[pesHeaderLen++] = 0x80; - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = 0x00; - } - else - pesHeader[pesHeaderLen++] = 0x0F; - } - // append the first three bytes of the start code - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = 0x01; - // the next packet's payload will begin with the fourth byte of - // the start code (= the actual code) - Payload = Data; - // as there is no length information available, assume the - // maximum we can hold in one PES packet - packetTodo = maxPacketSize - pesHeaderLen; - // go on with finding the picture data - state++; - } - break; - case 0x01 ... 0xAF: // slice start codes - if (state == findPicture) { - // go on with scanning the picture data - state++; - } - break; - } -} - -bool cVideoRepacker::ScanDataForStartCodeSlow(const uchar *const Data) -{ - scanner <<= 8; - bool FoundStartCode = (scanner == 0x00000100); - scanner |= *Data; - return FoundStartCode; -} - -bool cVideoRepacker::ScanDataForStartCodeFast(const uchar *&Data, const uchar *Limit) -{ - Limit--; - - while (Data < Limit && (Data = (const uchar *)memchr(Data, 0x01, Limit - Data))) { - if (Data[-2] || Data[-1]) - Data += 3; - else { - scanner = 0x00000100 | *++Data; - return true; - } - } - - Data = Limit; - uint32_t *Scanner = (uint32_t *)(Data - 3); - scanner = ntohl(*Scanner); - return false; -} - -bool cVideoRepacker::ScanDataForStartCode(const uchar *&Data, int &Done, int &Todo) -{ - const uchar *const DataOrig = Data; - const int MinDataSize = 4; - - if (Todo < MinDataSize || (state != syncing && packetTodo < MinDataSize)) - return ScanDataForStartCodeSlow(Data); - - int Limit = Todo; - if (state != syncing && Limit > packetTodo) - Limit = packetTodo; - - if (ScanDataForStartCodeSlow(Data)) - return true; - - if (ScanDataForStartCodeSlow(++Data)) { - AdjustCounters(1, Done, Todo); - return true; - } - ++Data; - - bool FoundStartCode = ScanDataForStartCodeFast(Data, DataOrig + Limit); - AdjustCounters(Data - DataOrig, Done, Todo); - return FoundStartCode; -} - -void cVideoRepacker::AdjustCounters(const int Delta, int &Done, int &Todo) -{ - Done += Delta; - Todo -= Delta; - - if (state <= syncing) - skippedBytes += Delta; - else - packetTodo -= Delta; -} - -void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) -{ - // synchronisation is detected some bytes after frame start. - const int SkippedBytesLimit = 4; - - // reset local scanner - localStart = -1; - - int pesPayloadOffset = 0; - bool continuationHeader = false; - ePesHeader mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader); - if (mpegLevel <= phInvalid) { - DroppedData("cVideoRepacker: no valid PES packet header found", Count); - return; - } - if (!continuationHeader) { - // backup PES header - pesHeaderBackupLen = pesPayloadOffset; - memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); - } - - // skip PES header - int done = pesPayloadOffset; - int todo = Count - done; - const uchar *data = Data + done; - // remember start of the data - const uchar *payload = data; - - while (todo > 0) { - // collect number of skipped bytes while syncing - if (state <= syncing) - skippedBytes++; - // did we reach a start code? - if (ScanDataForStartCode(data, done, todo)) - HandleStartCode(data, ResultBuffer, payload, Data[3], mpegLevel); - // move on - data++; - done++; - todo--; - // do we have to start a new packet as there is no more space left? - if (state != syncing && --packetTodo <= 0) { - // we connot start a new packet here if the current might end in a start - // code and this start code shall possibly be put in the next packet. So - // overfill the current packet until we can safely detect that we won't - // break a start code into pieces: - // - // A) the last four bytes were a start code. - // B) the current byte introduces a start code. - // C) the last three bytes begin a start code. - // - // Todo : Data : Rule : Result - // -----:-------------------------------:------:------- - // : XX 00 00 00 01 YY|YY YY YY YY : : - // 0 : ^^| : A : push - // -----:-------------------------------:------:------- - // : XX XX 00 00 00 01|YY YY YY YY : : - // 0 : ^^| : B : wait - // -1 : |^^ : A : push - // -----:-------------------------------:------:------- - // : XX XX XX 00 00 00|01 YY YY YY : : - // 0 : ^^| : C : wait - // -1 : |^^ : B : wait - // -2 : | ^^ : A : push - // -----:-------------------------------:------:------- - // : XX XX XX XX 00 00|00 01 YY YY : : - // 0 : ^^| : C : wait - // -1 : |^^ : C : wait - // -2 : | ^^ : B : wait - // -3 : | ^^ : A : push - // -----:-------------------------------:------:------- - // : XX XX XX XX XX 00|00 00 01 YY : : - // 0 : ^^| : C : wait - // -1 : |^^ : C : wait - // -2 : | ^^ : : push - // -----:-------------------------------:------:------- - bool A = ((scanner & 0xFFFFFF00) == 0x00000100); - bool B = ((scanner & 0xFFFFFF) == 0x000001); - bool C = ((scanner & 0xFF) == 0x00) && (packetTodo >= -1); - if (A || (!B && !C)) { - // actually we cannot push out an overfull packet. So we'll have to - // adjust the byte count and payload start as necessary. If the byte - // count get's negative we'll have to append the excess from fragment's - // tail to the next PES header. - int bite = data + packetTodo - payload; - const uchar *excessData = fragmentData + fragmentLen + bite; - // a negative byte count means to drop some bytes from the current - // fragment's tail, to not exceed the maximum packet size. - PushOutPacket(ResultBuffer, payload, bite); - // create a continuation PES header - pesHeaderLen = 0; - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = 0x01; - pesHeader[pesHeaderLen++] = Data[3]; // video stream ID - pesHeader[pesHeaderLen++] = 0x00; // length still unknown - pesHeader[pesHeaderLen++] = 0x00; // length still unknown - - if (mpegLevel == phMPEG2) { - pesHeader[pesHeaderLen++] = 0x80; - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = 0x00; - } - else - pesHeader[pesHeaderLen++] = 0x0F; - - // copy any excess data - while (bite++ < 0) { - // append the excess data here - pesHeader[pesHeaderLen++] = *excessData++; - packetTodo++; - } - // the next packet's payload will begin here - payload = data + packetTodo; - // as there is no length information available, assume the - // maximum we can hold in one PES packet - packetTodo += maxPacketSize - pesHeaderLen; - } - } - } - // the packet is done. Now store any remaining data into fragment buffer - // if we are no longer syncing. - if (state != syncing) { - // append the PES header ... - int bite = pesHeaderLen; - pesHeaderLen = 0; - if (bite > 0) { - memcpy(fragmentData + fragmentLen, pesHeader, bite); - fragmentLen += bite; - } - // append payload. It may contain part of a start code at it's end, - // which will be removed when the next packet gets processed. - bite = data - payload; - if (bite > 0) { - memcpy(fragmentData + fragmentLen, payload, bite); - fragmentLen += bite; - } - } - // report that syncing dropped some bytes - if (skippedBytes > SkippedBytesLimit) { - if (!initiallySyncing) // omit report for the typical initial case - LOG("cVideoRepacker: skipped %d bytes while syncing on next picture", skippedBytes - SkippedBytesLimit); - skippedBytes = SkippedBytesLimit; - } -} - -bool cVideoRepacker::ScanForEndOfPictureSlow(const uchar *&Data) -{ - localScanner <<= 8; - localScanner |= *Data++; - // check start codes which follow picture data - switch (localScanner) { - case 0x00000100: // picture start code - case 0x000001B8: // group start code - case 0x000001B3: // sequence header code - case 0x000001B7: // sequence end code - return true; - } - return false; -} - -bool cVideoRepacker::ScanForEndOfPictureFast(const uchar *&Data, const uchar *Limit) -{ - Limit--; - - while (Data < Limit && (Data = (const uchar *)memchr(Data, 0x01, Limit - Data))) { - if (Data[-2] || Data[-1]) - Data += 3; - else { - localScanner = 0x00000100 | *++Data; - // check start codes which follow picture data - switch (localScanner) { - case 0x00000100: // picture start code - case 0x000001B8: // group start code - case 0x000001B3: // sequence header code - case 0x000001B7: // sequence end code - Data++; - return true; - default: - Data += 3; - } - } - } - - Data = Limit + 1; - uint32_t *LocalScanner = (uint32_t *)(Data - 4); - localScanner = ntohl(*LocalScanner); - return false; -} - -bool cVideoRepacker::ScanForEndOfPicture(const uchar *&Data, const uchar *Limit) -{ - const uchar *const DataOrig = Data; - const int MinDataSize = 4; - bool FoundEndOfPicture; - - if (Limit - Data <= MinDataSize) { - FoundEndOfPicture = false; - while (Data < Limit) { - if (ScanForEndOfPictureSlow(Data)) { - FoundEndOfPicture = true; - break; - } - } - } - else { - FoundEndOfPicture = true; - if (!ScanForEndOfPictureSlow(Data)) { - if (!ScanForEndOfPictureSlow(Data)) { - if (!ScanForEndOfPictureFast(Data, Limit)) - FoundEndOfPicture = false; - } - } - } - - localStart += (Data - DataOrig); - return FoundEndOfPicture; -} - -int cVideoRepacker::BreakAt(const uchar *Data, int Count) -{ - if (initiallySyncing) - return -1; // fill the packet buffer completely until we have synced once - - int PesPayloadOffset = 0; - - if (AnalyzePesHeader(Data, Count, PesPayloadOffset) <= phInvalid) - return -1; // not enough data for test - - // just detect end of picture - if (state == scanPicture) { - // setup local scanner - if (localStart < 0) { - localScanner = scanner; - localStart = 0; - } - // start where we've stopped at the last run - const uchar *data = Data + PesPayloadOffset + localStart; - const uchar *limit = Data + Count; - // scan data - if (ScanForEndOfPicture(data, limit)) - return data - Data; - } - // just fill up packet and append next start code - return PesPayloadOffset + packetTodo + 4; -} - -// --- cAudioRepacker -------------------------------------------------------- - -class cAudioRepacker : public cCommonRepacker { -private: - static int bitRates[2][3][16]; - enum eState { - syncing, - scanFrame - }; - int state; - int frameTodo; - int frameSize; - int cid; - static bool IsValidAudioHeader(uint32_t Header, bool Mpeg2, int *FrameSize = NULL); -public: - cAudioRepacker(int Cid); - virtual void Reset(void); - virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); - virtual int BreakAt(const uchar *Data, int Count); - }; - -int cAudioRepacker::bitRates[2][3][16] = { // all values are specified as kbits/s - { - { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1 }, // MPEG 1, Layer I - { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, // MPEG 1, Layer II - { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 } // MPEG 1, Layer III - }, - { - { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 }, // MPEG 2, Layer I - { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, // MPEG 2, Layer II/III - { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 } // MPEG 2, Layer II/III - } - }; - -cAudioRepacker::cAudioRepacker(int Cid) -{ - cid = Cid; - Reset(); -} - -void cAudioRepacker::Reset(void) -{ - cCommonRepacker::Reset(); - scanner = 0; - state = syncing; - frameTodo = 0; - frameSize = 0; -} - -bool cAudioRepacker::IsValidAudioHeader(uint32_t Header, bool Mpeg2, int *FrameSize) -{ - int syncword = (Header & 0xFFF00000) >> 20; - int id = (Header & 0x00080000) >> 19; - int layer = (Header & 0x00060000) >> 17; -//int protection_bit = (Header & 0x00010000) >> 16; - int bitrate_index = (Header & 0x0000F000) >> 12; - int sampling_frequency = (Header & 0x00000C00) >> 10; - int padding_bit = (Header & 0x00000200) >> 9; -//int private_bit = (Header & 0x00000100) >> 8; -//int mode = (Header & 0x000000C0) >> 6; -//int mode_extension = (Header & 0x00000030) >> 4; -//int copyright = (Header & 0x00000008) >> 3; -//int orignal_copy = (Header & 0x00000004) >> 2; - int emphasis = (Header & 0x00000003); - - if (syncword != 0xFFF) - return false; - - if (id == 0 && !Mpeg2) // reserved in MPEG 1 - return false; - - if (layer == 0) // reserved - return false; - - if (bitrate_index == 0xF) // forbidden - return false; - - if (sampling_frequency == 3) // reserved - return false; - - if (emphasis == 2) // reserved - return false; - - if (FrameSize) { - if (bitrate_index == 0) - *FrameSize = 0; - else { - static int samplingFrequencies[2][4] = { // all values are specified in Hz - { 44100, 48000, 32000, -1 }, // MPEG 1 - { 22050, 24000, 16000, -1 } // MPEG 2 - }; - - static int slots_per_frame[2][3] = { - { 12, 144, 144 }, // MPEG 1, Layer I, II, III - { 12, 144, 72 } // MPEG 2, Layer I, II, III - }; - - int mpegIndex = 1 - id; - int layerIndex = 3 - layer; - - // Layer I (i. e., layerIndex == 0) has a larger slot size - int slotSize = (layerIndex == 0) ? 4 : 1; // bytes - - int br = 1000 * bitRates[mpegIndex][layerIndex][bitrate_index]; // bits/s - int sf = samplingFrequencies[mpegIndex][sampling_frequency]; - - int N = slots_per_frame[mpegIndex][layerIndex] * br / sf; // slots - - *FrameSize = (N + padding_bit) * slotSize; // bytes - } - } - - return true; -} - -void cAudioRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) -{ - // synchronisation is detected some bytes after frame start. - const int SkippedBytesLimit = 4; - - // reset local scanner - localStart = -1; - - int pesPayloadOffset = 0; - bool continuationHeader = false; - ePesHeader mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader); - if (mpegLevel <= phInvalid) { - DroppedData("cAudioRepacker: no valid PES packet header found", Count); - return; - } - if (!continuationHeader) { - // backup PES header - pesHeaderBackupLen = pesPayloadOffset; - memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); - } - - // skip PES header - int done = pesPayloadOffset; - int todo = Count - done; - const uchar *data = Data + done; - // remember start of the data - const uchar *payload = data; - - while (todo > 0) { - // collect number of skipped bytes while syncing - if (state <= syncing) - skippedBytes++; - // did we reach an audio frame header? - scanner <<= 8; - scanner |= *data; - if ((scanner & 0xFFF00000) == 0xFFF00000) { - if (frameTodo <= 0 && (frameSize == 0 || skippedBytes >= 4) && IsValidAudioHeader(scanner, mpegLevel == phMPEG2, &frameSize)) { - if (state == scanFrame) { - // As a new audio frame starts here, the previous one is done. So push - // out the packet to start a new packet for the next audio frame. If - // the byte count gets negative then the current buffer ends in a - // partitial audio frame header that must be stripped off, as it shall - // be put in the next packet. - PushOutPacket(ResultBuffer, payload, data - 3 - payload); - // go on with syncing to the next audio frame - state = syncing; - } - if (state == syncing) { - if (initiallySyncing) // omit report for the typical initial case - initiallySyncing = false; - else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes - LOG("cAudioRepacker(0x%02X): skipped %d bytes to sync on next audio frame", cid, skippedBytes - SkippedBytesLimit); - skippedBytes = 0; - // if there is a PES header available, then use it ... - if (pesHeaderBackupLen > 0) { - // ISO 13818-1 says: - // In the case of audio, if a PTS is present in a PES packet header - // it shall refer to the access unit commencing in the PES packet. An - // audio access unit commences in a PES packet if the first byte of - // the audio access unit is present in the PES packet. - memcpy(pesHeader, pesHeaderBackup, pesHeaderBackupLen); - pesHeaderLen = pesHeaderBackupLen; - pesHeaderBackupLen = 0; - } - else { - // ... otherwise create a continuation PES header - pesHeaderLen = 0; - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = 0x01; - pesHeader[pesHeaderLen++] = Data[3]; // audio stream ID - pesHeader[pesHeaderLen++] = 0x00; // length still unknown - pesHeader[pesHeaderLen++] = 0x00; // length still unknown - - if (mpegLevel == phMPEG2) { - pesHeader[pesHeaderLen++] = 0x80; - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = 0x00; - } - else - pesHeader[pesHeaderLen++] = 0x0F; - } - // append the first three bytes of the audio frame header - pesHeader[pesHeaderLen++] = 0xFF; - pesHeader[pesHeaderLen++] = (scanner >> 16) & 0xFF; - pesHeader[pesHeaderLen++] = (scanner >> 8) & 0xFF; - // the next packet's payload will begin with the fourth byte of - // the audio frame header (= the actual byte) - payload = data; - // maximum we can hold in one PES packet - packetTodo = maxPacketSize - pesHeaderLen; - // expected remainder of audio frame: so far we have read 3 bytes from the frame header - frameTodo = frameSize - 3; - // go on with collecting the frame's data - state++; - } - } - } - data++; - done++; - todo--; - // do we have to start a new packet as the current is done? - if (frameTodo > 0) { - if (--frameTodo == 0) { - // the current audio frame is is done now. So push out the packet to - // start a new packet for the next audio frame. - PushOutPacket(ResultBuffer, payload, data - payload); - // go on with syncing to the next audio frame - state = syncing; - } - } - // do we have to start a new packet as there is no more space left? - if (state != syncing && --packetTodo <= 0) { - // We connot start a new packet here if the current might end in an audio - // frame header and this header shall possibly be put in the next packet. So - // overfill the current packet until we can safely detect that we won't - // break an audio frame header into pieces: - // - // A) the last four bytes were an audio frame header. - // B) the last three bytes introduce an audio frame header. - // C) the last two bytes introduce an audio frame header. - // D) the last byte introduces an audio frame header. - // - // Todo : Data : Rule : Result - // -----:-------------------------------:------:------- - // : XX XX FF Fz zz zz|YY YY YY YY : : - // 0 : ^^| : A : push - // -----:-------------------------------:------:------- - // : XX XX XX FF Fz zz|zz YY YY YY : : - // 0 : ^^| : B : wait - // -1 : |^^ : A : push - // -----:-------------------------------:------:------- - // : XX XX XX XX FF Fz|zz zz YY YY : : - // 0 : ^^| : C : wait - // -1 : |^^ : B : wait - // -2 : | ^^ : A : push - // -----:-------------------------------:------:------- - // : XX XX XX XX XX FF|Fz zz zz YY : : - // 0 : ^^| : D : wait - // -1 : |^^ : C : wait - // -2 : | ^^ : B : wait - // -3 : | ^^ : A : push - // -----:-------------------------------:------:------- - bool A = ((scanner & 0xFFF00000) == 0xFFF00000); - bool B = ((scanner & 0xFFF000) == 0xFFF000); - bool C = ((scanner & 0xFFF0) == 0xFFF0); - bool D = ((scanner & 0xFF) == 0xFF); - if (A || (!B && !C && !D)) { - // Actually we cannot push out an overfull packet. So we'll have to - // adjust the byte count and payload start as necessary. If the byte - // count gets negative we'll have to append the excess from fragment's - // tail to the next PES header. - int bite = data + packetTodo - payload; - const uchar *excessData = fragmentData + fragmentLen + bite; - // A negative byte count means to drop some bytes from the current - // fragment's tail, to not exceed the maximum packet size. - PushOutPacket(ResultBuffer, payload, bite); - // create a continuation PES header - pesHeaderLen = 0; - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = 0x01; - pesHeader[pesHeaderLen++] = Data[3]; // audio stream ID - pesHeader[pesHeaderLen++] = 0x00; // length still unknown - pesHeader[pesHeaderLen++] = 0x00; // length still unknown - - if (mpegLevel == phMPEG2) { - pesHeader[pesHeaderLen++] = 0x80; - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = 0x00; - } - else - pesHeader[pesHeaderLen++] = 0x0F; - - // copy any excess data - while (bite++ < 0) { - // append the excess data here - pesHeader[pesHeaderLen++] = *excessData++; - packetTodo++; - } - // the next packet's payload will begin here - payload = data + packetTodo; - // as there is no length information available, assume the - // maximum we can hold in one PES packet - packetTodo += maxPacketSize - pesHeaderLen; - } - } - } - // The packet is done. Now store any remaining data into fragment buffer - // if we are no longer syncing. - if (state != syncing) { - // append the PES header ... - int bite = pesHeaderLen; - pesHeaderLen = 0; - if (bite > 0) { - memcpy(fragmentData + fragmentLen, pesHeader, bite); - fragmentLen += bite; - } - // append payload. It may contain part of an audio frame header at it's - // end, which will be removed when the next packet gets processed. - bite = data - payload; - if (bite > 0) { - memcpy(fragmentData + fragmentLen, payload, bite); - fragmentLen += bite; - } - } - // report that syncing dropped some bytes - if (skippedBytes > SkippedBytesLimit) { - if (!initiallySyncing) // omit report for the typical initial case - LOG("cAudioRepacker(0x%02X): skipped %d bytes while syncing on next audio frame", cid, skippedBytes - SkippedBytesLimit); - skippedBytes = SkippedBytesLimit; - } -} - -int cAudioRepacker::BreakAt(const uchar *Data, int Count) -{ - if (initiallySyncing) - return -1; // fill the packet buffer completely until we have synced once - - int PesPayloadOffset = 0; - - ePesHeader MpegLevel = AnalyzePesHeader(Data, Count, PesPayloadOffset); - if (MpegLevel <= phInvalid) - return -1; // not enough data for test - - // determine amount of data to fill up packet and to append next audio frame header - int packetRemainder = PesPayloadOffset + packetTodo + 4; - - // just detect end of an audio frame - if (state == scanFrame) { - // when remaining audio frame size is known, then omit scanning - if (frameTodo > 0) { - // determine amount of data to fill up audio frame and to append next audio frame header - int remaining = PesPayloadOffset + frameTodo + 4; - if (remaining < packetRemainder) - return remaining; - return packetRemainder; - } - // setup local scanner - if (localStart < 0) { - localScanner = scanner; - localStart = 0; - } - // start where we've stopped at the last run - const uchar *data = Data + PesPayloadOffset + localStart; - const uchar *limit = Data + Count; - // scan data - while (data < limit) { - localStart++; - localScanner <<= 8; - localScanner |= *data++; - // check whether the next audio frame follows - if (((localScanner & 0xFFF00000) == 0xFFF00000) && IsValidAudioHeader(localScanner, MpegLevel == phMPEG2)) - return data - Data; - } - } - // just fill up packet and append next audio frame header - return packetRemainder; -} - -// --- cDolbyRepacker -------------------------------------------------------- - -class cDolbyRepacker : public cRepacker { -private: - static int frameSizes[]; - uchar fragmentData[6 + 65535]; - int fragmentLen; - int fragmentTodo; - uchar pesHeader[6 + 3 + 255 + 4 + 4]; - int pesHeaderLen; - uchar pesHeaderBackup[6 + 3 + 255]; - int pesHeaderBackupLen; - uchar chk1; - uchar chk2; - int ac3todo; - enum eState { - find_0b, - find_77, - store_chk1, - store_chk2, - get_length, - output_packet - }; - int state; - int skippedBytes; - void ResetPesHeader(bool ContinuationFrame = false); - void AppendSubStreamID(bool ContinuationFrame = false); - bool FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite); - bool StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite); -public: - cDolbyRepacker(void); - virtual void Reset(void); - virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); - virtual int BreakAt(const uchar *Data, int Count); - }; - -// frameSizes are in words, i. e. multiply them by 2 to get bytes -int cDolbyRepacker::frameSizes[] = { - // fs = 48 kHz - 64, 64, 80, 80, 96, 96, 112, 112, 128, 128, 160, 160, 192, 192, 224, 224, - 256, 256, 320, 320, 384, 384, 448, 448, 512, 512, 640, 640, 768, 768, 896, 896, - 1024, 1024, 1152, 1152, 1280, 1280, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // fs = 44.1 kHz - 69, 70, 87, 88, 104, 105, 121, 122, 139, 140, 174, 175, 208, 209, 243, 244, - 278, 279, 348, 349, 417, 418, 487, 488, 557, 558, 696, 697, 835, 836, 975, 976, - 1114, 1115, 1253, 1254, 1393, 1394, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // fs = 32 kHz - 96, 96, 120, 120, 144, 144, 168, 168, 192, 192, 240, 240, 288, 288, 336, 336, - 384, 384, 480, 480, 576, 576, 672, 672, 768, 768, 960, 960, 1152, 1152, 1344, 1344, - 1536, 1536, 1728, 1728, 1920, 1920, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - }; - -cDolbyRepacker::cDolbyRepacker(void) -{ - pesHeader[0] = 0x00; - pesHeader[1] = 0x00; - pesHeader[2] = 0x01; - pesHeader[3] = 0xBD; - pesHeader[4] = 0x00; - pesHeader[5] = 0x00; - Reset(); -} - -void cDolbyRepacker::AppendSubStreamID(bool ContinuationFrame) -{ - if (subStreamId) { - pesHeader[pesHeaderLen++] = subStreamId; - // number of ac3 frames "starting" in this packet (1 by design). - pesHeader[pesHeaderLen++] = 0x01; - // offset to start of first ac3 frame (0 means "no ac3 frame starting" - // so 1 (by design) addresses the first byte after the next two bytes). - pesHeader[pesHeaderLen++] = 0x00; - pesHeader[pesHeaderLen++] = (ContinuationFrame ? 0x00 : 0x01); - } -} - -void cDolbyRepacker::ResetPesHeader(bool ContinuationFrame) -{ - pesHeader[6] = 0x80; - pesHeader[7] = 0x00; - pesHeader[8] = 0x00; - pesHeaderLen = 9; - AppendSubStreamID(ContinuationFrame); -} - -void cDolbyRepacker::Reset(void) -{ - cRepacker::Reset(); - ResetPesHeader(); - state = find_0b; - ac3todo = 0; - chk1 = 0; - chk2 = 0; - fragmentLen = 0; - fragmentTodo = 0; - pesHeaderBackupLen = 0; - skippedBytes = 0; -} - -bool cDolbyRepacker::FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite) -{ - bool success = true; - // enough data available to put PES packet into buffer? - if (fragmentTodo <= Todo) { - // output a previous fragment first - if (fragmentLen > 0) { - Bite = fragmentLen; - int n = Put(ResultBuffer, fragmentData, Bite, fragmentLen + fragmentTodo); - if (Bite != n) - success = false; - fragmentLen = 0; - } - Bite = fragmentTodo; - if (success) { - int n = Put(ResultBuffer, Data, Bite, Bite); - if (Bite != n) - success = false; - } - fragmentTodo = 0; - // ac3 frame completely processed? - if (Bite >= ac3todo) - state = find_0b; // go on with finding start of next packet - } - else { - // copy the fragment into separate buffer for later processing - Bite = Todo; - memcpy(fragmentData + fragmentLen, Data, Bite); - fragmentLen += Bite; - fragmentTodo -= Bite; - } - return success; -} - -bool cDolbyRepacker::StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite) -{ - bool success = true; - int packetLen = pesHeaderLen + ac3todo; - // limit packet to maximum size - if (packetLen > maxPacketSize) - packetLen = maxPacketSize; - pesHeader[4] = (packetLen - 6) >> 8; - pesHeader[5] = (packetLen - 6) & 0xFF; - Bite = pesHeaderLen; - // enough data available to put PES packet into buffer? - if (packetLen - pesHeaderLen <= Todo) { - int n = Put(ResultBuffer, pesHeader, Bite, packetLen); - if (Bite != n) - success = false; - Bite = packetLen - pesHeaderLen; - if (success) { - n = Put(ResultBuffer, Data, Bite, Bite); - if (Bite != n) - success = false; - } - // ac3 frame completely processed? - if (Bite >= ac3todo) - state = find_0b; // go on with finding start of next packet - } - else { - fragmentTodo = packetLen; - // copy the pesheader into separate buffer for later processing - memcpy(fragmentData + fragmentLen, pesHeader, Bite); - fragmentLen += Bite; - fragmentTodo -= Bite; - // copy the fragment into separate buffer for later processing - Bite = Todo; - memcpy(fragmentData + fragmentLen, Data, Bite); - fragmentLen += Bite; - fragmentTodo -= Bite; - } - return success; -} - -void cDolbyRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) -{ - // synchronisation is detected some bytes after frame start. - const int SkippedBytesLimit = 4; - - // check for MPEG 2 - if ((Data[6] & 0xC0) != 0x80) { - DroppedData("cDolbyRepacker: MPEG 2 PES header expected", Count); - return; - } - - // backup PES header - if (Data[6] != 0x80 || Data[7] != 0x00 || Data[8] != 0x00) { - pesHeaderBackupLen = 6 + 3 + Data[8]; - memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); - } - - // skip PES header - int done = 6 + 3 + Data[8]; - int todo = Count - done; - const uchar *data = Data + done; - - // look for 0x0B 0x77 <chk1> <chk2> <frameSize> - while (todo > 0) { - switch (state) { - case find_0b: - if (*data == 0x0B) { - state++; - // copy header information once for later use - if (pesHeaderBackupLen > 0) { - pesHeaderLen = pesHeaderBackupLen; - pesHeaderBackupLen = 0; - memcpy(pesHeader, pesHeaderBackup, pesHeaderLen); - AppendSubStreamID(); - } - } - data++; - done++; - todo--; - skippedBytes++; // collect number of skipped bytes while syncing - continue; - case find_77: - if (*data != 0x77) { - state = find_0b; - continue; - } - data++; - done++; - todo--; - skippedBytes++; // collect number of skipped bytes while syncing - state++; - continue; - case store_chk1: - chk1 = *data++; - done++; - todo--; - skippedBytes++; // collect number of skipped bytes while syncing - state++; - continue; - case store_chk2: - chk2 = *data++; - done++; - todo--; - skippedBytes++; // collect number of skipped bytes while syncing - state++; - continue; - case get_length: - ac3todo = 2 * frameSizes[*data]; - // frameSizeCode was invalid => restart searching - if (ac3todo <= 0) { - // reset PES header instead of using a wrong one - ResetPesHeader(); - if (chk1 == 0x0B) { - if (chk2 == 0x77) { - state = store_chk1; - continue; - } - if (chk2 == 0x0B) { - state = find_77; - continue; - } - state = find_0b; - continue; - } - if (chk2 == 0x0B) { - state = find_77; - continue; - } - state = find_0b; - continue; - } - if (initiallySyncing) // omit report for the typical initial case - initiallySyncing = false; - else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes - LOG("cDolbyRepacker: skipped %d bytes to sync on next AC3 frame", skippedBytes - SkippedBytesLimit); - skippedBytes = 0; - // append read data to header for common output processing - pesHeader[pesHeaderLen++] = 0x0B; - pesHeader[pesHeaderLen++] = 0x77; - pesHeader[pesHeaderLen++] = chk1; - pesHeader[pesHeaderLen++] = chk2; - ac3todo -= 4; - state++; - // fall through to output - case output_packet: { - int bite = 0; - // finish remainder of ac3 frame? - if (fragmentTodo > 0) - FinishRemainder(ResultBuffer, data, todo, bite); - else { - // start a new packet - StartNewPacket(ResultBuffer, data, todo, bite); - // prepare for next (continuation) packet - ResetPesHeader(state == output_packet); - } - data += bite; - done += bite; - todo -= bite; - ac3todo -= bite; - } - } - } - // report that syncing dropped some bytes - if (skippedBytes > SkippedBytesLimit) { - if (!initiallySyncing) // omit report for the typical initial case - LOG("cDolbyRepacker: skipped %d bytes while syncing on next AC3 frame", skippedBytes - 4); - skippedBytes = SkippedBytesLimit; - } -} - -int cDolbyRepacker::BreakAt(const uchar *Data, int Count) -{ - if (initiallySyncing) - return -1; // fill the packet buffer completely until we have synced once - // enough data for test? - if (Count < 6 + 3) - return -1; - // check for MPEG 2 - if ((Data[6] & 0xC0) != 0x80) - return -1; - int headerLen = Data[8] + 6 + 3; - // break after fragment tail? - if (ac3todo > 0) - return headerLen + ac3todo; - // enough data for test? - if (Count < headerLen + 5) - return -1; - const uchar *data = Data + headerLen; - // break after ac3 frame? - if (data[0] == 0x0B && data[1] == 0x77 && frameSizes[data[4]] > 0) - return headerLen + 2 * frameSizes[data[4]]; - return -1; -} - -// --- cTS2PES --------------------------------------------------------------- - -#include <netinet/in.h> - -//XXX TODO: these should really be available in some driver header file! -#define PROG_STREAM_MAP 0xBC -#ifndef PRIVATE_STREAM1 -#define PRIVATE_STREAM1 0xBD -#endif -#define PADDING_STREAM 0xBE -#ifndef PRIVATE_STREAM2 -#define PRIVATE_STREAM2 0xBF -#endif -#define AUDIO_STREAM_S 0xC0 -#define AUDIO_STREAM_E 0xDF #define VIDEO_STREAM_S 0xE0 -#define VIDEO_STREAM_E 0xEF -#define ECM_STREAM 0xF0 -#define EMM_STREAM 0xF1 -#define DSM_CC_STREAM 0xF2 -#define ISO13522_STREAM 0xF3 -#define PROG_STREAM_DIR 0xFF - -//pts_dts flags -#define PTS_ONLY 0x80 - -#define PID_MASK_HI 0x1F -#define CONT_CNT_MASK 0x0F - -// Flags: -#define PAY_LOAD 0x10 -#define ADAPT_FIELD 0x20 -#define PAY_START 0x40 -#define TS_ERROR 0x80 - -#define MAX_PLENGTH 0xFFFF // the maximum PES packet length (theoretically) -#define MMAX_PLENGTH (64*MAX_PLENGTH) // some stations send PES packets that are extremely large, e.g. DVB-T in Finland or HDTV 1920x1080 - -#define IPACKS 2048 - -// Start codes: -#define SC_SEQUENCE 0xB3 // "sequence header code" -#define SC_GROUP 0xB8 // "group start code" -#define SC_PICTURE 0x00 // "picture start code" - -#define MAXNONUSEFULDATA (10*1024*1024) -#define MAXNUMUPTERRORS 10 - -class cTS2PES { -private: - int pid; - int size; - int found; - int count; - uint8_t *buf; - uint8_t cid; - uint8_t rewriteCid; - uint8_t subStreamId; - int plength; - uint8_t plen[2]; - uint8_t flag1; - uint8_t flag2; - uint8_t hlength; - int mpeg; - uint8_t check; - int mpeg1_required; - int mpeg1_stuffing; - bool done; - cRingBufferLinear *resultBuffer; - int tsErrors; - int ccErrors; - int ccCounter; - cRepacker *repacker; - static uint8_t headr[]; - void store(uint8_t *Data, int Count); - void reset_ipack(void); - void send_ipack(void); - void write_ipack(const uint8_t *Data, int Count); - void instant_repack(const uint8_t *Buf, int Count); -public: - cTS2PES(int Pid, cRingBufferLinear *ResultBuffer, int Size, uint8_t RewriteCid = 0x00, uint8_t SubStreamId = 0x00, cRepacker *Repacker = NULL); - ~cTS2PES(); - int Pid(void) { return pid; } - void ts_to_pes(const uint8_t *Buf); // don't need count (=188) - void Clear(void); - }; - -uint8_t cTS2PES::headr[] = { 0x00, 0x00, 0x01 }; - -cTS2PES::cTS2PES(int Pid, cRingBufferLinear *ResultBuffer, int Size, uint8_t RewriteCid, uint8_t SubStreamId, cRepacker *Repacker) -{ - pid = Pid; - resultBuffer = ResultBuffer; - size = Size; - rewriteCid = RewriteCid; - subStreamId = SubStreamId; - repacker = Repacker; - if (repacker) { - repacker->SetMaxPacketSize(size); - repacker->SetSubStreamId(subStreamId); - size += repacker->QuerySnoopSize(); - } - - tsErrors = 0; - ccErrors = 0; - ccCounter = -1; - - if (!(buf = MALLOC(uint8_t, size))) - esyslog("Not enough memory for ts_transform"); - - reset_ipack(); -} - -cTS2PES::~cTS2PES() -{ - if (tsErrors || ccErrors) - dsyslog("cTS2PES got %d TS errors, %d TS continuity errors", tsErrors, ccErrors); - free(buf); - delete repacker; -} - -void cTS2PES::Clear(void) -{ - reset_ipack(); - if (repacker) - repacker->Reset(); -} - -void cTS2PES::store(uint8_t *Data, int Count) -{ - if (repacker) - repacker->Repack(resultBuffer, Data, Count); - else - cRepacker::Put(resultBuffer, Data, Count, Count); -} - -void cTS2PES::reset_ipack(void) -{ - found = 0; - cid = 0; - plength = 0; - flag1 = 0; - flag2 = 0; - hlength = 0; - mpeg = 0; - check = 0; - mpeg1_required = 0; - mpeg1_stuffing = 0; - done = false; - count = 0; -} - -void cTS2PES::send_ipack(void) -{ - if (count <= ((mpeg == 2) ? 9 : 7)) // skip empty packets - return; - buf[3] = rewriteCid ? rewriteCid : cid; - buf[4] = (uint8_t)(((count - 6) & 0xFF00) >> 8); - buf[5] = (uint8_t)((count - 6) & 0x00FF); - store(buf, count); - - switch (mpeg) { - case 2: - buf[6] = 0x80; - buf[7] = 0x00; - buf[8] = 0x00; - count = 9; - if (!repacker && subStreamId) { - buf[9] = subStreamId; - buf[10] = 1; - buf[11] = 0; - buf[12] = 1; - count = 13; - } - break; - case 1: - buf[6] = 0x0F; - count = 7; - break; - } -} - -void cTS2PES::write_ipack(const uint8_t *Data, int Count) -{ - if (count < 6) { - memcpy(buf, headr, 3); - count = 6; - } - - // determine amount of data to process - int bite = Count; - if (count + bite > size) - bite = size - count; - if (repacker) { - int breakAt = repacker->BreakAt(buf, count); - // avoid memcpy of data after break location - if (0 <= breakAt && breakAt < count + bite) { - bite = breakAt - count; - if (bite < 0) // should never happen - bite = 0; - } - } - - memcpy(buf + count, Data, bite); - count += bite; - - if (repacker) { - // determine break location - int breakAt = repacker->BreakAt(buf, count); - if (breakAt > size) // won't fit into packet? - breakAt = -1; - if (breakAt > count) // not enough data? - breakAt = -1; - // push out data before break location - if (breakAt > 0) { - // adjust bite if above memcpy was to large - bite -= count - breakAt; - count = breakAt; - send_ipack(); - // recurse for data after break location - if (Count - bite > 0) - write_ipack(Data + bite, Count - bite); - } - } - - // push out data when buffer is full - if (count >= size) { - send_ipack(); - // recurse for remaining data - if (Count - bite > 0) - write_ipack(Data + bite, Count - bite); - } -} - -void cTS2PES::instant_repack(const uint8_t *Buf, int Count) -{ - int c = 0; - - while (c < Count && (mpeg == 0 || (mpeg == 1 && found < mpeg1_required) || (mpeg == 2 && found < 9)) && (found < 5 || !done)) { - switch (found ) { - case 0: - case 1: - if (Buf[c] == 0x00) - found++; - else - found = 0; - c++; - break; - case 2: - if (Buf[c] == 0x01) - found++; - else if (Buf[c] != 0) - found = 0; - c++; - break; - case 3: - cid = 0; - switch (Buf[c]) { - case PROG_STREAM_MAP: - case PRIVATE_STREAM2: - case PROG_STREAM_DIR: - case ECM_STREAM : - case EMM_STREAM : - case PADDING_STREAM : - case DSM_CC_STREAM : - case ISO13522_STREAM: - done = true; - case PRIVATE_STREAM1: - case VIDEO_STREAM_S ... VIDEO_STREAM_E: - case AUDIO_STREAM_S ... AUDIO_STREAM_E: - found++; - cid = Buf[c++]; - break; - default: - found = 0; - break; - } - break; - case 4: - if (Count - c > 1) { - unsigned short *pl = (unsigned short *)(Buf + c); - plength = ntohs(*pl); - c += 2; - found += 2; - mpeg1_stuffing = 0; - } - else { - plen[0] = Buf[c]; - found++; - return; - } - break; - case 5: { - plen[1] = Buf[c++]; - unsigned short *pl = (unsigned short *)plen; - plength = ntohs(*pl); - found++; - mpeg1_stuffing = 0; - } - break; - case 6: - if (!done) { - flag1 = Buf[c++]; - found++; - if (mpeg1_stuffing == 0) { // first stuffing iteration: determine MPEG level - if ((flag1 & 0xC0) == 0x80) - mpeg = 2; - else { - mpeg = 1; - mpeg1_required = 7; - } - } - if (mpeg == 1) { - if (flag1 == 0xFF) { // MPEG1 stuffing - if (++mpeg1_stuffing > 16) - found = 0; // invalid MPEG1 header - else { // ignore stuffing - found--; - if (plength > 0) - plength--; - } - } - else if ((flag1 & 0xC0) == 0x40) // STD_buffer_scale/size - mpeg1_required += 2; - else if (flag1 != 0x0F && (flag1 & 0xF0) != 0x20 && (flag1 & 0xF0) != 0x30) - found = 0; // invalid MPEG1 header - else { - flag2 = 0; - hlength = 0; - } - } - } - break; - case 7: - if (!done && (mpeg == 2 || mpeg1_required > 7)) { - flag2 = Buf[c++]; - found++; - } - break; - case 8: - if (!done && (mpeg == 2 || mpeg1_required > 7)) { - hlength = Buf[c++]; - found++; - if (mpeg == 1 && hlength != 0x0F && (hlength & 0xF0) != 0x20 && (hlength & 0xF0) != 0x30) - found = 0; // invalid MPEG1 header - } - break; - default: - break; - } - } - - if (!plength) - plength = MMAX_PLENGTH - 6; - - if (done || ((mpeg == 2 && found >= 9) || (mpeg == 1 && found >= mpeg1_required))) { - switch (cid) { - case AUDIO_STREAM_S ... AUDIO_STREAM_E: - case VIDEO_STREAM_S ... VIDEO_STREAM_E: - case PRIVATE_STREAM1: - - if (mpeg == 2 && found == 9 && count < found) { // make sure to not write the data twice by looking at count - write_ipack(&flag1, 1); - write_ipack(&flag2, 1); - write_ipack(&hlength, 1); - } - - if (mpeg == 1 && found == mpeg1_required && count < found) { // make sure to not write the data twice by looking at count - write_ipack(&flag1, 1); - if (mpeg1_required > 7) { - write_ipack(&flag2, 1); - write_ipack(&hlength, 1); - } - } - - if (mpeg == 2 && (flag2 & PTS_ONLY) && found < 14) { - while (c < Count && found < 14) { - write_ipack(Buf + c, 1); - c++; - found++; - } - if (c == Count) - return; - } - - if (!repacker && subStreamId) { - while (c < Count && found < (hlength + 9) && found < plength + 6) { - write_ipack(Buf + c, 1); - c++; - found++; - } - if (found == (hlength + 9)) { - uchar sbuf[] = { 0x01, 0x00, 0x00 }; - write_ipack(&subStreamId, 1); - write_ipack(sbuf, 3); - } - } - - while (c < Count && found < plength + 6) { - int l = Count - c; - if (l + found > plength + 6) - l = plength + 6 - found; - write_ipack(Buf + c, l); - found += l; - c += l; - } - - break; - } - - if (done) { - if (found + Count - c < plength + 6) { - found += Count - c; - c = Count; - } - else { - c += plength + 6 - found; - found = plength + 6; - } - } - - if (plength && found == plength + 6) { - if (plength == MMAX_PLENGTH - 6) - esyslog("ERROR: PES packet length overflow in remuxer (stream corruption)"); - send_ipack(); - reset_ipack(); - if (c < Count) - instant_repack(Buf + c, Count - c); - } - } - return; -} - -void cTS2PES::ts_to_pes(const uint8_t *Buf) // don't need count (=188) -{ - if (!Buf) - return; - - if (Buf[1] & TS_ERROR) - tsErrors++; - - if (!(Buf[3] & (ADAPT_FIELD | PAY_LOAD))) - return; // discard TS packet with adaption_field_control set to '00'. - - if ((Buf[3] & PAY_LOAD) && ((Buf[3] ^ ccCounter) & CONT_CNT_MASK)) { - // This should check duplicates and packets which do not increase the counter. - // But as the errors usually come in bursts this should be enough to - // show you there is something wrong with signal quality. - if (ccCounter != -1 && ((Buf[3] ^ (ccCounter + 1)) & CONT_CNT_MASK)) { - ccErrors++; - // Enable this if you are having problems with signal quality. - // These are the errors I used to get with Nova-T when antenna - // was not positioned correcly (not transport errors). //tvr - //dsyslog("TS continuity error (%d)", ccCounter); - } - ccCounter = Buf[3] & CONT_CNT_MASK; - } - - if (Buf[1] & PAY_START) { - if (found > 6) { - if (plength != MMAX_PLENGTH - 6 && plength != found - 6) - dsyslog("PES packet shortened to %d bytes (expected: %d bytes)", found, plength + 6); - plength = found - 6; - send_ipack(); - reset_ipack(); - } - found = 0; - } - - uint8_t off = 0; - - if (Buf[3] & ADAPT_FIELD) { // adaptation field? - off = Buf[4] + 1; - if (off + 4 > 187) - return; - } - - if (Buf[3] & PAY_LOAD) - instant_repack(Buf + 4 + off, TS_SIZE - 4 - off); -} - -// --- cRingBufferLinearPes -------------------------------------------------- - -class cRingBufferLinearPes : public cRingBufferLinear { -protected: - virtual int DataReady(const uchar *Data, int Count); -public: - cRingBufferLinearPes(int Size, int Margin = 0, bool Statistics = false, const char *Description = NULL) - :cRingBufferLinear(Size, Margin, Statistics, Description) {} - }; - -int cRingBufferLinearPes::DataReady(const uchar *Data, int Count) -{ - int c = cRingBufferLinear::DataReady(Data, Count); - if (!c && Count >= 6) { - if (!Data[0] && !Data[1] && Data[2] == 0x01) { - int Length = 6 + Data[4] * 256 + Data[5]; - if (Length <= Count) - return Length; - } - } - return c; -} // --- cRemux ---------------------------------------------------------------- -#define RESULTBUFFERSIZE KILOBYTE(256) - -cRemux::cRemux(int VPid, const int *APids, const int *DPids, const int *SPids, bool ExitOnFailure) -{ - exitOnFailure = ExitOnFailure; - noVideo = VPid == 0 || VPid == 1 || VPid == 0x1FFF; - numUPTerrors = 0; - synced = false; - skipped = 0; - numTracks = 0; - resultSkipped = 0; - resultBuffer = new cRingBufferLinearPes(RESULTBUFFERSIZE, IPACKS, false, "Result"); - resultBuffer->SetTimeouts(0, 100); - if (VPid) -#define TEST_cVideoRepacker -#ifdef TEST_cVideoRepacker - ts2pes[numTracks++] = new cTS2PES(VPid, resultBuffer, IPACKS, 0xE0, 0x00, new cVideoRepacker); -#else - ts2pes[numTracks++] = new cTS2PES(VPid, resultBuffer, IPACKS, 0xE0); -#endif - if (APids) { - int n = 0; - while (*APids && numTracks < MAXTRACKS && n < MAXAPIDS) { -#define TEST_cAudioRepacker -#ifdef TEST_cAudioRepacker - ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n, 0x00, new cAudioRepacker(0xC0 + n)); - n++; -#else - ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n++); -#endif - } - } - if (DPids) { - int n = 0; - while (*DPids && numTracks < MAXTRACKS && n < MAXDPIDS) - ts2pes[numTracks++] = new cTS2PES(*DPids++, resultBuffer, IPACKS, 0x00, 0x80 + n++, new cDolbyRepacker); - } - if (SPids) { - int n = 0; - while (*SPids && numTracks < MAXTRACKS && n < MAXSPIDS) - ts2pes[numTracks++] = new cTS2PES(*SPids++, resultBuffer, IPACKS, 0x00, 0x20 + n++); - } -} - -cRemux::~cRemux() -{ - for (int t = 0; t < numTracks; t++) - delete ts2pes[t]; - delete resultBuffer; -} - -int cRemux::GetPid(const uchar *Data) -{ - return (((uint16_t)Data[0] & PID_MASK_HI) << 8) | (Data[1] & 0xFF); -} - -int cRemux::GetPacketLength(const uchar *Data, int Count, int Offset) -{ - // Returns the length of the packet starting at Offset, or -1 if Count is - // too small to contain the entire packet. - int Length = (Offset + 5 < Count) ? (Data[Offset + 4] << 8) + Data[Offset + 5] + 6 : -1; - if (Length > 0 && Offset + Length <= Count) - return Length; - return -1; -} - -int cRemux::ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType) -{ - // Scans the video packet starting at Offset and returns its length. - // If the return value is -1 the packet was not completely in the buffer. - int Length = GetPacketLength(Data, Count, Offset); - if (Length > 0) { - int PesPayloadOffset = 0; - if (AnalyzePesHeader(Data + Offset, Length, PesPayloadOffset) >= phMPEG1) { - const uchar *p = Data + Offset + PesPayloadOffset + 2; - const uchar *pLimit = Data + Offset + Length - 3; -#ifdef TEST_cVideoRepacker - // cVideoRepacker ensures that a new PES packet is started for a new sequence, - // group or picture which allows us to easily skip scanning through a huge - // amount of video data. - if (p < pLimit) { - if (p[-2] || p[-1] || p[0] != 0x01) - pLimit = 0; // skip scanning: packet doesn't start with 0x000001 - else { - switch (p[1]) { - case SC_SEQUENCE: - case SC_GROUP: - case SC_PICTURE: - break; - default: // skip scanning: packet doesn't start a new sequence, group or picture - pLimit = 0; - } - } - } -#endif - while (p < pLimit && (p = (const uchar *)memchr(p, 0x01, pLimit - p))) { - if (!p[-2] && !p[-1]) { // found 0x000001 - switch (p[1]) { - case SC_PICTURE: PictureType = (p[3] >> 3) & 0x07; - return Length; - } - p += 4; // continue scanning after 0x01ssxxyy - } - else - p += 3; // continue scanning after 0x01xxyy - } - } - PictureType = NO_PICTURE; - return Length; - } - return -1; -} - -int cRemux::Put(const uchar *Data, int Count) -{ - int used = 0; - - // Make sure we are looking at a TS packet: - - while (Count > TS_SIZE) { - if (Data[0] == TS_SYNC_BYTE && Data[TS_SIZE] == TS_SYNC_BYTE) - break; - Data++; - Count--; - used++; - } - if (used) - esyslog("ERROR: skipped %d byte to sync on TS packet", used); - - // Convert incoming TS data into multiplexed PES: - - for (int i = 0; i < Count; i += TS_SIZE) { - if (Count - i < TS_SIZE) - break; - if (Data[i] != TS_SYNC_BYTE) - break; - if (resultBuffer->Free() < 2 * IPACKS) - break; // A cTS2PES might write one full packet and also a small rest - int pid = GetPid(Data + i + 1); - if (Data[i + 3] & 0x10) { // got payload - for (int t = 0; t < numTracks; t++) { - if (ts2pes[t]->Pid() == pid) { - ts2pes[t]->ts_to_pes(Data + i); - break; - } - } - } - used += TS_SIZE; - } - - // Check if we're getting anywhere here: - if (!synced && skipped >= 0) { - if (skipped > MAXNONUSEFULDATA) { - esyslog("ERROR: no useful data seen within %d byte of video stream", skipped); - skipped = -1; - if (exitOnFailure) - ShutdownHandler.RequestEmergencyExit(); - } - else - skipped += used; - } - - return used; -} - -uchar *cRemux::Get(int &Count, uchar *PictureType) -{ - // Remove any previously skipped data from the result buffer: - - if (resultSkipped > 0) { - resultBuffer->Del(resultSkipped); - resultSkipped = 0; - } - -#if 0 - // Test recording without determining the real frame borders: - if (PictureType) - *PictureType = I_FRAME; - return resultBuffer->Get(Count); -#endif - - // Check for frame borders: - - if (PictureType) - *PictureType = NO_PICTURE; - - Count = 0; - uchar *resultData = NULL; - int resultCount = 0; - uchar *data = resultBuffer->Get(resultCount); - if (data) { - for (int i = 0; i < resultCount - 3; i++) { - if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) { - int l = 0; - uchar StreamType = data[i + 3]; - if (VIDEO_STREAM_S <= StreamType && StreamType <= VIDEO_STREAM_E) { - uchar pt = NO_PICTURE; - l = ScanVideoPacket(data, resultCount, i, pt); - if (l < 0) - return resultData; - if (pt != NO_PICTURE) { - if (pt < I_FRAME || B_FRAME < pt) { - esyslog("ERROR: unknown picture type '%d'", pt); - if (++numUPTerrors > MAXNUMUPTERRORS && exitOnFailure) { - ShutdownHandler.RequestEmergencyExit(); - numUPTerrors = 0; - } - } - else if (!synced) { - if (pt == I_FRAME) { - if (PictureType) - *PictureType = pt; - resultSkipped = i; // will drop everything before this position - SetBrokenLink(data + i, l); - synced = true; - } - } - else if (Count) - return resultData; - else if (PictureType) - *PictureType = pt; - } - } - else { //if (AUDIO_STREAM_S <= StreamType && StreamType <= AUDIO_STREAM_E || StreamType == PRIVATE_STREAM1) { - l = GetPacketLength(data, resultCount, i); - if (l < 0) - return resultData; - if (noVideo) { - if (!synced) { - if (PictureType) - *PictureType = I_FRAME; - resultSkipped = i; // will drop everything before this position - synced = true; - } - else if (Count) - return resultData; - else if (PictureType) - *PictureType = I_FRAME; - } - } - if (synced) { - if (!Count) - resultData = data + i; - Count += l; - } - else - resultSkipped = i + l; - if (l > 0) - i += l - 1; // the loop increments, too - } - } - } - return resultData; -} - -void cRemux::Del(int Count) -{ - resultBuffer->Del(Count); -} - -void cRemux::Clear(void) -{ - for (int t = 0; t < numTracks; t++) - ts2pes[t]->Clear(); - resultBuffer->Clear(); - synced = false; - skipped = 0; - resultSkipped = 0; -} - void cRemux::SetBrokenLink(uchar *Data, int Length) { int PesPayloadOffset = 0; @@ -2682,3 +602,117 @@ void PesDump(const char *Name, const u_char *Data, int Length) { TsDump(Name, Data, Length); } + +// --- cFrameDetector -------------------------------------------------------- + +cFrameDetector::cFrameDetector(int Pid, int Type) +{ + pid = Pid; + type = Type; + newFrame = independentFrame = false; + lastPts = 0; + isVideo = type == 0x02 || type == 0x1B; // MPEG 2 or MPEG 4 + frameDuration = 0; + framesPerPayloadUnit = 0; + scanning = false; + scanner = 0; +} + +int cFrameDetector::Analyze(const uchar *Data, int Length) +{ + newFrame = independentFrame = false; + if (Length >= TS_SIZE) { + if (TsHasPayload(Data) && !TsIsScrambled(Data) && TsPid(Data) == pid) { + if (TsPayloadStart(Data)) { + if (!frameDuration) { + const uchar *Pes = Data + TsPayloadOffset(Data); + if (PesHasPts(Pes)) { + int64_t Pts = PesGetPts(Pes); + if (Pts < lastPts) { // avoid wrapping + lastPts = 0; + framesPerPayloadUnit = 0; + } + if ((!lastPts || !framesPerPayloadUnit) && Pts != lastPts) + lastPts = Pts; + else { + int64_t Delta = Pts - lastPts; + if (isVideo) { + if (Delta % 3600 == 0) + frameDuration = 3600; // PAL, 25 fps + else if (Delta % 3003 == 0) + frameDuration = 3003; // NTSC, 29.97 fps + else { + frameDuration = 3600; // unknown, assuming 25 fps + dsyslog("unknown frame duration, assuming 25 fps (PTS: %lld - %lld = %lld FPPU = %d)\n", Pts, lastPts, Delta, framesPerPayloadUnit); + } + } + else // audio + frameDuration = Delta; // PTS of audio frames is always increasing + dbgframes("PTS: %lld - %lld = %lld -> FD = %d FPS = %5.2f FPPU = %d\n", Pts, lastPts, Delta, frameDuration, 90000.0 / frameDuration, framesPerPayloadUnit); + } + } + } + scanner = 0; + scanning = true; + } + if (scanning) { + int PayloadOffset = TsPayloadOffset(Data); + if (TsPayloadStart(Data)) + PayloadOffset += PesPayloadOffset(Data + PayloadOffset); + for (int i = PayloadOffset; i < TS_SIZE; i++) { + scanner <<= 8; + scanner |= Data[i]; + switch (type) { + case 0x02: // MPEG 2 video + if (scanner == 0x00000100) { // Picture Start Code + if (frameDuration) { + newFrame = true; + independentFrame = ((Data[i + 2] >> 3) & 0x07) == 1; // I-Frame + if (framesPerPayloadUnit == 1) { + scanning = false; + return TS_SIZE; + } + } + else { + framesPerPayloadUnit++; + dbgframes("%d ", (Data[i + 2] >> 3) & 0x07); + } + scanner = 0; + } + break; + case 0x1B: // MPEG 4 video + if (scanner == 0x00000109) { // Access Unit Delimiter + if (frameDuration) { + newFrame = true; + independentFrame = Data[i + 1] == 0x10; + if (framesPerPayloadUnit == 1) { + scanning = false; + return TS_SIZE; + } + } + else { + framesPerPayloadUnit++; + dbgframes("%02X ", Data[i + 1]); + } + scanner = 0; + } + break; + case 0x04: // MPEG audio + case 0x06: // AC3 audio + if (frameDuration) { + newFrame = true; + independentFrame = true; + } + else + framesPerPayloadUnit = 1; + break; + default: esyslog("ERROR: unknown stream type %d (PID %d) in frame detector", type, pid); + pid = 0; // let's just ignore any further data + } + } + } + } + return TS_SIZE; + } + return 0; +} @@ -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 - -inline int TsHasPayload(const uchar *p) +#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 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 @@ -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 @@ -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; } @@ -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 (@); |