From 24b0b0197a777c801e681beba5aff9a39f5283fc Mon Sep 17 00:00:00 2001
From: Richard <peper03@yahoo.com>
Date: Fri, 1 Mar 2013 22:13:21 +0100
Subject: [PATCH] DVD PTS discontinuities are now handled by 'flattening' the
 timecodes of incoming packets.  This prevents
 AVFormatDecoder getting stuck buffering video frames when
 the timecodes jump backwards.

---
 mythtv/libs/libmythtv/DVD/avformatdecoderdvd.cpp |   42 +++++++++++++
 mythtv/libs/libmythtv/DVD/avformatdecoderdvd.h   |    4 ++
 mythtv/libs/libmythtv/DVD/dvdringbuffer.cpp      |   71 ++++++++++++++++++----
 mythtv/libs/libmythtv/DVD/dvdringbuffer.h        |   18 ++++++
 mythtv/libs/libmythtv/avformatdecoder.cpp        |    7 ++-
 mythtv/libs/libmythtv/avformatdecoder.h          |    3 +
 6 files changed, 133 insertions(+), 12 deletions(-)

diff --git a/mythtv/libs/libmythtv/DVD/avformatdecoderdvd.cpp b/mythtv/libs/libmythtv/DVD/avformatdecoderdvd.cpp
index a27dfe5..ce7731b 100644
--- a/mythtv/libs/libmythtv/DVD/avformatdecoderdvd.cpp
+++ b/mythtv/libs/libmythtv/DVD/avformatdecoderdvd.cpp
@@ -32,6 +32,48 @@ bool AvFormatDecoderDVD::GetFrame(DecodeType decodetype)
     return AvFormatDecoder::GetFrame( kDecodeAV );
 }
 
+int64_t AvFormatDecoderDVD::AdjustTimestamp(int64_t timestamp)
+{
+    int64_t newTimestamp = timestamp;
+
+    if (newTimestamp != AV_NOPTS_VALUE)
+    {
+        int64_t timediff = ringBuffer->DVD()->GetTimeDiff();
+        if (newTimestamp >= timediff)
+        {
+            newTimestamp -= timediff;
+        }
+    }
+
+    return newTimestamp;
+}
+
+int AvFormatDecoderDVD::ReadPacket(AVFormatContext *ctx, AVPacket* pkt)
+{
+    int result = av_read_frame(ctx, pkt);
+
+    while (result == AVERROR_EOF && errno == EAGAIN)
+    {
+        if (ringBuffer->DVD()->IsReadingBlocked())
+        {
+            ringBuffer->DVD()->UnblockReading();
+            result = av_read_frame(ctx, pkt);
+        }
+        else
+        {
+            break;
+        }
+    }
+
+    if (result >= 0)
+    {
+        pkt->dts = AdjustTimestamp(pkt->dts);
+        pkt->pts = AdjustTimestamp(pkt->pts);
+    }
+
+    return result;
+}
+
 void AvFormatDecoderDVD::PostProcessTracks(void)
 {
     if (!ringBuffer)
diff --git a/mythtv/libs/libmythtv/DVD/avformatdecoderdvd.h b/mythtv/libs/libmythtv/DVD/avformatdecoderdvd.h
index 1b15425..bdb64e4 100644
--- a/mythtv/libs/libmythtv/DVD/avformatdecoderdvd.h
+++ b/mythtv/libs/libmythtv/DVD/avformatdecoderdvd.h
@@ -12,6 +12,10 @@ class AvFormatDecoderDVD : public AvFormatDecoder
     virtual void UpdateFramesPlayed(void);
     virtual bool GetFrame(DecodeType decodetype); // DecoderBase
 
+  protected:
+    int64_t AdjustTimestamp(int64_t timestamp);
+    virtual int  ReadPacket(AVFormatContext *ctx, AVPacket *pkt);
+
   private:
     virtual bool DoRewindSeek(long long desiredFrame);
     virtual void DoFastForwardSeek(long long desiredFrame, bool &needflush);
diff --git a/mythtv/libs/libmythtv/DVD/dvdringbuffer.cpp b/mythtv/libs/libmythtv/DVD/dvdringbuffer.cpp
index b0fa1ae..2698142 100644
--- a/mythtv/libs/libmythtv/DVD/dvdringbuffer.cpp
+++ b/mythtv/libs/libmythtv/DVD/dvdringbuffer.cpp
@@ -89,7 +89,9 @@ DVDRingBuffer::DVDRingBuffer(const QString &lfilename) :
     m_lastNav(NULL),    m_part(0), m_lastPart(0),
     m_title(0),         m_lastTitle(0),   m_playerWait(false),
     m_titleParts(0),    m_gotStop(false), m_currentAngle(0),
-    m_currentTitleAngleCount(0), m_newSequence(false),
+    m_currentTitleAngleCount(0),
+    m_endPts(0),        m_timeDiff(0),
+    m_newSequence(false),
     m_still(0), m_lastStill(0),
     m_audioStreamsChanged(false),
     m_dvdWaiting(false),
@@ -109,6 +111,10 @@ DVDRingBuffer::DVDRingBuffer(const QString &lfilename) :
     m_currentTime(0),
     m_parent(NULL),
     m_forcedAspect(-1.0f),
+    m_processState(PROCESS_NORMAL),
+    m_dvdStat(DVDNAV_STATUS_OK),
+    m_dvdEvent(0),
+    m_dvdEventSize(0),
 
     // Menu/buttons
     m_inMenu(false), m_buttonVersion(1), m_buttonStreamID(0),
@@ -397,6 +403,9 @@ bool DVDRingBuffer::StartFromBeginning(void)
         m_audioStreamsChanged = true;
     }
 
+    m_endPts = 0;
+    m_timeDiff = 0;
+
     return m_dvdnav;
 }
 
@@ -486,14 +495,12 @@ void DVDRingBuffer::WaitForPlayer(void)
 
 int DVDRingBuffer::safe_read(void *data, uint sz)
 {
-    dvdnav_status_t dvdStat;
     unsigned char  *blockBuf     = NULL;
     uint            tot          = 0;
-    int32_t         dvdEvent     = 0;
-    int32_t         dvdEventSize = 0;
     int             needed       = sz;
     char           *dest         = (char*) data;
     int             offset       = 0;
+    bool            bReprocessing = false;
 
     if (m_gotStop)
     {
@@ -505,14 +512,24 @@ int DVDRingBuffer::safe_read(void *data, uint sz)
     if (readaheadrunning)
         LOG(VB_GENERAL, LOG_ERR, LOC + "read ahead thread running.");
 
-    while (needed)
+    while ((m_processState != PROCESS_WAIT) && needed)
     {
         blockBuf = m_dvdBlockWriteBuf;
 
-        dvdStat = dvdnav_get_next_cache_block(
-            m_dvdnav, &blockBuf, &dvdEvent, &dvdEventSize);
+        if (m_processState == PROCESS_REPROCESS)
+        {
+            m_processState = PROCESS_NORMAL;
+            bReprocessing = true;
+        }
+        else
+        {
+            m_dvdStat = dvdnav_get_next_cache_block(
+                m_dvdnav, &blockBuf, &m_dvdEvent, &m_dvdEventSize);
 
-        if (dvdStat == DVDNAV_STATUS_ERR)
+            bReprocessing = false;
+        }
+
+        if (m_dvdStat == DVDNAV_STATUS_ERR)
         {
             LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to read block: %1")
                     .arg(dvdnav_err_to_string(m_dvdnav)));
@@ -520,7 +537,7 @@ int DVDRingBuffer::safe_read(void *data, uint sz)
             return -1;
         }
 
-        switch (dvdEvent)
+        switch (m_dvdEvent)
         {
             // Standard packet for decoding
             case DVDNAV_BLOCK_OK:
@@ -729,6 +746,30 @@ int DVDRingBuffer::safe_read(void *data, uint sz)
             {
                 QMutexLocker lock(&m_seekLock);
 
+                pci_t *pci = dvdnav_get_current_nav_pci(m_dvdnav);
+
+                // If the start PTS of this block is not the
+                // same as the end PTS of the last block,
+                // we've got a timestamp discontinuity
+                int64_t diff = (int64_t)pci->pci_gi.vobu_s_ptm - m_endPts;
+                if (diff != 0)
+                {
+                    if (!bReprocessing && !m_skipstillorwait)
+                    {
+                        LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("PTS discontinuity - waiting for decoder: this %1, last %2, diff %3")
+                            .arg(pci->pci_gi.vobu_s_ptm)
+                            .arg(m_endPts)
+                            .arg(diff));
+
+                        m_processState = PROCESS_WAIT;
+                        break;
+                    }
+
+                    m_timeDiff += diff;
+                }
+
+                m_endPts = pci->pci_gi.vobu_e_ptm;
+
                 // get the latest nav
                 m_lastNav = (dvdnav_t *)blockBuf;
 
@@ -941,7 +982,7 @@ int DVDRingBuffer::safe_read(void *data, uint sz)
             default:
             {
                 LOG(VB_GENERAL, LOG_ERR, LOC +
-                    QString("Unknown DVD event: %1").arg(dvdEvent));
+                    QString("Unknown DVD event: %1").arg(m_dvdEvent));
             }
             break;
         }
@@ -950,7 +991,15 @@ int DVDRingBuffer::safe_read(void *data, uint sz)
         offset = tot;
     }
 
-    return tot;
+    if (m_processState == PROCESS_WAIT)
+    {
+        errno = EAGAIN;
+        return 0;
+    }
+    else
+    {
+        return tot;
+    }
 }
 
 bool DVDRingBuffer::playTrack(int track)
diff --git a/mythtv/libs/libmythtv/DVD/dvdringbuffer.h b/mythtv/libs/libmythtv/DVD/dvdringbuffer.h
index 3a31c1b..5468db7 100644
--- a/mythtv/libs/libmythtv/DVD/dvdringbuffer.h
+++ b/mythtv/libs/libmythtv/DVD/dvdringbuffer.h
@@ -78,6 +78,7 @@ class MTV_PUBLIC DVDRingBuffer : public RingBuffer
     bool IsWaiting(void) const           { return m_dvdWaiting;          }
     int  NumPartsInTitle(void)     const { return m_titleParts;          }
     void GetMenuSPUPkt(uint8_t *buf, int len, int stream_id);
+    int64_t GetTimeDiff(void)      const { return m_timeDiff; }
 
     // Public menu/button stuff
     AVSubtitle *GetMenuSubtitle(uint &version);
@@ -119,6 +120,8 @@ class MTV_PUBLIC DVDRingBuffer : public RingBuffer
     void SkipStillFrame(void);
     void WaitSkip(void);
     void SkipDVDWaitingForPlayer(void)    { m_playerWait = false;           }
+    void UnblockReading(void)             { m_processState = PROCESS_REPROCESS; }
+    bool IsReadingBlocked(void)           { return (m_processState == PROCESS_WAIT); }
     bool GoToMenu(const QString str);
     void GoToNextProgram(void);
     void GoToPreviousProgram(void);
@@ -137,6 +140,14 @@ class MTV_PUBLIC DVDRingBuffer : public RingBuffer
     void SetParent(MythDVDPlayer *p) { m_parent = p; }
 
   protected:
+
+    typedef enum
+    {
+        PROCESS_NORMAL,
+        PROCESS_REPROCESS,
+        PROCESS_WAIT
+    }processState_t;
+
     dvdnav_t      *m_dvdnav;
     unsigned char  m_dvdBlockWriteBuf[DVD_BLOCK_SIZE];
     unsigned char *m_dvdBlockReadBuf;
@@ -159,6 +170,8 @@ class MTV_PUBLIC DVDRingBuffer : public RingBuffer
     bool           m_gotStop;
     int            m_currentAngle;
     int            m_currentTitleAngleCount;
+    int64_t        m_endPts;
+    int64_t        m_timeDiff;
 
     bool           m_newSequence;
     int            m_still;
@@ -191,6 +204,11 @@ class MTV_PUBLIC DVDRingBuffer : public RingBuffer
     MythDVDPlayer *m_parent;
     float          m_forcedAspect;
 
+    processState_t  m_processState;
+    dvdnav_status_t m_dvdStat;
+    int32_t        m_dvdEvent;
+    int32_t        m_dvdEventSize;
+
     // Private menu/button stuff
     void ActivateButton(void);
     void MoveButtonLeft(void);
diff --git a/mythtv/libs/libmythtv/avformatdecoder.cpp b/mythtv/libs/libmythtv/avformatdecoder.cpp
index 3e3ce96..d4f6013 100644
--- a/mythtv/libs/libmythtv/avformatdecoder.cpp
+++ b/mythtv/libs/libmythtv/avformatdecoder.cpp
@@ -4646,7 +4646,7 @@ bool AvFormatDecoder::GetFrame(DecodeType decodetype)
             }
 
             int retval = 0;
-            if (!ic || ((retval = av_read_frame(ic, pkt)) < 0))
+            if (!ic || ((retval = ReadPacket(ic, pkt)) < 0))
             {
                 if (retval == -EAGAIN)
                     continue;
@@ -4821,6 +4821,11 @@ bool AvFormatDecoder::GetFrame(DecodeType decodetype)
     return true;
 }
 
+int AvFormatDecoder::ReadPacket(AVFormatContext *ctx, AVPacket *pkt)
+{
+    return av_read_frame(ctx, pkt);
+}
+
 bool AvFormatDecoder::HasVideo(const AVFormatContext *ic)
 {
     if (ic && ic->cur_pmt_sect)
diff --git a/mythtv/libs/libmythtv/avformatdecoder.h b/mythtv/libs/libmythtv/avformatdecoder.h
index 379dc96..ea08fc9 100644
--- a/mythtv/libs/libmythtv/avformatdecoder.h
+++ b/mythtv/libs/libmythtv/avformatdecoder.h
@@ -264,6 +264,9 @@ class AvFormatDecoder : public DecoderBase
 
     int DecodeAudio(AVCodecContext *ctx, uint8_t *buffer, int &data_size,
                     AVPacket *pkt);
+
+    virtual int ReadPacket(AVFormatContext *ctx, AVPacket* pkt);
+
     PrivateDecoder *private_dec;
 
     bool is_db_ignored;
-- 
1.7.9.5

