From 8e68840e9ba921b50ae240c2e04831a125811658 Mon Sep 17 00:00:00 2001 From: Artem Dyomin Date: Wed, 21 Dec 2022 13:36:12 +0100 Subject: Code cleanup: remove old ffmpeg decoder What's done: - remove old decoder - namespace PlaybackEngineInternal seems to be not needed, so it's removed - some minor clean up with unique_ptr usage - logic is not changed Pick-to: 6.5 Change-Id: I5089c87ef4c424930bca96d5f2935bfd88c20f5f Reviewed-by: Qt CI Bot Reviewed-by: Lars Knoll --- src/plugins/multimedia/ffmpeg/CMakeLists.txt | 4 +- .../ffmpeg/playbackengine/qffmpegaudiorenderer.cpp | 4 +- .../ffmpeg/playbackengine/qffmpegaudiorenderer_p.h | 4 +- .../ffmpeg/playbackengine/qffmpegcodec.cpp | 69 ++ .../ffmpeg/playbackengine/qffmpegcodec_p.h | 60 + .../ffmpeg/playbackengine/qffmpegdemuxer.cpp | 12 +- .../ffmpeg/playbackengine/qffmpegdemuxer_p.h | 7 +- .../ffmpeg/playbackengine/qffmpegframe_p.h | 98 ++ .../playbackengine/qffmpegmediadataholder.cpp | 4 +- .../playbackengine/qffmpegmediadataholder_p.h | 7 +- .../ffmpeg/playbackengine/qffmpegpacket_p.h | 50 + .../playbackengine/qffmpegplaybackenginedefs_p.h | 4 +- .../playbackengine/qffmpegplaybackengineobject.cpp | 4 +- .../playbackengine/qffmpegplaybackengineobject_p.h | 4 +- .../ffmpeg/playbackengine/qffmpegrenderer.cpp | 4 +- .../ffmpeg/playbackengine/qffmpegrenderer_p.h | 6 +- .../ffmpeg/playbackengine/qffmpegstreamdecoder.cpp | 4 +- .../ffmpeg/playbackengine/qffmpegstreamdecoder_p.h | 10 +- .../playbackengine/qffmpegsubtitlerenderer.cpp | 4 +- .../playbackengine/qffmpegsubtitlerenderer_p.h | 4 +- .../playbackengine/qffmpegtimecontroller.cpp | 4 +- .../playbackengine/qffmpegtimecontroller_p.h | 4 +- .../ffmpeg/playbackengine/qffmpegvideorenderer.cpp | 4 +- .../ffmpeg/playbackengine/qffmpegvideorenderer_p.h | 4 +- src/plugins/multimedia/ffmpeg/qffmpeg_p.h | 22 + .../multimedia/ffmpeg/qffmpegaudiodecoder.cpp | 5 +- src/plugins/multimedia/ffmpeg/qffmpegdecoder.cpp | 1283 -------------------- src/plugins/multimedia/ffmpeg/qffmpegdecoder_p.h | 512 -------- .../multimedia/ffmpeg/qffmpegmediaplayer.cpp | 1 - .../multimedia/ffmpeg/qffmpegplaybackengine_p.h | 12 +- src/plugins/multimedia/ffmpeg/qffmpegresampler.cpp | 2 +- src/plugins/multimedia/ffmpeg/qffmpegresampler_p.h | 2 +- .../multimedia/ffmpeg/qffmpegvideoframeencoder.cpp | 17 +- .../multimedia/ffmpeg/qffmpegvideoframeencoder_p.h | 2 +- 34 files changed, 367 insertions(+), 1870 deletions(-) create mode 100644 src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec.cpp create mode 100644 src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec_p.h create mode 100644 src/plugins/multimedia/ffmpeg/playbackengine/qffmpegframe_p.h create mode 100644 src/plugins/multimedia/ffmpeg/playbackengine/qffmpegpacket_p.h delete mode 100644 src/plugins/multimedia/ffmpeg/qffmpegdecoder.cpp delete mode 100644 src/plugins/multimedia/ffmpeg/qffmpegdecoder_p.h (limited to 'src/plugins') diff --git a/src/plugins/multimedia/ffmpeg/CMakeLists.txt b/src/plugins/multimedia/ffmpeg/CMakeLists.txt index a5c70c059..934f99c90 100644 --- a/src/plugins/multimedia/ffmpeg/CMakeLists.txt +++ b/src/plugins/multimedia/ffmpeg/CMakeLists.txt @@ -18,7 +18,6 @@ qt_internal_add_plugin(QFFmpegMediaPlugin qffmpegaudiodecoder.cpp qffmpegaudiodecoder_p.h qffmpegaudioinput.cpp qffmpegaudioinput_p.h qffmpegclock.cpp qffmpegclock_p.h - qffmpegdecoder.cpp qffmpegdecoder_p.h qffmpeghwaccel.cpp qffmpeghwaccel_p.h qffmpegencoderoptions.cpp qffmpegencoderoptions_p.h qffmpegmediametadata.cpp qffmpegmediametadata_p.h @@ -47,6 +46,9 @@ qt_internal_add_plugin(QFFmpegMediaPlugin playbackengine/qffmpegsubtitlerenderer.cpp playbackengine/qffmpegsubtitlerenderer_p.h playbackengine/qffmpegtimecontroller.cpp playbackengine/qffmpegtimecontroller_p.h playbackengine/qffmpegmediadataholder.cpp playbackengine/qffmpegmediadataholder_p.h + playbackengine/qffmpegcodec.cpp playbackengine/qffmpegcodec_p.h + playbackengine/qffmpegpacket_p.h + playbackengine/qffmpegframe_p.h DEFINES QT_COMPILING_FFMPEG LIBRARIES diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegaudiorenderer.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegaudiorenderer.cpp index bea93dba0..e75d86b80 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegaudiorenderer.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegaudiorenderer.cpp @@ -11,7 +11,7 @@ QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { constexpr std::chrono::microseconds audioSinkBufferSize(100000); @@ -148,7 +148,7 @@ void AudioRenderer::updateOutput(const Codec *codec) } } -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegaudiorenderer_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegaudiorenderer_p.h index ae0f0bbe2..270a4abf8 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegaudiorenderer_p.h +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegaudiorenderer_p.h @@ -27,7 +27,7 @@ namespace QFFmpeg { class Resampler; }; -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { class AudioRenderer : public Renderer { @@ -65,7 +65,7 @@ private: bool m_deviceChanged = false; }; -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec.cpp new file mode 100644 index 000000000..f2d094e5f --- /dev/null +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec.cpp @@ -0,0 +1,69 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "playbackengine/qffmpegcodec_p.h" + +QT_BEGIN_NAMESPACE + +namespace QFFmpeg { + +Codec::Data::Data(AVCodecContextUPtr context, AVStream *stream, + std::unique_ptr hwAccel) + : context(std::move(context)), stream(stream), hwAccel(std::move(hwAccel)) +{ +} + +Codec::Data::~Data() +{ + // TODO: investigate if we can remove avcodec_close + // FFmpeg doc says that avcodec_free_context is enough + avcodec_close(context.get()); +} + +QMaybe Codec::create(AVStream *stream) +{ + if (!stream) + return { "Invalid stream" }; + + const AVCodec *decoder = + QFFmpeg::HWAccel::hardwareDecoderForCodecId(stream->codecpar->codec_id); + if (!decoder) + return { "Failed to find a valid FFmpeg decoder" }; + + AVCodecContextUPtr context(avcodec_alloc_context3(decoder)); + if (!context) + return { "Failed to allocate a FFmpeg codec context" }; + + if (context->codec_type != AVMEDIA_TYPE_AUDIO && context->codec_type != AVMEDIA_TYPE_VIDEO + && context->codec_type != AVMEDIA_TYPE_SUBTITLE) { + return { "Unknown codec type" }; + } + + int ret = avcodec_parameters_to_context(context.get(), stream->codecpar); + if (ret < 0) + return { "Failed to set FFmpeg codec parameters" }; + + std::unique_ptr hwAccel; + if (decoder->type == AVMEDIA_TYPE_VIDEO) { + hwAccel = QFFmpeg::HWAccel::create(decoder); + if (hwAccel) + context->hw_device_ctx = av_buffer_ref(hwAccel->hwDeviceContextAsBuffer()); + } + // ### This still gives errors about wrong HW formats (as we accept all of them) + // But it would be good to get so we can filter out pixel format we don't support natively + context->get_format = QFFmpeg::getFormat; + + /* Init the decoder, with reference counting and threading */ + AVDictionary *opts = nullptr; + av_dict_set(&opts, "refcounted_frames", "1", 0); + av_dict_set(&opts, "threads", "auto", 0); + ret = avcodec_open2(context.get(), decoder, &opts); + if (ret < 0) + return "Failed to open FFmpeg codec context " + err2str(ret); + + return Codec(new Data(std::move(context), stream, std::move(hwAccel))); +} + +QT_END_NAMESPACE + +} // namespace QFFmpeg diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec_p.h new file mode 100644 index 000000000..4437c4df1 --- /dev/null +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodec_p.h @@ -0,0 +1,60 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QFFMPEGCODEC_P_H +#define QFFMPEGCODEC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qshareddata.h" +#include "qqueue.h" +#include "private/qmultimediautils_p.h" +#include "qffmpeg_p.h" +#include "qffmpeghwaccel_p.h" + +QT_BEGIN_NAMESPACE + +namespace QFFmpeg { + +class Codec +{ + struct Data + { + Data(AVCodecContextUPtr context, AVStream *stream, + std::unique_ptr hwAccel); + ~Data(); + QAtomicInt ref; + AVCodecContextUPtr context; + AVStream *stream = nullptr; + std::unique_ptr hwAccel; + }; + +public: + static QMaybe create(AVStream *); + + AVCodecContext *context() const { return d->context.get(); } + AVStream *stream() const { return d->stream; } + uint streamIndex() const { return d->stream->index; } + HWAccel *hwAccel() const { return d->hwAccel.get(); } + qint64 toMs(qint64 ts) const { return timeStampMs(ts, d->stream->time_base).value_or(0); } + qint64 toUs(qint64 ts) const { return timeStampUs(ts, d->stream->time_base).value_or(0); } + +private: + Codec(Data *data) : d(data) { } + QExplicitlySharedDataPointer d; +}; + +} // namespace QFFmpeg + +QT_END_NAMESPACE + +#endif // QFFMPEGCODEC_P_H diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegdemuxer.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegdemuxer.cpp index b5cdc2204..b80cd47c4 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegdemuxer.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegdemuxer.cpp @@ -5,7 +5,11 @@ QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +// queue up max 16M of encoded data, that should always be enough +// (it's around 2 secs of 4K HDR video, longer for almost all other formats) +static constexpr quint64 MaxQueueSize = 16 * 1024 * 1024; + +namespace QFFmpeg { Demuxer::Demuxer(AVFormatContext *context, qint64 seekPos, const StreamIndexes &streamIndexes) : m_context(context), m_seekPos(seekPos) @@ -22,7 +26,7 @@ void Demuxer::doNextStep() { ensureSeeked(); - Packet packet(av_packet_alloc()); + Packet packet(AVPacketUPtr{ av_packet_alloc() }); if (av_read_frame(m_context, packet.avPacket()) < 0) { setAtEnd(true); return; @@ -85,7 +89,7 @@ bool Demuxer::canDoNextStep() const return true; const auto dataSize = - std::accumulate(m_streams.begin(), m_streams.end(), 0, + std::accumulate(m_streams.begin(), m_streams.end(), quint64(0), [](quint64 value, const auto &s) { return value + s.second.dataSize; }); if (dataSize > MaxQueueSize) @@ -128,7 +132,7 @@ Demuxer::RequestingSignal Demuxer::signalByTrackType(QPlatformMediaPlayer::Track return nullptr; } -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegdemuxer_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegdemuxer_p.h index de65a035f..6bfd4431f 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegdemuxer_p.h +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegdemuxer_p.h @@ -16,14 +16,13 @@ #include "playbackengine/qffmpegplaybackengineobject_p.h" #include "private/qplatformmediaplayer_p.h" - -#include "qffmpegdecoder_p.h" // TODO: remove +#include "playbackengine/qffmpegpacket_p.h" #include QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { class Demuxer : public PlaybackEngineObject { @@ -63,7 +62,7 @@ private: const qint64 m_seekPos = 0; }; -} +} // namespace QFFmpeg QT_END_NAMESPACE // QFFMPEGDEMUXER_P_H diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegframe_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegframe_p.h new file mode 100644 index 000000000..bccb13ebe --- /dev/null +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegframe_p.h @@ -0,0 +1,98 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QFFMPEGFRAME_P_H +#define QFFMPEGFRAME_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qffmpeg_p.h" +#include "playbackengine/qffmpegcodec_p.h" +#include "QtCore/qsharedpointer.h" +#include "qpointer.h" +#include "qobject.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QFFmpeg { + +struct Frame +{ + struct Data + { + Data(AVFrameUPtr f, const Codec &codec, qint64, const QObject *source) + : codec(codec), frame(std::move(f)), source(source) + { + Q_ASSERT(frame); + if (frame->pts != AV_NOPTS_VALUE) + pts = codec.toUs(frame->pts); + else + pts = codec.toUs(frame->best_effort_timestamp); + const auto &avgFrameRate = codec.stream()->avg_frame_rate; + duration = avgFrameRate.num + ? (1000000 * avgFrameRate.den + avgFrameRate.num / 2) / avgFrameRate.num + : 0; + } + Data(const QString &text, qint64 pts, qint64 duration, const QObject *source) + : text(text), pts(pts), duration(duration), source(source) + { + } + + QAtomicInt ref; + std::optional codec; + AVFrameUPtr frame; + QString text; + qint64 pts = -1; + qint64 duration = -1; + QPointer source; + }; + Frame() = default; + + Frame(AVFrameUPtr f, const Codec &codec, qint64 pts, const QObject *source = nullptr) + : d(new Data(std::move(f), codec, pts, source)) + { + } + Frame(const QString &text, qint64 pts, qint64 duration, const QObject *source = nullptr) + : d(new Data(text, pts, duration, source)) + { + } + bool isValid() const { return !!d; } + + AVFrame *avFrame() const { return data().frame.get(); } + AVFrameUPtr takeAVFrame() { return std::move(data().frame); } + const Codec *codec() const { return data().codec ? &data().codec.value() : nullptr; } + qint64 pts() const { return data().pts; } + qint64 duration() const { return data().duration; } + qint64 end() const { return data().pts + data().duration; } + QString text() const { return data().text; } + const QObject *source() const { return data().source; }; + +private: + Data &data() const + { + Q_ASSERT(d); + return *d; + } + +private: + QExplicitlySharedDataPointer d; +}; + +} // namespace QFFmpeg + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QFFmpeg::Frame); + +#endif // QFFMPEGFRAME_P_H diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegmediadataholder.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegmediadataholder.cpp index 525cc691a..e9c1eca29 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegmediadataholder.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegmediadataholder.cpp @@ -9,7 +9,7 @@ QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { static void insertMediaData(QMediaMetaData &metaData, QPlatformMediaPlayer::TrackType trackType, const AVStream *stream) @@ -232,6 +232,6 @@ int MediaDataHolder::activeTrack(QPlatformMediaPlayer::TrackType type) const return type < QPlatformMediaPlayer::NTrackTypes ? m_requestedStreams[type] : -1; } -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegmediadataholder_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegmediadataholder_p.h index ec5ceb899..dc9f4c907 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegmediadataholder_p.h +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegmediadataholder_p.h @@ -17,15 +17,14 @@ #include "qmediametadata.h" #include "private/qplatformmediaplayer_p.h" - -#include "qffmpegdecoder_p.h" // TODO: align headers and remove +#include "qffmpeg_p.h" #include #include QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { struct AVFormatContextDeleter { @@ -75,7 +74,7 @@ protected: QMediaMetaData m_metaData; }; -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegpacket_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegpacket_p.h new file mode 100644 index 000000000..5e489b3cd --- /dev/null +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegpacket_p.h @@ -0,0 +1,50 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QFFMPEGPACKET_P_H +#define QFFMPEGPACKET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qffmpeg_p.h" +#include "QtCore/qsharedpointer.h" + +QT_BEGIN_NAMESPACE + +namespace QFFmpeg { + +struct Packet +{ + struct Data + { + Data(AVPacketUPtr p) : packet(std::move(p)) { } + + QAtomicInt ref; + AVPacketUPtr packet; + }; + Packet() = default; + Packet(AVPacketUPtr p) : d(new Data(std::move(p))) { } + + bool isValid() const { return !!d; } + AVPacket *avPacket() const { return d->packet.get(); } + +private: + QExplicitlySharedDataPointer d; +}; + +} // namespace QFFmpeg + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QFFmpeg::Packet) + +#endif // QFFMPEGPACKET_P_H diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegplaybackenginedefs_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegplaybackenginedefs_p.h index 7fa34f627..fc0b16f62 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegplaybackenginedefs_p.h +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegplaybackenginedefs_p.h @@ -25,7 +25,7 @@ namespace QFFmpeg { class PlaybackEngine; } -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { using StreamIndexes = std::array; @@ -38,6 +38,6 @@ class SubtitleRenderer; class AudioRenderer; class VideoRenderer; -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegplaybackengineobject.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegplaybackengineobject.cpp index 20a045849..0b6a5dfff 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegplaybackengineobject.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegplaybackengineobject.cpp @@ -5,7 +5,7 @@ QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { bool PlaybackEngineObject::isPaused() const { @@ -82,7 +82,7 @@ void PlaybackEngineObject::scheduleNextStep(bool allowDoImmediatelly) timer().stop(); } } -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegplaybackengineobject_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegplaybackengineobject_p.h index 7a2b89416..48a14d0ca 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegplaybackengineobject_p.h +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegplaybackengineobject_p.h @@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { class PlaybackEngineObject : public QObject { @@ -66,7 +66,7 @@ private: std::atomic_bool m_atEnd = false; std::atomic_bool m_deleting = false; }; -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegrenderer.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegrenderer.cpp index 552092064..d12f38e84 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegrenderer.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegrenderer.cpp @@ -5,7 +5,7 @@ QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { Renderer::Renderer(const TimeController &tc, const std::chrono::microseconds &seekPosTimeOffset) : m_timeController(tc), @@ -151,7 +151,7 @@ void Renderer::doNextStep() scheduleNextStep(false); } -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegrenderer_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegrenderer_p.h index 71d0fe1b1..23ccab883 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegrenderer_p.h +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegrenderer_p.h @@ -16,13 +16,13 @@ #include "playbackengine/qffmpegplaybackengineobject_p.h" #include "playbackengine/qffmpegtimecontroller_p.h" -#include "qffmpegdecoder_p.h" // to be removed +#include "playbackengine/qffmpegframe_p.h" #include QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { class Renderer : public PlaybackEngineObject { @@ -87,7 +87,7 @@ private: std::atomic_bool m_isStepForced = false; }; -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder.cpp index e6c6baa40..01dfb1f84 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder.cpp @@ -6,7 +6,7 @@ QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { StreamDecoder::StreamDecoder(const Codec &codec, qint64 seekPos) : m_codec(codec), @@ -201,7 +201,7 @@ void StreamDecoder::decodeSubtitle(Packet packet) // TODO: maybe optimize onFrameFound({ QString(), end, 0, this }); } -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder_p.h index c3fcc1429..cc12b1e9a 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder_p.h +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegstreamdecoder_p.h @@ -13,16 +13,16 @@ // // We mean it. // - #include "playbackengine/qffmpegplaybackengineobject_p.h" - -#include "qffmpegdecoder_p.h" // to be removed +#include "playbackengine/qffmpegframe_p.h" +#include "playbackengine/qffmpegpacket_p.h" +#include "private/qplatformmediaplayer_p.h" #include QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { class StreamDecoder : public PlaybackEngineObject { @@ -72,7 +72,7 @@ private: QQueue m_packets; }; -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegsubtitlerenderer.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegsubtitlerenderer.cpp index 70c07eb5c..4e86ed482 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegsubtitlerenderer.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegsubtitlerenderer.cpp @@ -8,7 +8,7 @@ QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { SubtitleRenderer::SubtitleRenderer(const TimeController &tc, QVideoSink *sink) : Renderer(tc), m_sink(sink) @@ -29,7 +29,7 @@ Renderer::RenderingResult SubtitleRenderer::renderInternal(Frame frame) return {}; } -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegsubtitlerenderer_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegsubtitlerenderer_p.h index 4a6585797..146269fc3 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegsubtitlerenderer_p.h +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegsubtitlerenderer_p.h @@ -20,7 +20,7 @@ QT_BEGIN_NAMESPACE class QVideoSink; -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { class SubtitleRenderer : public Renderer { @@ -37,7 +37,7 @@ private: QPointer m_sink; }; -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegtimecontroller.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegtimecontroller.cpp index 989c3c222..798f4913d 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegtimecontroller.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegtimecontroller.cpp @@ -10,7 +10,7 @@ QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { TimeController::TimeController() { @@ -157,6 +157,6 @@ TimeController::TrackTime TimeController::toTrackTime(const T &t) return std::chrono::duration_cast(t); } -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegtimecontroller_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegtimecontroller_p.h index 26ad5bba4..3713f1bbb 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegtimecontroller_p.h +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegtimecontroller_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { class TimeController { @@ -87,7 +87,7 @@ private: std::optional m_softSyncData; }; -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegvideorenderer.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegvideorenderer.cpp index f87e3ae27..72ae0881f 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegvideorenderer.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegvideorenderer.cpp @@ -7,7 +7,7 @@ QT_BEGIN_NAMESPACE -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { VideoRenderer::VideoRenderer(const TimeController &tc, QVideoSink *sink) : Renderer(tc), m_sink(sink) @@ -56,7 +56,7 @@ VideoRenderer::RenderingResult VideoRenderer::renderInternal(Frame frame) return {}; } -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegvideorenderer_p.h b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegvideorenderer_p.h index 707eab45f..e604af9f8 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegvideorenderer_p.h +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegvideorenderer_p.h @@ -20,7 +20,7 @@ QT_BEGIN_NAMESPACE class QVideoSink; -namespace QFFmpeg::PlaybackEngineInternal { +namespace QFFmpeg { class VideoRenderer : public Renderer { @@ -35,7 +35,7 @@ private: QPointer m_sink; }; -} +} // namespace QFFmpeg QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/qffmpeg_p.h b/src/plugins/multimedia/ffmpeg/qffmpeg_p.h index 060531ac3..e9da8319d 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpeg_p.h +++ b/src/plugins/multimedia/ffmpeg/qffmpeg_p.h @@ -69,6 +69,28 @@ inline AVFrameUPtr makeAVFrame() return AVFrameUPtr(av_frame_alloc()); } +struct AVPacketDeleter +{ + void operator()(AVPacket *packet) const + { + if (packet) + av_packet_free(&packet); + } +}; + +using AVPacketUPtr = std::unique_ptr; + +struct AVCodecContextDeleter +{ + void operator()(AVCodecContext *context) const + { + if (context) + avcodec_free_context(&context); + } +}; + +using AVCodecContextUPtr = std::unique_ptr; + QT_END_NAMESPACE } diff --git a/src/plugins/multimedia/ffmpeg/qffmpegaudiodecoder.cpp b/src/plugins/multimedia/ffmpeg/qffmpegaudiodecoder.cpp index 7eb67eba0..803d11fc3 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegaudiodecoder.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpegaudiodecoder.cpp @@ -1,7 +1,6 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qffmpegaudiodecoder_p.h" -#include "qffmpegdecoder_p.h" #include "qffmpegresampler_p.h" #include "qaudiobuffer.h" @@ -17,7 +16,7 @@ QT_BEGIN_NAMESPACE namespace QFFmpeg { -class SteppingAudioRenderer : public PlaybackEngineInternal::Renderer +class SteppingAudioRenderer : public Renderer { Q_OBJECT public: @@ -77,7 +76,7 @@ signals: void newAudioBuffer(QAudioBuffer); private: - QPointer m_audioRenderer; + QPointer m_audioRenderer; QAudioFormat m_format; }; } diff --git a/src/plugins/multimedia/ffmpeg/qffmpegdecoder.cpp b/src/plugins/multimedia/ffmpeg/qffmpegdecoder.cpp deleted file mode 100644 index c87211910..000000000 --- a/src/plugins/multimedia/ffmpeg/qffmpegdecoder.cpp +++ /dev/null @@ -1,1283 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "qffmpegdecoder_p.h" -#include "qffmpegmediaformatinfo_p.h" -#include "qffmpeg_p.h" -#include "qffmpegmediametadata_p.h" -#include "qffmpegvideobuffer_p.h" -#include "private/qplatformaudiooutput_p.h" -#include "qffmpeghwaccel_p.h" -#include "qffmpegvideosink_p.h" -#include "qvideosink.h" -#include "qaudiosink.h" -#include "qaudiooutput.h" -#include "qffmpegaudiodecoder_p.h" -#include "qffmpegresampler_p.h" - -#include -#include - -#include - -extern "C" { -#include -} - -QT_BEGIN_NAMESPACE - -using namespace QFFmpeg; - -Q_LOGGING_CATEGORY(qLcDemuxer, "qt.multimedia.ffmpeg.demuxer") -Q_LOGGING_CATEGORY(qLcDecoder, "qt.multimedia.ffmpeg.decoder") -Q_LOGGING_CATEGORY(qLcVideoRenderer, "qt.multimedia.ffmpeg.videoRenderer") -Q_LOGGING_CATEGORY(qLcAudioRenderer, "qt.multimedia.ffmpeg.audioRenderer") - -Codec::Data::Data(UniqueAVCodecContext &&context, AVStream *stream, std::unique_ptr &&hwAccel) - : context(std::move(context)) - , stream(stream) - , hwAccel(std::move(hwAccel)) -{ -} - -Codec::Data::~Data() -{ - avcodec_close(context.get()); -} - -QMaybe Codec::create(AVStream *stream) -{ - if (!stream) - return { "Invalid stream" }; - - const AVCodec *decoder = - QFFmpeg::HWAccel::hardwareDecoderForCodecId(stream->codecpar->codec_id); - if (!decoder) - return { "Failed to find a valid FFmpeg decoder" }; - - //avcodec_free_context - UniqueAVCodecContext context(avcodec_alloc_context3(decoder)); - if (!context) - return { "Failed to allocate a FFmpeg codec context" }; - - if (context->codec_type != AVMEDIA_TYPE_AUDIO && - context->codec_type != AVMEDIA_TYPE_VIDEO && - context->codec_type != AVMEDIA_TYPE_SUBTITLE) { - return { "Unknown codec type" }; - } - - int ret = avcodec_parameters_to_context(context.get(), stream->codecpar); - if (ret < 0) - return { "Failed to set FFmpeg codec parameters" }; - - std::unique_ptr hwAccel; - if (decoder->type == AVMEDIA_TYPE_VIDEO) { - hwAccel = QFFmpeg::HWAccel::create(decoder); - if (hwAccel) - context->hw_device_ctx = av_buffer_ref(hwAccel->hwDeviceContextAsBuffer()); - } - // ### This still gives errors about wrong HW formats (as we accept all of them) - // But it would be good to get so we can filter out pixel format we don't support natively - context->get_format = QFFmpeg::getFormat; - - /* Init the decoder, with reference counting and threading */ - AVDictionary *opts = nullptr; - av_dict_set(&opts, "refcounted_frames", "1", 0); - av_dict_set(&opts, "threads", "auto", 0); - ret = avcodec_open2(context.get(), decoder, &opts); - if (ret < 0) - return "Failed to open FFmpeg codec context " + err2str(ret); - - return Codec(new Data(std::move(context), stream, std::move(hwAccel))); -} - - -Demuxer::Demuxer(Decoder *decoder, AVFormatContext *context) - : Thread() - , decoder(decoder) - , context(context) -{ - QString objectName = QLatin1String("Demuxer"); - setObjectName(objectName); - - streamDecoders.resize(context->nb_streams); -} - -Demuxer::~Demuxer() -{ - if (context) { - if (context->pb) { - avio_context_free(&context->pb); - context->pb = nullptr; - } - avformat_free_context(context); - } -} - -StreamDecoder *Demuxer::addStream(int streamIndex) -{ - if (streamIndex < 0 || streamIndex >= (int)context->nb_streams) - return nullptr; - - AVStream *avStream = context->streams[streamIndex]; - if (!avStream) - return nullptr; - - QMutexLocker locker(&mutex); - auto maybeCodec = Codec::create(avStream); - if (!maybeCodec) { - decoder->errorOccured(QMediaPlayer::FormatError, "Cannot open codec; " + maybeCodec.error()); - return nullptr; - } - auto *stream = new StreamDecoder(this, maybeCodec.value()); - Q_ASSERT(!streamDecoders.at(streamIndex)); - streamDecoders[streamIndex] = stream; - stream->start(); - updateEnabledStreams(); - return stream; -} - -void Demuxer::removeStream(int streamIndex) -{ - if (streamIndex < 0) - return; - QMutexLocker locker(&mutex); - Q_ASSERT(streamIndex < (int)context->nb_streams); - Q_ASSERT(streamDecoders.at(streamIndex) != nullptr); - streamDecoders[streamIndex] = nullptr; - updateEnabledStreams(); -} - -void Demuxer::stopDecoding() -{ - qCDebug(qLcDemuxer) << "StopDecoding"; - QMutexLocker locker(&mutex); - sendFinalPacketToStreams(); -} -int Demuxer::seek(qint64 pos) -{ - QMutexLocker locker(&mutex); - for (StreamDecoder *d : std::as_const(streamDecoders)) { - if (d) - d->mutex.lock(); - } - for (StreamDecoder *d : std::as_const(streamDecoders)) { - if (d) - d->flush(); - } - for (StreamDecoder *d : std::as_const(streamDecoders)) { - if (d) - d->mutex.unlock(); - } - qint64 seekPos = pos*AV_TIME_BASE/1000000; // usecs to AV_TIME_BASE - av_seek_frame(context, -1, seekPos, AVSEEK_FLAG_BACKWARD); - last_pts = -1; - loop(); - qCDebug(qLcDemuxer) << "Demuxer::seek" << pos << last_pts; - return last_pts; -} - -void Demuxer::updateEnabledStreams() -{ - if (isStopped()) - return; - for (uint i = 0; i < context->nb_streams; ++i) { - AVDiscard discard = AVDISCARD_DEFAULT; - if (!streamDecoders.at(i)) - discard = AVDISCARD_ALL; - context->streams[i]->discard = discard; - } -} - -void Demuxer::sendFinalPacketToStreams() -{ - if (m_isStopped.loadAcquire()) - return; - for (auto *streamDecoder : std::as_const(streamDecoders)) { - qCDebug(qLcDemuxer) << "Demuxer: sending last packet to stream" << streamDecoder; - if (!streamDecoder) - continue; - streamDecoder->addPacket(nullptr); - } - m_isStopped.storeRelease(true); -} - -void Demuxer::init() -{ - qCDebug(qLcDemuxer) << "Demuxer started"; -} - -void Demuxer::cleanup() -{ - qCDebug(qLcDemuxer) << "Demuxer::cleanup"; -#ifndef QT_NO_DEBUG - for (auto *streamDecoder : std::as_const(streamDecoders)) { - Q_ASSERT(!streamDecoder); - } -#endif - avformat_close_input(&context); - Thread::cleanup(); -} - -bool Demuxer::shouldWait() const -{ - if (m_isStopped) - return true; -// qCDebug(qLcDemuxer) << "XXXX Demuxer::shouldWait" << this << data->seek_pos.loadRelaxed(); - // require a minimum of 200ms of data - qint64 queueSize = 0; - bool buffersFull = true; - for (auto *d : streamDecoders) { - if (!d) - continue; - if (d->queuedDuration() < 200) - buffersFull = false; - queueSize += d->queuedPacketSize(); - } -// qCDebug(qLcDemuxer) << " queue size" << queueSize << MaxQueueSize; - if (queueSize > MaxQueueSize) - return true; -// qCDebug(qLcDemuxer) << " waiting!"; - return buffersFull; - -} - -void Demuxer::loop() -{ - AVPacket *packet = av_packet_alloc(); - if (av_read_frame(context, packet) < 0) { - sendFinalPacketToStreams(); - av_packet_free(&packet); - return; - } - - if (last_pts < 0 && packet->pts != AV_NOPTS_VALUE) { - auto *stream = context->streams[packet->stream_index]; - auto pts = timeStampMs(packet->pts, stream->time_base); - if (pts) - last_pts = *pts; - } - - auto *streamDecoder = streamDecoders.at(packet->stream_index); - if (!streamDecoder) { - av_packet_free(&packet); - return; - } - streamDecoder->addPacket(packet); -} - - -StreamDecoder::StreamDecoder(Demuxer *demuxer, const Codec &codec) - : Thread() - , demuxer(demuxer) - , codec(codec) -{ - QString objectName; - switch (codec.context()->codec_type) { - case AVMEDIA_TYPE_AUDIO: - objectName = QLatin1String("AudioDecoderThread"); - // Queue size: 3 frames for video/subtitle, 9 for audio - frameQueue.maxSize = 9; - break; - case AVMEDIA_TYPE_VIDEO: - objectName = QLatin1String("VideoDecoderThread"); - break; - case AVMEDIA_TYPE_SUBTITLE: - objectName = QLatin1String("SubtitleDecoderThread"); - break; - default: - Q_UNREACHABLE(); - } - setObjectName(objectName); -} - -void StreamDecoder::addPacket(AVPacket *packet) -{ - { - QMutexLocker locker(&packetQueue.mutex); -// qCDebug(qLcDecoder) << "enqueuing packet of type" << type() -// << "size" << packet->size -// << "stream index" << packet->stream_index -// << "pts" << codec.toMs(packet->pts) -// << "duration" << codec.toMs(packet->duration); - packetQueue.queue.enqueue(Packet(packet)); - if (packet) { - packetQueue.size += packet->size; - packetQueue.duration += codec.toMs(packet->duration); - } - eos.storeRelease(false); - } - wake(); -} - -void StreamDecoder::flush() -{ - qCDebug(qLcDecoder) << ">>>> flushing stream decoder" << type(); - avcodec_flush_buffers(codec.context()); - { - QMutexLocker locker(&packetQueue.mutex); - packetQueue.queue.clear(); - packetQueue.size = 0; - packetQueue.duration = 0; - } - { - QMutexLocker locker(&frameQueue.mutex); - frameQueue.queue.clear(); - } - qCDebug(qLcDecoder) << ">>>> done flushing stream decoder" << type(); -} - -void StreamDecoder::setRenderer(Renderer *r) -{ - QMutexLocker locker(&mutex); - m_renderer = r; - if (m_renderer) - m_renderer->wake(); -} - -void StreamDecoder::killHelper() -{ - m_renderer = nullptr; - demuxer->removeStream(codec.streamIndex()); -} - -Packet StreamDecoder::peekPacket() -{ - QMutexLocker locker(&packetQueue.mutex); - if (packetQueue.queue.isEmpty()) { - if (demuxer) - demuxer->wake(); - return {}; - } - auto packet = packetQueue.queue.first(); - - if (demuxer) - demuxer->wake(); - return packet; -} - -Packet StreamDecoder::takePacket() -{ - QMutexLocker locker(&packetQueue.mutex); - if (packetQueue.queue.isEmpty()) { - if (demuxer) - demuxer->wake(); - return {}; - } - auto packet = packetQueue.queue.dequeue(); - if (packet.avPacket()) { - packetQueue.size -= packet.avPacket()->size; - packetQueue.duration -= codec.toMs(packet.avPacket()->duration); - } -// qCDebug(qLcDecoder) << "<<<< dequeuing packet of type" << type() -// << "size" << packet.avPacket()->size -// << "stream index" << packet.avPacket()->stream_index -// << "pts" << codec.toMs(packet.avPacket()->pts) -// << "duration" << codec.toMs(packet.avPacket()->duration) -// << "ts" << decoder->clockController.currentTime(); - if (demuxer) - demuxer->wake(); - return packet; -} - -void StreamDecoder::addFrame(const Frame &f) -{ - Q_ASSERT(f.isValid()); - QMutexLocker locker(&frameQueue.mutex); - frameQueue.queue.append(std::move(f)); - if (m_renderer) - m_renderer->wake(); -} - -Frame StreamDecoder::takeFrame() -{ - QMutexLocker locker(&frameQueue.mutex); - // wake up the decoder so it delivers more frames - if (frameQueue.queue.isEmpty()) { - wake(); - return {}; - } - auto f = frameQueue.queue.dequeue(); - wake(); - return f; -} - -void StreamDecoder::init() -{ - qCDebug(qLcDecoder) << "Starting decoder"; -} - -bool StreamDecoder::shouldWait() const -{ - if (eos.loadAcquire() || (hasNoPackets() && decoderHasNoFrames) || hasEnoughFrames()) - return true; - return false; -} - -void StreamDecoder::loop() -{ - if (codec.context()->codec->type == AVMEDIA_TYPE_SUBTITLE) - decodeSubtitle(); - else - decode(); -} - -void StreamDecoder::decode() -{ - Q_ASSERT(codec.context()); - - auto frame = makeAVFrame(); - // if (type() == 0) - // qCDebug(qLcDecoder) << "receiving frame"; - int res = avcodec_receive_frame(codec.context(), frame.get()); - - if (res >= 0) { - qint64 pts; - if (frame->pts != AV_NOPTS_VALUE) - pts = codec.toUs(frame->pts); - else - pts = codec.toUs(frame->best_effort_timestamp); - addFrame(Frame{ std::move(frame), codec, pts }); - } else if (res == AVERROR(EOF) || res == AVERROR_EOF) { - eos.storeRelease(true); - timeOut = -1; - return; - } else if (res != AVERROR(EAGAIN)) { - qWarning() << "error in decoder" << res << err2str(res); - return; - } else { - // EAGAIN - decoderHasNoFrames = true; - } - - Packet packet = peekPacket(); - if (!packet.isValid()) { - timeOut = -1; - return; - } - - res = avcodec_send_packet(codec.context(), packet.avPacket()); - if (res != AVERROR(EAGAIN)) { - takePacket(); - } - decoderHasNoFrames = false; -} - -void StreamDecoder::decodeSubtitle() -{ - // qCDebug(qLcDecoder) << " decoding subtitle" << "has delay:" << (codec->codec->capabilities & AV_CODEC_CAP_DELAY); - AVSubtitle subtitle; - memset(&subtitle, 0, sizeof(subtitle)); - int gotSubtitle = 0; - Packet packet = takePacket(); - if (!packet.isValid()) - return; - - int res = avcodec_decode_subtitle2(codec.context(), &subtitle, &gotSubtitle, packet.avPacket()); - // qCDebug(qLcDecoder) << " subtitle got:" << res << gotSubtitle << subtitle.format << Qt::hex << (quint64)subtitle.pts; - if (res >= 0 && gotSubtitle) { - // apparently the timestamps in the AVSubtitle structure are not always filled in - // if they are missing, use the packets pts and duration values instead - qint64 start, end; - if (subtitle.pts == AV_NOPTS_VALUE) { - start = codec.toUs(packet.avPacket()->pts); - end = start + codec.toUs(packet.avPacket()->duration); - } else { - auto pts = timeStampUs(subtitle.pts, AVRational{1, AV_TIME_BASE}); - start = *pts + qint64(subtitle.start_display_time)*1000; - end = *pts + qint64(subtitle.end_display_time)*1000; - } - // qCDebug(qLcDecoder) << " got subtitle (" << start << "--" << end << "):"; - QString text; - for (uint i = 0; i < subtitle.num_rects; ++i) { - const auto *r = subtitle.rects[i]; - // qCDebug(qLcDecoder) << " subtitletext:" << r->text << "/" << r->ass; - if (i) - text += QLatin1Char('\n'); - if (r->text) - text += QString::fromUtf8(r->text); - else { - const char *ass = r->ass; - int nCommas = 0; - while (*ass) { - if (nCommas == 9) - break; - if (*ass == ',') - ++nCommas; - ++ass; - } - text += QString::fromUtf8(ass); - } - } - text.replace(QLatin1String("\\N"), QLatin1String("\n")); - text.replace(QLatin1String("\\n"), QLatin1String("\n")); - text.replace(QLatin1String("\r\n"), QLatin1String("\n")); - if (text.endsWith(QLatin1Char('\n'))) - text.chop(1); - -// qCDebug(qLcDecoder) << " >>> subtitle adding" << text << start << end; - Frame sub{text, start, end - start}; - addFrame(sub); - } -} - -QPlatformMediaPlayer::TrackType StreamDecoder::type() const -{ - switch (codec.stream()->codecpar->codec_type) { - case AVMEDIA_TYPE_AUDIO: - return QPlatformMediaPlayer::AudioStream; - case AVMEDIA_TYPE_VIDEO: - return QPlatformMediaPlayer::VideoStream; - case AVMEDIA_TYPE_SUBTITLE: - return QPlatformMediaPlayer::SubtitleStream; - default: - return QPlatformMediaPlayer::NTrackTypes; - } -} - -Renderer::Renderer(QPlatformMediaPlayer::TrackType type) - : Thread() - , type(type) -{ - QString objectName; - if (type == QPlatformMediaPlayer::AudioStream) - objectName = QLatin1String("AudioRenderThread"); - else - objectName = QLatin1String("VideoRenderThread"); - setObjectName(objectName); -} - -void Renderer::setStream(StreamDecoder *stream) -{ - QMutexLocker locker(&mutex); - if (streamDecoder == stream) - return; - if (streamDecoder) - streamDecoder->kill(); - streamDecoder = stream; - if (streamDecoder) - streamDecoder->setRenderer(this); - streamChanged(); - wake(); -} - -void Renderer::killHelper() -{ - if (streamDecoder) - streamDecoder->kill(); - streamDecoder = nullptr; -} - -bool Renderer::shouldWait() const -{ - if (!streamDecoder) - return true; - if (!paused) - return false; - if (step) - return false; - return true; -} - - -void ClockedRenderer::setPaused(bool paused) -{ - Clock::setPaused(paused); - Renderer::setPaused(paused); -} - -VideoRenderer::VideoRenderer(Decoder *decoder, QVideoSink *sink) - : ClockedRenderer(decoder, QPlatformMediaPlayer::VideoStream) - , sink(sink) -{} - -void VideoRenderer::killHelper() -{ - if (subtitleStreamDecoder) - subtitleStreamDecoder->kill(); - subtitleStreamDecoder = nullptr; - if (streamDecoder) - streamDecoder->kill(); - streamDecoder = nullptr; -} - -void VideoRenderer::setSubtitleStream(StreamDecoder *stream) -{ - QMutexLocker locker(&mutex); - qCDebug(qLcVideoRenderer) << "setting subtitle stream to" << stream; - if (stream == subtitleStreamDecoder) - return; - if (subtitleStreamDecoder) - subtitleStreamDecoder->kill(); - subtitleStreamDecoder = stream; - if (subtitleStreamDecoder) - subtitleStreamDecoder->setRenderer(this); - sink->setSubtitleText({}); - wake(); -} - -void VideoRenderer::init() -{ - qCDebug(qLcVideoRenderer) << "starting video renderer"; - ClockedRenderer::init(); -} - -void VideoRenderer::loop() -{ - if (!streamDecoder) { - timeOut = -1; // Avoid 100% CPU load before play() - return; - } - - Frame frame = streamDecoder->takeFrame(); - if (!frame.isValid()) { - if (streamDecoder->isAtEnd()) { - timeOut = -1; - eos.storeRelease(true); - mutex.unlock(); - emit atEnd(); - mutex.lock(); - return; - } - timeOut = 1; -// qCDebug(qLcVideoRenderer) << "no valid frame" << timer.elapsed(); - return; - } - eos.storeRelease(false); -// qCDebug(qLcVideoRenderer) << "received video frame" << frame.pts(); - if (frame.pts() < seekTime()) { - qCDebug(qLcVideoRenderer) << " discarding" << frame.pts() << seekTime(); - return; - } - - AVStream *stream = frame.codec()->stream(); - qint64 startTime = frame.pts(); - qint64 duration = stream->avg_frame_rate.num == 0 ? 0 : - (1000000*stream->avg_frame_rate.den + (stream->avg_frame_rate.num>>1)) - / stream->avg_frame_rate.num; - - if (sink) { - qint64 startTime = frame.pts(); -// qCDebug(qLcVideoRenderer) << "RHI:" << accel.isNull() << accel.rhi() << sink->rhi(); - - // in practice this only happens with mediacodec -#ifdef Q_OS_ANDROID - // QTBUG-108446 - // In general case, just creation of frames context is not correct since - // frames may require additional specific data for hw contexts, so - // just setting of hw_frames_ctx is not enough. - // TODO: investigate the case in order to remove or fix the code. - if (frame.codec()->hwAccel() && !frame.avFrame()->hw_frames_ctx) { - HWAccel *hwaccel = frame.codec()->hwAccel(); - AVFrame *avframe = frame.avFrame(); - if (!hwaccel->hwFramesContext()) - hwaccel->createFramesContext(AVPixelFormat(avframe->format), - { avframe->width, avframe->height }); - - avframe->hw_frames_ctx = av_buffer_ref(hwaccel->hwFramesContextAsBuffer()); - } -#endif - - QFFmpegVideoBuffer *buffer = new QFFmpegVideoBuffer(frame.takeAVFrame()); - QVideoFrameFormat format(buffer->size(), buffer->pixelFormat()); - format.setColorSpace(buffer->colorSpace()); - format.setColorTransfer(buffer->colorTransfer()); - format.setColorRange(buffer->colorRange()); - format.setMaxLuminance(buffer->maxNits()); - QVideoFrame videoFrame(buffer, format); - videoFrame.setStartTime(startTime); - videoFrame.setEndTime(startTime + duration); -// qCDebug(qLcVideoRenderer) << "Creating video frame" << startTime << (startTime + duration) << subtitleStreamDecoder; - - // add in subtitles - const Frame *currentSubtitle = nullptr; - if (subtitleStreamDecoder) - currentSubtitle = subtitleStreamDecoder->lockAndPeekFrame(); - - if (currentSubtitle && currentSubtitle->isValid()) { -// qCDebug(qLcVideoRenderer) << "frame: subtitle" << currentSubtitle->text() << currentSubtitle->pts() << currentSubtitle->duration(); - qCDebug(qLcVideoRenderer) << " " << currentSubtitle->pts() << currentSubtitle->duration() << currentSubtitle->text(); - if (currentSubtitle->pts() <= startTime && currentSubtitle->end() > startTime) { -// qCDebug(qLcVideoRenderer) << " setting text"; - sink->setSubtitleText(currentSubtitle->text()); - } - if (currentSubtitle->end() < startTime) { -// qCDebug(qLcVideoRenderer) << " removing subtitle item"; - sink->setSubtitleText({}); - subtitleStreamDecoder->removePeekedFrame(); - } - } else { - sink->setSubtitleText({}); - } - if (subtitleStreamDecoder) - subtitleStreamDecoder->unlockAndReleaseFrame(); - -// qCDebug(qLcVideoRenderer) << " sending a video frame" << startTime << duration << decoder->baseTimer.elapsed(); - sink->setVideoFrame(videoFrame); - doneStep(); - } - const Frame *nextFrame = streamDecoder->lockAndPeekFrame(); - qint64 nextFrameTime = 0; - if (nextFrame) - nextFrameTime = nextFrame->pts(); - else - nextFrameTime = startTime + duration; - streamDecoder->unlockAndReleaseFrame(); - qint64 mtime = timeUpdated(startTime); - timeOut = usecsTo(mtime, nextFrameTime) / 1000; - // qCDebug(qLcVideoRenderer) << " next video frame in" << startTime << nextFrameTime << - // currentTime() << timeOut; -} - -AudioRenderer::AudioRenderer(Decoder *decoder, QAudioOutput *output) - : ClockedRenderer(decoder, QPlatformMediaPlayer::AudioStream) - , output(output) -{ - connect(output, &QAudioOutput::deviceChanged, this, &AudioRenderer::updateAudio); - connect(output, &QAudioOutput::volumeChanged, this, &AudioRenderer::setSoundVolume); -} - -void AudioRenderer::syncTo(qint64 usecs) -{ - QMutexLocker locker(&mutex); - - Clock::syncTo(usecs); - audioBaseTime = usecs; - processedBase = processedUSecs; -} - -void AudioRenderer::setPlaybackRate(float rate, qint64 currentTime) -{ - QMutexLocker locker(&mutex); - - audioBaseTime = currentTime; - processedBase = processedUSecs; - Clock::setPlaybackRate(rate, currentTime); - deviceChanged = true; -} - -void AudioRenderer::updateOutput(const Codec *codec) -{ - qCDebug(qLcAudioRenderer) << ">>>>>> updateOutput" << currentTime() << seekTime() << processedUSecs << isMaster(); - freeOutput(); - qCDebug(qLcAudioRenderer) << " " << currentTime() << seekTime() << processedUSecs; - - AVStream *audioStream = codec->stream(); - - auto dev = output->device(); - format = QFFmpegMediaFormatInfo::audioFormatFromCodecParameters(audioStream->codecpar); - format.setChannelConfig(dev.channelConfiguration()); - - initResempler(codec); - - audioSink = new QAudioSink(dev, format); - audioSink->setVolume(output->volume()); - - audioSink->setBufferSize(format.bytesForDuration(100000)); - audioDevice = audioSink->start(); - - latencyUSecs = format.durationForBytes(audioSink->bufferSize()); // ### ideally get full latency - qCDebug(qLcAudioRenderer) << " -> have an audio sink" << audioDevice; -} - -void AudioRenderer::initResempler(const Codec *codec) -{ - // init resampler. It's ok to always do this, as the resampler will be a no-op if - // formats agree. - AVSampleFormat requiredFormat = QFFmpegMediaFormatInfo::avSampleFormat(format.sampleFormat()); - -#if QT_FFMPEG_OLD_CHANNEL_LAYOUT - qCDebug(qLcAudioRenderer) << "init resampler" << requiredFormat - << codec->stream()->codecpar->channels; -#else - qCDebug(qLcAudioRenderer) << "init resampler" << requiredFormat - << codec->stream()->codecpar->ch_layout.nb_channels; -#endif - - auto resamplerFormat = format; - resamplerFormat.setSampleRate(qRound(format.sampleRate() / playbackRate())); - resampler.reset(new Resampler(codec, resamplerFormat)); -} - -void AudioRenderer::freeOutput() -{ - if (audioSink) { - audioSink->reset(); - delete audioSink; - audioSink = nullptr; - audioDevice = nullptr; - } - - bufferedData = {}; - bufferWritten = 0; - - audioBaseTime = currentTime(); - processedBase = 0; - processedUSecs = writtenUSecs = 0; -} - -void AudioRenderer::init() -{ - qCDebug(qLcAudioRenderer) << "Starting audio renderer"; - ClockedRenderer::init(); -} - -void AudioRenderer::cleanup() -{ - freeOutput(); -} - -void AudioRenderer::loop() -{ - if (!streamDecoder) { - timeOut = -1; // Avoid 100% CPU load before play() - return; - } - - if (deviceChanged) - freeOutput(); - deviceChanged = false; - doneStep(); - - qint64 bytesWritten = 0; - if (bufferedData.isValid()) { - bytesWritten = audioDevice->write(bufferedData.constData() + bufferWritten, bufferedData.byteCount() - bufferWritten); - bufferWritten += bytesWritten; - if (bufferWritten == bufferedData.byteCount()) { - bufferedData = {}; - bufferWritten = 0; - } - processedUSecs = audioSink->processedUSecs(); - } else { - Frame frame = streamDecoder->takeFrame(); - if (!frame.isValid()) { - if (streamDecoder->isAtEnd()) { - if (audioSink) - processedUSecs = audioSink->processedUSecs(); - timeOut = -1; - eos.storeRelease(true); - mutex.unlock(); - emit atEnd(); - mutex.lock(); - return; - } - timeOut = 1; - return; - } - eos.storeRelease(false); - if (!audioSink) - updateOutput(frame.codec()); - - qint64 startTime = frame.pts(); - if (startTime < seekTime()) - return; - - if (!paused) { - auto buffer = resampler->resample(frame.avFrame()); - - if (output->isMuted()) - // This is somewhat inefficient, but it'll work - memset(buffer.data(), 0, buffer.byteCount()); - - bytesWritten = audioDevice->write(buffer.constData(), buffer.byteCount()); - if (bytesWritten < buffer.byteCount()) { - bufferedData = buffer; - bufferWritten = bytesWritten; - } - - processedUSecs = audioSink->processedUSecs(); - } - } - - qint64 duration = format.durationForBytes(bytesWritten); - writtenUSecs += duration; - - timeOut = (writtenUSecs - processedUSecs - latencyUSecs)/1000; - if (timeOut < 0) - // Don't use a zero timeout if the sink didn't want any more data, rather wait for 10ms. - timeOut = bytesWritten > 0 ? 0 : 10; - -// if (!bufferedData.isEmpty()) -// qCDebug(qLcAudioRenderer) << ">>>>>>>>>>>>>>>>>>>>>>>> could not write all data" << (bufferedData.size() - bufferWritten); -// qCDebug(qLcAudioRenderer) << "Audio: processed" << processedUSecs << "written" << writtenUSecs -// << "delta" << (writtenUSecs - processedUSecs) << "timeOut" << timeOut; -// qCDebug(qLcAudioRenderer) << " updating time to" << currentTimeNoLock(); - timeUpdated(audioBaseTime + qRound((processedUSecs - processedBase) * playbackRate())); -} - -void AudioRenderer::streamChanged() -{ - // mutex is already locked - deviceChanged = true; -} - -void AudioRenderer::updateAudio() -{ - QMutexLocker locker(&mutex); - deviceChanged = true; -} - -void AudioRenderer::setSoundVolume(float volume) -{ - QMutexLocker locker(&mutex); - if (audioSink) - audioSink->setVolume(volume); -} - -Decoder::Decoder() -{ -} - -Decoder::~Decoder() -{ - pause(); - if (videoRenderer) - videoRenderer->kill(); - if (audioRenderer) - audioRenderer->kill(); - if (demuxer) - demuxer->kill(); -} - -static int readQIODevice(void *opaque, uint8_t *buf, int buf_size) -{ - auto *dev = static_cast(opaque); - if (dev->atEnd()) - return AVERROR_EOF; - return dev->read(reinterpret_cast(buf), buf_size); -} - -static int64_t seekQIODevice(void *opaque, int64_t offset, int whence) -{ - QIODevice *dev = static_cast(opaque); - - if (dev->isSequential()) - return AVERROR(EINVAL); - - if (whence & AVSEEK_SIZE) - return dev->size(); - - whence &= ~AVSEEK_FORCE; - - if (whence == SEEK_CUR) - offset += dev->pos(); - else if (whence == SEEK_END) - offset += dev->size(); - - if (!dev->seek(offset)) - return AVERROR(EINVAL); - return offset; -} - -static void insertVideoData(QMediaMetaData &metaData, AVStream *stream) -{ - Q_ASSERT(stream); - auto *codecPar = stream->codecpar; - metaData.insert(QMediaMetaData::VideoBitRate, (int)codecPar->bit_rate); - metaData.insert(QMediaMetaData::VideoCodec, QVariant::fromValue(QFFmpegMediaFormatInfo::videoCodecForAVCodecId(codecPar->codec_id))); - metaData.insert(QMediaMetaData::Resolution, QSize(codecPar->width, codecPar->height)); - auto fr = toFloat(stream->avg_frame_rate); - if (fr) - metaData.insert(QMediaMetaData::VideoFrameRate, *fr); -}; - -static void insertAudioData(QMediaMetaData &metaData, AVStream *stream) -{ - Q_ASSERT(stream); - auto *codecPar = stream->codecpar; - metaData.insert(QMediaMetaData::AudioBitRate, (int)codecPar->bit_rate); - metaData.insert(QMediaMetaData::AudioCodec, - QVariant::fromValue(QFFmpegMediaFormatInfo::audioCodecForAVCodecId(codecPar->codec_id))); -}; - -static int getDefaultStreamIndex(QList &streams) -{ - if (streams.empty()) - return -1; - for (qsizetype i = 0; i < streams.size(); i++) - if (streams[i].isDefault) - return i; - return 0; -} - -static void readStreams(const AVFormatContext *context, - QList (&map)[QPlatformMediaPlayer::NTrackTypes], qint64 &maxDuration) -{ - maxDuration = 0; - - for (unsigned int i = 0; i < context->nb_streams; ++i) { - auto *stream = context->streams[i]; - if (!stream) - continue; - - auto *codecPar = stream->codecpar; - if (!codecPar) - continue; - - QMediaMetaData metaData = QFFmpegMetaData::fromAVMetaData(stream->metadata); - bool isDefault = stream->disposition & AV_DISPOSITION_DEFAULT; - QPlatformMediaPlayer::TrackType type = QPlatformMediaPlayer::VideoStream; - - switch (codecPar->codec_type) { - case AVMEDIA_TYPE_UNKNOWN: - case AVMEDIA_TYPE_DATA: ///< Opaque data information usually continuous - case AVMEDIA_TYPE_ATTACHMENT: ///< Opaque data information usually sparse - case AVMEDIA_TYPE_NB: - continue; - case AVMEDIA_TYPE_VIDEO: - type = QPlatformMediaPlayer::VideoStream; - insertVideoData(metaData, stream); - break; - case AVMEDIA_TYPE_AUDIO: - type = QPlatformMediaPlayer::AudioStream; - insertAudioData(metaData, stream); - break; - case AVMEDIA_TYPE_SUBTITLE: - type = QPlatformMediaPlayer::SubtitleStream; - break; - } - - map[type].append({ (int)i, isDefault, metaData }); - auto maybeDuration = mul(1'000'000ll * stream->duration, stream->time_base); - if (maybeDuration) - maxDuration = qMax(maxDuration, *maybeDuration); - } -} - -void Decoder::setMedia(const QUrl &media, QIODevice *stream) -{ - QByteArray url = media.toEncoded(QUrl::PreferLocalFile); - - AVFormatContext *context = nullptr; - if (stream) { - if (!stream->isOpen()) { - if (!stream->open(QIODevice::ReadOnly)) { - emit errorOccured(QMediaPlayer::ResourceError, - QLatin1String("Could not open source device.")); - return; - } - } - if (!stream->isSequential()) - stream->seek(0); - context = avformat_alloc_context(); - constexpr int bufferSize = 32768; - unsigned char *buffer = (unsigned char *)av_malloc(bufferSize); - context->pb = avio_alloc_context(buffer, bufferSize, false, stream, &readQIODevice, nullptr, &seekQIODevice); - } - - int ret = avformat_open_input(&context, url.constData(), nullptr, nullptr); - if (ret < 0) { - auto code = QMediaPlayer::ResourceError; - if (ret == AVERROR(EACCES)) - code = QMediaPlayer::AccessDeniedError; - else if (ret == AVERROR(EINVAL)) - code = QMediaPlayer::FormatError; - - emit errorOccured(code, QMediaPlayer::tr("Could not open file")); - return; - } - - ret = avformat_find_stream_info(context, nullptr); - if (ret < 0) { - emit errorOccured(QMediaPlayer::FormatError, - QMediaPlayer::tr("Could not find stream information for media file")); - avformat_free_context(context); - return; - } - -#ifndef QT_NO_DEBUG - av_dump_format(context, 0, url.constData(), 0); -#endif - - readStreams(context, m_streamMap, m_duration); - - m_requestedStreams[QPlatformMediaPlayer::VideoStream] = getDefaultStreamIndex(m_streamMap[QPlatformMediaPlayer::VideoStream]); - m_requestedStreams[QPlatformMediaPlayer::AudioStream] = getDefaultStreamIndex(m_streamMap[QPlatformMediaPlayer::AudioStream]); - m_requestedStreams[QPlatformMediaPlayer::SubtitleStream] = -1; - - m_metaData = QFFmpegMetaData::fromAVMetaData(context->metadata); - m_metaData.insert(QMediaMetaData::FileFormat, - QVariant::fromValue(QFFmpegMediaFormatInfo::fileFormatForAVInputFormat(context->iformat))); - - if (m_requestedStreams[QPlatformMediaPlayer::VideoStream] >= 0) - insertVideoData(m_metaData, context->streams[avStreamIndex(QPlatformMediaPlayer::VideoStream)]); - - if (m_requestedStreams[QPlatformMediaPlayer::AudioStream] >= 0) - insertAudioData(m_metaData, context->streams[avStreamIndex(QPlatformMediaPlayer::AudioStream)]); - - m_isSeekable = !(context->ctx_flags & AVFMTCTX_UNSEEKABLE); - - demuxer = new Demuxer(this, context); - demuxer->start(); -} - -int Decoder::activeTrack(QPlatformMediaPlayer::TrackType type) -{ - return m_requestedStreams[type]; -} - -void Decoder::setActiveTrack(QPlatformMediaPlayer::TrackType type, int streamNumber) -{ - if (streamNumber < 0 || streamNumber >= m_streamMap[type].size()) - streamNumber = -1; - if (m_requestedStreams[type] == streamNumber) - return; - m_requestedStreams[type] = streamNumber; - changeAVTrack(type); -} - -void Decoder::setState(QMediaPlayer::PlaybackState state) -{ - if (m_state == state) - return; - - switch (state) { - case QMediaPlayer::StoppedState: - qCDebug(qLcDecoder) << "Decoder::stop"; - setPaused(true); - if (demuxer) - demuxer->stopDecoding(); - seek(0); - if (videoSink) - videoSink->setVideoFrame({}); - qCDebug(qLcDecoder) << "Decoder::stop: done"; - break; - case QMediaPlayer::PausedState: - qCDebug(qLcDecoder) << "Decoder::pause"; - setPaused(true); - if (demuxer) { - demuxer->startDecoding(); - demuxer->wake(); - if (m_state == QMediaPlayer::StoppedState) - triggerStep(); - } - break; - case QMediaPlayer::PlayingState: - qCDebug(qLcDecoder) << "Decoder::play"; - setPaused(false); - if (demuxer) - demuxer->startDecoding(); - break; - } - m_state = state; -} - -void Decoder::setPaused(bool b) -{ - clockController.setPaused(b); -} - -void Decoder::triggerStep() -{ - if (audioRenderer) - audioRenderer->singleStep(); - if (videoRenderer) - videoRenderer->singleStep(); -} - -void Decoder::setVideoSink(QVideoSink *sink) -{ - qCDebug(qLcDecoder) << "setVideoSink" << sink; - if (sink == videoSink) - return; - videoSink = sink; - if (!videoSink || m_requestedStreams[QPlatformMediaPlayer::VideoStream] < 0) { - if (videoRenderer) { - videoRenderer->kill(); - videoRenderer = nullptr; - } - } else if (!videoRenderer) { - videoRenderer = new VideoRenderer(this, sink); - connect(videoRenderer, &Renderer::atEnd, this, &Decoder::streamAtEnd); - videoRenderer->start(); - StreamDecoder *stream = demuxer->addStream(avStreamIndex(QPlatformMediaPlayer::VideoStream)); - videoRenderer->setStream(stream); - stream = demuxer->addStream(avStreamIndex(QPlatformMediaPlayer::SubtitleStream)); - videoRenderer->setSubtitleStream(stream); - } -} - -void Decoder::setAudioSink(QPlatformAudioOutput *output) -{ - if (audioOutput == output) - return; - - qCDebug(qLcDecoder) << "setAudioSink" << audioOutput; - audioOutput = output; - if (!output || m_requestedStreams[QPlatformMediaPlayer::AudioStream] < 0) { - if (audioRenderer) { - audioRenderer->kill(); - audioRenderer = nullptr; - } - } else if (!audioRenderer) { - audioRenderer = new AudioRenderer(this, output->q); - connect(audioRenderer, &Renderer::atEnd, this, &Decoder::streamAtEnd); - audioRenderer->start(); - auto *stream = demuxer->addStream(avStreamIndex(QPlatformMediaPlayer::AudioStream)); - audioRenderer->setStream(stream); - } -} - -void Decoder::changeAVTrack(QPlatformMediaPlayer::TrackType type) -{ - if (!demuxer) - return; - qCDebug(qLcDecoder) << " applying to renderer."; - if (m_state == QMediaPlayer::PlayingState) - setPaused(true); - auto *streamDecoder = demuxer->addStream(avStreamIndex(type)); - switch (type) { - case QPlatformMediaPlayer::AudioStream: - audioRenderer->setStream(streamDecoder); - break; - case QPlatformMediaPlayer::VideoStream: - videoRenderer->setStream(streamDecoder); - break; - case QPlatformMediaPlayer::SubtitleStream: - videoRenderer->setSubtitleStream(streamDecoder); - break; - default: - Q_UNREACHABLE(); - } - demuxer->seek(clockController.currentTime()); - if (m_state == QMediaPlayer::PlayingState) - setPaused(false); - else - triggerStep(); -} - -void Decoder::seek(qint64 pos) -{ - if (!demuxer) - return; - pos = qBound(0, pos, m_duration); - demuxer->seek(pos); - clockController.syncTo(pos); - demuxer->wake(); - if (m_state == QMediaPlayer::PausedState) - triggerStep(); -} - -void Decoder::setPlaybackRate(float rate) -{ - clockController.setPlaybackRate(rate); -} - -void Decoder::streamAtEnd() -{ - if (audioRenderer && !audioRenderer->isAtEnd()) - return; - if (videoRenderer && !videoRenderer->isAtEnd()) - return; - pause(); - - emit endOfStream(); -} - -qint64 Decoder::currentPosition() const { - return clockController.currentTime(); -} - -QT_END_NAMESPACE - -#include "moc_qffmpegdecoder_p.cpp" diff --git a/src/plugins/multimedia/ffmpeg/qffmpegdecoder_p.h b/src/plugins/multimedia/ffmpeg/qffmpegdecoder_p.h deleted file mode 100644 index b3b2dc605..000000000 --- a/src/plugins/multimedia/ffmpeg/qffmpegdecoder_p.h +++ /dev/null @@ -1,512 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef QFFMPEGDECODER_P_H -#define QFFMPEGDECODER_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "qffmpegthread_p.h" -#include "qffmpeg_p.h" -#include "qffmpegmediaplayer_p.h" -#include "qffmpeghwaccel_p.h" -#include "qffmpegclock_p.h" -#include "qaudiobuffer.h" -#include "qffmpegresampler_p.h" - -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class QAudioSink; -class QFFmpegAudioDecoder; -class QFFmpegMediaPlayer; - -namespace QFFmpeg -{ - -class Resampler; - -// queue up max 16M of encoded data, that should always be enough -// (it's around 2 secs of 4K HDR video, longer for almost all other formats) -enum { MaxQueueSize = 16*1024*1024 }; - -struct Packet -{ - struct Data { - Data(AVPacket *p) - : packet(p) - {} - ~Data() { - if (packet) - av_packet_free(&packet); - } - QAtomicInt ref; - AVPacket *packet = nullptr; - }; - Packet() = default; - Packet(AVPacket *p) - : d(new Data(p)) - {} - - bool isValid() const { return !!d; } - AVPacket *avPacket() const { return d->packet; } -private: - QExplicitlySharedDataPointer d; -}; - -struct Codec -{ - struct AVCodecFreeContext { void operator()(AVCodecContext *ctx) { avcodec_free_context(&ctx); } }; - using UniqueAVCodecContext = std::unique_ptr; - struct Data { - Data(UniqueAVCodecContext &&context, AVStream *stream, std::unique_ptr &&hwAccel); - ~Data(); - QAtomicInt ref; - UniqueAVCodecContext context; - AVStream *stream = nullptr; - std::unique_ptr hwAccel; - }; - - static QMaybe create(AVStream *); - - AVCodecContext *context() const { return d->context.get(); } - AVStream *stream() const { return d->stream; } - uint streamIndex() const { return d->stream->index; } - HWAccel *hwAccel() const { return d->hwAccel.get(); } - qint64 toMs(qint64 ts) const { return timeStampMs(ts, d->stream->time_base).value_or(0); } - qint64 toUs(qint64 ts) const { return timeStampUs(ts, d->stream->time_base).value_or(0); } - -private: - Codec(Data *data) : d(data) {} - QExplicitlySharedDataPointer d; -}; - - -struct Frame -{ - struct Data { - Data(AVFrameUPtr f, const Codec &codec, qint64, const QObject *source) - : codec(codec), frame(std::move(f)), source(source) - { - Q_ASSERT(frame); - if (frame->pts != AV_NOPTS_VALUE) - pts = codec.toUs(frame->pts); - else - pts = codec.toUs(frame->best_effort_timestamp); - const auto &avgFrameRate = codec.stream()->avg_frame_rate; - duration = avgFrameRate.num - ? (1000000 * avgFrameRate.den + avgFrameRate.num / 2) / avgFrameRate.num - : 0; - } - Data(const QString &text, qint64 pts, qint64 duration, const QObject *source) - : text(text), pts(pts), duration(duration), source(source) - {} - - QAtomicInt ref; - std::optional codec; - AVFrameUPtr frame; - QString text; - qint64 pts = -1; - qint64 duration = -1; - QPointer source; - }; - Frame() = default; - Frame(AVFrameUPtr f, const Codec &codec, qint64 pts, const QObject *source = nullptr) - : d(new Data(std::move(f), codec, pts, source)) - {} - Frame(const QString &text, qint64 pts, qint64 duration, const QObject *source = nullptr) - : d(new Data(text, pts, duration, source)) - {} - bool isValid() const { return !!d; } - - AVFrame *avFrame() const { return d->frame.get(); } - AVFrameUPtr takeAVFrame() { return std::move(d->frame); } - const Codec *codec() const { return d->codec ? &d->codec.value() : nullptr; } - qint64 pts() const { return d->pts; } - qint64 duration() const { return d->duration; } - qint64 end() const { return d->pts + d->duration; } - QString text() const { return d->text; } - const QObject *source() const { return d->source; }; - -private: - QExplicitlySharedDataPointer d; -}; - -class Demuxer; -class StreamDecoder; -class Renderer; -class AudioRenderer; -class VideoRenderer; - -class Decoder : public QObject -{ - Q_OBJECT -public: - Decoder(); - ~Decoder(); - - void setMedia(const QUrl &media, QIODevice *stream); - - void init(); - void setState(QMediaPlayer::PlaybackState state); - void play() { - setState(QMediaPlayer::PlayingState); - } - void pause() { - setState(QMediaPlayer::PausedState); - } - void stop() { - setState(QMediaPlayer::StoppedState); - } - - void triggerStep(); - - void setVideoSink(QVideoSink *sink); - void setAudioSink(QPlatformAudioOutput *output); - - void changeAVTrack(QPlatformMediaPlayer::TrackType type); - - void seek(qint64 pos); - void setPlaybackRate(float rate); - - qint64 currentPosition() const; - - int activeTrack(QPlatformMediaPlayer::TrackType type); - void setActiveTrack(QPlatformMediaPlayer::TrackType type, int streamNumber); - - bool isSeekable() const - { - return m_isSeekable; - } - -signals: - void endOfStream(); - void errorOccured(int error, const QString &errorString); - void positionChanged(qint64 time); - -public slots: - - void streamAtEnd(); - -public: - struct StreamInfo { - int avStreamIndex = -1; - bool isDefault = false; - QMediaMetaData metaData; - }; - - // Accessed from multiple threads, but API is threadsafe - ClockController clockController; - -private: - void setPaused(bool b); - -protected: - friend QFFmpegMediaPlayer; - - QMediaPlayer::PlaybackState m_state = QMediaPlayer::StoppedState; - bool m_isSeekable = false; - - Demuxer *demuxer = nullptr; - QVideoSink *videoSink = nullptr; - Renderer *videoRenderer = nullptr; - QPlatformAudioOutput *audioOutput = nullptr; - Renderer *audioRenderer = nullptr; - - QList m_streamMap[QPlatformMediaPlayer::NTrackTypes]; - int m_requestedStreams[QPlatformMediaPlayer::NTrackTypes] = { -1, -1, -1 }; - qint64 m_duration = 0; - QMediaMetaData m_metaData; - - int avStreamIndex(QPlatformMediaPlayer::TrackType type) - { - int i = m_requestedStreams[type]; - return i < 0 || i >= m_streamMap[type].size() ? -1 : m_streamMap[type][i].avStreamIndex; - } -}; - -class Demuxer : public Thread -{ - Q_OBJECT -public: - Demuxer(Decoder *decoder, AVFormatContext *context); - ~Demuxer(); - - StreamDecoder *addStream(int streamIndex); - void removeStream(int streamIndex); - - bool isStopped() const - { - return m_isStopped.loadRelaxed(); - } - void startDecoding() - { - m_isStopped.storeRelaxed(false); - updateEnabledStreams(); - wake(); - } - void stopDecoding(); - - int seek(qint64 pos); - -private: - void updateEnabledStreams(); - void sendFinalPacketToStreams(); - - void init() override; - void cleanup() override; - bool shouldWait() const override; - void loop() override; - - Decoder *decoder; - AVFormatContext *context = nullptr; - QList streamDecoders; - - QAtomicInteger m_isStopped = true; - qint64 last_pts = -1; -}; - - -class StreamDecoder : public Thread -{ - Q_OBJECT -protected: - Demuxer *demuxer = nullptr; - Renderer *m_renderer = nullptr; - - struct PacketQueue { - mutable QMutex mutex; - QQueue queue; - qint64 size = 0; - qint64 duration = 0; - }; - PacketQueue packetQueue; - - struct FrameQueue { - mutable QMutex mutex; - QQueue queue; - int maxSize = 3; - }; - FrameQueue frameQueue; - QAtomicInteger eos = false; - bool decoderHasNoFrames = false; - -public: - StreamDecoder(Demuxer *demuxer, const Codec &codec); - - void addPacket(AVPacket *packet); - - qint64 queuedPacketSize() const { - QMutexLocker locker(&packetQueue.mutex); - return packetQueue.size; - } - qint64 queuedDuration() const { - QMutexLocker locker(&packetQueue.mutex); - return packetQueue.duration; - } - - const Frame *lockAndPeekFrame() - { - frameQueue.mutex.lock(); - return frameQueue.queue.isEmpty() ? nullptr : &frameQueue.queue.first(); - } - void removePeekedFrame() - { - frameQueue.queue.takeFirst(); - wake(); - } - void unlockAndReleaseFrame() - { - frameQueue.mutex.unlock(); - } - Frame takeFrame(); - - void flush(); - - Codec codec; - - void setRenderer(Renderer *r); - Renderer *renderer() const { return m_renderer; } - - bool isAtEnd() const { return eos.loadAcquire(); } - - void killHelper() override; - -private: - Packet takePacket(); - Packet peekPacket(); - - void addFrame(const Frame &f); - - bool hasEnoughFrames() const - { - QMutexLocker locker(&frameQueue.mutex); - return frameQueue.queue.size() >= frameQueue.maxSize; - } - bool hasNoPackets() const - { - QMutexLocker locker(&packetQueue.mutex); - return packetQueue.queue.isEmpty(); - } - - void init() override; - bool shouldWait() const override; - void loop() override; - - void decode(); - void decodeSubtitle(); - - QPlatformMediaPlayer::TrackType type() const; -}; - -class Renderer : public Thread -{ - Q_OBJECT -protected: - QPlatformMediaPlayer::TrackType type; - - bool step = false; - bool paused = true; - StreamDecoder *streamDecoder = nullptr; - QAtomicInteger eos = false; - -public: - Renderer(QPlatformMediaPlayer::TrackType type); - - void setPaused(bool p) { - QMutexLocker locker(&mutex); - paused = p; - if (!p) - wake(); - } - void singleStep() { - QMutexLocker locker(&mutex); - if (!paused) - return; - step = true; - wake(); - } - void doneStep() { - step = false; - } - bool isAtEnd() { return !streamDecoder || eos.loadAcquire(); } - - void setStream(StreamDecoder *stream); - virtual void setSubtitleStream(StreamDecoder *) {} - - void killHelper() override; - - virtual void streamChanged() {} - -Q_SIGNALS: - void atEnd(); - -protected: - bool shouldWait() const override; - -public: -}; - -class ClockedRenderer : public Renderer, public Clock -{ -public: - ClockedRenderer(Decoder *decoder, QPlatformMediaPlayer::TrackType type) - : Renderer(type) - , Clock(&decoder->clockController) - { - } - ~ClockedRenderer() - { - } - void setPaused(bool paused) override; -}; - -class VideoRenderer : public ClockedRenderer -{ - Q_OBJECT - - StreamDecoder *subtitleStreamDecoder = nullptr; -public: - VideoRenderer(Decoder *decoder, QVideoSink *sink); - - void killHelper() override; - - void setSubtitleStream(StreamDecoder *stream) override; -private: - - void init() override; - void loop() override; - - QVideoSink *sink; -}; - -class AudioRenderer : public ClockedRenderer -{ - Q_OBJECT -public: - AudioRenderer(Decoder *decoder, QAudioOutput *output); - ~AudioRenderer() = default; - - // Clock interface - void syncTo(qint64 usecs) override; - void setPlaybackRate(float rate, qint64 currentTime) override; - -private slots: - void updateAudio(); - void setSoundVolume(float volume); - -private: - void updateOutput(const Codec *codec); - void initResempler(const Codec *codec); - void freeOutput(); - - void init() override; - void cleanup() override; - void loop() override; - void streamChanged() override; - Type type() const override { return AudioClock; } - - int outputSamples(int inputSamples) { - return qRound(inputSamples/playbackRate()); - } - - // Used for timing update calculations based on processed data - qint64 audioBaseTime = 0; - qint64 processedBase = 0; - qint64 processedUSecs = 0; - - bool deviceChanged = false; - QAudioOutput *output = nullptr; - qint64 writtenUSecs = 0; - qint64 latencyUSecs = 0; - - QAudioFormat format; - QAudioSink *audioSink = nullptr; - QIODevice *audioDevice = nullptr; - std::unique_ptr resampler; - QAudioBuffer bufferedData; - qsizetype bufferWritten = 0; -}; - -} - -QT_END_NAMESPACE - -Q_DECLARE_METATYPE(QFFmpeg::Packet) -Q_DECLARE_METATYPE(QFFmpeg::Frame) - -#endif - diff --git a/src/plugins/multimedia/ffmpeg/qffmpegmediaplayer.cpp b/src/plugins/multimedia/ffmpeg/qffmpegmediaplayer.cpp index 819e2b8bd..a3ea9170f 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegmediaplayer.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpegmediaplayer.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qffmpegmediaplayer_p.h" -#include "qffmpegdecoder_p.h" #include "qffmpegmediaformatinfo_p.h" #include "qlocale.h" #include "qffmpeg_p.h" diff --git a/src/plugins/multimedia/ffmpeg/qffmpegplaybackengine_p.h b/src/plugins/multimedia/ffmpeg/qffmpegplaybackengine_p.h index 979717113..48e939f82 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegplaybackengine_p.h +++ b/src/plugins/multimedia/ffmpeg/qffmpegplaybackengine_p.h @@ -48,6 +48,7 @@ #include "playbackengine/qffmpegplaybackenginedefs_p.h" #include "playbackengine/qffmpegtimecontroller_p.h" #include "playbackengine/qffmpegmediadataholder_p.h" +#include "playbackengine/qffmpegcodec_p.h" #include @@ -61,7 +62,7 @@ class QFFmpegMediaPlayer; namespace QFFmpeg { -class PlaybackEngine : public QObject, protected PlaybackEngineInternal::MediaDataHolder +class PlaybackEngine : public QObject, protected MediaDataHolder { Q_OBJECT public: @@ -115,15 +116,6 @@ signals: void errorOccured(int, const QString &); protected: // objects managing - using Demuxer = PlaybackEngineInternal::Demuxer; - using PlaybackEngineObject = PlaybackEngineInternal::PlaybackEngineObject; - using Renderer = PlaybackEngineInternal::Renderer; - using AudioRenderer = PlaybackEngineInternal::AudioRenderer; - using VideoRenderer = PlaybackEngineInternal::VideoRenderer; - using SubtitleRenderer = PlaybackEngineInternal::SubtitleRenderer; - using StreamDecoder = PlaybackEngineInternal::StreamDecoder; - using TimeController = PlaybackEngineInternal::TimeController; - struct ObjectDeleter { void operator()(PlaybackEngineObject *) const; diff --git a/src/plugins/multimedia/ffmpeg/qffmpegresampler.cpp b/src/plugins/multimedia/ffmpeg/qffmpegresampler.cpp index bb15aa0e1..ba407fd3a 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegresampler.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpegresampler.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qffmpegresampler_p.h" -#include "qffmpegdecoder_p.h" +#include "playbackengine/qffmpegcodec_p.h" #include "qffmpegmediaformatinfo_p.h" #include diff --git a/src/plugins/multimedia/ffmpeg/qffmpegresampler_p.h b/src/plugins/multimedia/ffmpeg/qffmpegresampler_p.h index 4b5b59537..2f23c3c81 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegresampler_p.h +++ b/src/plugins/multimedia/ffmpeg/qffmpegresampler_p.h @@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE namespace QFFmpeg { -struct Codec; +class Codec; class Resampler { diff --git a/src/plugins/multimedia/ffmpeg/qffmpegvideoframeencoder.cpp b/src/plugins/multimedia/ffmpeg/qffmpegvideoframeencoder.cpp index fe81a2952..87ab31181 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegvideoframeencoder.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpegvideoframeencoder.cpp @@ -25,7 +25,6 @@ VideoFrameEncoder::Data::~Data() { if (converter) sws_freeContext(converter); - avcodec_free_context(&codecContext); } VideoFrameEncoder::VideoFrameEncoder(const QMediaEncoderSettings &encoderSettings, @@ -246,14 +245,14 @@ void QFFmpeg::VideoFrameEncoder::initWithFormatContext(AVFormatContext *formatCo } Q_ASSERT(d->codec); - d->codecContext = avcodec_alloc_context3(d->codec); + d->codecContext.reset(avcodec_alloc_context3(d->codec)); if (!d->codecContext) { qWarning() << "Could not allocate codec context"; d = {}; return; } - avcodec_parameters_to_context(d->codecContext, d->stream->codecpar); + avcodec_parameters_to_context(d->codecContext.get(), d->stream->codecpar); d->codecContext->time_base = d->stream->time_base; qCDebug(qLcVideoFrameEncoder) << "requesting time base" << d->codecContext->time_base.num << d->codecContext->time_base.den; auto [num, den] = qRealToFraction(requestedRate); @@ -271,10 +270,10 @@ void QFFmpeg::VideoFrameEncoder::initWithFormatContext(AVFormatContext *formatCo bool VideoFrameEncoder::open() { AVDictionary *opts = nullptr; - applyVideoEncoderOptions(d->settings, d->codec->name, d->codecContext, &opts); - int res = avcodec_open2(d->codecContext, d->codec, &opts); + applyVideoEncoderOptions(d->settings, d->codec->name, d->codecContext.get(), &opts); + int res = avcodec_open2(d->codecContext.get(), d->codec, &opts); if (res < 0) { - avcodec_free_context(&d->codecContext); + d->codecContext.reset(); qWarning() << "Couldn't open codec for writing" << err2str(res); return false; } @@ -298,7 +297,7 @@ int VideoFrameEncoder::sendFrame(AVFrameUPtr frame) } if (!frame) - return avcodec_send_frame(d->codecContext, frame.get()); + return avcodec_send_frame(d->codecContext.get(), frame.get()); auto pts = frame->pts; if (d->downloadFromHW) { @@ -353,7 +352,7 @@ int VideoFrameEncoder::sendFrame(AVFrameUPtr frame) qCDebug(qLcVideoFrameEncoder) << "sending frame" << pts; frame->pts = pts; - return avcodec_send_frame(d->codecContext, frame.get()); + return avcodec_send_frame(d->codecContext.get(), frame.get()); } AVPacket *VideoFrameEncoder::retrievePacket() @@ -361,7 +360,7 @@ AVPacket *VideoFrameEncoder::retrievePacket() if (!d || !d->codecContext) return nullptr; AVPacket *packet = av_packet_alloc(); - int ret = avcodec_receive_packet(d->codecContext, packet); + int ret = avcodec_receive_packet(d->codecContext.get(), packet); if (ret < 0) { av_packet_free(&packet); if (ret != AVERROR(EOF) && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) diff --git a/src/plugins/multimedia/ffmpeg/qffmpegvideoframeencoder_p.h b/src/plugins/multimedia/ffmpeg/qffmpegvideoframeencoder_p.h index 2e4b11ddc..62eb6f916 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegvideoframeencoder_p.h +++ b/src/plugins/multimedia/ffmpeg/qffmpegvideoframeencoder_p.h @@ -36,7 +36,7 @@ class VideoFrameEncoder std::unique_ptr accel; const AVCodec *codec = nullptr; AVStream *stream = nullptr; - AVCodecContext *codecContext = nullptr; + AVCodecContextUPtr codecContext; SwsContext *converter = nullptr; AVPixelFormat sourceFormat = AV_PIX_FMT_NONE; AVPixelFormat sourceSWFormat = AV_PIX_FMT_NONE; -- cgit v1.2.3