diff options
-rw-r--r-- | CONTRIBUTORS | 2 | ||||
-rw-r--r-- | HISTORY | 13 | ||||
-rw-r--r-- | device.h | 9 | ||||
-rw-r--r-- | dvbplayer.c | 235 | ||||
-rw-r--r-- | player.h | 3 | ||||
-rw-r--r-- | remux.c | 17 | ||||
-rw-r--r-- | remux.h | 6 | ||||
-rw-r--r-- | ringbuffer.c | 5 | ||||
-rw-r--r-- | ringbuffer.h | 6 |
9 files changed, 186 insertions, 110 deletions
diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 3a1e8ee0..032b7f5d 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -662,6 +662,8 @@ Oliver Endriss <o.endriss@gmx.de> Transfer Mode for providing a driver patch that allows direct replaying of TS video on full-featured DVB cards + for improving the firmware of FF DVB cards to allow getting the current STC value + even in trick modes Reinhard Walter Buchner <rw.buchner@freenet.de> for adding some satellites to 'sources.conf' @@ -5979,7 +5979,7 @@ Video Disk Recorder Revision History cDevice class reimplements PlayTs() or PlayPes(), it also needs to make sure this new function works as expected. -2009-02-28: Version 1.7.5 +2009-03-13: Version 1.7.5 - Fixed a hangup when replaying a TS recording with subtitles activated (reported by Timo Helkio). @@ -5989,3 +5989,14 @@ Video Disk Recorder Revision History Ahrenberg). - Updated the Italian OSD texts (thanks to Diego Pierotto). - Added cRecordingInfo::GetEvent() (thanks to Marcel Unbehaun). +- Improved synchronizing the progress display, trick modes and subtitle display + to the actual audio/video. This now works independent of any buffer sizes the + output device might use. + + The cBackTrace class has been replaced with cPtsIndex, which keeps track + of the PTS timestamps of recently played frames. + + cDevice::GetSTC() is now required to deliver the STC even in trick modes. + It is sufficient if it returns the PTS of the most recently presented + audio/video frame. + + The full-featured DVB cards need an improved firmware in order to return + proper STC values in trick modes (thanks to Oliver Endriss for enhancing the + av7110 firmware). @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: device.h 2.6 2009/01/25 11:04:39 kls Exp $ + * $Id: device.h 2.7 2009/03/01 11:20:34 kls Exp $ */ #ifndef __DEVICE_H @@ -543,6 +543,13 @@ public: ///< Gets the current System Time Counter, which can be used to ///< synchronize audio and video. If this device is unable to ///< provide the STC, -1 will be returned. + ///< The value returned doesn't need to be an actual "clock" value, + ///< it is sufficient if it holds the PTS (Presentation Time Stamp) of + ///< the most recently presented frame. A proper value must be returned + ///< in normal replay mode as well as in any trick modes (like slow motion, + ///< fast forward/rewind). + ///< Only the lower 32 bit of this value are actually used, since some + ///< devices can't handle the msb correctly. virtual bool IsPlayingVideo(void) const { return isPlayingVideo; } ///< \return Returns true if the currently attached player has delivered ///< any video packets. diff --git a/dvbplayer.c b/dvbplayer.c index 6e004403..c206cac3 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 2.3 2009/01/25 11:11:39 kls Exp $ + * $Id: dvbplayer.c 2.4 2009/03/13 14:42:56 kls Exp $ */ #include "dvbplayer.h" @@ -16,59 +16,69 @@ #include "thread.h" #include "tools.h" -// --- cBackTrace ------------------------------------------------------------ +// --- cPtsIndex ------------------------------------------------------------- -#define AVG_FRAME_SIZE 15000 // an assumption about the average frame size -#define DVB_BUF_SIZE (256 * 1024) // an assumption about the dvb firmware buffer size -#define BACKTRACE_ENTRIES (DVB_BUF_SIZE / AVG_FRAME_SIZE + 20) // how many entries are needed to backtrace buffer contents +#define PTSINDEX_ENTRIES 100 -class cBackTrace { +class cPtsIndex { private: - int index[BACKTRACE_ENTRIES]; - int length[BACKTRACE_ENTRIES]; - int pos, num; + struct tPtsIndex { + uint32_t pts; // no need for 33 bit - some devices don't even supply the msb + int index; + }; + tPtsIndex pi[PTSINDEX_ENTRIES]; + int w, r; + int lastFound; + cMutex mutex; public: - cBackTrace(void); + cPtsIndex(void); void Clear(void); - void Add(int Index, int Length); - int Get(bool Forward); + void Put(uint32_t Pts, int Index); + int FindIndex(uint32_t Pts); }; -cBackTrace::cBackTrace(void) +cPtsIndex::cPtsIndex(void) { + lastFound = 0; Clear(); } -void cBackTrace::Clear(void) +void cPtsIndex::Clear(void) { - pos = num = 0; + cMutexLock MutexLock(&mutex); + w = r = 0; } -void cBackTrace::Add(int Index, int Length) +void cPtsIndex::Put(uint32_t Pts, int Index) { - index[pos] = Index; - length[pos] = Length; - if (++pos >= BACKTRACE_ENTRIES) - pos = 0; - if (num < BACKTRACE_ENTRIES) - num++; + cMutexLock MutexLock(&mutex); + pi[w].pts = Pts; + pi[w].index = Index; + w = (w + 1) % PTSINDEX_ENTRIES; + if (w == r) + r = (r + 1) % PTSINDEX_ENTRIES; } -int cBackTrace::Get(bool Forward) +int cPtsIndex::FindIndex(uint32_t Pts) { - int p = pos; - int n = num; - int l = DVB_BUF_SIZE + (Forward ? 0 : 256 * 1024); //XXX (256 * 1024) == DVB_BUF_SIZE ??? - int i = -1; - - while (n && l > 0) { - if (--p < 0) - p = BACKTRACE_ENTRIES - 1; - i = index[p] - 1; - l -= length[p]; - n--; - } - return i; + cMutexLock MutexLock(&mutex); + if (w == r) + return lastFound; // list is empty, let's not jump way off the last known position + uint32_t Delta = 0xFFFFFFFF; + int Index = -1; + for (int i = w; i != r; ) { + if (--i < 0) + i = PTSINDEX_ENTRIES - 1; + uint32_t d = pi[i].pts < Pts ? Pts - pi[i].pts : pi[i].pts - Pts; + if (d > 0x7FFFFFFF) + d = 0xFFFFFFFF - d; // handle rollover + if (d < Delta) { + Delta = d; + Index = pi[i].index; + } + } + lastFound = Index; + return Index; } // --- cNonBlockingFileReader ------------------------------------------------ @@ -183,8 +193,8 @@ bool cNonBlockingFileReader::WaitForDataMs(int msToWait) #define PLAYERBUFSIZE MEGABYTE(1) -// The number of seconds to back up when resuming an interrupted replay session: -#define RESUMEBACKUP 10 +#define RESUMEBACKUP 10 // number of seconds to back up when resuming an interrupted replay session +#define MAXSTUCKATEND 3 // max. number of seconds to wait in case the device doesn't play the last frame class cDvbPlayer : public cPlayer, cThread { private: @@ -193,7 +203,7 @@ private: static int Speeds[]; cNonBlockingFileReader *nonBlockingFileReader; cRingBufferFrame *ringBuffer; - cBackTrace *backTrace; + cPtsIndex ptsIndex; cFileName *fileName; cIndexFile *index; cUnbufferedFile *replayFile; @@ -204,7 +214,8 @@ private: ePlayModes playMode; ePlayDirs playDir; int trickSpeed; - int readIndex, writeIndex; + int readIndex; + bool readIndependent; cFrame *readFrame; cFrame *playFrame; void TrickSpeed(int Increment); @@ -242,7 +253,6 @@ cDvbPlayer::cDvbPlayer(const char *FileName) { nonBlockingFileReader = NULL; ringBuffer = NULL; - backTrace = NULL; index = NULL; cRecording Recording(FileName); framesPerSecond = Recording.FramesPerSecond(); @@ -252,7 +262,8 @@ cDvbPlayer::cDvbPlayer(const char *FileName) playMode = pmPlay; playDir = pdForward; trickSpeed = NORMAL_SPEED; - readIndex = writeIndex = -1; + readIndex = -1; + readIndependent = false; readFrame = NULL; playFrame = NULL; isyslog("replay %s", FileName); @@ -269,17 +280,15 @@ cDvbPlayer::cDvbPlayer(const char *FileName) delete index; index = NULL; } - backTrace = new cBackTrace; } cDvbPlayer::~cDvbPlayer() { - Detach(); Save(); + Detach(); delete readFrame; // might not have been stored in the buffer in Action() delete index; delete fileName; - delete backTrace; delete ringBuffer; } @@ -308,13 +317,13 @@ void cDvbPlayer::Empty(void) LOCK_THREAD; if (nonBlockingFileReader) nonBlockingFileReader->Clear(); - if ((readIndex = backTrace->Get(playDir == pdForward)) < 0) - readIndex = writeIndex; + if (!firstPacket) // don't set the readIndex twice if Empty() is called more than once + readIndex = ptsIndex.FindIndex(DeviceGetSTC()); delete readFrame; // might not have been stored in the buffer in Action() readFrame = NULL; playFrame = NULL; ringBuffer->Clear(); - backTrace->Clear(); + ptsIndex.Clear(); DeviceClear(); firstPacket = true; } @@ -346,7 +355,7 @@ int cDvbPlayer::Resume(void) bool cDvbPlayer::Save(void) { if (index) { - int Index = writeIndex; + int Index = ptsIndex.FindIndex(DeviceGetSTC()); if (Index >= 0) { Index -= int(round(RESUMEBACKUP * framesPerSecond)); if (Index > 0) @@ -384,6 +393,7 @@ void cDvbPlayer::Action(void) int Length = 0; bool Sleep = false; bool WaitingForData = false; + time_t StuckAtEnd = 0; while (Running() && (NextFile() || readIndex >= 0 || ringBuffer->Available() || !DeviceFlush(100))) { if (Sleep) { @@ -397,6 +407,8 @@ void cDvbPlayer::Action(void) if (DevicePoll(Poller, 100)) { LOCK_THREAD; + bool HitBegin = false; + bool HitEnd = false; // Read the next frame from the file: @@ -408,8 +420,9 @@ void cDvbPlayer::Action(void) off_t FileOffset; bool TimeShiftMode = index->IsStillRecording(); int Index = -1; + readIndependent = false; if (DeviceHasIBPTrickSpeed() && playDir == pdForward) { - if (index->Get(readIndex + 1, &FileNumber, &FileOffset, NULL, &Length)) + if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length)) Index = readIndex + 1; } else { @@ -417,42 +430,33 @@ void cDvbPlayer::Action(void) if (playDir != pdForward) d = -d; Index = index->GetNextIFrame(readIndex + d, playDir == pdForward, &FileNumber, &FileOffset, &Length, TimeShiftMode); + readIndependent = true; } if (Index >= 0) { - if (!NextFile(FileNumber, FileOffset)) { - readIndex = Index; + readIndex = Index; + if (!NextFile(FileNumber, FileOffset)) continue; - } } else { - if (!TimeShiftMode && playDir == pdForward) { - // hit end of recording: signal end of file but don't change playMode - readIndex = -1; - eof = true; - continue; + if (playDir == pdForward) { + if (!TimeShiftMode) { + // hit end of recording: signal end of file but don't change playMode + eof = true; + HitEnd = true; + } } - // hit begin of recording: wait for device buffers to drain - // before changing play mode: - if (!DeviceFlush(100)) - continue; - // can't call Play() here, because those functions may only be - // called from the foreground thread - and we also don't need - // to empty the buffer here - DevicePlay(); - playMode = pmPlay; - playDir = pdForward; - continue; + else + HitBegin = true; // hit begin of recording - trigger wait until device has replayed the very first frame: } - readIndex = Index; } else if (index) { uint16_t FileNumber; off_t FileOffset; - readIndex++; - if (!(index->Get(readIndex, &FileNumber, &FileOffset, NULL, &Length) && NextFile(FileNumber, FileOffset))) { - readIndex = -1; + if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset)) + readIndex++; + else { eof = true; - continue; + HitEnd = true; } } else // allows replay even if the index file is missing @@ -465,19 +469,24 @@ void cDvbPlayer::Action(void) } b = MALLOC(uchar, Length); } - int r = nonBlockingFileReader->Read(replayFile, b, Length); - if (r > 0) { - WaitingForData = false; - readFrame = new cFrame(b, -r, ftUnknown, readIndex); // hands over b to the ringBuffer - b = NULL; - } - else if (r == 0) - eof = true; - else if (r < 0 && errno == EAGAIN) - WaitingForData = true; - else if (r < 0 && FATALERRNO) { - LOG_ERROR; - break; + if (!eof) { + int r = nonBlockingFileReader->Read(replayFile, b, Length); + if (r > 0) { + WaitingForData = false; + uint32_t Pts = 0; + if (readIndependent) + Pts = isPesRecording ? PesGetPts(b) : TsGetPts(b, r); + readFrame = new cFrame(b, -r, ftUnknown, readIndependent ? readIndex : -1, Pts); // hands over b to the ringBuffer + b = NULL; + } + else if (r == 0) + eof = true; + else if (r < 0 && errno == EAGAIN) + WaitingForData = true; + else if (r < 0 && FATALERRNO) { + LOG_ERROR; + break; + } } } @@ -506,6 +515,8 @@ void cDvbPlayer::Action(void) p = playFrame->Data(); pc = playFrame->Count(); if (p) { + if (playFrame->Index() >= 0) + ptsIndex.Put(playFrame->Pts(), playFrame->Index()); if (firstPacket) { if (isPesRecording) { PlayPes(NULL, 0); @@ -533,8 +544,6 @@ void cDvbPlayer::Action(void) } } if (pc <= 0) { - writeIndex = playFrame->Index(); - backTrace->Add(playFrame->Index(), playFrame->Count()); ringBuffer->Drop(playFrame); playFrame = NULL; p = NULL; @@ -542,6 +551,31 @@ void cDvbPlayer::Action(void) } else Sleep = true; + + // Handle hitting begin/end of recording: + + if (HitBegin || HitEnd) { + if (DeviceFlush(10)) // give device a chance to display the last frame + cCondWait::SleepMs(10); // don't get into a tight loop + } + if (HitBegin) { + if (ptsIndex.FindIndex(DeviceGetSTC()) <= 0) { + Empty(); + DevicePlay(); + playMode = pmPlay; + playDir = pdForward; + } + } + else if (HitEnd) { + if (ptsIndex.FindIndex(DeviceGetSTC()) >= readIndex) + break; + else if (!StuckAtEnd) + StuckAtEnd = time(NULL); + else if (time(NULL) - StuckAtEnd > MAXSTUCKATEND) + break; + } + else + StuckAtEnd = 0; } } @@ -614,6 +648,7 @@ void cDvbPlayer::Forward(void) Pause(); break; } + Empty(); // run into pmPause case pmStill: case pmPause: @@ -661,6 +696,7 @@ void cDvbPlayer::Backward(void) Pause(); break; } + Empty(); // run into pmPause case pmStill: case pmPause: { @@ -696,14 +732,14 @@ void cDvbPlayer::SkipSeconds(int Seconds) { if (index && Seconds) { LOCK_THREAD; + int Index = ptsIndex.FindIndex(DeviceGetSTC()); Empty(); - int Index = writeIndex; if (Index >= 0) { Index = max(Index + SecondsToFrames(Seconds, framesPerSecond), 0); if (Index > 0) Index = index->GetNextIFrame(Index, false, NULL, NULL, NULL, true); if (Index >= 0) - readIndex = writeIndex = Index - 1; // Action() will first increment it! + readIndex = Index - 1; // Action() will first increment it! } Play(); } @@ -727,25 +763,22 @@ void cDvbPlayer::Goto(int Index, bool Still) if (playMode == pmPause) DevicePlay(); DeviceStillPicture(b, r); + ptsIndex.Put(isPesRecording ? PesGetPts(b) : TsGetPts(b, r), Index); } playMode = pmStill; } - readIndex = writeIndex = Index; + readIndex = Index; } } bool cDvbPlayer::GetIndex(int &Current, int &Total, bool SnapToIFrame) { if (index) { - if (playMode == pmStill) - Current = max(readIndex, 0); - else { - Current = max(writeIndex, 0); - if (SnapToIFrame) { - int i1 = index->GetNextIFrame(Current + 1, false); - int i2 = index->GetNextIFrame(Current, true); - Current = (abs(Current - i1) <= abs(Current - i2)) ? i1 : i2; - } + Current = ptsIndex.FindIndex(DeviceGetSTC()); + if (SnapToIFrame) { + int i1 = index->GetNextIFrame(Current + 1, false); + int i2 = index->GetNextIFrame(Current, true); + Current = (abs(Current - i1) <= abs(Current - i2)) ? i1 : i2; } Total = index->Last(); return true; @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: player.h 2.3 2009/01/25 11:03:44 kls Exp $ + * $Id: player.h 2.4 2009/03/08 12:29:10 kls Exp $ */ #ifndef __PLAYER_H @@ -34,6 +34,7 @@ protected: void DeviceMute(void) { if (device) device->Mute(); } void DeviceSetVideoDisplayFormat(eVideoDisplayFormat VideoDisplayFormat) { if (device) device->SetVideoDisplayFormat(VideoDisplayFormat); } void DeviceStillPicture(const uchar *Data, int Length) { if (device) device->StillPicture(Data, Length); } + uint64_t DeviceGetSTC(void) { return device ? device->GetSTC() : -1; } void Detach(void); virtual void Activate(bool On) {} // This function is called right after the cPlayer has been attached to @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remux.c 2.13 2009/01/24 13:44:45 kls Exp $ + * $Id: remux.c 2.14 2009/03/08 12:12:17 kls Exp $ */ #include "remux.h" @@ -109,6 +109,21 @@ void cRemux::SetBrokenLink(uchar *Data, int Length) dsyslog("SetBrokenLink: no video packet in frame"); } +// --- Some TS handling tools ------------------------------------------------ + +int64_t TsGetPts(const uchar *p, int l) +{ + // Find the first packet with a PTS and use it: + while (l > 0) { + const uchar *d = p; + if (TsPayloadStart(d) && TsGetPayload(&d) && PesHasPts(d)) + return PesGetPts(d); + p += TS_SIZE; + l -= TS_SIZE; + } + return -1; +} + // --- cPatPmtGenerator ------------------------------------------------------ cPatPmtGenerator::cPatPmtGenerator(cChannel *Channel) @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remux.h 2.7 2009/01/24 13:38:10 kls Exp $ + * $Id: remux.h 2.8 2009/03/08 12:05:12 kls Exp $ */ #ifndef __REMUX_H @@ -101,6 +101,10 @@ inline int TsGetAdaptationField(const uchar *p) return TsHasAdaptationField(p) ? p[5] : 0x00; } +// The following functions all take a pointer to a sequence of complete TS packets. + +int64_t TsGetPts(const uchar *p, int l); + // 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. diff --git a/ringbuffer.c b/ringbuffer.c index d74706ab..25a01596 100644 --- a/ringbuffer.c +++ b/ringbuffer.c @@ -7,7 +7,7 @@ * Parts of this file were inspired by the 'ringbuffy.c' from the * LinuxDVB driver (see linuxtv.org). * - * $Id: ringbuffer.c 1.25 2007/11/17 13:49:34 kls Exp $ + * $Id: ringbuffer.c 2.1 2009/03/01 11:20:34 kls Exp $ */ #include "ringbuffer.h" @@ -335,11 +335,12 @@ void cRingBufferLinear::Del(int Count) // --- cFrame ---------------------------------------------------------------- -cFrame::cFrame(const uchar *Data, int Count, eFrameType Type, int Index) +cFrame::cFrame(const uchar *Data, int Count, eFrameType Type, int Index, uint32_t Pts) { count = abs(Count); type = Type; index = Index; + pts = Pts; if (Count < 0) data = (uchar *)Data; else { diff --git a/ringbuffer.h b/ringbuffer.h index f6664cf8..fa9ccedf 100644 --- a/ringbuffer.h +++ b/ringbuffer.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: ringbuffer.h 1.18 2007/11/17 13:49:34 kls Exp $ + * $Id: ringbuffer.h 2.1 2009/03/01 11:20:34 kls Exp $ */ #ifndef __RINGBUFFER_H @@ -108,8 +108,9 @@ private: int count; eFrameType type; int index; + uint32_t pts; public: - cFrame(const uchar *Data, int Count, eFrameType = ftUnknown, int Index = -1); + cFrame(const uchar *Data, int Count, eFrameType = ftUnknown, int Index = -1, uint32_t Pts = 0); ///< Creates a new cFrame object. ///< If Count is negative, the cFrame object will take ownership of the given ///< Data. Otherwise it will allocate Count bytes of memory and copy Data. @@ -118,6 +119,7 @@ public: int Count(void) const { return count; } eFrameType Type(void) const { return type; } int Index(void) const { return index; } + uint32_t Pts(void) const { return pts; } }; class cRingBufferFrame : public cRingBuffer { |