Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qffmpegmediaplayer.cpp
Go to the documentation of this file.
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
5#include "private/qplatformaudiooutput_p.h"
6#include "qvideosink.h"
7#include "qaudiooutput.h"
10#include <qiodevice.h>
11#include <qvideosink.h>
12#include <QtMultimedia/qplaybackoptions.h>
13#include <qtimer.h>
14#include <QtConcurrent/QtConcurrent>
15
16#include <qloggingcategory.h>
17
19
20namespace QFFmpeg {
21
23{
24public:
25
26 bool isCancelled() const override { return m_cancelled.load(std::memory_order_acquire); }
27
28 void cancel() { m_cancelled.store(true, std::memory_order_release); }
29
30private:
31 std::atomic_bool m_cancelled = false;
32};
33
34} // namespace QFFmpeg
35
36using namespace QFFmpeg;
37
38QFFmpegMediaPlayer::QFFmpegMediaPlayer(QMediaPlayer *player)
39 : QPlatformMediaPlayer(player)
40{
41 m_positionUpdateTimer.setInterval(50);
42 m_positionUpdateTimer.setTimerType(Qt::PreciseTimer);
43 connect(&m_positionUpdateTimer, &QTimer::timeout, this, &QFFmpegMediaPlayer::updatePosition);
44}
45
47{
48 if (m_cancelToken)
49 m_cancelToken->cancel();
50
51 m_loadMedia.waitForFinished();
52};
53
55{
56 return m_playbackEngine ? toUserDuration(m_playbackEngine->duration()).get() : 0;
57}
58
59void QFFmpegMediaPlayer::setPosition(qint64 position)
60{
61 if (mediaStatus() == QMediaPlayer::LoadingMedia)
62 return;
63
64 if (m_playbackEngine) {
65 m_playbackEngine->seek(toTrackPosition(UserTrackPosition(position)));
66 updatePosition();
67 }
68
69 mediaStatusChanged(QMediaPlayer::LoadedMedia);
70}
71
72void QFFmpegMediaPlayer::updatePosition()
73{
74 positionChanged(m_playbackEngine ? toUserPosition(m_playbackEngine->currentPosition()).get()
75 : 0);
76}
77
78void QFFmpegMediaPlayer::endOfStream()
79{
80 // stop update timer and report end position anyway
81 m_positionUpdateTimer.stop();
82 QPointer currentPlaybackEngine(m_playbackEngine.get());
83 positionChanged(duration());
84
85 // skip changing state and mediaStatus if playbackEngine has been recreated,
86 // e.g. if new media has been loaded as a response to positionChanged signal
87 if (currentPlaybackEngine)
88 stateChanged(QMediaPlayer::StoppedState);
89 if (currentPlaybackEngine)
90 mediaStatusChanged(QMediaPlayer::EndOfMedia);
91}
92
93void QFFmpegMediaPlayer::onLoopChanged()
94{
95 // report about finish and start
96 // reporting both signals is a bit contraversial
97 // but it eshures the idea of notifications about
98 // imporatant position points.
99 // Also, it ensures more predictable flow for testing.
100 positionChanged(duration());
101 positionChanged(0);
102 m_positionUpdateTimer.stop();
103 m_positionUpdateTimer.start();
104}
105
106void QFFmpegMediaPlayer::onBuffered()
107{
108 if (mediaStatus() == QMediaPlayer::BufferingMedia)
109 mediaStatusChanged(QMediaPlayer::BufferedMedia);
110}
111
113{
114 return m_bufferProgress;
115}
116
117void QFFmpegMediaPlayer::mediaStatusChanged(QMediaPlayer::MediaStatus status)
118{
119 if (mediaStatus() == status)
120 return;
121
122 const auto newBufferProgress = status == QMediaPlayer::BufferingMedia ? 0.25f // to be improved
123 : status == QMediaPlayer::BufferedMedia ? 1.f
124 : 0.f;
125
126 if (!qFuzzyCompare(newBufferProgress, m_bufferProgress)) {
127 m_bufferProgress = newBufferProgress;
128 bufferProgressChanged(newBufferProgress);
129 }
130
131 QPlatformMediaPlayer::mediaStatusChanged(status);
132}
133
138
140{
141 return m_playbackRate;
142}
143
145{
146 const float effectiveRate = std::max(static_cast<float>(rate), 0.0f);
147
148 if (qFuzzyCompare(m_playbackRate, effectiveRate))
149 return;
150
151 m_playbackRate = effectiveRate;
152
153 if (m_playbackEngine)
154 m_playbackEngine->setPlaybackRate(effectiveRate);
155
156 playbackRateChanged(effectiveRate);
157}
158
160{
161 return m_url;
162}
163
165{
166 return m_device;
167}
168
169void QFFmpegMediaPlayer::handleIncorrectMedia(QMediaPlayer::MediaStatus status)
170{
171 seekableChanged(false);
172 audioAvailableChanged(false);
173 videoAvailableChanged(false);
174 metaDataChanged();
175 mediaStatusChanged(status);
176 m_playbackEngine = nullptr;
177};
178
179void QFFmpegMediaPlayer::setMedia(const QUrl &media, QIODevice *stream)
180{
181 // Wait for previous unfinished load attempts.
182 if (m_cancelToken)
183 m_cancelToken->cancel();
184
185 m_loadMedia.waitForFinished();
186
187 m_url = media;
188 m_device = stream;
189 m_playbackEngine = nullptr;
190
191 if (media.isEmpty() && !stream) {
192 handleIncorrectMedia(QMediaPlayer::NoMedia);
193 return;
194 }
195
196 mediaStatusChanged(QMediaPlayer::LoadingMedia);
197
198 m_requestedStatus = QMediaPlayer::StoppedState;
199
200 m_cancelToken = std::make_shared<CancelToken>();
201
202 // Load media asynchronously to keep GUI thread responsive while loading media
203 m_loadMedia = QtConcurrent::run([this, media, stream, cancelToken = m_cancelToken] {
204 // On worker thread
205 const MediaDataHolder::Maybe mediaHolder =
206 MediaDataHolder::create(media, stream, playbackOptions(), cancelToken);
207
208 // Transition back to calling thread using invokeMethod because
209 // QFuture continuations back on calling thread may deadlock (QTBUG-117918)
210 QMetaObject::invokeMethod(this, [this, mediaHolder, cancelToken] {
211 setMediaAsync(mediaHolder, cancelToken);
212 });
213 });
214}
215
216void QFFmpegMediaPlayer::setMediaAsync(QFFmpeg::MediaDataHolder::Maybe mediaDataHolder,
217 const std::shared_ptr<QFFmpeg::CancelToken> &cancelToken)
218{
219 // If loading was cancelled, we do not emit any signals about failing
220 // to load media (or any other events). The rationale is that cancellation
221 // either happens during destruction, where the signals are no longer
222 // of interest, or it happens as a response to user requesting to load
223 // another media file. In the latter case, we don't want to risk popping
224 // up error dialogs or similar.
225 if (cancelToken->isCancelled()) {
226 return;
227 }
228
229 Q_ASSERT(mediaStatus() == QMediaPlayer::LoadingMedia);
230
231 if (!mediaDataHolder) {
232 const auto [code, description] = mediaDataHolder.error();
233 error(code, description);
234 handleIncorrectMedia(QMediaPlayer::MediaStatus::InvalidMedia);
235 return;
236 }
237
238 m_playbackEngine = std::make_unique<PlaybackEngine>(playbackOptions());
239
240 connect(m_playbackEngine.get(), &PlaybackEngine::endOfStream, this,
241 &QFFmpegMediaPlayer::endOfStream);
242 connect(m_playbackEngine.get(), &PlaybackEngine::errorOccured, this,
243 &QFFmpegMediaPlayer::error);
244 connect(m_playbackEngine.get(), &PlaybackEngine::loopChanged, this,
245 &QFFmpegMediaPlayer::onLoopChanged);
246 connect(m_playbackEngine.get(), &PlaybackEngine::buffered, this,
247 &QFFmpegMediaPlayer::onBuffered);
248
249 m_playbackEngine->setMedia(std::move(*mediaDataHolder.value()));
250
251 m_playbackEngine->setAudioBufferOutput(m_audioBufferOutput);
252 m_playbackEngine->setAudioSink(m_audioOutput);
253 m_playbackEngine->setVideoSink(m_videoSink);
254
255 m_playbackEngine->setLoops(loops());
256 m_playbackEngine->setPlaybackRate(m_playbackRate);
257 m_playbackEngine->setPitchCompensation(m_pitchCompensation);
258
259 durationChanged(duration());
260 tracksChanged();
261 metaDataChanged();
262 seekableChanged(m_playbackEngine->isSeekable());
263
264 audioAvailableChanged(
265 !m_playbackEngine->streamInfo(QPlatformMediaPlayer::AudioStream).isEmpty());
266 videoAvailableChanged(
267 !m_playbackEngine->streamInfo(QPlatformMediaPlayer::VideoStream).isEmpty());
268
269 mediaStatusChanged(QMediaPlayer::LoadedMedia);
270
271 if (m_requestedStatus != QMediaPlayer::StoppedState) {
272 if (m_requestedStatus == QMediaPlayer::PlayingState)
273 play();
274 else if (m_requestedStatus == QMediaPlayer::PausedState)
275 pause();
276 }
277}
278
280{
281 if (mediaStatus() == QMediaPlayer::LoadingMedia) {
282 m_requestedStatus = QMediaPlayer::PlayingState;
283 return;
284 }
285
286 if (!m_playbackEngine)
287 return;
288
289 if (mediaStatus() == QMediaPlayer::EndOfMedia && state() == QMediaPlayer::StoppedState) {
290 m_playbackEngine->seek(TrackPosition(0));
291 positionChanged(0);
292 }
293
294 runPlayback();
295}
296
297void QFFmpegMediaPlayer::runPlayback()
298{
299 m_playbackEngine->play();
300 m_positionUpdateTimer.start();
301 stateChanged(QMediaPlayer::PlayingState);
302
303 if (mediaStatus() == QMediaPlayer::LoadedMedia || mediaStatus() == QMediaPlayer::EndOfMedia)
304 mediaStatusChanged(QMediaPlayer::BufferingMedia);
305}
306
308{
309 if (mediaStatus() == QMediaPlayer::LoadingMedia) {
310 m_requestedStatus = QMediaPlayer::PausedState;
311 return;
312 }
313
314 if (!m_playbackEngine)
315 return;
316
317 if (mediaStatus() == QMediaPlayer::EndOfMedia && state() == QMediaPlayer::StoppedState) {
318 m_playbackEngine->seek(TrackPosition(0));
319 positionChanged(0);
320 }
321 m_playbackEngine->pause();
322 m_positionUpdateTimer.stop();
323 stateChanged(QMediaPlayer::PausedState);
324
325 if (mediaStatus() == QMediaPlayer::LoadedMedia || mediaStatus() == QMediaPlayer::EndOfMedia)
326 mediaStatusChanged(QMediaPlayer::BufferingMedia);
327}
328
330{
331 if (mediaStatus() == QMediaPlayer::LoadingMedia) {
332 m_requestedStatus = QMediaPlayer::StoppedState;
333 return;
334 }
335
336 if (!m_playbackEngine)
337 return;
338
339 m_playbackEngine->stop();
340 m_positionUpdateTimer.stop();
341 m_playbackEngine->seek(TrackPosition(0));
342 positionChanged(0);
343 stateChanged(QMediaPlayer::StoppedState);
344 mediaStatusChanged(QMediaPlayer::LoadedMedia);
345}
346
347void QFFmpegMediaPlayer::setAudioOutput(QPlatformAudioOutput *output)
348{
349 m_audioOutput = output;
350 if (m_playbackEngine)
351 m_playbackEngine->setAudioSink(output);
352}
353
354void QFFmpegMediaPlayer::setAudioBufferOutput(QAudioBufferOutput *output) {
355 m_audioBufferOutput = output;
356 if (m_playbackEngine)
357 m_playbackEngine->setAudioBufferOutput(output);
358}
359
361{
362 return m_playbackEngine ? m_playbackEngine->metaData() : QMediaMetaData{};
363}
364
365void QFFmpegMediaPlayer::setVideoSink(QVideoSink *sink)
366{
367 m_videoSink = sink;
368 if (m_playbackEngine)
369 m_playbackEngine->setVideoSink(sink);
370}
371
372QVideoSink *QFFmpegMediaPlayer::videoSink() const
373{
374 return m_videoSink;
375}
376
377int QFFmpegMediaPlayer::trackCount(TrackType type)
378{
379 return m_playbackEngine ? m_playbackEngine->streamInfo(type).count() : 0;
380}
381
382QMediaMetaData QFFmpegMediaPlayer::trackMetaData(TrackType type, int streamNumber)
383{
384 if (!m_playbackEngine || streamNumber < 0
385 || streamNumber >= m_playbackEngine->streamInfo(type).count())
386 return {};
387 return m_playbackEngine->streamInfo(type).at(streamNumber).metaData;
388}
389
390int QFFmpegMediaPlayer::activeTrack(TrackType type)
391{
392 return m_playbackEngine ? m_playbackEngine->activeTrack(type) : -1;
393}
394
395void QFFmpegMediaPlayer::setActiveTrack(TrackType type, int streamNumber)
396{
397 if (m_playbackEngine)
398 m_playbackEngine->setActiveTrack(type, streamNumber);
399 else
400 qWarning() << "Cannot set active track without open source";
401}
402
404{
405 if (m_playbackEngine)
406 m_playbackEngine->setLoops(loops);
407
408 QPlatformMediaPlayer::setLoops(loops);
409}
410
412{
413 if (enabled == m_pitchCompensation)
414 return;
415
416 m_pitchCompensation = enabled;
417 if (m_playbackEngine)
418 m_playbackEngine->setPitchCompensation(enabled);
419 QPlatformMediaPlayer::pitchCompensationChanged(enabled);
420}
421
423{
424 return m_pitchCompensation;
425}
426
429{
430 return PitchCompensationAvailability::Available;
431}
432
433QT_END_NAMESPACE
434
435#include "moc_qffmpegmediaplayer_p.cpp"
void setActiveTrack(TrackType, int streamNumber) override
QMediaMetaData trackMetaData(TrackType type, int streamNumber) override
void setLoops(int loops) override
QVideoSink * videoSink() const
bool pitchCompensation() const override
void setAudioOutput(QPlatformAudioOutput *) override
qint64 duration() const override
float bufferProgress() const override
const QIODevice * mediaStream() const override
void setPitchCompensation(bool enabled) override
void setPosition(qint64 position) override
void setPlaybackRate(qreal rate) override
int activeTrack(TrackType) override
QUrl media() const override
QMediaTimeRange availablePlaybackRanges() const override
qreal playbackRate() const override
PitchCompensationAvailability pitchCompensationAvailability() const override
QMediaMetaData metaData() const override
void setMedia(const QUrl &media, QIODevice *stream) override
int trackCount(TrackType) override
bool isCancelled() const override
std::conditional_t< QT_FFMPEG_AVIO_WRITE_CONST, const uint8_t *, uint8_t * > AvioWriteBufferType