1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "playbackengine/qffmpegcodeccontext_p.h"
5#include "qffmpegcodecstorage_p.h"
6
7#include <QtMultimedia/qplaybackoptions.h>
8#include <QtCore/qloggingcategory.h>
9
10QT_BEGIN_NAMESPACE
11
12using namespace Qt::StringLiterals;
13
14Q_STATIC_LOGGING_CATEGORY(qLcPlaybackEngineCodec, "qt.multimedia.playbackengine.codec");
15
16namespace QFFmpeg {
17
18CodecContext::Data::Data(AVCodecContextUPtr context, AVStream *avStream,
19 AVFormatContext *avFormatContext,
20 std::unique_ptr<QFFmpeg::HWAccel> hwAccel)
21 : context(std::move(context)),
22 stream(avStream),
23 formatContext(avFormatContext),
24 hwAccel(std::move(hwAccel))
25{
26 if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
27 pixelAspectRatio = av_guess_sample_aspect_ratio(format: formatContext, stream, frame: nullptr);
28}
29
30q23::expected<CodecContext, QString> CodecContext::create(AVStream *stream,
31 AVFormatContext *formatContext,
32 const QPlaybackOptions &options)
33{
34 if (!stream)
35 return q23::unexpected{ u"Invalid stream"_s };
36
37 if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
38 auto hwCodec = create(stream, formatContext, playbackOptions: options, videoCodecPolicy: Hw);
39 if (hwCodec)
40 return hwCodec;
41
42 qCInfo(qLcPlaybackEngineCodec) << hwCodec.error();
43 }
44
45 auto context = create(stream, formatContext, playbackOptions: options, videoCodecPolicy: Sw);
46 if (!context)
47 qCWarning(qLcPlaybackEngineCodec) << context.error();
48
49 return context;
50}
51
52AVRational CodecContext::pixelAspectRatio(AVFrame *frame) const
53{
54 // does the same as av_guess_sample_aspect_ratio, but more efficient
55 return d->pixelAspectRatio.num && d->pixelAspectRatio.den ? d->pixelAspectRatio
56 : frame->sample_aspect_ratio;
57}
58
59q23::expected<CodecContext, QString> CodecContext::create(AVStream *stream,
60 AVFormatContext *formatContext,
61 const QPlaybackOptions &options,
62 VideoCodecCreationPolicy videoCodecPolicy)
63{
64 Q_ASSERT(stream);
65
66 if (videoCodecPolicy == Hw && stream->codecpar->codec_type != AVMEDIA_TYPE_VIDEO)
67 Q_ASSERT(!"Codec::create has been called with Hw policy on a non-video stream");
68
69 std::optional<Codec> decoder;
70 std::unique_ptr<QFFmpeg::HWAccel> hwAccel;
71
72 if (videoCodecPolicy == Hw)
73 std::tie(args&: decoder, args&: hwAccel) = HWAccel::findDecoderWithHwAccel(id: stream->codecpar->codec_id);
74 else
75 decoder = QFFmpeg::findAVDecoder(codecId: stream->codecpar->codec_id);
76
77 if (!decoder)
78 return q23::unexpected{
79 QString(u"No %1 decoder found").arg(a: videoCodecPolicy == Hw ? "HW" : "SW")
80 };
81
82 qCDebug(qLcPlaybackEngineCodec)
83 << "found decoder" << decoder->name() << "for id" << decoder->id();
84
85 AVCodecContextUPtr context(avcodec_alloc_context3(codec: decoder->get()));
86 if (!context)
87 return q23::unexpected{ u"Failed to allocate a FFmpeg codec context"_s };
88
89 // Use HW decoding even if the codec level doesn't match the reported capabilities
90 // of the hardware. FFmpeg documentation recommendeds setting this flag by default.
91 context->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL;
92
93 static const bool allowProfileMismatch = static_cast<bool>(
94 qEnvironmentVariableIntValue(varName: "QT_FFMPEG_HW_ALLOW_PROFILE_MISMATCH"));
95 if (allowProfileMismatch) {
96 // Use HW decoding even if the codec profile doesn't match the reported capabilities
97 // of the hardware.
98 context->hwaccel_flags |= AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH;
99 }
100
101 if (hwAccel)
102 context->hw_device_ctx = av_buffer_ref(buf: hwAccel->hwDeviceContextAsBuffer());
103
104 if (context->codec_type != AVMEDIA_TYPE_AUDIO && context->codec_type != AVMEDIA_TYPE_VIDEO
105 && context->codec_type != AVMEDIA_TYPE_SUBTITLE) {
106 return q23::unexpected{ u"Unknown codec type"_s };
107 }
108
109 int ret = avcodec_parameters_to_context(codec: context.get(), par: stream->codecpar);
110 if (ret < 0)
111 return q23::unexpected{
112 QStringLiteral("Failed to set FFmpeg codec parameters: %1").arg(a: err2str(errnum: ret))
113 };
114
115 // ### This still gives errors about wrong HW formats (as we accept all of them)
116 // But it would be good to get so we can filter out pixel format we don't support natively
117 context->get_format = QFFmpeg::getFormat;
118
119 /* Init the decoder, with reference counting and threading */
120 AVDictionaryHolder opts;
121 if (options.playbackIntent() == QPlaybackOptions::PlaybackIntent::LowLatencyStreaming)
122 av_dict_set(pm: opts, key: "flags", value: "low_delay", flags: 0);
123
124 av_dict_set(pm: opts, key: "refcounted_frames", value: "1", flags: 0);
125 av_dict_set(pm: opts, key: "threads", value: "auto", flags: 0);
126 applyExperimentalCodecOptions(codec: *decoder, opts);
127
128 ret = avcodec_open2(avctx: context.get(), codec: decoder->get(), options: opts);
129
130 if (ret < 0)
131 return q23::unexpected{
132 QStringLiteral("Failed to open FFmpeg codec context: %1").arg(a: err2str(errnum: ret))
133 };
134
135 return CodecContext(new Data(std::move(context), stream, formatContext, std::move(hwAccel)));
136}
137
138QT_END_NAMESPACE
139
140} // namespace QFFmpeg
141

source code of qtmultimedia/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegcodeccontext.cpp