1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "playbackengine/qffmpegrenderer_p.h"
5#include <qloggingcategory.h>
6
7QT_BEGIN_NAMESPACE
8
9namespace QFFmpeg {
10
11static Q_LOGGING_CATEGORY(qLcRenderer, "qt.multimedia.ffmpeg.renderer");
12
13Renderer::Renderer(const TimeController &tc)
14 : m_timeController(tc),
15 m_lastFrameEnd(tc.currentPosition()),
16 m_lastPosition(m_lastFrameEnd.get()),
17 m_seekPos(tc.currentPosition().get())
18{
19}
20
21void Renderer::syncSoft(TimePoint tp, TrackPosition trackPos)
22{
23 QMetaObject::invokeMethod(object: this, function: [this, tp, trackPos]() {
24 m_timeController.syncSoft(tp, pos: trackPos);
25 scheduleNextStep(allowDoImmediatelly: true);
26 });
27}
28
29TrackPosition Renderer::seekPosition() const
30{
31 return TrackPosition(m_seekPos);
32}
33
34TrackPosition Renderer::lastPosition() const
35{
36 return TrackPosition(m_lastPosition);
37}
38
39void Renderer::setPlaybackRate(float rate)
40{
41 QMetaObject::invokeMethod(object: this, function: [this, rate]() {
42 m_timeController.setPlaybackRate(rate);
43 onPlaybackRateChanged();
44 scheduleNextStep();
45 });
46}
47
48void Renderer::doForceStep()
49{
50 if (m_isStepForced.testAndSetOrdered(expectedValue: false, newValue: true))
51 QMetaObject::invokeMethod(object: this, function: [this]() {
52 // maybe set m_forceStepMaxPos
53
54 if (isAtEnd()) {
55 setForceStepDone();
56 }
57 else {
58 m_explicitNextFrameTime = RealClock::now();
59 scheduleNextStep();
60 }
61 });
62}
63
64bool Renderer::isStepForced() const
65{
66 return m_isStepForced;
67}
68
69void Renderer::start(const TimeController &tc)
70{
71 QMetaObject::invokeMethod(object: this, function: [this, tc]() {
72 m_timeController = tc;
73 m_started = true;
74 scheduleNextStep();
75 });
76}
77
78void Renderer::onFinalFrameReceived()
79{
80 render({});
81}
82
83void Renderer::render(Frame frame)
84{
85 const auto isFrameOutdated = frame.isValid() && frame.absoluteEnd() < seekPosition();
86
87 if (isFrameOutdated) {
88 qCDebug(qLcRenderer) << "frame outdated! absEnd:" << frame.absoluteEnd().get() << "absPts"
89 << frame.absolutePts().get() << "seekPos:" << seekPosition().get();
90 emit frameProcessed(frame);
91 return;
92 }
93
94 m_frames.enqueue(t: frame);
95
96 if (m_frames.size() == 1)
97 scheduleNextStep();
98}
99
100void Renderer::onPauseChanged()
101{
102 m_timeController.setPaused(isPaused());
103 PlaybackEngineObject::onPauseChanged();
104}
105
106bool Renderer::canDoNextStep() const
107{
108 if (m_frames.empty())
109 return false;
110 if (m_isStepForced)
111 return true;
112 if (!m_started)
113 return false;
114 return PlaybackEngineObject::canDoNextStep();
115}
116
117float Renderer::playbackRate() const
118{
119 return m_timeController.playbackRate();
120}
121
122std::chrono::milliseconds Renderer::timerInterval() const
123{
124 using namespace std::chrono_literals;
125
126 if (m_frames.empty())
127 return 0ms;
128
129 auto calculateInterval = [](const TimePoint &nextTime) {
130 using namespace std::chrono;
131
132 const milliseconds delay = duration_cast<milliseconds>(d: nextTime - RealClock::now());
133 return std::max(a: 0ms, b: std::chrono::duration_cast<milliseconds>(d: delay));
134 };
135
136 if (m_explicitNextFrameTime)
137 return calculateInterval(*m_explicitNextFrameTime);
138
139 if (m_frames.front().isValid())
140 return calculateInterval(m_timeController.timeFromPosition(pos: m_frames.front().absolutePts()));
141
142 if (m_lastFrameEnd > TrackPosition(0))
143 return calculateInterval(m_timeController.timeFromPosition(pos: m_lastFrameEnd));
144
145 return 0ms;
146}
147
148bool Renderer::setForceStepDone()
149{
150 if (!m_isStepForced.testAndSetOrdered(expectedValue: true, newValue: false))
151 return false;
152
153 m_explicitNextFrameTime.reset();
154 emit forceStepDone();
155 return true;
156}
157
158void Renderer::doNextStep()
159{
160 auto frame = m_frames.front();
161
162 if (setForceStepDone()) {
163 // if (frame.isValid() && frame.pts() > m_forceStepMaxPos) {
164 // scheduleNextStep(false);
165 // return;
166 // }
167 }
168
169 const auto result = renderInternal(frame);
170
171 if (result.done) {
172 m_explicitNextFrameTime.reset();
173 m_frames.dequeue();
174
175 if (frame.isValid()) {
176 m_lastPosition.storeRelease(newValue: std::max(a: frame.absolutePts(), b: lastPosition()).get());
177
178 // TODO: get rid of m_lastFrameEnd or m_seekPos
179 m_lastFrameEnd = frame.absoluteEnd();
180 m_seekPos.storeRelaxed(newValue: m_lastFrameEnd.get());
181
182 const auto loopIndex = frame.loopOffset().loopIndex;
183 if (m_loopIndex < loopIndex) {
184 m_loopIndex = loopIndex;
185 emit loopChanged(id: id(), offset: frame.loopOffset().loopStartTimeUs, index: m_loopIndex);
186 }
187
188 emit frameProcessed(frame);
189 } else {
190 m_lastPosition.storeRelease(newValue: std::max(a: m_lastFrameEnd, b: lastPosition()).get());
191 }
192 } else {
193 m_explicitNextFrameTime = RealClock::now() + result.recheckInterval;
194 }
195
196 setAtEnd(result.done && !frame.isValid());
197
198 scheduleNextStep(allowDoImmediatelly: false);
199}
200
201std::chrono::microseconds Renderer::frameDelay(const Frame &frame, TimePoint timePoint) const
202{
203 return std::chrono::duration_cast<std::chrono::microseconds>(
204 d: timePoint - m_timeController.timeFromPosition(pos: frame.absolutePts()));
205}
206
207void Renderer::changeRendererTime(std::chrono::microseconds offset)
208{
209 const auto now = RealClock::now();
210 const auto pos = m_timeController.positionFromTime(tp: now);
211 m_timeController.sync(tp: now + offset, pos);
212 emit synchronized(id: id(), tp: now + offset, pos);
213}
214
215} // namespace QFFmpeg
216
217QT_END_NAMESPACE
218
219#include "moc_qffmpegrenderer_p.cpp"
220

source code of qtmultimedia/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegrenderer.cpp