MythTV  master
mythplayeravsync.cpp
Go to the documentation of this file.
1 // Qt
2 #include <QThread>
3 
4 // MythTV
5 #include "mythlogging.h"
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  LOG(VB_PLAYBACK | VB_TIMESTAMP, LOG_INFO, LOC + "Reset");
25 }
26 
27 void MythPlayerAVSync::WaitForFrame(std::chrono::microseconds FrameDue)
28 {
29  auto unow = std::chrono::microseconds(m_avTimer.nsecsElapsed() / 1000);
30  auto delay = FrameDue - unow;
31  if (delay > 0us)
32  QThread::usleep(delay.count());
33 }
34 
35 std::chrono::milliseconds& MythPlayerAVSync::DisplayTimecode()
36 {
37  return m_dispTimecode;
38 }
39 
41 {
42  m_rtcBase = 0us;
43 }
44 
46 {
47  return m_avsyncAudioPaused;
48 }
49 
51 {
52  m_avsyncAudioPaused = Pause;
53 }
54 
56 {
57  bool result = m_rtcBase != 0us;
58  Audio->Pause(true);
59  m_avsyncAudioPaused = true;
60  m_rtcBase = 0us;
61  return result;
62 }
63 
65 {
66  m_avsyncAudioPaused = false;
67  Audio->Pause(false);
68 }
69 
71 {
72  Map.insert("avsync", QObject::tr("%1 ms").arg(m_avsyncAvg / 1000));
73 }
74 
75 static constexpr std::chrono::microseconds AVSYNC_MAX_LATE { 10s };
76 std::chrono::microseconds MythPlayerAVSync::AVSync(AudioPlayer *Audio, MythVideoFrame *Frame,
77  std::chrono::microseconds FrameInterval, float PlaySpeed,
78  bool HaveVideo, bool Force)
79 {
80  std::chrono::milliseconds videotimecode = 0ms;
81  bool dropframe = false;
82  bool pause_audio = false;
83  std::chrono::microseconds framedue = 0us;
84  std::chrono::milliseconds audio_adjustment = 0ms;
85  std::chrono::microseconds unow = 0ms;
86  std::chrono::microseconds lateness = 0us;
87  bool reset = false;
88  auto intervalms = duration_cast<std::chrono::milliseconds>(FrameInterval);
89  // controller gain
90  static float const s_av_control_gain = 0.4F;
91  // time weighted exponential filter coefficient
92  static float const s_sync_fc = 0.9F;
93 
94  while (framedue == 0us)
95  {
96  if (Frame)
97  {
98  videotimecode = std::chrono::milliseconds(Frame->m_timecode.count() & 0x0000ffffffffffff);
99  // Detect bogus timecodes from DVD and ignore them.
100  if (videotimecode != Frame->m_timecode)
101  videotimecode = m_maxTcVal;
102  }
103 
104  unow = std::chrono::microseconds(m_avTimer.nsecsElapsed() / 1000);
105 
106  if (Force)
107  {
108  framedue = unow + FrameInterval;
109  break;
110  }
111 
112  // first time or after a seek - setup of m_rtcBase
113  if (m_rtcBase == 0us)
114  {
115  // cater for DVB radio
116  if (videotimecode == 0ms)
117  videotimecode = Audio->GetAudioTime();
118 
119  // cater for data only streams (i.e. MHEG)
120  bool dataonly = !Audio->HasAudioIn() && !HaveVideo;
121 
122  // On first frame we get nothing, so exit out.
123  // FIXME - does this mean we skip the first frame? Should be avoidable.
124  if (videotimecode == 0ms && !dataonly)
125  return 0us;
126 
127  m_rtcBase = unow - chronodivide<std::chrono::microseconds>(videotimecode, PlaySpeed);
128  m_maxTcVal = 0ms;
129  m_maxTcFrames = 0;
130  m_numDroppedFrames = 0;
131  }
132 
133  if (videotimecode == 0ms)
134  videotimecode = m_maxTcVal + intervalms;
135  std::chrono::milliseconds tcincr = videotimecode - m_maxTcVal;
136  if (tcincr > 0ms || tcincr < -100ms)
137  {
138  m_maxTcVal = videotimecode;
139  m_maxTcFrames = 0;
140  }
141  else
142  {
143  m_maxTcFrames++;
144  videotimecode = m_maxTcVal + m_maxTcFrames * intervalms;
145  }
146 
147  if (PlaySpeed > 0.0F)
148  framedue = m_rtcBase + chronodivide<std::chrono::microseconds>(videotimecode, PlaySpeed);
149  else
150  framedue = unow + FrameInterval / 2;
151 
152  lateness = unow - framedue;
153  dropframe = false;
154  if (lateness > 30ms)
155  dropframe = m_numDroppedFrames < 10;
156 
157  if (lateness <= 30ms && m_priorAudioTimecode > 0ms && m_priorVideoTimecode > 0ms)
158  {
159  // Get video in sync with audio
160  audio_adjustment = m_priorAudioTimecode - m_priorVideoTimecode;
161  // If there is excess audio - throw it away.
162  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  m_avsyncAudioPaused = false;
203  Audio->Pause(false);
204  }
205  else if (pause_audio && !m_avsyncAudioPaused)
206  {
207  m_avsyncAudioPaused = true;
208  Audio->Pause(true);
209  }
210 
211  // get time codes for calculating difference next time
213 
214  if (dropframe)
215  {
217  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Dropping frame: Video is behind by %1ms")
218  .arg(duration_cast<std::chrono::milliseconds>(lateness).count()));
219  return -1us;
220  }
221 
222  m_numDroppedFrames = 0;
223 
224  LOG(VB_PLAYBACK | VB_TIMESTAMP, LOG_INFO, LOC +
225  QString("A/V timecodes audio=%1 video=%2 frameinterval=%3 audioadj=%4 unow=%5 udue=%6 ")
226  .arg(m_priorAudioTimecode.count()).arg(m_priorVideoTimecode.count()).arg(FrameInterval.count())
227  .arg(audio_adjustment.count()).arg(unow.count()).arg(framedue.count()));
228  return framedue;
229 }
MythPlayerAVSync::m_priorVideoTimecode
std::chrono::milliseconds m_priorVideoTimecode
Definition: mythplayeravsync.h:40
AudioPlayer::Reset
void Reset(void)
Definition: audioplayer.cpp:83
AudioPlayer
Definition: audioplayer.h:28
Frame
Definition: zmdefines.h:93
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:39
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
MythPlayerAVSync::DisplayTimecode
std::chrono::milliseconds & DisplayTimecode()
Definition: mythplayeravsync.cpp:35
MythPlayerAVSync::ResetAVSyncForLiveTV
bool ResetAVSyncForLiveTV(AudioPlayer *Audio)
Definition: mythplayeravsync.cpp:55
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
MythPlayerAVSync::GetAVSyncAudioPause
bool GetAVSyncAudioPause() const
Definition: mythplayeravsync.cpp:45
AVSYNC_MAX_LATE
static constexpr std::chrono::microseconds AVSYNC_MAX_LATE
Definition: mythplayeravsync.cpp:75
MythPlayerAVSync::m_numDroppedFrames
int m_numDroppedFrames
Definition: mythplayeravsync.h:42
AudioPlayer::HasAudioIn
bool HasAudioIn(void) const
Definition: audioplayer.h:57
MythPlayerAVSync::m_maxTcVal
std::chrono::milliseconds m_maxTcVal
Definition: mythplayeravsync.h:38
MythPlayerAVSync::m_rtcBase
std::chrono::microseconds m_rtcBase
Definition: mythplayeravsync.h:37
MythPlayerAVSync::SetAVSyncMusicChoice
void SetAVSyncMusicChoice(AudioPlayer *Audio)
Definition: mythplayeravsync.cpp:64
MythPlayerAVSync::WaitForFrame
void WaitForFrame(std::chrono::microseconds FrameDue)
Definition: mythplayeravsync.cpp:27
MythPlayerAVSync::GetAVSyncData
void GetAVSyncData(InfoMap &Map) const
Definition: mythplayeravsync.cpp:70
MythPlayerAVSync::ResetAVSyncClockBase
void ResetAVSyncClockBase()
Definition: mythplayeravsync.cpp:40
MythPlayerAVSync::InitAVSync
void InitAVSync()
Definition: mythplayeravsync.cpp:17
MythPlayerAVSync::m_avsyncAvg
int m_avsyncAvg
Definition: mythplayeravsync.h:35
MythPlayerAVSync::m_dispTimecode
std::chrono::milliseconds m_dispTimecode
Definition: mythplayeravsync.h:36
MythPlayerAVSync::m_maxTcFrames
int m_maxTcFrames
Definition: mythplayeravsync.h:41
audioplayer.h
AudioPlayer::Pause
bool Pause(bool pause)
Definition: audioplayer.cpp:178
MythVideoFrame
Definition: mythframe.h:88
MythPlayerAVSync::AVSync
std::chrono::microseconds AVSync(AudioPlayer *Audio, MythVideoFrame *Frame, std::chrono::microseconds FrameInterval, float PlaySpeed, bool HaveVideo, bool Force)
Definition: mythplayeravsync.cpp:76
MythPlayerAVSync::SetAVSyncAudioPause
void SetAVSyncAudioPause(bool Pause)
Definition: mythplayeravsync.cpp:50
AudioPlayer::GetAudioTime
std::chrono::milliseconds GetAudioTime(void)
Definition: audioplayer.cpp:335
MythPlayerAVSync::m_avsyncAudioPaused
bool m_avsyncAudioPaused
Definition: mythplayeravsync.h:34
mythplayeravsync.h
MythPlayerAVSync::m_lastFix
float m_lastFix
Definition: mythplayeravsync.h:43
MythPlayerAVSync::m_avTimer
QElapsedTimer m_avTimer
Definition: mythplayeravsync.h:33