Revision control

++ // Copyright (C) 2024 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qffmpegcodec_p.h"
++ #include "qffmpeg_p.h"
++
++ #include <QtCore/qloggingcategory.h>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace QFFmpeg {
++ namespace {
++
++ template <typename T>
++ inline constexpr auto InvalidAvValue = T{};
++
++ template <>
++ inline constexpr auto InvalidAvValue<AVSampleFormat> = AV_SAMPLE_FMT_NONE;
++
++ template <>
++ inline constexpr auto InvalidAvValue<AVPixelFormat> = AV_PIX_FMT_NONE;
++
++ template <typename T>
++ QSpan<const T> makeSpan(const T *values)
++ {
++ if (!values)
++ return {};
++
++ qsizetype size = 0;
++ while (values[size] != InvalidAvValue<T>)
++ ++size;
++
++ return QSpan<const T>{ values, size };
++ }
++
++ #if QT_FFMPEG_HAS_AVCODEC_GET_SUPPORTED_CONFIG
++
++ Q_STATIC_LOGGING_CATEGORY(qLcFFmpegUtils, "qt.multimedia.ffmpeg.utils");
++
++ void logGetCodecConfigError(const AVCodec *codec, AVCodecConfig config, int error)
++ {
++ qCWarning(qLcFFmpegUtils) << "Failed to retrieve config" << config << "for codec" << codec->name
++ << "with error" << error << AVError(error);
++ }
++
++ template <typename T>
++ QSpan<const T> getCodecConfig(const AVCodec *codec, AVCodecConfig config)
++ {
++ const T *result = nullptr;
++ int size = 0;
++ const auto error = avcodec_get_supported_config(
++ nullptr, codec, config, 0u, reinterpret_cast<const void **>(&result), &size);
++ if (error != 0) {
++ logGetCodecConfigError(codec, config, error);
++ return {};
++ }
++
++ // Sanity check of FFmpeg's array layout. If it is not nullptr, it should end with a terminator,
++ // and be non-empty. A non-null but empty config would mean that no values are accepted by the
++ // codec, which does not make sense.
++ Q_ASSERT(!result || (size > 0 && result[size] == InvalidAvValue<T>));
++
++ // Returns empty span if 'result' is nullptr. This can be misleading, as it may
++ // mean that 'any' value is allowed, or that the result is 'unknown'.
++ return QSpan<const T>{ result, size };
++ }
++ #endif
++
++ QSpan<const AVPixelFormat> getCodecPixelFormats(const AVCodec *codec)
++ {
++ #if QT_FFMPEG_HAS_AVCODEC_GET_SUPPORTED_CONFIG
++ return getCodecConfig<AVPixelFormat>(codec, AV_CODEC_CONFIG_PIX_FORMAT);
++ #else
++ return makeSpan(codec->pix_fmts);
++ #endif
++ }
++
++ QSpan<const AVSampleFormat> getCodecSampleFormats(const AVCodec *codec)
++ {
++ #if QT_FFMPEG_HAS_AVCODEC_GET_SUPPORTED_CONFIG
++ return getCodecConfig<AVSampleFormat>(codec, AV_CODEC_CONFIG_SAMPLE_FORMAT);
++ #else
++ return makeSpan(codec->sample_fmts);
++ #endif
++ }
++
++ QSpan<const int> getCodecSampleRates(const AVCodec *codec)
++ {
++ #if QT_FFMPEG_HAS_AVCODEC_GET_SUPPORTED_CONFIG
++ return getCodecConfig<int>(codec, AV_CODEC_CONFIG_SAMPLE_RATE);
++ #else
++ return makeSpan(codec->supported_samplerates);
++ #endif
++ }
++
++ #ifdef Q_OS_WINDOWS
++
++ auto stereoLayout() // unused function on non-Windows platforms
++ {
++ constexpr uint64_t mask = AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT;
++ #if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
++ AVChannelLayout channelLayout{};
++ av_channel_layout_from_mask(&channelLayout, mask);
++ return channelLayout;
++ #else
++ return mask;
++ #endif
++ };
++
++ #endif
++
++ QSpan<const ChannelLayoutT> getCodecChannelLayouts(const AVCodec *codec)
++ {
++ QSpan<const ChannelLayoutT> layout;
++ #if QT_FFMPEG_HAS_AVCODEC_GET_SUPPORTED_CONFIG
++ layout = getCodecConfig<AVChannelLayout>(codec, AV_CODEC_CONFIG_CHANNEL_LAYOUT);
++ #elif QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
++ layout = makeSpan(codec->ch_layouts);
++ #else
++ layout = makeSpan(codec->channel_layouts);
++ #endif
++
++ #ifdef Q_OS_WINDOWS
++ // The mp3_mf codec does not report any layout restrictions, but does not
++ // handle more than 2 channels. We therefore make this explicit here.
++ if (layout.empty() && QLatin1StringView(codec->name) == QLatin1StringView("mp3_mf")) {
++ static const ChannelLayoutT defaultLayout[] = { stereoLayout() };
++ layout = defaultLayout;
++ }
++ #endif
++ return layout;
++ }
++
++ QSpan<const AVRational> getCodecFrameRates(const AVCodec *codec)
++ {
++ #if QT_FFMPEG_HAS_AVCODEC_GET_SUPPORTED_CONFIG
++ return getCodecConfig<AVRational>(codec, AV_CODEC_CONFIG_FRAME_RATE);
++ #else
++ return makeSpan(codec->supported_framerates);
++ #endif
++ }
++ } // namespace
++
++ Codec::Codec(const AVCodec *codec) : m_codec{ codec }
++ {
++ Q_ASSERT(m_codec);
++ }
++
++ const AVCodec* Codec::get() const noexcept
++ {
++ Q_ASSERT(m_codec);
++ return m_codec;
++ }
++
++ AVCodecID Codec::id() const noexcept
++ {
++ Q_ASSERT(m_codec);
++
++ return m_codec->id;
++ }
++
++ QLatin1StringView Codec::name() const noexcept
++ {
++ Q_ASSERT(m_codec);
++
++ return QLatin1StringView{ m_codec->name };
++ }
++
++ AVMediaType Codec::type() const noexcept
++ {
++ Q_ASSERT(m_codec);
++
++ return m_codec->type;
++ }
++
++ // See AV_CODEC_CAP_*
++ int Codec::capabilities() const noexcept
++ {
++ Q_ASSERT(m_codec);
++
++ return m_codec->capabilities;
++ }
++
++ bool Codec::isEncoder() const noexcept
++ {
++ Q_ASSERT(m_codec);
++
++ return av_codec_is_encoder(m_codec) != 0;
++ }
++
++ bool Codec::isDecoder() const noexcept
++ {
++ Q_ASSERT(m_codec);
++
++ return av_codec_is_decoder(m_codec) != 0;
++ }
++
++ bool Codec::isExperimental() const noexcept
++ {
++ Q_ASSERT(m_codec);
++
++ return (m_codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) != 0;
++ }
++
++ QSpan<const AVPixelFormat> Codec::pixelFormats() const noexcept
++ {
++ Q_ASSERT(m_codec);
++
++ if (m_codec->type != AVMEDIA_TYPE_VIDEO)
++ return {};
++
++ return getCodecPixelFormats(m_codec);
++ }
++
++ QSpan<const AVSampleFormat> Codec::sampleFormats() const noexcept
++ {
++ Q_ASSERT(m_codec);
++
++ if (m_codec->type != AVMEDIA_TYPE_AUDIO)
++ return {};
++
++ return getCodecSampleFormats(m_codec);
++ }
++
++ QSpan<const int> Codec::sampleRates() const noexcept
++ {
++ Q_ASSERT(m_codec);
++
++ if (m_codec->type != AVMEDIA_TYPE_AUDIO)
++ return {};
++
++ return getCodecSampleRates(m_codec);
++ }
++
++ QSpan<const ChannelLayoutT> Codec::channelLayouts() const noexcept
++ {
++ Q_ASSERT(m_codec);
++
++ if (m_codec->type != AVMEDIA_TYPE_AUDIO)
++ return {};
++
++ return getCodecChannelLayouts(m_codec);
++ }
++
++ QSpan<const AVRational> Codec::frameRates() const noexcept
++ {
++ Q_ASSERT(m_codec);
++
++ if (m_codec->type != AVMEDIA_TYPE_VIDEO)
++ return {};
++
++ return getCodecFrameRates(m_codec);
++ }
++
++ std::vector<const AVCodecHWConfig *> Codec::hwConfigs() const noexcept
++ {
++ Q_ASSERT(m_codec);
++
++ // For most codecs, hwConfig is empty so we optimize for
++ // the hot path and do not preallocate/reserve any memory.
++ std::vector<const AVCodecHWConfig *> configs;
++
++ for (int index = 0; auto config = avcodec_get_hw_config(m_codec, index); ++index)
++ configs.push_back(config);
++
++ return configs;
++ }
++
++ const AVCodecHWConfig *Codec::hwConfigForPixelFormat(AVPixelFormat pix) const noexcept
++ {
++ Q_ASSERT(m_codec);
++
++ for (int index = 0; auto config = avcodec_get_hw_config(m_codec, index); ++index) {
++ if (config->pix_fmt == pix)
++ return config;
++ }
++ return nullptr;
++ }
++
++ QSpan<const AVPixelFormat> makeSpan(const AVPixelFormat *values)
++ {
++ return makeSpan<AVPixelFormat>(values);
++ }
++
++
++ } // namespace QFFmpeg
++
++ QT_END_NAMESPACE