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 | |
8 | QT_BEGIN_NAMESPACE |
9 | |
10 | QMediaFormat::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 | |
48 | QMediaFormat::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 | |
86 | QMediaFormat::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 | |
122 | QImageCapture::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 | |
138 | static 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 | |
178 | QList<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 | |
273 | static 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 |
309 | static 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 | |
316 | static 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 | |
323 | static 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 | |
338 | QGstreamerFormatInfo::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 | |
352 | QGstreamerFormatInfo::~QGstreamerFormatInfo() = default; |
353 | |
354 | QGstCaps 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 | |
377 | QGstCaps 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 | |
399 | QGstCaps 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 | |
421 | QT_END_NAMESPACE |
422 | |