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

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