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) |
14 | : m_timeController(tc), |
15 | m_lastFrameEnd(tc.currentPosition()), |
16 | m_lastPosition(m_lastFrameEnd.get()), |
17 | m_seekPos(tc.currentPosition().get()) |
18 | { |
19 | } |
20 | |
21 | void 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 | |
29 | TrackPosition Renderer::seekPosition() const |
30 | { |
31 | return TrackPosition(m_seekPos); |
32 | } |
33 | |
34 | TrackPosition Renderer::lastPosition() const |
35 | { |
36 | return TrackPosition(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 = RealClock::now(); |
59 | scheduleNextStep(); |
60 | } |
61 | }); |
62 | } |
63 | |
64 | bool Renderer::isStepForced() const |
65 | { |
66 | return m_isStepForced; |
67 | } |
68 | |
69 | void 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 | |
78 | void Renderer::onFinalFrameReceived() |
79 | { |
80 | render({}); |
81 | } |
82 | |
83 | void 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 | |
100 | void Renderer::onPauseChanged() |
101 | { |
102 | m_timeController.setPaused(isPaused()); |
103 | PlaybackEngineObject::onPauseChanged(); |
104 | } |
105 | |
106 | bool 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 | |
117 | float Renderer::playbackRate() const |
118 | { |
119 | return m_timeController.playbackRate(); |
120 | } |
121 | |
122 | std::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 | |
148 | bool 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 | |
158 | void 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 | |
201 | std::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 | |
207 | void 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 | |
217 | QT_END_NAMESPACE |
218 | |
219 | #include "moc_qffmpegrenderer_p.cpp" |
220 | |