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