| 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 |  |