From cdcf28b051c71a84632384346bb7a2a481a95373 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sun, 19 Jan 2003 15:43:58 +0100 Subject: [PATCH] Implemented non blocking file reader for cDvbPlayer --- HISTORY | 3 + dvbplayer.c | 199 +++++++++++++++++++++++++++++++++++++++++---------- ringbuffer.c | 18 +++-- ringbuffer.h | 5 +- 4 files changed, 178 insertions(+), 47 deletions(-) diff --git a/HISTORY b/HISTORY index 221eb197..3e64bd5e 100644 --- a/HISTORY +++ b/HISTORY @@ -1922,3 +1922,6 @@ Video Disk Recorder Revision History - Added 'Hrvatska radiotelevizija' and 'RTV Slovenija' to ca.conf (thanks to Paul Gohn). - Implemented actual user input for CAM enquiry menus. +- Since disk file systems apparently don't honor the O_NONBLOCK flag to read from + a file in non-blocking mode the cDvbPlayer now uses a non blocking file reader + class to make sure replay remains smooth even under heavy system load. diff --git a/dvbplayer.c b/dvbplayer.c index 2d9cc1ce..1d140ea9 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.16 2002/11/03 11:23:47 kls Exp $ + * $Id: dvbplayer.c 1.17 2003/01/19 15:43:58 kls Exp $ */ #include "dvbplayer.h" @@ -69,6 +69,103 @@ int cBackTrace::Get(bool Forward) return i; } +// --- cNonBlockingFileReader ------------------------------------------------ + +class cNonBlockingFileReader : public cThread { +private: + int f; + uchar *buffer; + int wanted; + int length; + bool hasData; + bool active; + cMutex mutex; + cCondVar newSet; +protected: + void Action(void); +public: + cNonBlockingFileReader(void); + ~cNonBlockingFileReader(); + void Clear(void); + int Read(int FileHandle, uchar *Buffer, int Length); + bool Reading(void) { return buffer; } + }; + +cNonBlockingFileReader::cNonBlockingFileReader(void) +{ + f = -1; + buffer = NULL; + wanted = length = 0; + hasData = false; + active = false; + Start(); +} + +cNonBlockingFileReader::~cNonBlockingFileReader() +{ + active = false; + newSet.Broadcast(); + Cancel(3); + free(buffer); +} + +void cNonBlockingFileReader::Clear(void) +{ + cMutexLock MutexLock(&mutex); + f = -1; + buffer = NULL; + wanted = length = 0; + hasData = false; + newSet.Broadcast(); +} + +int cNonBlockingFileReader::Read(int FileHandle, uchar *Buffer, int Length) +{ + if (hasData && buffer) { + if (buffer != Buffer) { + esyslog("ERROR: cNonBlockingFileReader::Read() called with different buffer!"); + errno = EINVAL; + return -1; + } + buffer = NULL; + return length; + } + if (!buffer) { + f = FileHandle; + buffer = Buffer; + wanted = Length; + length = 0; + hasData = false; + newSet.Broadcast(); + } + errno = EAGAIN; + return -1; +} + +void cNonBlockingFileReader::Action(void) +{ + dsyslog("non blocking file reader thread started (pid=%d)", getpid()); + active = true; + while (active) { + cMutexLock MutexLock(&mutex); + if (!hasData && f >= 0 && buffer) { + int r = safe_read(f, buffer + length, wanted - length); + if (r >= 0) { + length += r; + if (!r || length == wanted) // r == 0 means EOF + hasData = true; + } + else if (r < 0 && FATALERRNO) { + LOG_ERROR; + length = r; // this will forward the error status to the caller + hasData = true; + } + } + newSet.TimedWait(mutex, 1000); + } + dsyslog("non blocking file reader thread ended (pid=%d)", getpid()); +} + // --- cDvbPlayer ------------------------------------------------------------ //XXX+ also used in recorder.c - find a better place??? @@ -84,6 +181,7 @@ private: enum ePlayModes { pmPlay, pmPause, pmSlow, pmFast, pmStill }; enum ePlayDirs { pdForward, pdBackward }; static int Speeds[]; + cNonBlockingFileReader *nonBlockingFileReader; cRingBufferFrame *ringBuffer; cBackTrace *backTrace; cFileName *fileName; @@ -135,6 +233,7 @@ int cDvbPlayer::Speeds[] = { 0, -2, -4, -8, 1, 2, 4, 12, 0 }; cDvbPlayer::cDvbPlayer(const char *FileName) { + nonBlockingFileReader = NULL; ringBuffer = NULL; backTrace = NULL; index = NULL; @@ -198,7 +297,9 @@ void cDvbPlayer::TrickSpeed(int Increment) void cDvbPlayer::Empty(void) { - Lock(); + LOCK_THREAD; + if (nonBlockingFileReader) + nonBlockingFileReader->Clear(); if ((readIndex = backTrace->Get(playDir == pdForward)) < 0) readIndex = writeIndex; readFrame = NULL; @@ -206,7 +307,6 @@ void cDvbPlayer::Empty(void) ringBuffer->Clear(); backTrace->Clear(); DeviceClear(); - Unlock(); } void cDvbPlayer::StripAudioPackets(uchar *b, int Length, uchar Except) @@ -305,7 +405,7 @@ void cDvbPlayer::Action(void) { dsyslog("dvbplayer thread started (pid=%d)", getpid()); - uchar b[MAXFRAMESIZE]; + uchar *b = NULL; const uchar *p = NULL; int pc = 0; @@ -313,6 +413,10 @@ void cDvbPlayer::Action(void) if (readIndex >= 0) isyslog("resuming replay at index %d (%s)", readIndex, IndexToHMSF(readIndex, true)); + nonBlockingFileReader = new cNonBlockingFileReader; + int Length = 0; + int AudioTrack = 0; // -1 = any, 0 = none, >0 = audioTrack + running = true; while (running && (NextFile() || readIndex >= 0 || ringBuffer->Available())) { cPoller Poller; @@ -326,46 +430,59 @@ void cDvbPlayer::Action(void) if (!readFrame && (replayFile >= 0 || readIndex >= 0)) { if (playMode != pmStill) { - int r = 0; - if (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) { - uchar FileNumber; - int FileOffset, Length; - int Index = index->GetNextIFrame(readIndex, playDir == pdForward, &FileNumber, &FileOffset, &Length, true); - if (Index >= 0) { - if (!NextFile(FileNumber, FileOffset)) + if (!nonBlockingFileReader->Reading()) { + if (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) { + uchar FileNumber; + int FileOffset; + int Index = index->GetNextIFrame(readIndex, playDir == pdForward, &FileNumber, &FileOffset, &Length, true); + if (Index >= 0) { + if (!NextFile(FileNumber, FileOffset)) + continue; + } + else { + // 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; + } + readIndex = Index; + AudioTrack = 0; + // must clear all audio packets because the buffer is not emptied + // when falling back from "fast forward" to "play" (see above) } - else { - // 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 if (index) { + uchar FileNumber; + int FileOffset; + readIndex++; + if (!(index->Get(readIndex, &FileNumber, &FileOffset, NULL, &Length) && NextFile(FileNumber, FileOffset))) { + readIndex = -1; + eof = true; + continue; + } + AudioTrack = audioTrack; } - readIndex = Index; - r = ReadFrame(replayFile, b, Length, sizeof(b)); - // must call StripAudioPackets() here because the buffer is not emptied - // when falling back from "fast forward" to "play" (see above) - StripAudioPackets(b, r); + else { // allows replay even if the index file is missing + Length = MAXFRAMESIZE; + AudioTrack = -1; + } + if (Length == -1) + Length = MAXFRAMESIZE; // this means we read up to EOF (see cIndex) + else if (Length > MAXFRAMESIZE) { + esyslog("ERROR: frame larger than buffer (%d > %d)", Length, MAXFRAMESIZE); + Length = MAXFRAMESIZE; + } + b = MALLOC(uchar, Length); } - else if (index) { - uchar FileNumber; - int FileOffset, Length; - readIndex++; - if (!(index->Get(readIndex, &FileNumber, &FileOffset, NULL, &Length) && NextFile(FileNumber, FileOffset))) { - readIndex = -1; - eof = true; - continue; - } - r = ReadFrame(replayFile, b, Length, sizeof(b)); - StripAudioPackets(b, r, audioTrack); + int r = nonBlockingFileReader->Read(replayFile, b, Length); + if (r > 0) { + if (AudioTrack >= 0) + StripAudioPackets(b, r, AudioTrack); + readFrame = new cFrame(b, -r, ftUnknown, readIndex); // hands over b to the ringBuffer + b = NULL; } - else // allows replay even if the index file is missing - r = read(replayFile, b, sizeof(b)); - if (r > 0) - readFrame = new cFrame(b, r, ftUnknown, readIndex); else if (r == 0) eof = true; else if (r < 0 && FATALERRNO) { @@ -422,6 +539,10 @@ void cDvbPlayer::Action(void) } active = running = false; + cNonBlockingFileReader *nbfr = nonBlockingFileReader; + nonBlockingFileReader = NULL; + delete nbfr; + dsyslog("dvbplayer thread ended (pid=%d)", getpid()); } diff --git a/ringbuffer.c b/ringbuffer.c index b57b23e2..ed92be59 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.10 2002/07/28 12:47:32 kls Exp $ + * $Id: ringbuffer.c 1.11 2003/01/19 15:03:00 kls Exp $ */ #include "ringbuffer.h" @@ -174,14 +174,18 @@ int cRingBufferLinear::Get(uchar *Data, int Count) cFrame::cFrame(const uchar *Data, int Count, eFrameType Type, int Index) { - count = Count; + count = abs(Count); type = Type; index = Index; - data = new uchar[count]; - if (data) - memcpy(data, Data, count); - else - esyslog("ERROR: can't allocate frame buffer (count=%d)", count); + if (Count < 0) + data = (uchar *)Data; + else { + data = new uchar[count]; + if (data) + memcpy(data, Data, count); + else + esyslog("ERROR: can't allocate frame buffer (count=%d)", count); + } next = NULL; } diff --git a/ringbuffer.h b/ringbuffer.h index 660a08b0..f96143c9 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.7 2002/08/04 10:27:30 kls Exp $ + * $Id: ringbuffer.h 1.8 2003/01/19 15:03:00 kls Exp $ */ #ifndef __RINGBUFFER_H @@ -69,6 +69,9 @@ private: int index; public: cFrame(const uchar *Data, int Count, eFrameType = ftUnknown, int Index = -1); + ///< 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. ~cFrame(); const uchar *Data(void) const { return data; } int Count(void) const { return count; }