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;
25 LOG(VB_PLAYBACK | VB_TIMESTAMP, LOG_INFO, LOC + "Reset");
26}
27
28void 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
54static constexpr std::chrono::microseconds AVSYNC_MAX_LATE { 10s };
55std::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;
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 {
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
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}
std::chrono::milliseconds GetAudioTime(void)
bool HasAudioIn(void) const
Definition: audioplayer.h:51
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