From 831de7855973c206fad7af9b297c4f2739a3f9ea Mon Sep 17 00:00:00 2001
From: Lawrence Rust <lvr@softsystem.co.uk>
Date: Thu, 2 Jun 2011 12:55:13 +0200
Subject: [PATCH 1/2] MythPlayer: Improve low bit rate / high latency stream playback

DVB-S radio programs are low bit rate (64Kbps..256Kbps) and suffer
occasional latency.

MHEG interaction streams are internet sourced and often suffer congestion
resulting in high latency for some packets.

- Fix a number of issues to do with changing program to DVB-S radio and
  the lack of video stream causing garbage video display.

- Make FileRingBuffer::safe_read retry if reading from a remote file.
  Low bit rate sources such as radio can have considerable latency.

- FileRingBuffer::OpenFile reduce the initial rawbitrate from 8 Mbps to
  128 or 256 Kbps, otherwise considerable delay can be caused during
  radio program startup.

- Increase the wait period in MythPlayer::OpenFile from 1 to 30 secs
  while reading the probe buffer. This allows for typical http stream
  startup latency.

Signed-off-by: Lawrence Rust <lvr@softsystem.co.uk>
---
 mythtv/libs/libmythtv/avformatdecoder.cpp |   13 ++++
 mythtv/libs/libmythtv/fileringbuffer.cpp  |   48 +++++++++-----
 mythtv/libs/libmythtv/mythplayer.cpp      |  106 ++++++++++++++++++-----------
 mythtv/libs/libmythtv/tv_play.cpp         |    4 +-
 4 files changed, 113 insertions(+), 58 deletions(-)

diff --git a/mythtv/libs/libmythtv/avformatdecoder.cpp b/mythtv/libs/libmythtv/avformatdecoder.cpp
index 89cc07b..0f6e4eb 100644
--- a/mythtv/libs/libmythtv/avformatdecoder.cpp
+++ b/mythtv/libs/libmythtv/avformatdecoder.cpp
@@ -4313,6 +4313,9 @@ bool AvFormatDecoder::GetFrame(DecodeType decodetype)
                 av_init_packet(pkt);
             }
 
+            // NB av_read_frame will block until
+            // either a frame is read or an error occurs
+            // so MythPlayer::DecoderLoop will be unable to pause or stop
             int retval = 0;
             if (!ic || ((retval = av_read_frame(ic, pkt)) < 0))
             {
@@ -4422,7 +4425,17 @@ bool AvFormatDecoder::GetFrame(DecodeType decodetype)
             case CODEC_TYPE_AUDIO:
             {
                 if (!ProcessAudioPacket(curstream, pkt, decodetype))
+                {
                     have_err = true;
+                    if (!hasVideo)
+                    {
+                        LOG(VB_PLAYBACK, LOG_INFO, LOC +
+                            "GetFrame: exiting due to audio decode error");
+                        av_free_packet(pkt);
+                        delete pkt;
+                        return false;
+                    }
+                }
                 else
                     GenerateDummyVideoFrames();
                 break;
diff --git a/mythtv/libs/libmythtv/fileringbuffer.cpp b/mythtv/libs/libmythtv/fileringbuffer.cpp
index 944c98d..1449883 100644
--- a/mythtv/libs/libmythtv/fileringbuffer.cpp
+++ b/mythtv/libs/libmythtv/fileringbuffer.cpp
@@ -356,7 +356,15 @@ bool FileRingBuffer::OpenFile(const QString &lfilename, uint retry_ms)
     commserror = false;
     numfailures = 0;
 
-    rawbitrate = 8000;
+    // The initial bitrate needs to be set with consideration for low bit rate
+    // streams (e.g. radio @ 64Kbps) such that fill_min bytes are received
+    // in a reasonable time period to enable decoders to peek the first few KB
+    // to determine type & settings.
+    if (is_local)
+        rawbitrate = 256; // Allow for radio
+    else
+        rawbitrate = 128; // remotefile
+
     CalcReadAheadThresh();
 
     bool ok = fd2 >= 0 || remotefile;
@@ -487,24 +495,32 @@ int FileRingBuffer::safe_read(int fd, void *data, uint sz)
  */
 int FileRingBuffer::safe_read(RemoteFile *rf, void *data, uint sz)
 {
-    int ret = rf->Read(data, sz);
-    if (ret < 0)
-    {
-        LOG(VB_GENERAL, LOG_ERR, LOC +
-            "safe_read(RemoteFile* ...): read failed");
-            
-        poslock.lockForRead();
-        rf->Seek(internalreadpos - readAdjust, SEEK_SET);
-        poslock.unlock();
-        numfailures++;
-    }
-    else if (ret == 0)
+    for (int retries = 0; ; ++retries)
     {
-        LOG(VB_FILE, LOG_INFO, LOC +
-            "safe_read(RemoteFile* ...): at EOF");
+        int ret = rf->Read(data, sz);
+        if (ret > 0)
+            return ret;
+        else if (ret < 0)
+        {
+            LOG(VB_GENERAL, LOG_ERR, LOC +
+                "safe_read(RemoteFile* ...): read failed");
+
+            poslock.lockForRead();
+            rf->Seek(internalreadpos - readAdjust, SEEK_SET);
+            poslock.unlock();
+            numfailures++;
+            return ret;
+        }
+        // Retry for 300mS if liveTV for low bit rate (radio) streams
+        else if (!livetvchain || retries >= 5)
+            break;
+
+        usleep(60000);
     }
 
-    return ret;
+    LOG(VB_FILE, LOG_INFO, LOC +
+        "safe_read(RemoteFile* ...): at EOF");
+    return 0;
 }
 
 long long FileRingBuffer::GetReadPosition(void) const
diff --git a/mythtv/libs/libmythtv/mythplayer.cpp b/mythtv/libs/libmythtv/mythplayer.cpp
index 5e93d4f..59fa3b8 100644
--- a/mythtv/libs/libmythtv/mythplayer.cpp
+++ b/mythtv/libs/libmythtv/mythplayer.cpp
@@ -363,7 +363,7 @@ bool MythPlayer::Pause(void)
 
 bool MythPlayer::Play(float speed, bool normal, bool unpauseaudio)
 {
-    pauseLock.lock();
+    QMutexLocker locker(&pauseLock);
     LOG(VB_PLAYBACK, LOG_INFO, LOC +
         QString("Play(%1, normal %2, unpause audio %3)")
             .arg(speed,5,'f',1).arg(normal).arg(unpauseaudio));
@@ -371,7 +371,6 @@ bool MythPlayer::Play(float speed, bool normal, bool unpauseaudio)
     if (deleteMap.IsEditing())
     {
         LOG(VB_GENERAL, LOG_ERR, LOC + "Ignoring Play(), in edit mode.");
-        pauseLock.unlock();
         return false;
     }
 
@@ -383,7 +382,6 @@ bool MythPlayer::Play(float speed, bool normal, bool unpauseaudio)
     allpaused = false;
     next_play_speed   = speed;
     next_normal_speed = normal;
-    pauseLock.unlock();
     return true;
 }
 
@@ -902,7 +900,8 @@ int MythPlayer::OpenFile(uint retries)
         MythTimer peekTimer; peekTimer.start();
         while (player_ctx->buffer->Peek(testbuf, testreadsize) != testreadsize)
         {
-            if (peekTimer.elapsed() > 1000 || bigTimer.elapsed() > timeout)
+            // NB need to allow for streams encountering network congestion
+            if (peekTimer.elapsed() > 30000 || bigTimer.elapsed() > timeout)
             {
                 LOG(VB_GENERAL, LOG_ERR, LOC +
                     QString("OpenFile(): Could not read first %1 bytes of '%2'")
@@ -912,7 +911,7 @@ int MythPlayer::OpenFile(uint retries)
                 return -1;
             }
             LOG(VB_GENERAL, LOG_WARNING, LOC + "OpenFile() waiting on data");
-            usleep(50 * 1000);
+            usleep(150 * 1000);
         }
 
         player_ctx->LockPlayingInfo(__FILE__, __LINE__);
@@ -2065,7 +2064,7 @@ bool MythPlayer::PrebufferEnoughFrames(int min_buffers)
             // to recover from serious problems if frames get leaked.
             DiscardVideoFrames(true);
         }
-        if (waited_for > 20000) // 20 seconds
+        if (waited_for > 30000) // 30 seconds for internet streamed media
         {
             LOG(VB_GENERAL, LOG_ERR, LOC +
                 "Waited too long for decoder to fill video buffers. Exiting..");
@@ -2109,7 +2108,10 @@ void MythPlayer::DisplayNormalFrame(bool check_prebuffer)
     SetBuffering(false);
 
     // retrieve the next frame
-    videoOutput->StartDisplayingFrame();
+    bool const bDisplayFrame = videoOutput->ValidVideoFrames() > 0;
+    if (bDisplayFrame)
+        videoOutput->StartDisplayingFrame();
+
     VideoFrame *frame = videoOutput->GetLastShownFrame();
 
     // Check aspect ratio
@@ -2118,9 +2120,12 @@ void MythPlayer::DisplayNormalFrame(bool check_prebuffer)
     // Player specific processing (dvd, bd, mheg etc)
     PreProcessNormalFrame();
 
-    // handle scan type changes
-    AutoDeint(frame);
-    detect_letter_box->SwitchTo(frame);
+    if (GetTrackCount(kTrackTypeVideo))
+    {
+        // handle scan type changes
+        AutoDeint(frame);
+        detect_letter_box->SwitchTo(frame);
+    }
 
     FrameScanType ps = m_scan;
     if (kScan_Detect == m_scan || kScan_Ignore == m_scan)
@@ -2133,7 +2138,8 @@ void MythPlayer::DisplayNormalFrame(bool check_prebuffer)
     osdLock.unlock();
 
     AVSync(frame, 0);
-    videoOutput->DoneDisplayingFrame(frame);
+    if (bDisplayFrame)
+        videoOutput->DoneDisplayingFrame(frame);
 }
 
 void MythPlayer::PreProcessNormalFrame(void)
@@ -2142,10 +2148,12 @@ void MythPlayer::PreProcessNormalFrame(void)
     // handle Interactive TV
     if (GetInteractiveTV())
     {
-        osdLock.lock();
-        itvLock.lock();
+        QMutexLocker lk1(&osdLock);
+
         if (osd && videoOutput->GetOSDPainter())
         {
+            QMutexLocker lk2(&itvLock);
+
             InteractiveScreen *window =
                 (InteractiveScreen*)osd->GetWindow(OSD_WIN_INTERACT);
             if ((interactiveTV->ImageHasChanged() || !itvVisible) && window)
@@ -2153,9 +2161,11 @@ void MythPlayer::PreProcessNormalFrame(void)
                 interactiveTV->UpdateOSD(window, videoOutput->GetOSDPainter());
                 itvVisible = true;
             }
+            // Hide the iTV window if OSD is active otherwise OSD messages
+            // can be hidden
+            if (window && itvVisible && GetTrackCount(kTrackTypeVideo) == 0)
+                window->SetVisible(!osd->IsVisible());
         }
-        itvLock.unlock();
-        osdLock.unlock();
     }
 #endif // USING_MHEG
 }
@@ -2301,7 +2311,7 @@ bool MythPlayer::VideoLoop(void)
         DisplayPauseFrame();
     }
     else
-        DisplayNormalFrame();
+        DisplayNormalFrame(GetTrackCount(kTrackTypeVideo));
 
     if (FlagIsSet(kVideoIsNull) && decoder)
         decoder->UpdateFramesPlayed();
@@ -2401,7 +2411,10 @@ void MythPlayer::SwitchToProgram(void)
     int newid = -1;
     ProgramInfo *pginfo = player_ctx->tvchain->GetSwitchProgram(d1, d2, newid);
     if (!pginfo)
+    {
+        LOG(VB_GENERAL, LOG_ERR, LOC + "SwitchToProgram - No ProgramInfo");
         return;
+    }
 
     bool newIsDummy = player_ctx->tvchain->GetCardType(newid) == "DUMMY";
 
@@ -2503,13 +2516,15 @@ void MythPlayer::JumpToProgram(void)
     long long nextpos = player_ctx->tvchain->GetJumpPos();
     ProgramInfo *pginfo = player_ctx->tvchain->GetSwitchProgram(d1, d2, newid);
     if (!pginfo)
+    {
+        LOG(VB_GENERAL, LOG_ERR, LOC + "JumpToProgram - No ProgramInfo");
         return;
+    }
 
     bool newIsDummy = player_ctx->tvchain->GetCardType(newid) == "DUMMY";
     SetPlayingInfo(*pginfo);
 
     Pause();
-    ChangeSpeed();
     ResetCaptions();
     player_ctx->tvchain->SetProgram(*pginfo);
     player_ctx->buffer->Reset(true);
@@ -2703,12 +2718,12 @@ void MythPlayer::EventLoop(void)
         player_ctx->tvchain->JumpToNext(true, 1);
         JumpToProgram();
     }
-    else if ((!allpaused || GetEof()) && player_ctx->tvchain &&
-             (decoder && !decoder->GetWaitForChange()))
+    else if ((!allpaused || GetEof()) &&
+             decoder && !decoder->GetWaitForChange() &&
+             player_ctx->tvchain && player_ctx->tvchain->NeedsToSwitch())
     {
         // Switch to the next program in livetv
-        if (player_ctx->tvchain->NeedsToSwitch())
-            SwitchToProgram();
+        SwitchToProgram();
     }
 
     // Jump to the next program in livetv
@@ -2864,49 +2879,47 @@ void MythPlayer::AudioEnd(void)
 
 bool MythPlayer::PauseDecoder(void)
 {
-    decoderPauseLock.lock();
+    QMutexLocker locker(&decoderPauseLock);
     if (is_current_thread(decoderThread))
     {
+        pauseDecoder = false;
         decoderPaused = true;
         decoderThreadPause.wakeAll();
-        decoderPauseLock.unlock();
-        return decoderPaused;
+        return true;
     }
 
-    int tries = 0;
     pauseDecoder = true;
-    while (decoderThread && !killdecoder && (tries++ < 100) &&
-           !decoderThreadPause.wait(&decoderPauseLock, 100))
+    int tries = 0;
+    while (!decoderPaused && decoderThread && !killdecoder && (tries++ < 10) &&
+          !decoderThreadPause.wait(locker.mutex(), 100))
     {
         LOG(VB_GENERAL, LOG_WARNING, LOC + "Waited 100ms for decoder to pause");
     }
     pauseDecoder = false;
-    decoderPauseLock.unlock();
     return decoderPaused;
-}
+ }
 
 void MythPlayer::UnpauseDecoder(void)
 {
-    decoderPauseLock.lock();
+    QMutexLocker locker(&decoderPauseLock);
 
     if (is_current_thread(decoderThread))
     {
+        unpauseDecoder = false;
         decoderPaused = false;
         decoderThreadUnpause.wakeAll();
-        decoderPauseLock.unlock();
         return;
     }
 
-    int tries = 0;
     unpauseDecoder = true;
-    while (decoderThread && !killdecoder && (tries++ < 100) &&
-          !decoderThreadUnpause.wait(&decoderPauseLock, 100))
+    int tries = 0;
+    while (decoderPaused && decoderThread && !killdecoder && (tries++ < 10) &&
+          !decoderThreadUnpause.wait(locker.mutex(), 100))
     {
         LOG(VB_GENERAL, LOG_WARNING, LOC +
             "Waited 100ms for decoder to unpause");
     }
     unpauseDecoder = false;
-    decoderPauseLock.unlock();
 }
 
 void MythPlayer::DecoderStart(bool start_paused)
@@ -2932,7 +2945,7 @@ void MythPlayer::DecoderEnd(void)
     SetPlaying(false);
     killdecoder = true;
     int tries = 0;
-    while (decoderThread && !decoderThread->wait(100) && (tries++ < 50))
+    while (decoderThread && !decoderThread->wait(100) && (tries++ < 20))
         LOG(VB_PLAYBACK, LOG_INFO, LOC +
             "Waited 100ms for decoder loop to stop");
 
@@ -2944,12 +2957,23 @@ void MythPlayer::DecoderEnd(void)
 
 void MythPlayer::DecoderPauseCheck(void)
 {
-    if (is_current_thread(decoderThread))
+    if (!is_current_thread(decoderThread))
+        return;
+
+    QMutexLocker locker(&decoderPauseLock);
+
+    if (pauseDecoder)
     {
-        if (pauseDecoder)
-            PauseDecoder();
-        if (unpauseDecoder)
-            UnpauseDecoder();
+        pauseDecoder = false;
+        decoderPaused = true;
+        decoderThreadPause.wakeAll();
+    }
+
+    if (unpauseDecoder)
+    {
+        unpauseDecoder = false;
+        decoderPaused = false;
+        decoderThreadUnpause.wakeAll();
     }
 }
 
diff --git a/mythtv/libs/libmythtv/tv_play.cpp b/mythtv/libs/libmythtv/tv_play.cpp
index 3bc2fec..c206ab9 100644
--- a/mythtv/libs/libmythtv/tv_play.cpp
+++ b/mythtv/libs/libmythtv/tv_play.cpp
@@ -7092,7 +7092,8 @@ void TV::ChangeChannel(PlayerContext *ctx, uint chanid, const QString &chan)
     if (ctx->prevChan.empty())
         ctx->PushPreviousChannel();
 
-    PauseAudioUntilBuffered(ctx);
+    if (ctx->player)
+        ctx->player->GetAudio()->Pause(true);
     PauseLiveTV(ctx);
 
     ctx->LockDeletePlayer(__FILE__, __LINE__);
@@ -7109,6 +7110,7 @@ void TV::ChangeChannel(PlayerContext *ctx, uint chanid, const QString &chan)
         ctx->player->GetAudio()->Reset();
 
     UnpauseLiveTV(ctx, chanid && GetQueuedChanID());
+    PauseAudioUntilBuffered(ctx);
 
     if (oldinputname != ctx->recorder->GetInput())
         UpdateOSDInput(ctx);
-- 
1.7.4.1

