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

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