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 | |
8 | QT_BEGIN_NAMESPACE |
9 | |
10 | static Q_LOGGING_CATEGORY(qLcPlaybackEngineCodec, "qt.multimedia.playbackengine.codec" ); |
11 | |
12 | namespace QFFmpeg { |
13 | |
14 | Codec::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 | |
22 | QMaybe<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 | |
42 | AVRational 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 | |
49 | QMaybe<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 | |
116 | QT_END_NAMESPACE |
117 | |
118 | } // namespace QFFmpeg |
119 | |