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, const std::chrono::microseconds &seekPosTimeOffset)
14 : m_timeController(tc),
15 m_lastFrameEnd(tc.currentPosition()),
16 m_lastPosition(m_lastFrameEnd),
17 m_seekPos(tc.currentPosition(offset: -seekPosTimeOffset))
18{
19}
20
21void Renderer::syncSoft(TimePoint tp, qint64 trackTime)
22{
23 QMetaObject::invokeMethod(object: this, function: [this, tp, trackTime]() {
24 m_timeController.syncSoft(tp, pos: trackTime);
25 scheduleNextStep(allowDoImmediatelly: true);
26 });
27}
28
29qint64 Renderer::seekPosition() const
30{
31 return m_seekPos;
32}
33
34qint64 Renderer::lastPosition() const
35{
36 return 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 = Clock::now();
59 scheduleNextStep();
60 }
61 });
62}
63
64bool Renderer::isStepForced() const
65{
66 return m_isStepForced;
67}
68
69void Renderer::setInitialPosition(TimePoint tp, qint64 trackPos)
70{
71 QMetaObject::invokeMethod(object: this, function: [this, tp, trackPos]() {
72 Q_ASSERT(m_loopIndex == 0);
73 Q_ASSERT(m_frames.empty());
74
75 m_loopIndex = 0;
76 m_lastPosition.storeRelease(newValue: trackPos);
77 m_seekPos.storeRelease(newValue: trackPos);
78
79 m_timeController.sync(tp, pos: trackPos);
80 });
81}
82
83void Renderer::onFinalFrameReceived()
84{
85 render({});
86}
87
88void Renderer::render(Frame frame)
89{
90 const auto isFrameOutdated = frame.isValid() && frame.absoluteEnd() < seekPosition();
91
92 if (isFrameOutdated) {
93 qCDebug(qLcRenderer) << "frame outdated! absEnd:" << frame.absoluteEnd() << "absPts"
94 << frame.absolutePts() << "seekPos:" << seekPosition();
95 emit frameProcessed(frame);
96 return;
97 }
98
99 m_frames.enqueue(t: frame);
100
101 if (m_frames.size() == 1)
102 scheduleNextStep();
103}
104
105void Renderer::onPauseChanged()
106{
107 m_timeController.setPaused(isPaused());
108 PlaybackEngineObject::onPauseChanged();
109}
110
111bool Renderer::canDoNextStep() const
112{
113 return !m_frames.empty() && (m_isStepForced || PlaybackEngineObject::canDoNextStep());
114}
115
116float Renderer::playbackRate() const
117{
118 return m_timeController.playbackRate();
119}
120
121int Renderer::timerInterval() const
122{
123 if (m_frames.empty())
124 return 0;
125
126 auto calculateInterval = [](const TimePoint &nextTime) {
127 using namespace std::chrono;
128
129 const auto delay = nextTime - Clock::now();
130 return std::max(a: 0, b: static_cast<int>(duration_cast<milliseconds>(d: delay).count()));
131 };
132
133 if (m_explicitNextFrameTime)
134 return calculateInterval(*m_explicitNextFrameTime);
135
136 if (m_frames.front().isValid())
137 return calculateInterval(m_timeController.timeFromPosition(pos: m_frames.front().absolutePts()));
138
139 if (m_lastFrameEnd > 0)
140 return calculateInterval(m_timeController.timeFromPosition(pos: m_lastFrameEnd));
141
142 return 0;
143}
144
145bool Renderer::setForceStepDone()
146{
147 if (!m_isStepForced.testAndSetOrdered(expectedValue: true, newValue: false))
148 return false;
149
150 m_explicitNextFrameTime.reset();
151 emit forceStepDone();
152 return true;
153}
154
155void Renderer::doNextStep()
156{
157 auto frame = m_frames.front();
158
159 if (setForceStepDone()) {
160 // if (frame.isValid() && frame.pts() > m_forceStepMaxPos) {
161 // scheduleNextStep(false);
162 // return;
163 // }
164 }
165
166 const auto result = renderInternal(frame);
167
168 if (result.done) {
169 m_explicitNextFrameTime.reset();
170 m_frames.dequeue();
171
172 if (frame.isValid()) {
173 m_lastPosition.storeRelease(newValue: std::max(a: frame.absolutePts(), b: lastPosition()));
174
175 // TODO: get rid of m_lastFrameEnd or m_seekPos
176 m_lastFrameEnd = frame.absoluteEnd();
177 m_seekPos.storeRelaxed(newValue: m_lastFrameEnd);
178
179 const auto loopIndex = frame.loopOffset().index;
180 if (m_loopIndex < loopIndex) {
181 m_loopIndex = loopIndex;
182 emit loopChanged(id: id(), offset: frame.loopOffset().pos, index: m_loopIndex);
183 }
184
185 emit frameProcessed(frame);
186 } else {
187 m_lastPosition.storeRelease(newValue: std::max(a: m_lastFrameEnd, b: lastPosition()));
188 }
189 } else {
190 m_explicitNextFrameTime = Clock::now() + result.recheckInterval;
191 }
192
193 setAtEnd(result.done && !frame.isValid());
194
195 scheduleNextStep(allowDoImmediatelly: false);
196}
197
198std::chrono::microseconds Renderer::frameDelay(const Frame &frame, TimePoint timePoint) const
199{
200 return std::chrono::duration_cast<std::chrono::microseconds>(
201 d: timePoint - m_timeController.timeFromPosition(pos: frame.absolutePts()));
202}
203
204void Renderer::changeRendererTime(std::chrono::microseconds offset)
205{
206 const auto now = Clock::now();
207 const auto pos = m_timeController.positionFromTime(tp: now);
208 m_timeController.sync(tp: now + offset, pos);
209 emit synchronized(id: id(), tp: now + offset, pos);
210}
211
212} // namespace QFFmpeg
213
214QT_END_NAMESPACE
215
216#include "moc_qffmpegrenderer_p.cpp"
217

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