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 | |
7 | QT_BEGIN_NAMESPACE |
8 | |
9 | namespace QFFmpeg { |
10 | |
11 | static Q_LOGGING_CATEGORY(qLcRenderer, "qt.multimedia.ffmpeg.renderer" ); |
12 | |
13 | Renderer::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 | |
21 | void 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 | |
29 | qint64 Renderer::seekPosition() const |
30 | { |
31 | return m_seekPos; |
32 | } |
33 | |
34 | qint64 Renderer::lastPosition() const |
35 | { |
36 | return m_lastPosition; |
37 | } |
38 | |
39 | void 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 | |
48 | void 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 | |
64 | bool Renderer::isStepForced() const |
65 | { |
66 | return m_isStepForced; |
67 | } |
68 | |
69 | void 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 | |
83 | void Renderer::onFinalFrameReceived() |
84 | { |
85 | render({}); |
86 | } |
87 | |
88 | void 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 | |
105 | void Renderer::onPauseChanged() |
106 | { |
107 | m_timeController.setPaused(isPaused()); |
108 | PlaybackEngineObject::onPauseChanged(); |
109 | } |
110 | |
111 | bool Renderer::canDoNextStep() const |
112 | { |
113 | return !m_frames.empty() && (m_isStepForced || PlaybackEngineObject::canDoNextStep()); |
114 | } |
115 | |
116 | float Renderer::playbackRate() const |
117 | { |
118 | return m_timeController.playbackRate(); |
119 | } |
120 | |
121 | int 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 | |
145 | bool 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 | |
155 | void 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 | |
198 | std::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 | |
204 | void 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 | |
214 | QT_END_NAMESPACE |
215 | |
216 | #include "moc_qffmpegrenderer_p.cpp" |
217 | |