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 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | namespace QFFmpeg { |
13 | namespace { |
14 | |
15 | template <typename T> |
16 | inline constexpr auto InvalidAvValue = T{}; |
17 | |
18 | template <> |
19 | inline constexpr auto InvalidAvValue<AVSampleFormat> = AV_SAMPLE_FMT_NONE; |
20 | |
21 | template <> |
22 | inline constexpr auto InvalidAvValue<AVPixelFormat> = AV_PIX_FMT_NONE; |
23 | |
24 | template <typename T> |
25 | QSpan<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 | |
39 | static Q_LOGGING_CATEGORY(qLcFFmpegUtils, "qt.multimedia.ffmpeg.utils"); |
40 | |
41 | void 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 | |
47 | template <typename T> |
48 | QSpan<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 | |
70 | QSpan<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 | |
79 | QSpan<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 | |
88 | QSpan<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 | |
99 | auto 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 | |
113 | QSpan<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 | |
135 | QSpan<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 | |
145 | Codec::Codec(const AVCodec *codec) : m_codec{ codec } |
146 | { |
147 | Q_ASSERT(m_codec); |
148 | } |
149 | |
150 | const AVCodec* Codec::get() const noexcept |
151 | { |
152 | Q_ASSERT(m_codec); |
153 | return m_codec; |
154 | } |
155 | |
156 | AVCodecID Codec::id() const noexcept |
157 | { |
158 | Q_ASSERT(m_codec); |
159 | |
160 | return m_codec->id; |
161 | } |
162 | |
163 | QLatin1StringView Codec::name() const noexcept |
164 | { |
165 | Q_ASSERT(m_codec); |
166 | |
167 | return QLatin1StringView{ m_codec->name }; |
168 | } |
169 | |
170 | AVMediaType Codec::type() const noexcept |
171 | { |
172 | Q_ASSERT(m_codec); |
173 | |
174 | return m_codec->type; |
175 | } |
176 | |
177 | // See AV_CODEC_CAP_* |
178 | int Codec::capabilities() const noexcept |
179 | { |
180 | Q_ASSERT(m_codec); |
181 | |
182 | return m_codec->capabilities; |
183 | } |
184 | |
185 | bool Codec::isEncoder() const noexcept |
186 | { |
187 | Q_ASSERT(m_codec); |
188 | |
189 | return av_codec_is_encoder(codec: m_codec) != 0; |
190 | } |
191 | |
192 | bool Codec::isDecoder() const noexcept |
193 | { |
194 | Q_ASSERT(m_codec); |
195 | |
196 | return av_codec_is_decoder(codec: m_codec) != 0; |
197 | } |
198 | |
199 | bool Codec::isExperimental() const noexcept |
200 | { |
201 | Q_ASSERT(m_codec); |
202 | |
203 | return (m_codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) != 0; |
204 | } |
205 | |
206 | QSpan<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 | |
216 | QSpan<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 | |
226 | QSpan<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 | |
236 | QSpan<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 | |
246 | QSpan<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 | |
256 | std::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 | |
270 | CodecIterator CodecIterator::begin() |
271 | { |
272 | CodecIterator iterator; |
273 | iterator.m_codec = av_codec_iterate(opaque: &iterator.m_state); |
274 | return iterator; |
275 | } |
276 | |
277 | CodecIterator CodecIterator::end() |
278 | { |
279 | return { }; |
280 | } |
281 | |
282 | CodecIterator &CodecIterator::operator++() noexcept |
283 | { |
284 | Q_ASSERT(m_codec); |
285 | m_codec = av_codec_iterate(opaque: &m_state); |
286 | return *this; |
287 | } |
288 | |
289 | Codec CodecIterator::operator*() const noexcept |
290 | { |
291 | Q_ASSERT(m_codec); // Avoid dereferencing end() iterator |
292 | return Codec{ m_codec }; |
293 | } |
294 | |
295 | bool CodecIterator::operator!=(const CodecIterator &other) const noexcept |
296 | { |
297 | return m_codec != other.m_codec; |
298 | } |
299 | |
300 | QSpan<const AVPixelFormat> makeSpan(const AVPixelFormat *values) |
301 | { |
302 | return makeSpan<AVPixelFormat>(values); |
303 | } |
304 | |
305 | |
306 | } // namespace QFFmpeg |
307 | |
308 | QT_END_NAMESPACE |
309 |
Definitions
- InvalidAvValue
- InvalidAvValue
- InvalidAvValue
- InvalidAvValue
- InvalidAvValue
- InvalidAvValue
- makeSpan
- getCodecPixelFormats
- getCodecSampleFormats
- getCodecSampleRates
- getCodecChannelLayouts
- getCodecFrameRates
- Codec
- get
- id
- name
- type
- capabilities
- isEncoder
- isDecoder
- isExperimental
- pixelFormats
- sampleFormats
- sampleRates
- channelLayouts
- frameRates
- hwConfigs
- begin
- end
- operator++
- operator*
- operator!=
Learn to use CMake with our Intro Training
Find out more