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