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 <common/qglist_helper_p.h>
5#include "qgstreamerformatinfo_p.h"
6
7#include <gst/gst.h>
8
9QT_BEGIN_NAMESPACE
10
11QMediaFormat::AudioCodec QGstreamerFormatInfo::audioCodecForCaps(QGstStructureView structure)
12{
13 using namespace std::string_view_literals;
14 const char *name = structure.name().data();
15
16 if (!name || (strncmp(s1: name, s2: "audio/", n: 6) != 0))
17 return QMediaFormat::AudioCodec::Unspecified;
18 name += 6;
19 if (name == "mpeg"sv) {
20 auto version = structure["mpegversion"].toInt();
21 if (version == 1) {
22 auto layer = structure["layer"];
23 if (!layer.isNull())
24 return QMediaFormat::AudioCodec::MP3;
25 }
26 if (version == 4)
27 return QMediaFormat::AudioCodec::AAC;
28 return QMediaFormat::AudioCodec::Unspecified;
29 }
30 if (name == "x-ac3"sv)
31 return QMediaFormat::AudioCodec::AC3;
32
33 if (name == "x-eac3"sv)
34 return QMediaFormat::AudioCodec::EAC3;
35
36 if (name == "x-flac"sv)
37 return QMediaFormat::AudioCodec::FLAC;
38
39 if (name == "x-alac"sv)
40 return QMediaFormat::AudioCodec::ALAC;
41
42 if (name == "x-true-hd"sv)
43 return QMediaFormat::AudioCodec::DolbyTrueHD;
44
45 if (name == "x-vorbis"sv)
46 return QMediaFormat::AudioCodec::Vorbis;
47
48 if (name == "x-opus"sv)
49 return QMediaFormat::AudioCodec::Opus;
50
51 if (name == "x-wav"sv)
52 return QMediaFormat::AudioCodec::Wave;
53
54 if (name == "x-wma"sv)
55 return QMediaFormat::AudioCodec::WMA;
56
57 return QMediaFormat::AudioCodec::Unspecified;
58}
59
60QMediaFormat::VideoCodec QGstreamerFormatInfo::videoCodecForCaps(QGstStructureView structure)
61{
62 using namespace std::string_view_literals;
63 const char *name = structure.name().data();
64
65 if (!name || (strncmp(s1: name, s2: "video/", n: 6) != 0))
66 return QMediaFormat::VideoCodec::Unspecified;
67 name += 6;
68
69 if (name == "mpeg"sv) {
70 auto version = structure["mpegversion"].toInt();
71 if (version == 1)
72 return QMediaFormat::VideoCodec::MPEG1;
73 if (version == 2)
74 return QMediaFormat::VideoCodec::MPEG2;
75 if (version == 4)
76 return QMediaFormat::VideoCodec::MPEG4;
77 return QMediaFormat::VideoCodec::Unspecified;
78 }
79 if (name == "x-h264"sv)
80 return QMediaFormat::VideoCodec::H264;
81
82 if (name == "x-h265"sv)
83 return QMediaFormat::VideoCodec::H265;
84
85 if (name == "x-vp8"sv)
86 return QMediaFormat::VideoCodec::VP8;
87
88 if (name == "x-vp9"sv)
89 return QMediaFormat::VideoCodec::VP9;
90
91 if (name == "x-av1"sv)
92 return QMediaFormat::VideoCodec::AV1;
93
94 if (name == "x-theora"sv)
95 return QMediaFormat::VideoCodec::Theora;
96
97 if (name == "x-jpeg"sv)
98 return QMediaFormat::VideoCodec::MotionJPEG;
99
100 if (name == "x-wmv"sv)
101 return QMediaFormat::VideoCodec::WMV;
102
103 return QMediaFormat::VideoCodec::Unspecified;
104}
105
106QMediaFormat::FileFormat QGstreamerFormatInfo::fileFormatForCaps(QGstStructureView structure)
107{
108 using namespace std::string_view_literals;
109 const char *name = structure.name().data();
110
111 if (name == "video/x-ms-asf"sv)
112 return QMediaFormat::FileFormat::WMV;
113
114 if (name == "video/x-msvideo"sv)
115 return QMediaFormat::FileFormat::AVI;
116
117 if (name == "video/x-matroska"sv)
118 return QMediaFormat::FileFormat::Matroska;
119
120 if (name == "video/quicktime"sv) {
121 const char *variant = structure["variant"].toString();
122 if (!variant)
123 return QMediaFormat::FileFormat::QuickTime;
124 if (variant == "iso"sv)
125 return QMediaFormat::FileFormat::MPEG4;
126 }
127 if (name == "video/ogg"sv)
128 return QMediaFormat::FileFormat::Ogg;
129
130 if (name == "video/webm"sv)
131 return QMediaFormat::FileFormat::WebM;
132
133 if (name == "audio/x-m4a"sv)
134 return QMediaFormat::FileFormat::Mpeg4Audio;
135
136 if (name == "audio/x-wav"sv)
137 return QMediaFormat::FileFormat::Wave;
138
139 if (name == "audio/mpeg"sv) {
140 auto mpegversion = structure["mpegversion"].toInt();
141 if (mpegversion == 1) {
142 auto layer = structure["layer"];
143 if (!layer.isNull())
144 return QMediaFormat::FileFormat::MP3;
145 }
146 }
147
148 if (name == "audio/aac"sv)
149 return QMediaFormat::FileFormat::AAC;
150
151 if (name == "audio/x-ms-wma"sv)
152 return QMediaFormat::FileFormat::WMA;
153
154 if (name == "audio/x-flac"sv)
155 return QMediaFormat::FileFormat::FLAC;
156
157 return QMediaFormat::UnspecifiedFormat;
158}
159
160
161QImageCapture::FileFormat QGstreamerFormatInfo::imageFormatForCaps(QGstStructureView structure)
162{
163 using namespace std::string_view_literals;
164 const char *name = structure.name().data();
165
166 if (name == "image/jpeg"sv)
167 return QImageCapture::JPEG;
168
169 if (name == "image/png"sv)
170 return QImageCapture::PNG;
171
172 if (name == "image/webp"sv)
173 return QImageCapture::WebP;
174
175 if (name == "image/tiff"sv)
176 return QImageCapture::Tiff;
177
178 return QImageCapture::UnspecifiedFormat;
179}
180
181static QPair<QList<QMediaFormat::AudioCodec>, QList<QMediaFormat::VideoCodec>> getCodecsList(bool decode)
182{
183 QList<QMediaFormat::AudioCodec> audio;
184 QList<QMediaFormat::VideoCodec> video;
185
186 GstPadDirection padDirection = decode ? GST_PAD_SINK : GST_PAD_SRC;
187
188 GList *elementList = gst_element_factory_list_get_elements(type: decode ? GST_ELEMENT_FACTORY_TYPE_DECODER : GST_ELEMENT_FACTORY_TYPE_ENCODER,
189 minrank: GST_RANK_MARGINAL);
190
191 for (GstElementFactory *factory :
192 QGstUtils::GListRangeAdaptor<GstElementFactory *>(elementList)) {
193 for (GstStaticPadTemplate *padTemplate :
194 QGstUtils::GListConstRangeAdaptor<GstStaticPadTemplate *>(
195 gst_element_factory_get_static_pad_templates(factory))) {
196 if (padTemplate->direction == padDirection) {
197 auto caps = QGstCaps(gst_static_caps_get(static_caps: &padTemplate->static_caps), QGstCaps::HasRef);
198
199 for (int i = 0; i < caps.size(); i++) {
200 QGstStructureView structure = caps.at(index: i);
201 auto a = QGstreamerFormatInfo::audioCodecForCaps(structure);
202 if (a != QMediaFormat::AudioCodec::Unspecified && !audio.contains(t: a))
203 audio.append(t: a);
204 auto v = QGstreamerFormatInfo::videoCodecForCaps(structure);
205 if (v != QMediaFormat::VideoCodec::Unspecified && !video.contains(t: v))
206 video.append(t: v);
207 }
208 }
209 }
210 }
211 gst_plugin_feature_list_free(list: elementList);
212 return {audio, video};
213}
214
215QList<QGstreamerFormatInfo::CodecMap>
216QGstreamerFormatInfo::getCodecMaps(QMediaFormat::ConversionMode conversionMode,
217 QList<QMediaFormat::AudioCodec> supportedAudioCodecs,
218 QList<QMediaFormat::VideoCodec> supportedVideoCodecs)
219{
220 QList<QGstreamerFormatInfo::CodecMap> maps;
221
222 GstPadDirection dataPadDirection =
223 (conversionMode == QMediaFormat::Decode) ? GST_PAD_SINK : GST_PAD_SRC;
224
225 auto encodableFactoryTypes =
226 (GST_ELEMENT_FACTORY_TYPE_MUXER | GST_ELEMENT_FACTORY_TYPE_PAYLOADER
227 | GST_ELEMENT_FACTORY_TYPE_ENCRYPTOR | GST_ELEMENT_FACTORY_TYPE_ENCODER);
228 GList *elementList = gst_element_factory_list_get_elements(
229 type: (conversionMode == QMediaFormat::Decode) ? GST_ELEMENT_FACTORY_TYPE_DECODABLE
230 : encodableFactoryTypes,
231 minrank: GST_RANK_MARGINAL);
232
233 for (GstElementFactory *factory :
234 QGstUtils::GListRangeAdaptor<GstElementFactory *>(elementList)) {
235 QList<QMediaFormat::FileFormat> fileFormats;
236
237 for (GstStaticPadTemplate *padTemplate :
238 QGstUtils::GListConstRangeAdaptor<GstStaticPadTemplate *>(
239 gst_element_factory_get_static_pad_templates(factory))) {
240
241 // Check pads on data side for file formats, except for parsers check source side
242 if (padTemplate->direction == dataPadDirection
243 || (gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_PARSER)
244 && padTemplate->direction == GST_PAD_SRC)) {
245 auto caps = QGstCaps(gst_static_caps_get(static_caps: &padTemplate->static_caps), QGstCaps::HasRef);
246
247 for (int i = 0; i < caps.size(); i++) {
248 QGstStructureView structure = caps.at(index: i);
249 auto fmt = fileFormatForCaps(structure);
250 if (fmt != QMediaFormat::UnspecifiedFormat)
251 fileFormats.append(t: fmt);
252 }
253 }
254 }
255
256 if (fileFormats.isEmpty())
257 continue;
258
259 QList<QMediaFormat::AudioCodec> audioCodecs;
260 QList<QMediaFormat::VideoCodec> videoCodecs;
261
262 for (GstStaticPadTemplate *padTemplate :
263 QGstUtils::GListConstRangeAdaptor<GstStaticPadTemplate *>(
264 gst_element_factory_get_static_pad_templates(factory))) {
265
266 // check the other side for supported inputs/outputs
267 if (padTemplate->direction != dataPadDirection) {
268 auto caps = QGstCaps(gst_static_caps_get(static_caps: &padTemplate->static_caps), QGstCaps::HasRef);
269
270 bool acceptsRawAudio = false;
271 for (int i = 0; i < caps.size(); i++) {
272 QGstStructureView structure = caps.at(index: i);
273 if (structure.name() == "audio/x-raw")
274 acceptsRawAudio = true;
275 auto audio = audioCodecForCaps(structure);
276 if (audio != QMediaFormat::AudioCodec::Unspecified && supportedAudioCodecs.contains(t: audio))
277 audioCodecs.append(t: audio);
278 auto video = videoCodecForCaps(structure);
279 if (video != QMediaFormat::VideoCodec::Unspecified && supportedVideoCodecs.contains(t: video))
280 videoCodecs.append(t: video);
281 }
282 if (acceptsRawAudio && fileFormats.size() == 1) {
283 switch (fileFormats.at(i: 0)) {
284 case QMediaFormat::Mpeg4Audio:
285 default:
286 break;
287 case QMediaFormat::MP3:
288 audioCodecs.append(t: QMediaFormat::AudioCodec::MP3);
289 break;
290 case QMediaFormat::FLAC:
291 audioCodecs.append(t: QMediaFormat::AudioCodec::FLAC);
292 break;
293 case QMediaFormat::Wave:
294 audioCodecs.append(t: QMediaFormat::AudioCodec::Wave);
295 break;
296 }
297 }
298 }
299 }
300 if (!audioCodecs.isEmpty() || !videoCodecs.isEmpty()) {
301 for (auto f : std::as_const(t&: fileFormats)) {
302 maps.append(t: {.format: f, .audio: audioCodecs, .video: videoCodecs});
303 if (f == QMediaFormat::MPEG4 && !fileFormats.contains(t: QMediaFormat::Mpeg4Audio)) {
304 maps.append(t: {.format: QMediaFormat::Mpeg4Audio, .audio: audioCodecs, .video: {}});
305 if (audioCodecs.contains(t: QMediaFormat::AudioCodec::AAC))
306 maps.append(t: {.format: QMediaFormat::AAC, .audio: { QMediaFormat::AudioCodec::AAC }, .video: {}});
307 } else if (f == QMediaFormat::WMV && !fileFormats.contains(t: QMediaFormat::WMA)) {
308 maps.append(t: {.format: QMediaFormat::WMA, .audio: audioCodecs, .video: {}});
309 }
310 }
311 }
312 }
313 gst_plugin_feature_list_free(list: elementList);
314 return maps;
315}
316
317static QList<QImageCapture::FileFormat> getImageFormatList()
318{
319 QSet<QImageCapture::FileFormat> formats;
320
321 GList *elementList = gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_ENCODER,
322 minrank: GST_RANK_MARGINAL);
323
324 for (GstElementFactory *factory :
325 QGstUtils::GListRangeAdaptor<GstElementFactory *>(elementList)) {
326
327 for (GstStaticPadTemplate *padTemplate :
328 QGstUtils::GListConstRangeAdaptor<GstStaticPadTemplate *>(
329 gst_element_factory_get_static_pad_templates(factory))) {
330 if (padTemplate->direction == GST_PAD_SRC) {
331 QGstCaps caps = QGstCaps(gst_static_caps_get(static_caps: &padTemplate->static_caps), QGstCaps::HasRef);
332
333 for (int i = 0; i < caps.size(); i++) {
334 QGstStructureView structure = caps.at(index: i);
335 auto f = QGstreamerFormatInfo::imageFormatForCaps(structure);
336 if (f != QImageCapture::UnspecifiedFormat) {
337// qDebug() << structure.toString() << f;
338 formats.insert(value: f);
339 }
340 }
341 }
342 }
343 }
344 gst_plugin_feature_list_free(list: elementList);
345 return formats.values();
346}
347
348#if 0
349static void dumpAudioCodecs(const QList<QMediaFormat::AudioCodec> &codecList)
350{
351 qDebug() << "Audio codecs:";
352 for (const auto &c : codecList)
353 qDebug() << " " << QMediaFormat::audioCodecName(c);
354}
355
356static void dumpVideoCodecs(const QList<QMediaFormat::VideoCodec> &codecList)
357{
358 qDebug() << "Video codecs:";
359 for (const auto &c : codecList)
360 qDebug() << " " << QMediaFormat::videoCodecName(c);
361}
362
363static void dumpMuxers(const QList<QPlatformMediaFormatInfo::CodecMap> &muxerList)
364{
365 for (const auto &m : muxerList) {
366 qDebug() << " " << QMediaFormat::fileFormatName(m.format);
367 qDebug() << " Audio";
368 for (const auto &a : m.audio)
369 qDebug() << " " << QMediaFormat::audioCodecName(a);
370 qDebug() << " Video";
371 for (const auto &v : m.video)
372 qDebug() << " " << QMediaFormat::videoCodecName(v);
373 }
374
375}
376#endif
377
378QGstreamerFormatInfo::QGstreamerFormatInfo()
379{
380 auto codecs = getCodecsList(/*decode = */ true);
381 decoders = getCodecMaps(conversionMode: QMediaFormat::Decode, supportedAudioCodecs: codecs.first, supportedVideoCodecs: codecs.second);
382
383 codecs = getCodecsList(/*decode = */ false);
384 encoders = getCodecMaps(conversionMode: QMediaFormat::Encode, supportedAudioCodecs: codecs.first, supportedVideoCodecs: codecs.second);
385// dumpAudioCodecs(codecs.first);
386// dumpVideoCodecs(codecs.second);
387// dumpMuxers(encoders);
388
389 imageFormats = getImageFormatList();
390}
391
392QGstreamerFormatInfo::~QGstreamerFormatInfo() = default;
393
394QGstCaps QGstreamerFormatInfo::formatCaps(const QMediaFormat &f) const
395{
396 auto format = f.fileFormat();
397 Q_ASSERT(format != QMediaFormat::UnspecifiedFormat);
398
399 const char *capsForFormat[QMediaFormat::LastFileFormat + 1] = {
400 "video/x-ms-asf", // WMV
401 "video/x-msvideo", // AVI
402 "video/x-matroska", // Matroska
403 "video/quicktime, variant=(string)iso", // MPEG4
404 "video/ogg", // Ogg
405 "video/quicktime", // QuickTime
406 "video/webm", // WebM
407 "video/quicktime, variant=(string)iso", // Mpeg4Audio is the same is mp4...
408 "video/quicktime, variant=(string)iso", // AAC is also an MP4 container
409 "video/x-ms-asf", // WMA, same as WMV
410 "audio/mpeg, mpegversion=(int)1, layer=(int)3", // MP3
411 "audio/x-flac", // FLAC
412 "audio/x-wav" // Wave
413 };
414 return QGstCaps(gst_caps_from_string(string: capsForFormat[format]), QGstCaps::HasRef);
415}
416
417QGstCaps QGstreamerFormatInfo::audioCaps(const QMediaFormat &f) const
418{
419 auto codec = f.audioCodec();
420 if (codec == QMediaFormat::AudioCodec::Unspecified)
421 return {};
422
423 const char *capsForCodec[(int)QMediaFormat::AudioCodec::LastAudioCodec + 1] = {
424 "audio/mpeg, mpegversion=(int)1, layer=(int)3", // MP3
425 "audio/mpeg, mpegversion=(int)4", // AAC
426 "audio/x-ac3", // AC3
427 "audio/x-eac3", // EAC3
428 "audio/x-flac", // FLAC
429 "audio/x-true-hd", // DolbyTrueHD
430 "audio/x-opus", // Opus
431 "audio/x-vorbis", // Vorbis
432 "audio/x-raw", // WAVE
433 "audio/x-wma", // WMA
434 "audio/x-alac", // ALAC
435 };
436 return QGstCaps(gst_caps_from_string(string: capsForCodec[(int)codec]), QGstCaps::HasRef);
437}
438
439QGstCaps QGstreamerFormatInfo::videoCaps(const QMediaFormat &f) const
440{
441 auto codec = f.videoCodec();
442 if (codec == QMediaFormat::VideoCodec::Unspecified)
443 return {};
444
445 const char *capsForCodec[(int)QMediaFormat::VideoCodec::LastVideoCodec + 1] = {
446 "video/mpeg, mpegversion=(int)1", // MPEG1,
447 "video/mpeg, mpegversion=(int)2", // MPEG2,
448 "video/mpeg, mpegversion=(int)4", // MPEG4,
449 "video/x-h264", // H264,
450 "video/x-h265", // H265,
451 "video/x-vp8", // VP8,
452 "video/x-vp9", // VP9,
453 "video/x-av1", // AV1,
454 "video/x-theora", // Theora,
455 "audio/x-wmv", // WMV
456 "video/x-jpeg", // MotionJPEG,
457 };
458 return QGstCaps(gst_caps_from_string(string: capsForCodec[(int)codec]), QGstCaps::HasRef);
459}
460
461QT_END_NAMESPACE
462

source code of qtmultimedia/src/plugins/multimedia/gstreamer/qgstreamerformatinfo.cpp