MythTV  master
mythplayeravsync.cpp
Go to the documentation of this file.
1 // Qt
2 #include <QThread>
3 
4 // MythTV
6 #include "audioplayer.h"
7 #include "mythplayeravsync.h"
8 
9 #define LOC QString("AVSync: ")
10 
12 {
13  m_avTimer.start();
14  InitAVSync();
15 }
16 
18 {
19  m_rtcBase = 0us;
22  m_lastFix = 0.0;
23  m_avsyncAvg = 0;
24  m_shortFrameDeltas = 0us;
25  LOG(VB_PLAYBACK | VB_TIMESTAMP, LOG_INFO, LOC + "Reset");
26 }
27 
28 void MythPlayerAVSync::WaitForFrame(std::chrono::microseconds FrameDue)
29 {
30  auto unow = std::chrono::microseconds(m_avTimer.nsecsElapsed() / 1000);
31  auto delay = FrameDue - unow;
32  if (delay > 0us)
33  QThread::usleep(delay.count());
34 }
35 
37 {
39  Audio->Pause(true);
40  m_rtcBase = 0us;
41 }
42 
44 {
46  Audio->Pause(false);
47 }
48 
50 {
51  Map.insert("avsync", QObject::tr("%1 ms").arg(m_avsyncAvg / 1000));
52 }
53 
54 static constexpr std::chrono::microseconds AVSYNC_MAX_LATE { 10s };
55 std::chrono::microseconds MythPlayerAVSync::AVSync(AudioPlayer *Audio, MythVideoFrame *Frame,
56  std::chrono::microseconds FrameInterval, float PlaySpeed,
57  bool HaveVideo, bool Force)
58 {
59  // If the frame interval is less than the refresh interval, we
60  // have to drop frames. Do that here deterministically. This
61  // produces much smoother playback than doing in the normal, A/V,
62  // sync code below. It is done by tracking the difference between
63  // the refresh and frame intervals. When the accumlated
64  // difference exceeds the refresh interval, drop the frame.
65  if (FrameInterval < m_refreshInterval)
66  {
67  m_shortFrameDeltas += (m_refreshInterval - FrameInterval);
69  {
70  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Dropping short frame"));
72  return -1ms;
73  }
74  }
75 
76  std::chrono::milliseconds videotimecode = 0ms;
77  bool dropframe = false;
78  bool pause_audio = false;
79  std::chrono::microseconds framedue = 0us;
80  std::chrono::milliseconds audio_adjustment = 0ms;
81  std::chrono::microseconds unow = 0ms;
82  std::chrono::microseconds lateness = 0us;
83  bool reset = false;
84  auto intervalms = duration_cast<std::chrono::milliseconds>(FrameInterval);
85  // controller gain
86  static float const s_av_control_gain = 0.4F;
87  // time weighted exponential filter coefficient
88  static float const s_sync_fc = 0.9F;
89 
91  m_rtcBase = 0us;
92 
93  while (framedue == 0us)
94  {
95  if (Frame)
96  {
97  videotimecode = std::chrono::milliseconds(Frame->m_timecode.count() & 0x0000ffffffffffff);
98  // Detect bogus timecodes from DVD and ignore them.
99  if (videotimecode != Frame->m_timecode)
100  videotimecode = m_maxTcVal;
101  }
102 
103  unow = std::chrono::microseconds(m_avTimer.nsecsElapsed() / 1000);
104 
105  if (Force)
106  {
107  framedue = unow + FrameInterval;
108  break;
109  }
110 
111  // first time or after a seek - setup of m_rtcBase
112  if (m_rtcBase == 0us)
113  {
114  // cater for DVB radio
115  if (videotimecode == 0ms)
116  videotimecode = Audio->GetAudioTime();
117 
118  // cater for data only streams (i.e. MHEG)
119  bool dataonly = !Audio->HasAudioIn() && !HaveVideo;
120 
121  // On first frame we get nothing, so exit out.
122  // FIXME - does this mean we skip the first frame? Should be avoidable.
123  if (videotimecode == 0ms && !dataonly)
124  return 0us;
125 
126  m_rtcBase = unow - chronodivide<std::chrono::microseconds>(videotimecode, PlaySpeed);
127  m_maxTcVal = 0ms;
128  m_maxTcFrames = 0;
129  m_numDroppedFrames = 0;
130  }
131 
132  if (videotimecode == 0ms)
133  videotimecode = m_maxTcVal + intervalms;
134  std::chrono::milliseconds tcincr = videotimecode - m_maxTcVal;
135  if (tcincr > 0ms || tcincr < -100ms)
136  {
137  m_maxTcVal = videotimecode;
138  m_maxTcFrames = 0;
139  }
140  else
141  {
142  m_maxTcFrames++;
143  videotimecode = m_maxTcVal + m_maxTcFrames * intervalms;
144  }
145 
146  if (PlaySpeed > 0.0F)
147  framedue = m_rtcBase + chronodivide<std::chrono::microseconds>(videotimecode, PlaySpeed);
148  else
149  framedue = unow + FrameInterval / 2;
150 
151  lateness = unow - framedue;
152  dropframe = false;
153  if (lateness > 30ms)
154  dropframe = m_numDroppedFrames < 10;
155 
156  if (lateness <= 30ms && m_priorAudioTimecode > 0ms && m_priorVideoTimecode > 0ms)
157  {
158  // Get video in sync with audio
159  audio_adjustment = m_priorAudioTimecode - m_priorVideoTimecode;
160  // If there is excess audio - throw it away.
161  if (audio_adjustment < -200ms
163  {
164  Audio->Reset();
165  audio_adjustment = 0ms;
166  }
167  int sign = audio_adjustment < 0ms ? -1 : 1;
168  float fix_amount_ms = (m_lastFix * s_sync_fc + (1 - s_sync_fc) * audio_adjustment.count()) * sign * s_av_control_gain;
169  m_lastFix = fix_amount_ms * sign;
170  m_rtcBase -= microsecondsFromFloat(1000 * fix_amount_ms * sign / PlaySpeed);
171 
172  if ((audio_adjustment * sign) > intervalms)
173  {
174  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Audio %1 by %2 ms")
175  .arg(audio_adjustment > 0ms ? "ahead" : "behind").arg(abs(audio_adjustment.count())));
176  }
177  if (audio_adjustment > 200ms)
178  pause_audio = true;
179  }
180 
181  // sanity check - reset m_rtcBase if time codes have gone crazy.
182  if ((lateness > AVSYNC_MAX_LATE || lateness < - AVSYNC_MAX_LATE))
183  {
184  framedue = 0us;
185  m_rtcBase = 0us;
186  if (reset)
187  {
188  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Resetting: lateness %1").arg(lateness.count()));
189  return -1us;
190  }
191  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Resetting: lateness = %1").arg(lateness.count()));
192  reset = true;
193  }
194  }
195  m_priorVideoTimecode = videotimecode;
196  m_dispTimecode = videotimecode;
197 
198  m_avsyncAvg = static_cast<int>(m_lastFix * 1000 / s_av_control_gain);
199 
200  if (!pause_audio && m_avsyncAudioPaused)
201  {
202  // If the audio was paused due to playing too close to live,
203  // don't unpause it until the video catches up. This helps to
204  // quickly achieve smooth playback.
206  || audio_adjustment < 0ms)
207  {
209  Audio->Pause(false);
210  }
211  }
212  else if (pause_audio && !m_avsyncAudioPaused)
213  {
215  Audio->Pause(true);
216  }
217 
218  // get time codes for calculating difference next time
220 
221  if (dropframe)
222  {
224  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Dropping frame: Video is behind by %1ms")
225  .arg(duration_cast<std::chrono::milliseconds>(lateness).count()));
226  return -1us;
227  }
228 
229  m_numDroppedFrames = 0;
230 
231  LOG(VB_PLAYBACK | VB_TIMESTAMP, LOG_INFO, LOC +
232  QString("A/V timecodes audio=%1 video=%2 frameinterval=%3 audioadj=%4 unow=%5 udue=%6 ")
233  .arg(m_priorAudioTimecode.count()).arg(m_priorVideoTimecode.count()).arg(FrameInterval.count())
234  .arg(audio_adjustment.count()).arg(unow.count()).arg(framedue.count()));
235  return framedue;
236 }
MythPlayerAVSync::m_priorVideoTimecode
std::chrono::milliseconds m_priorVideoTimecode
Definition: mythplayeravsync.h:53
AudioPlayer::Reset
void Reset(void)
Definition: audioplayer.cpp:86
AudioPlayer
Definition: audioplayer.h:22
Frame
Definition: zmdefines.h:102
microsecondsFromFloat
std::enable_if_t< std::is_floating_point_v< T >, std::chrono::microseconds > microsecondsFromFloat(T value)
Helper function for convert a floating point number to a duration.
Definition: mythchrono.h:102
MythPlayerAVSync::m_priorAudioTimecode
std::chrono::milliseconds m_priorAudioTimecode
Definition: mythplayeravsync.h:52
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
InfoMap
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
mythlogging.h
MythPlayerAVSync::MythPlayerAVSync
MythPlayerAVSync()
Definition: mythplayeravsync.cpp:11
LOC
#define LOC
Definition: mythplayeravsync.cpp:9
AVSYNC_MAX_LATE
static constexpr std::chrono::microseconds AVSYNC_MAX_LATE
Definition: mythplayeravsync.cpp:54
MythPlayerAVSync::m_numDroppedFrames
int m_numDroppedFrames
Definition: mythplayeravsync.h:55
AudioPlayer::HasAudioIn
bool HasAudioIn(void) const
Definition: audioplayer.h:51
MythPlayerAVSync::ResetAVSyncForLiveTV
void ResetAVSyncForLiveTV(AudioPlayer *Audio)
Definition: mythplayeravsync.cpp:36
MythPlayerAVSync::m_maxTcVal
std::chrono::milliseconds m_maxTcVal
Definition: mythplayeravsync.h:51
MythPlayerAVSync::m_rtcBase
std::chrono::microseconds m_rtcBase
Definition: mythplayeravsync.h:50
MythPlayerAVSync::SetAVSyncMusicChoice
void SetAVSyncMusicChoice(AudioPlayer *Audio)
Definition: mythplayeravsync.cpp:43
MythPlayerAVSync::WaitForFrame
void WaitForFrame(std::chrono::microseconds FrameDue)
Definition: mythplayeravsync.cpp:28
kAVSyncAudioNotPaused
@ kAVSyncAudioNotPaused
Definition: mythplayeravsync.h:15
MythPlayerAVSync::GetAVSyncData
void GetAVSyncData(InfoMap &Map) const
Definition: mythplayeravsync.cpp:49
MythPlayerAVSync::InitAVSync
void InitAVSync()
Definition: mythplayeravsync.cpp:17
MythPlayerAVSync::m_avsyncAvg
int m_avsyncAvg
Definition: mythplayeravsync.h:48
kAVSyncAudioPaused
@ kAVSyncAudioPaused
Definition: mythplayeravsync.h:16
kAVSyncAudioPausedLiveTV
@ kAVSyncAudioPausedLiveTV
Definition: mythplayeravsync.h:17
MythPlayerAVSync::m_dispTimecode
std::chrono::milliseconds m_dispTimecode
Definition: mythplayeravsync.h:49
MythPlayerAVSync::m_maxTcFrames
int m_maxTcFrames
Definition: mythplayeravsync.h:54
audioplayer.h
AudioPlayer::Pause
bool Pause(bool pause)
Definition: audioplayer.cpp:181
MythPlayerAVSync::m_shortFrameDeltas
std::chrono::microseconds m_shortFrameDeltas
Definition: mythplayeravsync.h:58
MythVideoFrame
Definition: mythframe.h:87
MythPlayerAVSync::AVSync
std::chrono::microseconds AVSync(AudioPlayer *Audio, MythVideoFrame *Frame, std::chrono::microseconds FrameInterval, float PlaySpeed, bool HaveVideo, bool Force)
Definition: mythplayeravsync.cpp:55
MythPlayerAVSync::m_refreshInterval
std::chrono::microseconds m_refreshInterval
Definition: mythplayeravsync.h:57
AudioPlayer::GetAudioTime
std::chrono::milliseconds GetAudioTime(void)
Definition: audioplayer.cpp:338
MythPlayerAVSync::m_avsyncAudioPaused
AVSyncAudioPausedType m_avsyncAudioPaused
Definition: mythplayeravsync.h:47
mythplayeravsync.h
MythPlayerAVSync::m_lastFix
float m_lastFix
Definition: mythplayeravsync.h:56
MythPlayerAVSync::m_avTimer
QElapsedTimer m_avTimer
Definition: mythplayeravsync.h:46