7#include "private/qplatformaudiooutput_p.h"
8#include "private/qplatformvideosink_p.h"
9#include "private/qaudiobufferoutput_p.h"
11#include "playbackengine/qffmpegdemuxer_p.h"
12#include "playbackengine/qffmpegstreamdecoder_p.h"
13#include "playbackengine/qffmpegsubtitlerenderer_p.h"
14#include "playbackengine/qffmpegvideorenderer_p.h"
15#include "playbackengine/qffmpegaudiorenderer_p.h"
17#include <qloggingcategory.h>
20namespace ranges = QtMultimediaPrivate::ranges;
28template <
typename Array>
31 using T =
typename Array::value_type;
32 return { T{ {}, {} }, T{ {}, {} }, T{ {}, {} } };
41 qCDebug(qLcPlaybackEngine) <<
"Create PlaybackEngine";
42 qRegisterMetaType<QFFmpeg::Packet>();
43 qRegisterMetaType<QFFmpeg::Frame>();
44 qRegisterMetaType<QFFmpeg::TrackPosition>();
45 qRegisterMetaType<QFFmpeg::PlaybackEngineObjectID>();
49 qCDebug(qLcPlaybackEngine) <<
"Delete PlaybackEngine";
52 forEachExistingObject([](
auto &object) { object.reset(); });
56void PlaybackEngine::onRendererFinished(
const PlaybackEngineObjectID &id)
61 auto isAtEnd = [
this](TrackType trackType) {
62 auto &renderer = m_renderers[trackType];
63 return !renderer || renderer->isAtEnd();
66 if (!isAtEnd(TrackType::VideoStream))
69 if (!isAtEnd(TrackType::AudioStream))
72 if (!isAtEnd(TrackType::SubtitleStream) && !hasMediaStream())
75 if (std::exchange(m_state, QMediaPlayer::StoppedState) == QMediaPlayer::StoppedState)
78 finilizeTime(duration().asTimePoint());
82 qCDebug(qLcPlaybackEngine) <<
"Playback engine end of stream";
87void PlaybackEngine::onRendererLoopChanged(
const PlaybackEngineObjectID &id, TrackPosition offset,
93 if (loopIndex > m_currentLoopOffset.loopIndex) {
94 m_currentLoopOffset = { offset, loopIndex };
96 }
else if (loopIndex == m_currentLoopOffset.loopIndex && offset != m_currentLoopOffset.loopStartTimeUs) {
97 qWarning() <<
"Unexpected offset for loop" << loopIndex <<
":" << offset.get() <<
"vs"
98 << m_currentLoopOffset.loopStartTimeUs.get();
99 m_currentLoopOffset.loopStartTimeUs = offset;
103void PlaybackEngine::onFirstPacketFound(
const PlaybackEngineObjectID &id, TrackPosition absSeekPos)
105 if (!checkObjectID(m_demuxer, id))
108 if (m_timeController.isStarted())
111 const SteadyClock::time_point now = SteadyClock::now();
112 const SteadyClock::time_point expectedTimePoint = m_timeController.timeFromPosition(absSeekPos);
114 std::chrono::round<std::chrono::microseconds>(now - expectedTimePoint);
115 qCDebug(qLcPlaybackEngine) <<
"Delay of demuxer initialization:" << delay;
116 m_timeController.sync(now, absSeekPos);
117 m_timeController.start();
119 forEachExistingObject<Renderer>(
120 [&](
auto &renderer) { renderer->setTimeController(m_timeController); });
123void PlaybackEngine::onRendererSynchronized(
const PlaybackEngineObjectID &id,
124 SteadyClock::time_point tp, TrackPosition pos)
126 if (!hasRenderer(id))
129 Q_ASSERT(checkObjectID(
130 m_renderers[TrackType::AudioStream], id));
132 forEachExistingObject<Renderer>([&](
auto &renderer) {
133 if (id.objectID != renderer->objectID()) {
134 auto tc = m_timeController;
135 tc.syncSoft(tp, pos);
136 renderer->setTimeController(tc);
140 m_timeController.sync(tp, pos);
144 if (!m_media.avContext())
147 if (state == m_state)
150 const auto prevState = std::exchange(m_state, state);
152 if (m_state == QMediaPlayer::StoppedState) {
154 finilizeTime(TrackPosition(0));
157 if (prevState == QMediaPlayer::StoppedState || m_state == QMediaPlayer::StoppedState)
160 if (prevState == QMediaPlayer::StoppedState)
161 triggerStepIfNeeded();
163 updateObjectsPausedState();
168 const bool paused = m_state != QMediaPlayer::PlayingState;
169 m_timeController.setPaused(paused);
171 forEachExistingObject([&](
auto &object) {
172 if constexpr (std::is_same_v<
decltype(*object), Renderer &>)
173 object->setPaused(paused);
175 object->setPaused(
false);
182 if (!std::exchange(engine->m_threadsDirty,
true))
183 QMetaObject::invokeMethod(engine, &PlaybackEngine::deleteFreeThreads, Qt::QueuedConnection);
190 connect(&object, &PlaybackEngineObject::error,
this, &PlaybackEngine::errorOccured);
192 auto threadName = objectThreadName(object);
193 auto &thread = m_threads[threadName];
195 thread = std::make_unique<QThread>();
196 thread->setObjectName(threadName);
200 Q_ASSERT(object.thread() != thread.get());
201 object.moveToThread(thread.get());
208 case TrackType::VideoStream:
209 return m_videoSink ? createPlaybackEngineObject<VideoRenderer>(
210 m_timeController, m_videoSink, m_media.transformation())
211 : RendererPtr{ {}, {} };
212 case TrackType::AudioStream:
213 return m_audioOutput || m_audioBufferOutput
214 ? createPlaybackEngineObject<AudioRenderer>(
215 m_timeController, m_audioOutput, m_audioBufferOutput, m_pitchCompensation)
216 : RendererPtr{ {}, {} };
217 case TrackType::SubtitleStream:
219 ? createPlaybackEngineObject<SubtitleRenderer>(m_timeController, m_videoSink)
220 : RendererPtr{ {}, {} };
226template<
typename C,
typename Action>
229 auto handleNotNullObject = [&](
auto &object) {
230 if constexpr (std::is_base_of_v<C, std::remove_reference_t<
decltype(*object)>>)
237 std::for_each(m_renderers.begin(), m_renderers.end(), handleNotNullObject);
238 std::for_each(m_streams.begin(), m_streams.end(), handleNotNullObject);
239 handleNotNullObject(m_demuxer);
242template<
typename Action>
245 forEachExistingObject<PlaybackEngineObject>(std::forward<Action>(action));
250 pos = boundPosition(pos);
252 m_timeController.sync(m_currentLoopOffset.loopStartTimeUs.asDuration() + pos);
253 if (!m_demuxer || !m_media.avContext()) {
254 m_seekPending =
true;
258 m_seekPending =
false;
259 ++m_currentID.sessionID;
261 m_timeController.deactivate();
262 m_timeController.setPaused(m_state != QMediaPlayer::PlayingState);
264 forEachExistingObject([&](
auto &object) {
265 if constexpr (std::is_same_v<
decltype(*object), Renderer &>)
266 object->seek(m_currentID.sessionID, m_timeController, m_currentLoopOffset);
268 object->seek(m_currentID.sessionID, pos, m_currentLoopOffset);
271 triggerStepIfNeeded();
277 qWarning() <<
"Cannot set loops for non-seekable source";
281 if (std::exchange(m_loops, loops) == loops)
284 qCDebug(qLcPlaybackEngine) <<
"set playback engine loops:" << loops <<
"prev loops:" << m_loops
285 <<
"index:" << m_currentLoopOffset.loopIndex;
288 m_demuxer->setLoops(loops);
293 if (m_state != QMediaPlayer::PausedState)
296 auto &videoRenderer = m_renderers[TrackType::VideoStream];
298 videoRenderer->doForceStep();
305QString
PlaybackEngine::objectThreadName(
const PlaybackEngineObject &object)
307 QString result = QString::fromLatin1(object.metaObject()->className());
308 if (
auto stream = qobject_cast<
const StreamDecoder *>(&object))
309 result += QString::number(qToUnderlying(stream->trackType()));
318 m_timeController.setPlaybackRate(rate);
319 forEachExistingObject<Renderer>([rate](
auto &renderer) { renderer->setPlaybackRate(rate); });
323 return m_timeController.playbackRate();
328 m_timeController.deactivate();
330 forEachExistingObject([](
auto &object) { object.reset(); });
332 createObjectsIfNeeded();
337 if (m_state == QMediaPlayer::StoppedState || !m_media.avContext())
340 for (size_t i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i)
341 createStreamAndRenderer(
static_cast<TrackType>(i));
347 m_timeController.start();
353 triggerStepIfNeeded();
354 updateObjectsPausedState();
359 auto codecContext = codecContextForTrack(trackType);
361 auto &renderer = m_renderers[trackType];
367 renderer = createRenderer(trackType);
372 connect(renderer.get(), &Renderer::synchronized,
this,
373 &PlaybackEngine::onRendererSynchronized);
375 connect(renderer.get(), &Renderer::loopChanged,
this,
376 &PlaybackEngine::onRendererLoopChanged);
378 connect(renderer.get(), &PlaybackEngineObject::atEnd,
this,
379 &PlaybackEngine::onRendererFinished);
382 Q_ASSERT(MediaDataHolder::trackTypeFromMediaType(codecContext->context()->codec_type));
384 auto &stream = m_streams[trackType] =
385 createPlaybackEngineObject<StreamDecoder>(*codecContext, renderer->seekPosition());
387 Q_ASSERT(trackType == stream->trackType());
389 connect(stream.get(), &StreamDecoder::requestHandleFrame, renderer.get(), &Renderer::render);
390 connect(stream.get(), &PlaybackEngineObject::atEnd, renderer.get(),
391 &Renderer::onFinalFrameReceived);
392 connect(renderer.get(), &Renderer::frameProcessed, stream.get(),
393 &StreamDecoder::onFrameProcessed);
396std::optional<CodecContext>
PlaybackEngine::codecContextForTrack(TrackType trackType)
398 const auto streamIndex = m_media.currentStreamIndex(trackType);
402 auto &codecContext = m_codecContexts[trackType];
406 <<
"Create codec for stream:" << streamIndex <<
"trackType:" << trackType;
407 auto maybeCodecContext = CodecContext::create(m_media.avContext()->streams[streamIndex],
408 m_media.avContext(), m_options);
410 if (!maybeCodecContext) {
411 emit errorOccured(QMediaPlayer::FormatError,
412 u"Cannot create codec," + maybeCodecContext.error());
416 codecContext = maybeCodecContext.value();
424 return m_renderers[TrackType::AudioStream]
425 || m_renderers[TrackType::VideoStream];
430 StreamIndexes streamIndexes = { -1, -1, -1 };
432 bool hasStreams =
false;
435 const auto trackType = stream->trackType();
436 streamIndexes[trackType] = m_media.currentStreamIndex(trackType);
442 const TrackPosition currentLoopPosUs = currentPosition(
false);
444 m_demuxer = createPlaybackEngineObject<Demuxer>(m_media.avContext(), currentLoopPosUs,
445 m_seekPending, m_currentLoopOffset,
446 streamIndexes, m_loops);
448 m_seekPending =
false;
453 connect(m_demuxer.get(), Demuxer::signalByTrackType(stream->trackType()), stream.get(),
454 &StreamDecoder::decode);
455 connect(m_demuxer.get(), &PlaybackEngineObject::atEnd, stream.get(),
456 &StreamDecoder::onFinalPacketReceived);
457 connect(stream.get(), &StreamDecoder::packetProcessed, m_demuxer.get(),
458 &Demuxer::onPacketProcessed);
461 connect(m_demuxer.get(), &Demuxer::firstPacketFound,
this, &PlaybackEngine::onFirstPacketFound);
465 m_threadsDirty =
false;
466 auto freeThreads =
std::move(m_threads);
468 forEachExistingObject([&](
auto &object) {
469 m_threads.insert(freeThreads.extract(objectThreadName(*object)));
472 for (
auto &[name, thr] : freeThreads)
475 for (
auto &[name, thr] : freeThreads)
481 Q_ASSERT(!m_media.avContext());
482 Q_ASSERT(m_state == QMediaPlayer::StoppedState);
483 Q_ASSERT(m_threads.empty());
485 m_media = std::move(media);
486 updateVideoSinkSize();
491 auto prev = std::exchange(m_videoSink, sink);
495 updateVideoSinkSize(prev);
498 if (!sink || !prev) {
510 QAudioOutput *prev = std::exchange(m_audioOutput, output);
514 updateActiveAudioOutput(output);
516 if (!output || !prev) {
524 QAudioBufferOutput *prev = std::exchange(m_audioBufferOutput, output);
527 updateActiveAudioOutput(output);
532 std::optional<TrackPosition> pos;
534 if (m_timeController.isStarted()) {
535 for (size_t i = 0; i < m_renderers.size(); ++i) {
536 const auto &renderer = m_renderers[i];
541 if (!topPos && i == qToUnderlying(TrackType::SubtitleStream)
545 const auto rendererPos = renderer->lastPosition();
546 pos = !pos ? rendererPos
547 : topPos ? std::max(*pos, rendererPos)
548 : std::min(*pos, rendererPos);
554 pos = m_timeController.currentPosition();
556 return boundPosition(*pos - m_currentLoopOffset.loopStartTimeUs.asDuration());
561 return m_media.duration();
569 return m_media.streamInfo(trackType);
574 return m_media.metaData();
579 return m_media.activeTrack(type);
584 m_pitchCompensation = enabled;
591 if (!m_media.setActiveTrack(trackType, streamNumber))
594 m_codecContexts[trackType] = {};
596 m_renderers[trackType].reset();
597 m_streams = defaultObjectsArray<
decltype(m_streams)>();
606 updateVideoSinkSize();
607 createObjectsIfNeeded();
608 updateObjectsPausedState();
613 Q_ASSERT(pos >= TrackPosition(0) && pos <= duration().asTimePoint());
615 m_timeController.deactivate();
616 m_timeController.sync(pos);
617 m_currentLoopOffset = {};
622 if (m_audioBufferOutput)
623 updateActiveAudioOutput(
static_cast<QAudioBufferOutput *>(
nullptr));
625 updateActiveAudioOutput(
static_cast<QAudioOutput *>(
nullptr));
629bool PlaybackEngine::hasRenderer(
const PlaybackEngineObjectID &id)
const
631 return ranges::any_of(m_renderers, [&](
auto &renderer) {
632 return checkObjectID(renderer, id);
636template <
typename AudioOutput>
640 renderer->setOutput(output);
645 if (
auto renderer = qobject_cast<SubtitleRenderer *>(
646 m_renderers[TrackType::SubtitleStream]
648 renderer->setOutput(sink, cleanOutput);
649 if (
auto renderer = qobject_cast<VideoRenderer *>(
650 m_renderers[TrackType::VideoStream].get()))
651 renderer->setOutput(sink, cleanOutput);
656 auto platformVideoSink = m_videoSink ? m_videoSink->platformVideoSink() :
nullptr;
657 if (!platformVideoSink)
660 if (prevSink && prevSink->platformVideoSink())
661 platformVideoSink->setNativeSize(prevSink->platformVideoSink()->nativeSize());
663 const auto streamIndex =
664 m_media.currentStreamIndex(TrackType::VideoStream);
665 if (streamIndex >= 0) {
666 const auto context = m_media.avContext();
667 const auto stream = context->streams[streamIndex];
668 const AVRational pixelAspectRatio =
669 av_guess_sample_aspect_ratio(context, stream,
nullptr);
672 qCalculateFrameSize({ stream->codecpar->width, stream->codecpar->height },
673 { pixelAspectRatio.num, pixelAspectRatio.den });
675 platformVideoSink->setNativeSize(
676 qRotatedFrameSize(size, m_media.transformation().rotation));
681TrackPosition
PlaybackEngine::boundPosition(TrackPosition position)
const
683 position = qMax(position, TrackPosition(0));
684 return duration() > TrackDuration(0) ? qMin(position, duration().asTimePoint()) : position;
690 m_renderers[TrackType::AudioStream].get());
697#include "moc_qffmpegplaybackengine_p.cpp"
void setPitchCompensation(bool enabled)
void seek(TrackPosition pos)
void setLoops(int loopsCount)
void setVideoSink(QVideoSink *sink)
void setState(QMediaPlayer::PlaybackState state)
void updateActiveAudioOutput(AudioOutput *output)
void setAudioBufferOutput(QAudioBufferOutput *output)
void setPlaybackRate(float rate)
void setAudioSink(QAudioOutput *output)
const QList< MediaDataHolder::StreamInfo > & streamInfo(TrackType trackType) const
void setPitchCompensation(bool enabled)
void setActiveTrack(TrackType type, int streamNumber)
~PlaybackEngine() override
TrackDuration duration() const
void setMedia(MediaDataHolder media)
TrackPosition currentPosition(bool topPos=true) const
const QMediaMetaData & metaData() const
int activeTrack(TrackType type) const
void updateActiveVideoOutput(QVideoSink *sink, bool cleanOutput=false)
float playbackRate() const
PlaybackEngine(const QPlaybackOptions &options)
virtual RendererPtr createRenderer(TrackType trackType)
The QPlaybackOptions class enables low-level control of media playback options.
static Array defaultObjectsArray()
Combined button and popup list for selecting options.
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)
void operator()(PlaybackEngineObject *) const