1// Copyright (C) 2024 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 "qffmpegcodec_p.h"
5#include "qffmpeg_p.h"
6#include "qffmpegmediaformatinfo_p.h"
7
8#include <QtCore/qloggingcategory.h>
9
10QT_BEGIN_NAMESPACE
11
12namespace QFFmpeg {
13namespace {
14
15template <typename T>
16inline constexpr auto InvalidAvValue = T{};
17
18template <>
19inline constexpr auto InvalidAvValue<AVSampleFormat> = AV_SAMPLE_FMT_NONE;
20
21template <>
22inline constexpr auto InvalidAvValue<AVPixelFormat> = AV_PIX_FMT_NONE;
23
24template <typename T>
25QSpan<const T> makeSpan(const T *values)
26{
27 if (!values)
28 return {};
29
30 qsizetype size = 0;
31 while (values[size] != InvalidAvValue<T>)
32 ++size;
33
34 return QSpan<const T>{ values, size };
35}
36
37#if QT_FFMPEG_HAS_AVCODEC_GET_SUPPORTED_CONFIG
38
39static Q_LOGGING_CATEGORY(qLcFFmpegUtils, "qt.multimedia.ffmpeg.utils");
40
41void logGetCodecConfigError(const AVCodec *codec, AVCodecConfig config, int error)
42{
43 qCWarning(qLcFFmpegUtils) << "Failed to retrieve config" << config << "for codec" << codec->name
44 << "with error" << error << err2str(error);
45}
46
47template <typename T>
48QSpan<const T> getCodecConfig(const AVCodec *codec, AVCodecConfig config)
49{
50 const T *result = nullptr;
51 int size = 0;
52 const auto error = avcodec_get_supported_config(
53 nullptr, codec, config, 0u, reinterpret_cast<const void **>(&result), &size);
54 if (error != 0) {
55 logGetCodecConfigError(codec, config, error);
56 return {};
57 }
58
59 // Sanity check of FFmpeg's array layout. If it is not nullptr, it should end with a terminator,
60 // and be non-empty. A non-null but empty config would mean that no values are accepted by the
61 // codec, which does not make sense.
62 Q_ASSERT(!result || (size > 0 && result[size] == InvalidAvValue<T>));
63
64 // Returns empty span if 'result' is nullptr. This can be misleading, as it may
65 // mean that 'any' value is allowed, or that the result is 'unknown'.
66 return QSpan<const T>{ result, size };
67}
68#endif
69
70QSpan<const AVPixelFormat> getCodecPixelFormats(const AVCodec *codec)
71{
72#if QT_FFMPEG_HAS_AVCODEC_GET_SUPPORTED_CONFIG
73 return getCodecConfig<AVPixelFormat>(codec, AV_CODEC_CONFIG_PIX_FORMAT);
74#else
75 return makeSpan(values: codec->pix_fmts);
76#endif
77}
78
79QSpan<const AVSampleFormat> getCodecSampleFormats(const AVCodec *codec)
80{
81#if QT_FFMPEG_HAS_AVCODEC_GET_SUPPORTED_CONFIG
82 return getCodecConfig<AVSampleFormat>(codec, AV_CODEC_CONFIG_SAMPLE_FORMAT);
83#else
84 return makeSpan(values: codec->sample_fmts);
85#endif
86}
87
88QSpan<const int> getCodecSampleRates(const AVCodec *codec)
89{
90#if QT_FFMPEG_HAS_AVCODEC_GET_SUPPORTED_CONFIG
91 return getCodecConfig<int>(codec, AV_CODEC_CONFIG_SAMPLE_RATE);
92#else
93 return makeSpan(values: codec->supported_samplerates);
94#endif
95}
96
97#ifdef Q_OS_WINDOWS
98
99auto stereoLayout() // unused function on non-Windows platforms
100{
101 constexpr uint64_t mask = AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT;
102#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
103 AVChannelLayout channelLayout{};
104 av_channel_layout_from_mask(&channelLayout, mask);
105 return channelLayout;
106#else
107 return mask;
108#endif
109};
110
111#endif
112
113QSpan<const ChannelLayoutT> getCodecChannelLayouts(const AVCodec *codec)
114{
115 QSpan<const ChannelLayoutT> layout;
116#if QT_FFMPEG_HAS_AVCODEC_GET_SUPPORTED_CONFIG
117 layout = getCodecConfig<AVChannelLayout>(codec, AV_CODEC_CONFIG_CHANNEL_LAYOUT);
118#elif QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
119 layout = makeSpan(codec->ch_layouts);
120#else
121 layout = makeSpan(values: codec->channel_layouts);
122#endif
123
124#ifdef Q_OS_WINDOWS
125 // The mp3_mf codec does not report any layout restrictions, but does not
126 // handle more than 2 channels. We therefore make this explicit here.
127 if (layout.empty() && QLatin1StringView(codec->name) == QLatin1StringView("mp3_mf")) {
128 static const ChannelLayoutT defaultLayout[] = { stereoLayout() };
129 layout = defaultLayout;
130 }
131#endif
132 return layout;
133}
134
135QSpan<const AVRational> getCodecFrameRates(const AVCodec *codec)
136{
137#if QT_FFMPEG_HAS_AVCODEC_GET_SUPPORTED_CONFIG
138 return getCodecConfig<AVRational>(codec, AV_CODEC_CONFIG_FRAME_RATE);
139#else
140 return makeSpan(values: codec->supported_framerates);
141#endif
142}
143} // namespace
144
145Codec::Codec(const AVCodec *codec) : m_codec{ codec }
146{
147 Q_ASSERT(m_codec);
148}
149
150const AVCodec* Codec::get() const noexcept
151{
152 Q_ASSERT(m_codec);
153 return m_codec;
154}
155
156AVCodecID Codec::id() const noexcept
157{
158 Q_ASSERT(m_codec);
159
160 return m_codec->id;
161}
162
163QLatin1StringView Codec::name() const noexcept
164{
165 Q_ASSERT(m_codec);
166
167 return QLatin1StringView{ m_codec->name };
168}
169
170AVMediaType Codec::type() const noexcept
171{
172 Q_ASSERT(m_codec);
173
174 return m_codec->type;
175}
176
177// See AV_CODEC_CAP_*
178int Codec::capabilities() const noexcept
179{
180 Q_ASSERT(m_codec);
181
182 return m_codec->capabilities;
183}
184
185bool Codec::isEncoder() const noexcept
186{
187 Q_ASSERT(m_codec);
188
189 return av_codec_is_encoder(codec: m_codec) != 0;
190}
191
192bool Codec::isDecoder() const noexcept
193{
194 Q_ASSERT(m_codec);
195
196 return av_codec_is_decoder(codec: m_codec) != 0;
197}
198
199bool Codec::isExperimental() const noexcept
200{
201 Q_ASSERT(m_codec);
202
203 return (m_codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) != 0;
204}
205
206QSpan<const AVPixelFormat> Codec::pixelFormats() const noexcept
207{
208 Q_ASSERT(m_codec);
209
210 if (m_codec->type != AVMEDIA_TYPE_VIDEO)
211 return {};
212
213 return getCodecPixelFormats(codec: m_codec);
214}
215
216QSpan<const AVSampleFormat> Codec::sampleFormats() const noexcept
217{
218 Q_ASSERT(m_codec);
219
220 if (m_codec->type != AVMEDIA_TYPE_AUDIO)
221 return {};
222
223 return getCodecSampleFormats(codec: m_codec);
224}
225
226QSpan<const int> Codec::sampleRates() const noexcept
227{
228 Q_ASSERT(m_codec);
229
230 if (m_codec->type != AVMEDIA_TYPE_AUDIO)
231 return {};
232
233 return getCodecSampleRates(codec: m_codec);
234}
235
236QSpan<const ChannelLayoutT> Codec::channelLayouts() const noexcept
237{
238 Q_ASSERT(m_codec);
239
240 if (m_codec->type != AVMEDIA_TYPE_AUDIO)
241 return {};
242
243 return getCodecChannelLayouts(codec: m_codec);
244}
245
246QSpan<const AVRational> Codec::frameRates() const noexcept
247{
248 Q_ASSERT(m_codec);
249
250 if (m_codec->type != AVMEDIA_TYPE_VIDEO)
251 return {};
252
253 return getCodecFrameRates(codec: m_codec);
254}
255
256std::vector<const AVCodecHWConfig *> Codec::hwConfigs() const noexcept
257{
258 Q_ASSERT(m_codec);
259
260 // For most codecs, hwConfig is empty so we optimize for
261 // the hot path and do not preallocate/reserve any memory.
262 std::vector<const AVCodecHWConfig *> configs;
263
264 for (int index = 0; auto config = avcodec_get_hw_config(codec: m_codec, index); ++index)
265 configs.push_back(x: config);
266
267 return configs;
268}
269
270CodecIterator CodecIterator::begin()
271{
272 CodecIterator iterator;
273 iterator.m_codec = av_codec_iterate(opaque: &iterator.m_state);
274 return iterator;
275}
276
277CodecIterator CodecIterator::end()
278{
279 return { };
280}
281
282CodecIterator &CodecIterator::operator++() noexcept
283{
284 Q_ASSERT(m_codec);
285 m_codec = av_codec_iterate(opaque: &m_state);
286 return *this;
287}
288
289Codec CodecIterator::operator*() const noexcept
290{
291 Q_ASSERT(m_codec); // Avoid dereferencing end() iterator
292 return Codec{ m_codec };
293}
294
295bool CodecIterator::operator!=(const CodecIterator &other) const noexcept
296{
297 return m_codec != other.m_codec;
298}
299
300QSpan<const AVPixelFormat> makeSpan(const AVPixelFormat *values)
301{
302 return makeSpan<AVPixelFormat>(values);
303}
304
305
306} // namespace QFFmpeg
307
308QT_END_NAMESPACE
309

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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