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 "qffmpeg_p.h"
5
6#include <QtCore/qdebug.h>
7#include <QtCore/qloggingcategory.h>
8#include <QtCore/qscopeguard.h>
9
10extern "C" {
11#include <libavutil/pixdesc.h>
12#include <libavutil/samplefmt.h>
13#include <libavutil/error.h>
14
15#ifdef Q_OS_DARWIN
16#include <libavutil/hwcontext_videotoolbox.h>
17#endif
18}
19
20QT_BEGIN_NAMESPACE
21
22Q_STATIC_LOGGING_CATEGORY(qLcFFmpegUtils, "qt.multimedia.ffmpeg.utils");
23
24namespace QFFmpeg {
25
26bool isAVFormatSupported(const Codec &codec, PixelOrSampleFormat format)
27{
28 if (codec.type() == AVMEDIA_TYPE_VIDEO) {
29 auto checkFormat = [format](AVPixelFormat f) { return f == format; };
30 return findAVPixelFormat(codec, predicate: checkFormat).has_value();
31 }
32
33 if (codec.type() == AVMEDIA_TYPE_AUDIO) {
34 const auto sampleFormats = codec.sampleFormats();
35 return hasValue(range: sampleFormats, value: AVSampleFormat(format));
36 }
37
38 return false;
39}
40
41bool isHwPixelFormat(AVPixelFormat format)
42{
43 const auto desc = av_pix_fmt_desc_get(pix_fmt: format);
44 return desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL) != 0;
45}
46
47void applyExperimentalCodecOptions(const Codec &codec, AVDictionary **opts)
48{
49 if (codec.isExperimental()) {
50 qCWarning(qLcFFmpegUtils) << "Applying the option 'strict -2' for the experimental codec"
51 << codec.name() << ". it's unlikely to work properly";
52 av_dict_set(pm: opts, key: "strict", value: "-2", flags: 0);
53 }
54}
55
56AVPixelFormat pixelFormatForHwDevice(AVHWDeviceType deviceType)
57{
58 switch (deviceType) {
59 case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
60 return AV_PIX_FMT_VIDEOTOOLBOX;
61 case AV_HWDEVICE_TYPE_VAAPI:
62 return AV_PIX_FMT_VAAPI;
63 case AV_HWDEVICE_TYPE_MEDIACODEC:
64 return AV_PIX_FMT_MEDIACODEC;
65 case AV_HWDEVICE_TYPE_CUDA:
66 return AV_PIX_FMT_CUDA;
67 case AV_HWDEVICE_TYPE_VDPAU:
68 return AV_PIX_FMT_VDPAU;
69 case AV_HWDEVICE_TYPE_OPENCL:
70 return AV_PIX_FMT_OPENCL;
71 case AV_HWDEVICE_TYPE_QSV:
72 return AV_PIX_FMT_QSV;
73 case AV_HWDEVICE_TYPE_D3D11VA:
74 return AV_PIX_FMT_D3D11;
75#if QT_FFMPEG_HAS_D3D12VA
76 case AV_HWDEVICE_TYPE_D3D12VA:
77 return AV_PIX_FMT_D3D12;
78#endif
79 case AV_HWDEVICE_TYPE_DXVA2:
80 return AV_PIX_FMT_DXVA2_VLD;
81 case AV_HWDEVICE_TYPE_DRM:
82 return AV_PIX_FMT_DRM_PRIME;
83#if QT_FFMPEG_HAS_VULKAN
84 case AV_HWDEVICE_TYPE_VULKAN:
85 return AV_PIX_FMT_VULKAN;
86#endif
87 default:
88 return AV_PIX_FMT_NONE;
89 }
90}
91
92AVPacketSideData *addStreamSideData(AVStream *stream, AVPacketSideData sideData)
93{
94 QScopeGuard freeData([&sideData]() { av_free(ptr: sideData.data); });
95#if QT_FFMPEG_STREAM_SIDE_DATA_DEPRECATED
96 AVPacketSideData *result = av_packet_side_data_add(
97 sd: &stream->codecpar->coded_side_data,
98 nb_sd: &stream->codecpar->nb_coded_side_data,
99 type: sideData.type,
100 data: sideData.data,
101 size: sideData.size,
102 flags: 0);
103 if (result) {
104 // If the result is not null, the ownership is taken by AVStream,
105 // otherwise the data must be deleted.
106 freeData.dismiss();
107 return result;
108 }
109#else
110 Q_UNUSED(stream);
111 // TODO: implement for older FFmpeg versions
112 qWarning() << "Adding stream side data is not supported for FFmpeg < 6.1";
113#endif
114
115 return nullptr;
116}
117
118const AVPacketSideData *streamSideData(const AVStream *stream, AVPacketSideDataType type)
119{
120 Q_ASSERT(stream);
121
122#if QT_FFMPEG_STREAM_SIDE_DATA_DEPRECATED
123 return av_packet_side_data_get(sd: stream->codecpar->coded_side_data,
124 nb_sd: stream->codecpar->nb_coded_side_data, type);
125#else
126 auto checkType = [type](const auto &item) { return item.type == type; };
127 const auto end = stream->side_data + stream->nb_side_data;
128 const auto found = std::find_if(stream->side_data, end, checkType);
129 return found == end ? nullptr : found;
130#endif
131}
132
133SwrContextUPtr createResampleContext(const AVAudioFormat &inputFormat,
134 const AVAudioFormat &outputFormat)
135{
136 SwrContext *resampler = nullptr;
137#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
138
139#if QT_FFMPEG_SWR_CONST_CH_LAYOUT
140 using AVChannelLayoutPrm = const AVChannelLayout*;
141#else
142 using AVChannelLayoutPrm = AVChannelLayout*;
143#endif
144
145 swr_alloc_set_opts2(ps: &resampler,
146 out_ch_layout: const_cast<AVChannelLayoutPrm>(&outputFormat.channelLayout),
147 out_sample_fmt: outputFormat.sampleFormat,
148 out_sample_rate: outputFormat.sampleRate,
149 in_ch_layout: const_cast<AVChannelLayoutPrm>(&inputFormat.channelLayout),
150 in_sample_fmt: inputFormat.sampleFormat,
151 in_sample_rate: inputFormat.sampleRate,
152 log_offset: 0,
153 log_ctx: nullptr);
154
155#else
156
157 resampler = swr_alloc_set_opts(nullptr,
158 outputFormat.channelLayoutMask,
159 outputFormat.sampleFormat,
160 outputFormat.sampleRate,
161 inputFormat.channelLayoutMask,
162 inputFormat.sampleFormat,
163 inputFormat.sampleRate,
164 0,
165 nullptr);
166
167#endif
168
169 auto error = QFFmpeg::AVError{
170 swr_init(s: resampler),
171 };
172 if (error != QFFmpeg::AVError::Success) {
173 qCWarning(qLcFFmpegUtils) << "Failed to initialize audio resampler:" << error;
174 return nullptr;
175 }
176 return SwrContextUPtr(resampler);
177}
178
179QVideoFrameFormat::ColorTransfer fromAvColorTransfer(AVColorTransferCharacteristic colorTrc) {
180 switch (colorTrc) {
181 case AVCOL_TRC_BT709:
182 // The following three cases have transfer characteristics identical to BT709
183 case AVCOL_TRC_BT1361_ECG:
184 case AVCOL_TRC_BT2020_10:
185 case AVCOL_TRC_BT2020_12:
186 case AVCOL_TRC_SMPTE240M: // almost identical to bt709
187 return QVideoFrameFormat::ColorTransfer_BT709;
188 case AVCOL_TRC_GAMMA22:
189 case AVCOL_TRC_SMPTE428: // No idea, let's hope for the best...
190 case AVCOL_TRC_IEC61966_2_1: // sRGB, close enough to 2.2...
191 case AVCOL_TRC_IEC61966_2_4: // not quite, but probably close enough
192 return QVideoFrameFormat::ColorTransfer_Gamma22;
193 case AVCOL_TRC_GAMMA28:
194 return QVideoFrameFormat::ColorTransfer_Gamma28;
195 case AVCOL_TRC_SMPTE170M:
196 return QVideoFrameFormat::ColorTransfer_BT601;
197 case AVCOL_TRC_LINEAR:
198 return QVideoFrameFormat::ColorTransfer_Linear;
199 case AVCOL_TRC_SMPTE2084:
200 return QVideoFrameFormat::ColorTransfer_ST2084;
201 case AVCOL_TRC_ARIB_STD_B67:
202 return QVideoFrameFormat::ColorTransfer_STD_B67;
203 default:
204 break;
205 }
206 return QVideoFrameFormat::ColorTransfer_Unknown;
207}
208
209AVColorTransferCharacteristic toAvColorTransfer(QVideoFrameFormat::ColorTransfer colorTrc)
210{
211 switch (colorTrc) {
212 case QVideoFrameFormat::ColorTransfer_BT709:
213 return AVCOL_TRC_BT709;
214 case QVideoFrameFormat::ColorTransfer_BT601:
215 return AVCOL_TRC_BT709; // which one is the best?
216 case QVideoFrameFormat::ColorTransfer_Linear:
217 return AVCOL_TRC_SMPTE2084;
218 case QVideoFrameFormat::ColorTransfer_Gamma22:
219 return AVCOL_TRC_GAMMA22;
220 case QVideoFrameFormat::ColorTransfer_Gamma28:
221 return AVCOL_TRC_GAMMA28;
222 case QVideoFrameFormat::ColorTransfer_ST2084:
223 return AVCOL_TRC_SMPTE2084;
224 case QVideoFrameFormat::ColorTransfer_STD_B67:
225 return AVCOL_TRC_ARIB_STD_B67;
226 default:
227 return AVCOL_TRC_UNSPECIFIED;
228 }
229}
230
231QVideoFrameFormat::ColorSpace fromAvColorSpace(AVColorSpace colorSpace)
232{
233 switch (colorSpace) {
234 default:
235 case AVCOL_SPC_UNSPECIFIED:
236 case AVCOL_SPC_RESERVED:
237 case AVCOL_SPC_FCC:
238 case AVCOL_SPC_SMPTE240M:
239 case AVCOL_SPC_YCGCO:
240 case AVCOL_SPC_SMPTE2085:
241 case AVCOL_SPC_CHROMA_DERIVED_NCL:
242 case AVCOL_SPC_CHROMA_DERIVED_CL:
243 case AVCOL_SPC_ICTCP: // BT.2100 ICtCp
244 return QVideoFrameFormat::ColorSpace_Undefined;
245 case AVCOL_SPC_RGB:
246 return QVideoFrameFormat::ColorSpace_AdobeRgb;
247 case AVCOL_SPC_BT709:
248 return QVideoFrameFormat::ColorSpace_BT709;
249 case AVCOL_SPC_BT470BG: // BT601
250 case AVCOL_SPC_SMPTE170M: // Also BT601
251 return QVideoFrameFormat::ColorSpace_BT601;
252 case AVCOL_SPC_BT2020_NCL: // Non constant luminence
253 case AVCOL_SPC_BT2020_CL: // Constant luminence
254 return QVideoFrameFormat::ColorSpace_BT2020;
255 }
256}
257
258AVColorSpace toAvColorSpace(QVideoFrameFormat::ColorSpace colorSpace)
259{
260 switch (colorSpace) {
261 case QVideoFrameFormat::ColorSpace_BT601:
262 return AVCOL_SPC_BT470BG;
263 case QVideoFrameFormat::ColorSpace_BT709:
264 return AVCOL_SPC_BT709;
265 case QVideoFrameFormat::ColorSpace_AdobeRgb:
266 return AVCOL_SPC_RGB;
267 case QVideoFrameFormat::ColorSpace_BT2020:
268 return AVCOL_SPC_BT2020_CL;
269 default:
270 return AVCOL_SPC_UNSPECIFIED;
271 }
272}
273
274QVideoFrameFormat::ColorRange fromAvColorRange(AVColorRange colorRange)
275{
276 switch (colorRange) {
277 case AVCOL_RANGE_MPEG:
278 return QVideoFrameFormat::ColorRange_Video;
279 case AVCOL_RANGE_JPEG:
280 return QVideoFrameFormat::ColorRange_Full;
281 default:
282 return QVideoFrameFormat::ColorRange_Unknown;
283 }
284}
285
286AVColorRange toAvColorRange(QVideoFrameFormat::ColorRange colorRange)
287{
288 switch (colorRange) {
289 case QVideoFrameFormat::ColorRange_Video:
290 return AVCOL_RANGE_MPEG;
291 case QVideoFrameFormat::ColorRange_Full:
292 return AVCOL_RANGE_JPEG;
293 default:
294 return AVCOL_RANGE_UNSPECIFIED;
295 }
296}
297
298AVHWDeviceContext* avFrameDeviceContext(const AVFrame* frame) {
299 if (!frame)
300 return {};
301 if (!frame->hw_frames_ctx)
302 return {};
303
304 const auto *frameCtx = reinterpret_cast<AVHWFramesContext *>(frame->hw_frames_ctx->data);
305 if (!frameCtx)
306 return {};
307
308 return frameCtx->device_ctx;
309}
310
311SwsContextUPtr createSwsContext(const QSize &srcSize, AVPixelFormat srcPixFmt, const QSize &dstSize,
312 AVPixelFormat dstPixFmt, int conversionType)
313{
314
315 SwsContext *result =
316 sws_getContext(srcW: srcSize.width(), srcH: srcSize.height(), srcFormat: srcPixFmt, dstW: dstSize.width(),
317 dstH: dstSize.height(), dstFormat: dstPixFmt, flags: conversionType, srcFilter: nullptr, dstFilter: nullptr, param: nullptr);
318
319 if (!result)
320 qCWarning(qLcFFmpegUtils) << "Cannot create sws context for:\n"
321 << "srcSize:" << srcSize
322 << "srcPixFmt:" << srcPixFmt
323 << "dstSize:" << dstSize
324 << "dstPixFmt:" << dstPixFmt
325 << "conversionType:" << conversionType;
326
327 return SwsContextUPtr(result);
328}
329
330#ifdef Q_OS_DARWIN
331bool isCVFormatSupported(uint32_t cvFormat)
332{
333 return av_map_videotoolbox_format_to_pixfmt(cvFormat) != AV_PIX_FMT_NONE;
334}
335
336std::string cvFormatToString(uint32_t cvFormat)
337{
338 auto formatDescIt = std::make_reverse_iterator(reinterpret_cast<const char *>(&cvFormat));
339 return std::string(formatDescIt - 4, formatDescIt);
340}
341
342#endif
343
344} // namespace QFFmpeg
345
346QDebug operator<<(QDebug dbg, const AVRational &value)
347{
348 dbg << value.num << "/" << value.den;
349 return dbg;
350}
351
352QDebug operator<<(QDebug dbg, const AVDictionary &dict)
353{
354 char *buffer = 0;
355 auto freeBuffer = QScopeGuard([&] {
356 av_free(ptr: buffer);
357 });
358
359 int status = av_dict_get_string(m: &dict, buffer: &buffer, key_val_sep: '=', pairs_sep: ',');
360 if (status < 0 || !buffer)
361 return dbg << "Failed to print AVDictionary";
362
363 dbg << buffer;
364 return dbg;
365}
366
367QDebug operator<<(QDebug dbg, const QFFmpeg::AVDictionaryHolder &dict)
368{
369 const AVDictionary *rawDict = dict.opts;
370 if (rawDict)
371 return dbg << *rawDict;
372 else
373 return dbg << "Empty AVDictionaryHolder";
374}
375
376QDebug operator<<(QDebug dbg, QFFmpeg::AVError error)
377{
378 char errBuf[AV_ERROR_MAX_STRING_SIZE];
379 dbg << av_make_error_string(errbuf: errBuf, AV_ERROR_MAX_STRING_SIZE, errnum: qToUnderlying(e: error));
380 return dbg;
381}
382
383#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
384QDebug operator<<(QDebug dbg, const AVChannelLayout &layout)
385{
386 dbg << '[';
387 dbg << "nb_channels:" << layout.nb_channels;
388 dbg << ", order:" << layout.order;
389
390 if (layout.order == AV_CHANNEL_ORDER_NATIVE || layout.order == AV_CHANNEL_ORDER_AMBISONIC)
391 dbg << ", mask:" << Qt::bin << layout.u.mask << Qt::dec;
392 else if (layout.order == AV_CHANNEL_ORDER_CUSTOM && layout.u.map)
393 dbg << ", id: " << layout.u.map->id;
394
395 dbg << ']';
396
397 return dbg;
398}
399#endif
400
401#if QT_FFMPEG_HAS_AVCODEC_GET_SUPPORTED_CONFIG
402QDebug operator<<(QDebug dbg, const AVCodecConfig value)
403{
404 switch (value) {
405 case AV_CODEC_CONFIG_CHANNEL_LAYOUT:
406 dbg << "AV_CODEC_CONFIG_CHANNEL_LAYOUT";
407 break;
408 case AV_CODEC_CONFIG_COLOR_RANGE:
409 dbg << "AV_CODEC_CONFIG_COLOR_RANGE";
410 break;
411 case AV_CODEC_CONFIG_COLOR_SPACE:
412 dbg << "AV_CODEC_CONFIG_COLOR_SPACE";
413 break;
414 case AV_CODEC_CONFIG_FRAME_RATE:
415 dbg << "AV_CODEC_CONFIG_FRAME_RATE";
416 break;
417 case AV_CODEC_CONFIG_PIX_FORMAT:
418 dbg << "AV_CODEC_CONFIG_PIX_FORMAT";
419 break;
420 case AV_CODEC_CONFIG_SAMPLE_FORMAT:
421 dbg << "AV_CODEC_CONFIG_SAMPLE_FORMAT";
422 break;
423 case AV_CODEC_CONFIG_SAMPLE_RATE:
424 dbg << "AV_CODEC_CONFIG_SAMPLE_RATE";
425 break;
426 default:
427 dbg << "<UNKNOWN_CODEC_CONFIG>";
428 break;
429 }
430
431 return dbg;
432}
433#endif
434
435QT_END_NAMESPACE
436

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