commit 986030b212c8ecfdeb4ca0ccc3dfb2dcb6f161f3
Author: Mark Spieth <mspieth@digivation.com.au>
Date:   Tue Apr 27 07:51:51 2010 +1000

    smoother vsync with predictive frame skipping

diff --git a/mythtv/libs/libmyth/audiooutputbase.cpp b/mythtv/libs/libmyth/audiooutputbase.cpp
index e9febbb..bbdeaa1 100644
--- a/mythtv/libs/libmyth/audiooutputbase.cpp
+++ b/mythtv/libs/libmyth/audiooutputbase.cpp
@@ -1237,6 +1237,12 @@ void AudioOutputBase::OutputAudioLoop(void)
     unsigned char zeros[fragment_size];
     unsigned char fragment[fragment_size];
 
+    // to reduce startup latency, write silence in 8ms chunks
+    int zero_fragment_size = (int)(0.008*audio_samplerate/audio_channels);
+    zero_fragment_size *= audio_channels * audio_bits / 16;   // make sure its a multiple of audio_channels
+    if (zero_fragment_size > fragment_size)
+        zero_fragment_size = fragment_size;
+
     bzero(zeros, fragment_size);
     last_space_on_soundcard = 0;
 
@@ -1269,11 +1275,11 @@ void AudioOutputBase::OutputAudioLoop(void)
 
             // only send zeros if card doesn't already have at least one
             // fragment of zeros -dag
-            if (fragment_size >= soundcard_buffer_size - space_on_soundcard)
+            if (zero_fragment_size >= soundcard_buffer_size - space_on_soundcard)
             {
-                if (fragment_size <= space_on_soundcard) 
+                if (zero_fragment_size <= space_on_soundcard) 
                 {
-                    WriteAudio(zeros, fragment_size);
+                    WriteAudio(zeros, zero_fragment_size);
                 }
                 else 
                 {
@@ -1281,7 +1287,7 @@ void AudioOutputBase::OutputAudioLoop(void)
                     VERBOSE(VB_AUDIO+VB_TIMESTAMP, LOC +
                             QString("waiting for space on soundcard "
                                     "to write zeros: have %1 need %2")
-                            .arg(space_on_soundcard).arg(fragment_size));
+                            .arg(space_on_soundcard).arg(zero_fragment_size));
                     usleep(5000);
                 }
             }
diff --git a/mythtv/libs/libmythtv/NuppelVideoPlayer.cpp b/mythtv/libs/libmythtv/NuppelVideoPlayer.cpp
index 0da4da0..c6491ba 100644
--- a/mythtv/libs/libmythtv/NuppelVideoPlayer.cpp
+++ b/mythtv/libs/libmythtv/NuppelVideoPlayer.cpp
@@ -206,7 +206,9 @@ NuppelVideoPlayer::NuppelVideoPlayer(bool muted)
       videosync(NULL),              delay(0),
       vsynctol(30/4),               avsync_delay(0),
       avsync_adjustment(0),         avsync_avg(0),
-      avsync_oldavg(0),             refreshrate(0),
+      avsync_oldavg(0),             
+      avsync_predictor(0),          avsync_predictor_enabled(false),
+      refreshrate(0),
       lastsync(false),              m_playing_slower(false),
       m_stored_audio_stretchfactor(1.0),
       audio_paused(false),
@@ -238,6 +240,7 @@ NuppelVideoPlayer::NuppelVideoPlayer(bool muted)
     db_prefer708     = gContext->GetNumSetting("Prefer708Captions", 1);
     autocommercialskip = (CommSkipMode)
         gContext->GetNumSetting("AutoCommercialSkip", kCommSkipOff);
+    usesmoothsync    = gContext->GetNumSetting("UseSmoothSync", 1) != 0;
 
     lastIgnoredManualSkip = QDateTime::currentDateTime().addSecs(-10);
 
@@ -1120,7 +1123,7 @@ void NuppelVideoPlayer::SetVideoParams(int width, int height, double fps,
         video_frame_rate = fps;
         float temp_speed = (play_speed == 0.0f) ?
             audio_stretchfactor : play_speed;
-        frame_interval = (int)(1000000.0f / video_frame_rate / temp_speed);
+        SetFrameInterval(kScan_Progressive, 1.0 / (video_frame_rate * temp_speed));
     }
 
     if (videoOutput)
@@ -2312,6 +2315,33 @@ float NuppelVideoPlayer::WarpFactor(void)
     return divergence;
 }
 
+void NuppelVideoPlayer::SetFrameInterval(FrameScanType scan, double frame_period)
+{
+    frame_interval = (int)(1000000.0f * frame_period + 0.5f);
+    avsync_predictor = 0;
+    avsync_predictor_enabled = false;
+
+    VERBOSE(VB_PLAYBACK, LOC + QString("SetFrameInterval ps:%1 scan:%2 usesmoothsync:%3")
+            .arg(play_speed).arg(scan).arg(usesmoothsync)
+           );
+    //if (play_speed <= 1 || play_speed > 2 || scan != kScan_Progressive || !usesmoothsync)
+    if (play_speed < 1 || play_speed > 2 || refreshrate <= 0 || !usesmoothsync)
+        return;
+
+    avsync_predictor_enabled = ((frame_interval-(frame_interval/200)) < refreshrate);
+}
+
+void NuppelVideoPlayer::ResetAVSync(void)
+{
+    avsync_avg = 0;
+    avsync_oldavg = 0;
+    avsync_predictor = 0;
+    prevtc = 0;
+    warpfactor = 1.0f;
+    warpfactor_avg = 1.0f;
+    VERBOSE(VB_PLAYBACK+VB_TIMESTAMP, LOC + "A/V sync reset");
+}
+
 void NuppelVideoPlayer::InitAVSync(void)
 {
     videosync->Start();
@@ -2333,7 +2363,9 @@ void NuppelVideoPlayer::InitAVSync(void)
         VERBOSE(VB_GENERAL, msg);
         msg = QString("Refresh rate: %1, frame interval: %2")
                        .arg(refreshrate).arg(frame_interval);
-        VERBOSE(VB_PLAYBACK, msg);
+        VERBOSE(VB_PLAYBACK, LOC + msg);
+
+        SetFrameInterval(m_scan, 1.0 / (video_frame_rate * play_speed));
 
         // try to get preferential scheduling, but ignore if we fail to.
         myth_nice(-19);
@@ -2382,13 +2414,33 @@ void NuppelVideoPlayer::AVSync(void)
         ps = kScan_Progressive;
 
     bool dropframe = false;
+    QString dbg;
+
+    if (avsync_predictor_enabled)
+    {
+        avsync_predictor += frame_interval;
+        if (avsync_predictor >= refreshrate)
+        {
+            int refreshperiodsinframe = avsync_predictor/refreshrate;
+            avsync_predictor -= refreshrate * refreshperiodsinframe;
+        }
+        else
+        {
+            dropframe = true;
+            dbg = "A/V predict drop frame, ";
+        }
+    }
+
     if (diverge < -MAXDIVERGE)
     {
         dropframe = true;
         // If video is way behind of audio, adjust for it...
-        QString dbg = QString("Video is %1 frames behind audio (too slow), ")
+        dbg = QString("Video is %1 frames behind audio (too slow), ")
             .arg(-diverge);
+    }
 
+    if (dropframe)
+    {
         // Reset A/V Sync
         lastsync = true;
 
@@ -2416,16 +2468,16 @@ void NuppelVideoPlayer::AVSync(void)
         if (buffer)
             videoOutput->PrepareFrame(buffer, ps);
 
-        VERBOSE(VB_PLAYBACK|VB_TIMESTAMP, QString("AVSync waitforframe %1 %2")
+        VERBOSE(VB_PLAYBACK|VB_TIMESTAMP, LOC + QString("AVSync waitforframe %1 %2")
                 .arg(avsync_adjustment).arg(m_double_framerate));
         videosync->WaitForFrame(avsync_adjustment + repeat_delay);
-        VERBOSE(VB_PLAYBACK|VB_TIMESTAMP, "AVSync show");
+        VERBOSE(VB_PLAYBACK|VB_TIMESTAMP, LOC + "AVSync show");
         if (!resetvideo)
             videoOutput->Show(ps);
 
         if (videoOutput->IsErrored())
         {
-            VERBOSE(VB_IMPORTANT, "NVP: Error condition detected "
+            VERBOSE(VB_IMPORTANT, LOC + "Error condition detected "
                     "in videoOutput after Show(), aborting playback.");
             SetErrored(QObject::tr("Serious error detected in Video Output"));
             return;
@@ -2474,7 +2526,7 @@ void NuppelVideoPlayer::AVSync(void)
         repeat_delay = frame_interval * buffer->repeat_pict * 0.5;
 
         if (repeat_delay)
-            VERBOSE(VB_TIMESTAMP, QString("A/V repeat_pict, adding %1 repeat "
+            VERBOSE(VB_TIMESTAMP, LOC + QString("A/V repeat_pict, adding %1 repeat "
                     "delay").arg(repeat_delay));
     }
     else
@@ -2484,7 +2536,7 @@ void NuppelVideoPlayer::AVSync(void)
 
     if (output_jmeter && output_jmeter->RecordCycleTime())
     {
-        VERBOSE(VB_PLAYBACK+VB_TIMESTAMP, QString("A/V avsync_delay: %1, "
+        VERBOSE(VB_PLAYBACK+VB_TIMESTAMP, LOC + QString("A/V avsync_delay: %1, "
                 "avsync_avg: %2, warpfactor: %3, warpfactor_avg: %4")
                 .arg(avsync_delay / 1000).arg(avsync_avg / 1000)
                 .arg(warpfactor).arg(warpfactor_avg));
@@ -2500,7 +2552,9 @@ void NuppelVideoPlayer::AVSync(void)
         // by cutting the frame rate in half for the length of this frame
 
 #ifdef NEW_AVSYNC
-        avsync_adjustment = refreshrate;
+        //avsync_adjustment = refreshrate;
+        avsync_adjustment = frame_interval;
+        //avsync_adjustment = frame_interval*(((int)MAXDIVERGE)-1);
 #else
         avsync_adjustment = frame_interval;
 #endif
@@ -2516,35 +2570,41 @@ void NuppelVideoPlayer::AVSync(void)
         long long currentaudiotime = audioOutput->GetAudiotime();
         audio_lock.unlock();
 #if 1
-        VERBOSE(VB_PLAYBACK+VB_TIMESTAMP, QString(
+        VERBOSE(VB_PLAYBACK+VB_TIMESTAMP, LOC + QString(
                     "A/V timecodes audio %1 video %2 frameinterval %3 "
-                    "avdel %4 avg %5 tcoffset %6")
+                    "avdel %4 avg %5 tcoffset %6"
+                    " avp %7 avpen %8"
+                    )
                 .arg(currentaudiotime)
                 .arg(buffer->timecode)
                 .arg(frame_interval)
                 .arg(buffer->timecode - currentaudiotime)
                 .arg(avsync_avg)
                 .arg(tc_wrap[TC_AUDIO])
+                .arg(avsync_predictor)
+                .arg(avsync_predictor_enabled)
                  );
 #endif
         if (currentaudiotime != 0 && buffer->timecode != 0)
         { // currentaudiotime == 0 after a seek
             // The time at the start of this frame (ie, now) is given by
             // last->timecode
-            int delta = (int)((buffer->timecode - prevtc)/play_speed) - (frame_interval / 1000);
-            prevtc = buffer->timecode;
-            //cerr << delta << " ";
-
-            // If the timecode is off by a frame (dropped frame) wait to sync
-            if (delta > (int) frame_interval / 1200 &&
-                delta < (int) frame_interval / 1000 * 3 &&
-                prevrp == 0)
+            if (prevtc != 0)
             {
-                //cerr << "+ ";
-                videosync->AdvanceTrigger();
-                if (m_double_framerate)
+                int delta = (int)((buffer->timecode - prevtc)/play_speed) - (frame_interval / 1000);
+                // If the timecode is off by a frame (dropped frame) wait to sync
+                if (delta > (int) frame_interval / 1200 &&
+                    delta < (int) frame_interval / 1000 * 3 &&
+                    prevrp == 0)
+                {
+                    //cerr << "+ ";
+                    VERBOSE(VB_PLAYBACK+VB_TIMESTAMP, LOC + QString("A/V delay %1").arg(delta));
                     videosync->AdvanceTrigger();
+                    if (m_double_framerate)
+                        videosync->AdvanceTrigger();
+                }
             }
+            prevtc = buffer->timecode;
             prevrp = buffer->repeat_pict;
 
             avsync_delay = (buffer->timecode - currentaudiotime) * 1000;//usec
@@ -2557,15 +2617,17 @@ void NuppelVideoPlayer::AVSync(void)
                the video by one interlaced field (1/2 frame) */
             if (!lastsync)
             {
-                if (avsync_avg > frame_interval * 3 / 2)
+                if (avsync_avg > frame_interval + refreshrate)
                 {
-                    avsync_adjustment = refreshrate;
+                    avsync_adjustment += frame_interval;
                     lastsync = true;
+                    VERBOSE(VB_PLAYBACK+VB_TIMESTAMP, LOC + "A/V avg high extend");
                 }
-                else if (avsync_avg < 0 - frame_interval * 3 / 2)
+                else if (avsync_avg < 0 - frame_interval - refreshrate)
                 {
-                    avsync_adjustment = -refreshrate;
+                    avsync_adjustment -= frame_interval;
                     lastsync = true;
+                    VERBOSE(VB_PLAYBACK+VB_TIMESTAMP, LOC + "A/V avg high skip");
                 }
             }
             else
@@ -2573,12 +2635,14 @@ void NuppelVideoPlayer::AVSync(void)
         }
         else
         {
-            avsync_avg = 0;
-            avsync_oldavg = 0;
+            ResetAVSync();
         }
     }
     else
+    {
         audio_lock.unlock();
+        VERBOSE(VB_PLAYBACK+VB_TIMESTAMP, LOC + QString("A/V no sync proc ns:%1 ao:%2").arg(normal_speed).arg(audioOutput != NULL));
+    }
 }
 
 void NuppelVideoPlayer::DisplayPauseFrame(void)
@@ -4227,7 +4291,7 @@ void NuppelVideoPlayer::DoPause(void)
     }
 
     float temp_speed = audio_stretchfactor;
-    frame_interval = (int)(1000000.0 * ffrew_skip / video_frame_rate / temp_speed);
+    SetFrameInterval(m_scan, ffrew_skip / (video_frame_rate * temp_speed));
     VERBOSE(VB_PLAYBACK, QString("rate: %1 speed: %2 skip: %3 = interval %4")
                                  .arg(video_frame_rate).arg(temp_speed)
                                  .arg(ffrew_skip).arg(frame_interval));
@@ -4289,8 +4353,7 @@ void NuppelVideoPlayer::DoPlay(void)
         ClearAfterSeek();
     }
 
-    frame_interval = (int) (1000000.0f * ffrew_skip / video_frame_rate /
-                            play_speed);
+    SetFrameInterval(m_scan, ffrew_skip / (video_frame_rate * play_speed));
 
     VERBOSE(VB_PLAYBACK, LOC + "DoPlay: " +
             QString("rate: %1 speed: %2 skip: %3 => new interval %4")
@@ -4688,6 +4751,7 @@ void NuppelVideoPlayer::ClearAfterSeek(bool clearvideobuffers)
         savedAudioTimecodeOffset = 0;
     }
 
+    ResetAVSync();
     SetPrebuffering(true);
     audio_lock.lock();
     if (audioOutput)
diff --git a/mythtv/libs/libmythtv/NuppelVideoPlayer.h b/mythtv/libs/libmythtv/NuppelVideoPlayer.h
index d19ff73..27676e4 100644
--- a/mythtv/libs/libmythtv/NuppelVideoPlayer.h
+++ b/mythtv/libs/libmythtv/NuppelVideoPlayer.h
@@ -519,6 +519,8 @@ class MPUBLIC NuppelVideoPlayer : public CC608Reader, public CC708Reader
     float WarpFactor(void);
     void  WrapTimecode(long long &timecode, TCTypes tc_type);
     void  InitAVSync(void);
+    void  ResetAVSync(void);
+    void  SetFrameInterval(FrameScanType scan, double speed);
     void  AVSync(void);
     void  FallbackDeint(void);
     void  CheckExtraAudioDecode(void);
@@ -805,6 +807,9 @@ class MPUBLIC NuppelVideoPlayer : public CC608Reader, public CC708Reader
     int        avsync_adjustment;
     int        avsync_avg;
     int        avsync_oldavg;
+    bool       usesmoothsync;
+    int        avsync_predictor;
+    bool       avsync_predictor_enabled;
     int        refreshrate;
     bool       lastsync;
     bool       m_playing_slower;
diff --git a/mythtv/libs/libmythtv/avformatdecoder.cpp b/mythtv/libs/libmythtv/avformatdecoder.cpp
index 0ba5d33..97e085a 100644
--- a/mythtv/libs/libmythtv/avformatdecoder.cpp
+++ b/mythtv/libs/libmythtv/avformatdecoder.cpp
@@ -481,6 +481,7 @@ AvFormatDecoder::AvFormatDecoder(NuppelVideoPlayer *parent,
       start_code_state(0xffffffff),
       lastvpts(0),                  lastapts(0),
       lastccptsu(0),
+      firstvpts(0),                 firstvptsinuse(false),
       using_null_videoout(use_null_videoout),
       video_codec_id(kCodec_NONE),
       no_hardware_decoders(no_hardware_decode),
@@ -929,6 +930,12 @@ void AvFormatDecoder::SeekReset(long long newKey, uint skipFrames,
         if (decoded_video_frame)
             GetNVP()->DiscardVideoFrame(decoded_video_frame);
     }
+
+    if (doflush)
+    {
+        firstvpts = 0;
+        firstvptsinuse = true;
+    }
 }
 
 void AvFormatDecoder::Reset(bool reset_video_data, bool seek_reset)
@@ -2929,7 +2936,9 @@ void AvFormatDecoder::MpegPreProcessPkt(AVStream *stream, AVPacket *pkt)
 
                 gopset = false;
                 prevgoppos = 0;
+                firstvpts =
                 lastapts = lastvpts = lastccptsu = 0;
+                firstvptsinuse = true;
 
                 // fps debugging info
                 float avFPS = normalized_fps(stream, context);
@@ -3039,7 +3048,9 @@ bool AvFormatDecoder::H264PreProcessPkt(AVStream *stream, AVPacket *pkt)
 
             gopset = false;
             prevgoppos = 0;
+            firstvpts =
             lastapts = lastvpts = lastccptsu = 0;
+            firstvptsinuse = true;
 
             // fps debugging info
             float avFPS = normalized_fps(stream, context);
@@ -3261,6 +3272,8 @@ bool AvFormatDecoder::ProcessVideoPacket(AVStream *curstream, AVPacket *pkt)
     framesPlayed++;
 
     lastvpts = temppts;
+    if (!firstvpts && firstvptsinuse)
+        firstvpts = temppts;
 
     return true;
 }
@@ -4029,6 +4042,16 @@ bool AvFormatDecoder::ProcessAudioPacket(AVStream *curstream, AVPacket *pkt,
                 skipaudio = false;
         }
 
+        // skip any audio frames preceding first video frame
+        if (firstvptsinuse && firstvpts && (lastapts < firstvpts))
+        {
+            VERBOSE(VB_PLAYBACK+VB_TIMESTAMP,
+                LOC + QString("discarding early audio timecode %1 %2 %3")
+                .arg(pkt->pts).arg(pkt->dts).arg(lastapts));
+            break;
+        }
+        firstvptsinuse = false;
+
         avcodeclock->lock();
         data_size = 0;
 
diff --git a/mythtv/libs/libmythtv/avformatdecoder.h b/mythtv/libs/libmythtv/avformatdecoder.h
index 90b8f58..f48bc18 100644
--- a/mythtv/libs/libmythtv/avformatdecoder.h
+++ b/mythtv/libs/libmythtv/avformatdecoder.h
@@ -262,6 +262,8 @@ class AvFormatDecoder : public DecoderBase
     long long lastvpts;
     long long lastapts;
     long long lastccptsu;
+    long long firstvpts;
+    bool      firstvptsinuse;
 
     bool using_null_videoout;
     MythCodecID video_codec_id;
diff --git a/mythtv/libs/libmythtv/vsync.cpp b/mythtv/libs/libmythtv/vsync.cpp
index 060402d..2d9691d 100644
--- a/mythtv/libs/libmythtv/vsync.cpp
+++ b/mythtv/libs/libmythtv/vsync.cpp
@@ -360,7 +360,7 @@ void DRMVideoSync::WaitForFrame(int sync_delay)
     if (m_delay > 0)
     {
         // Wait for any remaining retrace intervals in one pass.
-        int n = m_delay / m_refresh_interval + 1;
+        int n = (m_delay + m_refresh_interval - 1) / m_refresh_interval;
 
         drm_wait_vblank_t blank;
         blank.request.type = DRM_VBLANK_RELATIVE;
@@ -533,7 +533,7 @@ void OpenGLVideoSync::WaitForFrame(int sync_delay)
     // Wait for any remaining retrace intervals in one pass.
     if (m_delay > 0)
     {
-        uint n = m_delay / m_refresh_interval + 1;
+        uint n = (m_delay + m_refresh_interval - 1) / m_refresh_interval;
         err = gMythGLXWaitVideoSyncSGI((n+1), (frameNum+n)%(n+1), &frameNum);
         checkGLSyncError(msg2, err);
         m_delay = CalcDelay();
