1 /* Webcamoid, webcam capture application.
2 * Copyright (C) 2016 Gonzalo Exequiel Pedone
3 *
4 * Webcamoid is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * Webcamoid is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with Webcamoid. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Web-Site: http://webcamoid.github.io/
18 */
19
20 #include <limits>
21 #include <QDebug>
22 #include <QVariant>
23 #include <QMap>
24 #include <QVector>
25 #include <QFileInfo>
26 #include <QtConcurrent>
27 #include <QThreadPool>
28 #include <akfrac.h>
29 #include <akcaps.h>
30 #include <akaudiocaps.h>
31 #include <akpacket.h>
32 #include <akaudiopacket.h>
33 #include <akvideopacket.h>
34 #include <gst/gst.h>
35 #include <gst/app/gstappsrc.h>
36 #include <gst/pbutils/encoding-profile.h>
37
38 #include "mediawritergstreamer.h"
39 #include "outputparams.h"
40
41 #define MINIMUM_PLUGIN_RANK GST_RANK_SECONDARY
42
43 using VectorInt = QVector<int>;
44 using SizeList = QList<QSize>;
45 using GstToSampleFormatMap = QMap<AkAudioCaps::SampleFormat, QString>;
46 using GstToPixelFormatMap = QMap<AkVideoCaps::PixelFormat, QString>;
47 using VectorVideoCaps = QVector<AkVideoCaps>;
48 using StringVectorIntMap = QMap<QString, VectorInt>;
49 using OptionTypeStrMap = QMap<GType, QString>;
50
51 class MediaWriterGStreamerPrivate
52 {
53 public:
54 MediaWriterGStreamer *self;
55 QString m_outputFormat;
56 QMap<QString, QVariantMap> m_formatOptions;
57 QMap<QString, QVariantMap> m_codecOptions;
58 QList<QVariantMap> m_streamConfigs;
59 QList<OutputParams> m_streamParams;
60 QThreadPool m_threadPool;
61 GstElement *m_pipeline {nullptr};
62 GMainLoop *m_mainLoop {nullptr};
63 guint m_busWatchId {0};
64 bool m_isRecording {false};
65
66 explicit MediaWriterGStreamerPrivate(MediaWriterGStreamer *self);
67 QString guessFormat(const QString &fileName);
68 QStringList readCaps(const QString &element);
69 QVariantList parseOptions(const GstElement *element) const;
70 void waitState(GstState state);
71 static gboolean busCallback(GstBus *bus,
72 GstMessage *message,
73 gpointer userData);
74 void setElementOptions(GstElement *element, const QVariantMap &options);
75 AkVideoCaps nearestDVCaps(const AkVideoCaps &caps) const;
76 AkAudioCaps nearestFLVAudioCaps(const AkAudioCaps &caps,
77 const QString &codec) const;
78 AkAudioCaps nearestSampleRate(const AkAudioCaps &caps,
79 const QVariantList &sampleRates) const;
80 AkAudioCaps nearestSampleRate(const AkAudioCaps &caps,
81 const QList<int> &sampleRates) const;
82 AkVideoCaps nearestFrameRate(const AkVideoCaps &caps,
83 const QVariantList &frameRates) const;
84 AkVideoCaps nearestFrameRate(const AkVideoCaps &caps,
85 const QList<AkFrac> &frameRates) const;
86 AkVideoCaps nearestFrameSize(const AkVideoCaps &caps,
87 const QList<QSize> &frameSizes) const;
88
gstToSampleFormat()89 inline static const GstToSampleFormatMap &gstToSampleFormat()
90 {
91 static const GstToSampleFormatMap gstToFormat {
92 {AkAudioCaps::SampleFormat_s8 , "S8" },
93 {AkAudioCaps::SampleFormat_u8 , "U8" },
94 {AkAudioCaps::SampleFormat_s16le, "S16LE"},
95 {AkAudioCaps::SampleFormat_s16be, "S16BE"},
96 {AkAudioCaps::SampleFormat_u16le, "U16LE"},
97 {AkAudioCaps::SampleFormat_u16be, "U16BE"},
98 {AkAudioCaps::SampleFormat_s32le, "S32LE"},
99 {AkAudioCaps::SampleFormat_s32be, "S32BE"},
100 {AkAudioCaps::SampleFormat_u32le, "U32LE"},
101 {AkAudioCaps::SampleFormat_u32be, "U32BE"},
102 {AkAudioCaps::SampleFormat_fltle, "F32LE"},
103 {AkAudioCaps::SampleFormat_fltbe, "F32BE"},
104 {AkAudioCaps::SampleFormat_dblle, "F64LE"},
105 {AkAudioCaps::SampleFormat_dblbe, "F64BE"},
106 };
107
108 return gstToFormat;
109 }
110
gstToPixelFormat()111 inline static const GstToPixelFormatMap &gstToPixelFormat()
112 {
113 static const GstToPixelFormatMap gstToFormat {
114 {AkVideoCaps::Format_yuv420p , "I420" },
115 {AkVideoCaps::Format_yuyv422 , "YUY2" },
116 {AkVideoCaps::Format_uyvy422 , "UYVY" },
117 {AkVideoCaps::Format_rgb0 , "RGBx" },
118 {AkVideoCaps::Format_bgr0 , "BGRx" },
119 {AkVideoCaps::Format_0rgb , "xRGB" },
120 {AkVideoCaps::Format_0bgr , "xBGR" },
121 {AkVideoCaps::Format_rgba , "RGBA" },
122 {AkVideoCaps::Format_bgra , "BGRA" },
123 {AkVideoCaps::Format_argb , "ARGB" },
124 {AkVideoCaps::Format_abgr , "ABGR" },
125 {AkVideoCaps::Format_rgb24 , "RGB" },
126 {AkVideoCaps::Format_bgr24 , "BGR" },
127 {AkVideoCaps::Format_yuv411p , "Y41B" },
128 {AkVideoCaps::Format_yuv422p , "Y42B" },
129 {AkVideoCaps::Format_yuv444p , "Y444" },
130 {AkVideoCaps::Format_nv12 , "NV12" },
131 {AkVideoCaps::Format_nv21 , "NV21" },
132 {AkVideoCaps::Format_gray , "GRAY8" },
133 {AkVideoCaps::Format_gray16be , "GRAY16_BE"},
134 {AkVideoCaps::Format_gray16le , "GRAY16_LE"},
135 {AkVideoCaps::Format_rgb565le , "RGB16" },
136 {AkVideoCaps::Format_bgr565le , "BGR16" },
137 {AkVideoCaps::Format_rgb555le , "RGB15" },
138 {AkVideoCaps::Format_rgb555le , "BGR15" },
139 {AkVideoCaps::Format_yuva420p , "A420" },
140 {AkVideoCaps::Format_pal8 , "RGB8P" },
141 {AkVideoCaps::Format_yuv410p , "YUV9" },
142 {AkVideoCaps::Format_ayuv64le , "AYUV64" },
143 {AkVideoCaps::Format_yuv420p10be , "I420_10BE"},
144 {AkVideoCaps::Format_yuv420p10le , "I420_10LE"},
145 {AkVideoCaps::Format_yuv422p10be , "I422_10BE"},
146 {AkVideoCaps::Format_yuv422p10le , "I422_10LE"},
147 {AkVideoCaps::Format_yuv444p10be , "Y444_10BE"},
148 {AkVideoCaps::Format_yuv444p10le , "Y444_10LE"},
149 {AkVideoCaps::Format_gbrp , "GBR" },
150 {AkVideoCaps::Format_gbrp10be , "GBR_10BE" },
151 {AkVideoCaps::Format_gbrp10le , "GBR_10LE" },
152 {AkVideoCaps::Format_nv16 , "NV16" },
153 {AkVideoCaps::Format_yuva420p10be, "A420_10BE"},
154 {AkVideoCaps::Format_yuva420p10le, "A420_10LE"},
155 {AkVideoCaps::Format_yuva422p10be, "A422_10BE"},
156 {AkVideoCaps::Format_yuva422p10le, "A422_10LE"},
157 {AkVideoCaps::Format_yuva444p10be, "A444_10BE"},
158 {AkVideoCaps::Format_yuva444p10le, "A444_10LE"},
159 };
160
161 return gstToFormat;
162 }
163
dvSupportedCaps()164 inline static const VectorVideoCaps &dvSupportedCaps()
165 {
166 static const VectorVideoCaps dvSupportedCaps {
167 // Digital Video doesn't support height > 576 yet.
168 /*
169 {AkVideoCaps::Format_yuv422p, 1440, 1080, {25, 1} },
170 {AkVideoCaps::Format_yuv422p, 1280, 1080, {30000, 1001}},
171 {AkVideoCaps::Format_yuv422p, 960 , 720 , {60000, 1001}},
172 {AkVideoCaps::Format_yuv422p, 960 , 720 , {50, 1} },*/
173 {AkVideoCaps::Format_yuv422p, 720 , 576 , {25, 1} },
174 {AkVideoCaps::Format_yuv420p, 720 , 576 , {25, 1} },
175 {AkVideoCaps::Format_yuv411p, 720 , 576 , {25, 1} },
176 {AkVideoCaps::Format_yuv422p, 720 , 480 , {30000, 1001}},
177 {AkVideoCaps::Format_yuv411p, 720 , 480 , {30000, 1001}},
178 };
179
180 return dvSupportedCaps;
181 }
182
flvSupportedSampleRates()183 inline static const StringVectorIntMap &flvSupportedSampleRates()
184 {
185 static const StringVectorIntMap flvSupportedSampleRates {
186 {"avenc_adpcm_swf" , {5512, 11025, 22050, 44100} },
187 {"lamemp3enc" , {5512, 8000 , 11025, 22050, 44100} },
188 {"faac" , {} },
189 {"avenc_nellymoser", {5512, 8000 , 11025, 16000, 22050, 44100}},
190 {"identity" , {5512, 11025, 22050, 44100} },
191 {"alawenc" , {5512, 11025, 22050, 44100} },
192 {"mulawenc" , {5512, 11025, 22050, 44100} },
193 {"speexenc" , {16000} },
194 };
195
196 return flvSupportedSampleRates;
197 }
198
codecGstOptionTypeToStr()199 inline static const OptionTypeStrMap &codecGstOptionTypeToStr()
200 {
201 static const OptionTypeStrMap optionTypeStrMap {
202 {G_TYPE_STRING , "string" },
203 {G_TYPE_BOOLEAN , "boolean"},
204 {G_TYPE_ULONG , "number" },
205 {G_TYPE_LONG , "number" },
206 {G_TYPE_UINT , "number" },
207 {G_TYPE_INT , "number" },
208 {G_TYPE_UINT64 , "number" },
209 {G_TYPE_INT64 , "number" },
210 {G_TYPE_FLOAT , "number" },
211 {G_TYPE_DOUBLE , "number" },
212 {G_TYPE_CHAR , "number" },
213 {G_TYPE_UCHAR , "number" },
214 {G_TYPE_PARAM_ENUM , "menu" },
215 {G_TYPE_PARAM_FLAGS , "flags" },
216 {GST_TYPE_CAPS , "caps" },
217 {GST_TYPE_PARAM_FRACTION, "frac" },
218 };
219
220 return optionTypeStrMap;
221 }
222
223 template<typename R, typename S>
align(R value,S align)224 inline static R align(R value, S align)
225 {
226 return (value + (align >> 1)) & ~(align - 1);
227 }
228 };
229
MediaWriterGStreamer(QObject * parent)230 MediaWriterGStreamer::MediaWriterGStreamer(QObject *parent):
231 MediaWriter(parent)
232 {
233 this->d = new MediaWriterGStreamerPrivate(this);
234 // setenv("GST_DEBUG", "2", 1);
235 gst_init(nullptr, nullptr);
236
237 this->m_formatsBlackList = QStringList {
238 "3gppmux",
239 "mp4mux",
240 "qtmux"
241 };
242 }
243
~MediaWriterGStreamer()244 MediaWriterGStreamer::~MediaWriterGStreamer()
245 {
246 this->uninit();
247 delete this->d;
248 }
249
defaultFormat()250 QString MediaWriterGStreamer::defaultFormat()
251 {
252 auto formats = this->supportedFormats();
253
254 if (formats.isEmpty())
255 return {};
256
257 if (formats.contains("webmmux"))
258 return QStringLiteral("webmmux");
259
260 return formats.first();
261 }
262
outputFormat() const263 QString MediaWriterGStreamer::outputFormat() const
264 {
265 return this->d->m_outputFormat;
266 }
267
streams() const268 QVariantList MediaWriterGStreamer::streams() const
269 {
270 QVariantList streams;
271
272 for (auto &stream: this->d->m_streamConfigs)
273 streams << stream;
274
275 return streams;
276 }
277
supportedFormats()278 QStringList MediaWriterGStreamer::supportedFormats()
279 {
280 QStringList supportedFormats;
281 auto factoryList =
282 gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_MUXER,
283 MINIMUM_PLUGIN_RANK);
284
285 for (auto featureItem = factoryList;
286 featureItem;
287 featureItem = g_list_next(featureItem)) {
288 if (G_UNLIKELY(featureItem->data == nullptr))
289 continue;
290
291 auto factory = GST_ELEMENT_FACTORY(featureItem->data);
292
293 if (this->m_formatsBlackList.contains(GST_OBJECT_NAME(factory)))
294 continue;
295
296 if (!supportedFormats.contains(GST_OBJECT_NAME(factory)))
297 supportedFormats << GST_OBJECT_NAME(factory);
298 }
299
300 gst_plugin_list_free(factoryList);
301 std::sort(supportedFormats.begin(), supportedFormats.end());
302
303 return supportedFormats;
304 }
305
fileExtensions(const QString & format)306 QStringList MediaWriterGStreamer::fileExtensions(const QString &format)
307 {
308 static const QMap<QString, QStringList> alternativeExtensions = {
309 {"3gppmux" , {"3gp" }},
310 {"avmux_3gp" , {"3gp" }},
311 {"avmux_3g2" , {"3g2" }},
312 {"ismlmux" , {"isml", "ismv", "isma"}},
313 {"mp4mux" , {"mp4" }},
314 {"avmux_mp4" , {"mp4" }},
315 {"avmux_psp" , {"psp" , "mp4" }},
316 {"avmux_ipod", {"m4v" , "m4a" }},
317 };
318
319 if (alternativeExtensions.contains(format))
320 return alternativeExtensions[format];
321
322 auto supportedCaps = this->d->readCaps(format);
323 QStringList extensions;
324
325 for (auto &formatCaps: supportedCaps) {
326 auto caps = gst_caps_from_string(formatCaps.toStdString().c_str());
327 caps = gst_caps_fixate(caps);
328 auto prof = gst_encoding_container_profile_new(nullptr,
329 nullptr,
330 caps,
331 nullptr);
332 gst_caps_unref(caps);
333
334 auto extension =
335 gst_encoding_profile_get_file_extension(reinterpret_cast<GstEncodingProfile *>(prof));
336
337 if (extension && !extensions.contains(extension))
338 extensions << extension;
339
340 gst_encoding_profile_unref(prof);
341 }
342
343 return extensions;
344 }
345
formatDescription(const QString & format)346 QString MediaWriterGStreamer::formatDescription(const QString &format)
347 {
348 QString description;
349 auto factory = gst_element_factory_find(format.toStdString().c_str());
350
351 if (factory) {
352 auto feature = gst_plugin_feature_load(GST_PLUGIN_FEATURE(factory));
353
354 if (feature) {
355 auto longName =
356 gst_element_factory_get_metadata(GST_ELEMENT_FACTORY(feature),
357 GST_ELEMENT_METADATA_LONGNAME);
358 description = QString(longName);
359 gst_object_unref(feature);
360 }
361
362 gst_object_unref(factory);
363 }
364
365 return description;
366 }
367
formatOptions()368 QVariantList MediaWriterGStreamer::formatOptions()
369 {
370 QString outputFormat = this->d->m_outputFormat.isEmpty()?
371 this->d->guessFormat(this->m_location):
372 this->d->m_outputFormat;
373
374 if (outputFormat.isEmpty())
375 return {};
376
377 auto element = gst_element_factory_make(outputFormat.toStdString().c_str(),
378 nullptr);
379
380 if (!element)
381 return {};
382
383 auto options = this->d->parseOptions(element);
384 gst_object_unref(element);
385 auto globalFormatOptions =
386 this->d->m_formatOptions.value(outputFormat);
387 QVariantList formatOptions;
388
389 for (auto &option: options) {
390 auto optionList = option.toList();
391 auto key = optionList[0].toString();
392
393 if (globalFormatOptions.contains(key))
394 optionList[7] = globalFormatOptions[key];
395
396 formatOptions << QVariant(optionList);
397 }
398
399 return formatOptions;
400 }
401
supportedCodecs(const QString & format)402 QStringList MediaWriterGStreamer::supportedCodecs(const QString &format)
403 {
404 return this->supportedCodecs(format, "");
405 }
406
supportedCodecs(const QString & format,const QString & type)407 QStringList MediaWriterGStreamer::supportedCodecs(const QString &format,
408 const QString &type)
409 {
410 auto factory = gst_element_factory_find(format.toStdString().c_str());
411
412 if (!factory)
413 return {};
414
415 auto feature = gst_plugin_feature_load(GST_PLUGIN_FEATURE(factory));
416
417 if (!feature) {
418 gst_object_unref(factory);
419
420 return {};
421 }
422
423 static GstStaticCaps staticRawCaps =
424 GST_STATIC_CAPS("video/x-raw;"
425 "audio/x-raw;"
426 "text/x-raw;"
427 "subpicture/x-dvd;"
428 "subpicture/x-pgs");
429
430 auto rawCaps = gst_static_caps_get(&staticRawCaps);
431 auto encodersList =
432 gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_ENCODER,
433 MINIMUM_PLUGIN_RANK);
434
435 auto pads = gst_element_factory_get_static_pad_templates(GST_ELEMENT_FACTORY(feature));
436 QStringList supportedCodecs;
437
438 for (auto padItem = pads; padItem; padItem = g_list_next(padItem)) {
439 auto padtemplate =
440 reinterpret_cast<GstStaticPadTemplate *>(padItem->data);
441
442 if (padtemplate->direction == GST_PAD_SINK) {
443 auto caps = gst_caps_from_string(padtemplate->static_caps.string);
444
445 for (guint i = 0; i < gst_caps_get_size(caps); i++) {
446 auto capsStructure = gst_caps_get_structure(caps, i);
447 auto structureName = gst_structure_get_name(capsStructure);
448 QString structureType(structureName);
449 auto structureStr = gst_structure_to_string(capsStructure);
450 auto compCaps = gst_caps_from_string(structureStr);
451
452 if (gst_caps_can_intersect(compCaps, rawCaps)) {
453 if (!type.isEmpty() && structureType != type) {
454 gst_caps_unref(compCaps);
455 g_free(structureStr);
456
457 continue;
458 }
459
460 auto codecType = structureType.mid(0, type.indexOf('/'));
461
462 if (gst_structure_has_field(capsStructure, "format")) {
463 GType fieldType = gst_structure_get_field_type(capsStructure, "format");
464
465 if (fieldType == G_TYPE_STRING) {
466 auto format = gst_structure_get_string(capsStructure, "format");
467 auto codecId = QString("identity/%1/%2")
468 .arg(codecType,format);
469
470 if (!supportedCodecs.contains(codecId)
471 && !this->m_codecsBlackList.contains(codecId))
472 supportedCodecs << codecId;
473 } else if (fieldType == GST_TYPE_LIST) {
474 auto formats = gst_structure_get_value(capsStructure, "format");
475
476 for (guint i = 0; i < gst_value_list_get_size(formats); i++) {
477 auto format = gst_value_list_get_value(formats, i);
478 auto codecId =
479 QString("identity/%1/%2")
480 .arg(codecType,
481 g_value_get_string(format));
482
483 if (!supportedCodecs.contains(codecId)
484 && !this->m_codecsBlackList.contains(codecId))
485 supportedCodecs << codecId;
486 }
487 }
488 }
489 } else {
490 auto encoders =
491 gst_element_factory_list_filter(encodersList,
492 caps,
493 GST_PAD_SRC,
494 FALSE);
495
496 for (auto encoderItem = encoders;
497 encoderItem;
498 encoderItem = g_list_next(encoderItem)) {
499 auto encoder =
500 reinterpret_cast<GstElementFactory *>(encoderItem->data);
501
502 if (this->m_codecsBlackList.contains(GST_OBJECT_NAME(encoder)))
503 continue;
504
505 auto klass =
506 gst_element_factory_get_metadata(encoder,
507 GST_ELEMENT_METADATA_KLASS);
508 QString codecType =
509 !strcmp(klass, "Codec/Encoder/Audio")?
510 "audio/x-raw":
511 (!strcmp(klass, "Codec/Encoder/Video")
512 || !strcmp(klass, "Codec/Encoder/Image"))?
513 "video/x-raw": "";
514
515 if (!type.isEmpty() && type != codecType)
516 continue;
517
518 if (!supportedCodecs.contains(GST_OBJECT_NAME(encoder)))
519 supportedCodecs << GST_OBJECT_NAME(encoder);
520 }
521
522 gst_plugin_feature_list_free(encoders);
523 }
524
525 gst_caps_unref(compCaps);
526 g_free(structureStr);
527 }
528
529 gst_caps_unref(caps);
530 }
531 }
532
533 gst_plugin_feature_list_free(encodersList);
534 gst_caps_unref(rawCaps);
535 gst_object_unref(feature);
536 gst_object_unref(factory);
537
538 // Disable conflictive codecs
539 static const QMap<QString, QStringList> unsupportedCodecs = {
540 {"mp4mux" , {"schroenc" }},
541 {"flvmux" , {"lamemp3enc" }},
542 {"matroskamux", {"avenc_tta",
543 "identity/audio/F32LE",
544 "identity/audio/F64LE",
545 "identity/audio/S16BE",
546 "identity/audio/S16LE",
547 "identity/audio/S24BE",
548 "identity/audio/S24LE",
549 "identity/audio/S32BE",
550 "identity/audio/S32LE",
551 "identity/audio/U8" }},
552 {"mpegtsmux" , {"lamemp3enc",
553 "twolamemp2enc" }},
554 {"*" , {"av1enc",
555 "avenc_dca",
556 "avenc_dnxhd",
557 "avenc_jpeg2000",
558 "avenc_prores_ks",
559 "avenc_rv10",
560 "avenc_rv20",
561 "openjpegenc",
562 "pngenc",
563 "theoraenc" }},
564 };
565
566 for (auto &codec: unsupportedCodecs.value(format) + unsupportedCodecs["*"])
567 supportedCodecs.removeAll(codec);
568
569 std::sort(supportedCodecs.begin(), supportedCodecs.end());
570
571 return supportedCodecs;
572 }
573
defaultCodec(const QString & format,const QString & type)574 QString MediaWriterGStreamer::defaultCodec(const QString &format,
575 const QString &type)
576 {
577 auto codecs = this->supportedCodecs(format, type);
578
579 if (codecs.isEmpty())
580 return {};
581
582 return codecs.first();
583 }
584
codecDescription(const QString & codec)585 QString MediaWriterGStreamer::codecDescription(const QString &codec)
586 {
587 if (codec.startsWith("identity/")) {
588 QStringList parts = codec.split("/");
589
590 return QString("%1 (%2)").arg(parts[0], parts[2]);
591 }
592
593 QString description;
594 auto factory = gst_element_factory_find(codec.toStdString().c_str());
595
596 if (factory) {
597 auto feature = gst_plugin_feature_load(GST_PLUGIN_FEATURE(factory));
598
599 if (feature) {
600 auto longName =
601 gst_element_factory_get_metadata(GST_ELEMENT_FACTORY(feature),
602 GST_ELEMENT_METADATA_LONGNAME);
603 description = QString(longName);
604 gst_object_unref(feature);
605 }
606
607 gst_object_unref(factory);
608 }
609
610 return description;
611 }
612
codecType(const QString & codec)613 QString MediaWriterGStreamer::codecType(const QString &codec)
614 {
615 if (codec.startsWith("identity/audio"))
616 return {"audio/x-raw"};
617
618 if (codec.startsWith("identity/video"))
619 return {"video/x-raw"};
620
621 if (codec.startsWith("identity/text"))
622 return {"text/x-raw"};
623
624 QString codecType;
625 auto factory = gst_element_factory_find(codec.toStdString().c_str());
626
627 if (factory) {
628 auto feature = gst_plugin_feature_load(GST_PLUGIN_FEATURE(factory));
629
630 if (feature) {
631 auto klass = gst_element_factory_get_metadata(GST_ELEMENT_FACTORY(feature),
632 GST_ELEMENT_METADATA_KLASS);
633 codecType =
634 !strcmp(klass, "Codec/Encoder/Audio")?
635 "audio/x-raw":
636 (!strcmp(klass, "Codec/Encoder/Video")
637 || !strcmp(klass, "Codec/Encoder/Image"))?
638 "video/x-raw": "";
639 gst_object_unref(feature);
640 }
641
642 gst_object_unref(factory);
643 }
644
645 return codecType;
646 }
647
defaultCodecParams(const QString & codec)648 QVariantMap MediaWriterGStreamer::defaultCodecParams(const QString &codec)
649 {
650 QVariantMap codecParams;
651 QString codecType = this->codecType(codec);
652
653 static GstStaticCaps staticRawCaps = GST_STATIC_CAPS("video/x-raw;"
654 "audio/x-raw;"
655 "text/x-raw;"
656 "subpicture/x-dvd;"
657 "subpicture/x-pgs");
658
659 auto rawCaps = gst_static_caps_get(&staticRawCaps);
660
661 if (codecType == "audio/x-raw") {
662 if (codec.startsWith("identity/audio")) {
663 auto gstFormat = codec.split("/").at(2);
664 auto sampleFormat =
665 MediaWriterGStreamerPrivate::gstToSampleFormat().key(gstFormat,
666 AkAudioCaps::SampleFormat_none);
667 auto sampleFormatStr = AkAudioCaps::sampleFormatToString(sampleFormat);
668 codecParams["defaultBitRate"] = 128000;
669 codecParams["supportedSampleFormats"] = QStringList {sampleFormatStr};
670 codecParams["supportedChannelLayouts"] = QStringList {"mono", "stereo"};
671 codecParams["supportedSampleRates"] = QVariantList();
672 codecParams["defaultSampleFormat"] = sampleFormatStr;
673 codecParams["defaultChannelLayout"] = "stereo";
674 codecParams["defaultChannels"] = 2;
675 codecParams["defaultSampleRate"] = 44100;
676 } else {
677 auto factory =
678 gst_element_factory_find(codec.toStdString().c_str());
679
680 if (!factory) {
681 gst_caps_unref(rawCaps);
682
683 return {};
684 }
685
686 auto feature = gst_plugin_feature_load(GST_PLUGIN_FEATURE(factory));
687
688 if (!feature) {
689 gst_object_unref(factory);
690 gst_caps_unref(rawCaps);
691
692 return {};
693 }
694
695 QStringList supportedSampleFormats;
696 QVariantList supportedSamplerates;
697 QStringList supportedChannelLayouts;
698
699 auto pads = gst_element_factory_get_static_pad_templates(GST_ELEMENT_FACTORY(feature));
700
701 for (auto padItem = pads;
702 padItem;
703 padItem = g_list_next(padItem)) {
704 auto padtemplate =
705 reinterpret_cast<GstStaticPadTemplate *>(padItem->data);
706
707 if (padtemplate->direction == GST_PAD_SINK
708 && padtemplate->presence == GST_PAD_ALWAYS) {
709 auto caps =
710 gst_caps_from_string(padtemplate->static_caps.string);
711
712 for (guint i = 0; i < gst_caps_get_size(caps); i++) {
713 auto capsStructure = gst_caps_get_structure(caps, i);
714 auto structureStr = gst_structure_to_string(capsStructure);
715 auto compCaps = gst_caps_from_string(structureStr);
716
717 if (gst_caps_can_intersect(compCaps, rawCaps)) {
718 // Get supported formats
719 if (gst_structure_has_field(capsStructure, "format")) {
720 GType fieldType = gst_structure_get_field_type(capsStructure, "format");
721
722 if (fieldType == G_TYPE_STRING) {
723 auto format = gst_structure_get_string(capsStructure, "format");
724 auto formatFF =
725 MediaWriterGStreamerPrivate::gstToSampleFormat().key(format,
726 AkAudioCaps::SampleFormat_none);
727 auto formatFFStr = AkAudioCaps::sampleFormatToString(formatFF);
728
729 if (!formatFFStr.isEmpty()
730 && formatFFStr != "none"
731 && !supportedSampleFormats.contains(formatFFStr))
732 supportedSampleFormats << formatFFStr;
733 } else if (fieldType == GST_TYPE_LIST) {
734 const GValue *formats = gst_structure_get_value(capsStructure, "format");
735
736 for (guint i = 0; i < gst_value_list_get_size(formats); i++) {
737 auto format = gst_value_list_get_value(formats, i);
738 auto formatId = g_value_get_string(format);
739 auto formatFF =
740 MediaWriterGStreamerPrivate::gstToSampleFormat().key(formatId,
741 AkAudioCaps::SampleFormat_none);
742 auto formatFFStr = AkAudioCaps::sampleFormatToString(formatFF);
743
744 if (!formatFFStr.isEmpty()
745 && formatFFStr != "none"
746 && !supportedSampleFormats.contains(formatFFStr))
747 supportedSampleFormats << formatFFStr;
748 }
749 }
750 }
751
752 // Get supported sample rates
753 if (gst_structure_has_field(capsStructure, "rate")) {
754 GType fieldType = gst_structure_get_field_type(capsStructure, "rate");
755
756 if (fieldType == G_TYPE_INT) {
757 gint rate;
758 gst_structure_get_int(capsStructure, "rate", &rate);
759
760 if (!supportedSamplerates.contains(rate))
761 supportedSamplerates << rate;
762 } else if (fieldType == GST_TYPE_INT_RANGE) {
763 } else if (fieldType == GST_TYPE_LIST) {
764 auto rates = gst_structure_get_value(capsStructure, "rate");
765
766 for (guint i = 0;
767 i < gst_value_list_get_size(rates);
768 i++) {
769 auto rate = gst_value_list_get_value(rates, i);
770 gint rateId = g_value_get_int(rate);
771
772 if (!supportedSamplerates.contains(rateId))
773 supportedSamplerates << rateId;
774 }
775 }
776 }
777
778 // Get supported channel layouts
779 if (gst_structure_has_field(capsStructure, "channels")) {
780 GType fieldType = gst_structure_get_field_type(capsStructure, "channels");
781
782 if (fieldType == G_TYPE_INT) {
783 gint channels;
784 gst_structure_get_int(capsStructure, "channels", &channels);
785 auto layout = AkAudioCaps::defaultChannelLayoutString(channels);
786
787 if (!supportedChannelLayouts.contains(layout))
788 supportedChannelLayouts << layout;
789 } else if (fieldType == GST_TYPE_INT_RANGE) {
790 auto channels = gst_structure_get_value(capsStructure, "channels");
791
792 int min = gst_value_get_int_range_min(channels);
793 int max = gst_value_get_int_range_max(channels) + 1;
794 int step = gst_value_get_int_range_step(channels);
795
796 for (int i = min; i < max; i += step) {
797 auto layout = AkAudioCaps::defaultChannelLayoutString(i);
798
799 if (!supportedChannelLayouts.contains(layout))
800 supportedChannelLayouts << layout;
801 }
802 } else if (fieldType == GST_TYPE_LIST) {
803 auto channels = gst_structure_get_value(capsStructure, "channels");
804
805 for (guint i = 0; i < gst_value_list_get_size(channels); i++) {
806 auto nchannels = gst_value_list_get_value(channels, i);
807 gint nchannelsId = g_value_get_int(nchannels);
808 auto layout = AkAudioCaps::defaultChannelLayoutString(nchannelsId);
809
810 if (!supportedChannelLayouts.contains(layout))
811 supportedChannelLayouts << layout;
812 }
813 }
814 }
815 }
816
817 gst_caps_unref(compCaps);
818 g_free(structureStr);
819 }
820
821 gst_caps_unref(caps);
822 }
823 }
824
825 auto element =
826 gst_element_factory_create(GST_ELEMENT_FACTORY(feature),
827 nullptr);
828
829 if (!element) {
830 gst_object_unref(feature);
831 gst_object_unref(factory);
832 gst_caps_unref(rawCaps);
833
834 return {};
835 }
836
837 int bitrate = 0;
838
839 if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "bitrate"))
840 g_object_get(G_OBJECT(element), "bitrate", &bitrate, nullptr);
841
842 if (codec == "lamemp3enc")
843 bitrate *= 1000;
844
845 if (bitrate < 1)
846 bitrate = 128000;
847
848 codecParams["defaultBitRate"] = bitrate;
849 codecParams["supportedSampleFormats"] = supportedSampleFormats;
850 codecParams["supportedChannelLayouts"] = supportedChannelLayouts;
851 codecParams["supportedSampleRates"] = supportedSamplerates;
852 codecParams["defaultSampleFormat"] =
853 supportedSampleFormats.isEmpty()?
854 QString("s16"): supportedSampleFormats.at(0);
855 QString channelLayout =
856 supportedChannelLayouts.isEmpty()?
857 QString("stereo"): supportedChannelLayouts.at(0);
858 codecParams["defaultChannelLayout"] = channelLayout;
859 codecParams["defaultChannels"] = AkAudioCaps::channelCount(channelLayout);
860 codecParams["defaultSampleRate"] = supportedSamplerates.isEmpty()?
861 44100: supportedSamplerates.at(0);
862
863 gst_object_unref(element);
864 gst_object_unref(feature);
865 gst_object_unref(factory);
866 }
867 } else if (codecType == "video/x-raw") {
868 if (codec.startsWith("identity/video")) {
869 auto gstFormat = codec.split("/").at(2);
870 auto pixelFormat =
871 MediaWriterGStreamerPrivate::gstToPixelFormat().key(gstFormat,
872 AkVideoCaps::Format_none);
873 auto pixelFormatStr = AkVideoCaps::pixelFormatToString(pixelFormat);
874 codecParams["defaultBitRate"] = 1500000;
875 codecParams["defaultGOP"] = 12;
876 codecParams["supportedFrameRates"] = QVariantList();
877 codecParams["supportedPixelFormats"] = QStringList {pixelFormatStr};
878 codecParams["supportedFrameSizes"] = QVariantList();
879 codecParams["defaultPixelFormat"] = pixelFormatStr;
880 } else {
881 auto factory = gst_element_factory_find(codec.toStdString().c_str());
882
883 if (!factory) {
884 gst_caps_unref(rawCaps);
885
886 return {};
887 }
888
889 auto feature = gst_plugin_feature_load(GST_PLUGIN_FEATURE(factory));
890
891 if (!feature) {
892 gst_object_unref(factory);
893 gst_caps_unref(rawCaps);
894
895 return {};
896 }
897
898 QStringList supportedPixelFormats;
899 QVariantList supportedFramerates;
900 SizeList supportedFrameSizes;
901
902 auto pads = gst_element_factory_get_static_pad_templates(GST_ELEMENT_FACTORY(feature));
903
904 for (auto padItem = pads; padItem; padItem = g_list_next(padItem)) {
905 auto padtemplate =
906 reinterpret_cast<GstStaticPadTemplate *>(padItem->data);
907
908 if (padtemplate->direction == GST_PAD_SINK
909 && padtemplate->presence == GST_PAD_ALWAYS) {
910 auto caps = gst_caps_from_string(padtemplate->static_caps.string);
911
912 for (guint i = 0; i < gst_caps_get_size(caps); i++) {
913 auto capsStructure = gst_caps_get_structure(caps, i);
914 auto structureStr = gst_structure_to_string(capsStructure);
915 auto compCaps = gst_caps_from_string(structureStr);
916
917 if (gst_caps_can_intersect(compCaps, rawCaps)) {
918 // Get supported formats
919 if (gst_structure_has_field(capsStructure, "format")) {
920 GType fieldType = gst_structure_get_field_type(capsStructure, "format");
921
922 if (fieldType == G_TYPE_STRING) {
923 auto format = gst_structure_get_string(capsStructure, "format");
924 auto formatFF =
925 MediaWriterGStreamerPrivate::gstToPixelFormat()
926 .key(format, AkVideoCaps::Format_none);
927 auto formatFFStr =
928 AkVideoCaps::pixelFormatToString(formatFF);
929
930 if (!formatFFStr.isEmpty()
931 && formatFFStr != "none"
932 && !supportedPixelFormats.contains(formatFFStr))
933 supportedPixelFormats << formatFFStr;
934 } else if (fieldType == GST_TYPE_LIST) {
935 auto formats = gst_structure_get_value(capsStructure, "format");
936
937 for (guint i = 0; i < gst_value_list_get_size(formats); i++) {
938 auto format = gst_value_list_get_value(formats, i);
939 auto formatId = g_value_get_string(format);
940 auto formatFF =
941 MediaWriterGStreamerPrivate::gstToPixelFormat()
942 .key(formatId, AkVideoCaps::Format_none);
943 auto formatFFStr =
944 AkVideoCaps::pixelFormatToString(formatFF);
945
946 if (!formatFFStr.isEmpty()
947 && formatFFStr != "none"
948 && !supportedPixelFormats.contains(formatFFStr))
949 supportedPixelFormats << formatFFStr;
950 }
951 }
952 }
953
954 // Get supported frame sizes
955 if (gst_structure_has_field(capsStructure, "width")
956 && gst_structure_has_field(capsStructure, "height")) {
957 gint width = 0;
958 gint height = 0;
959
960 GType fieldType = gst_structure_get_field_type(capsStructure, "width");
961
962 if (fieldType == G_TYPE_INT)
963 gst_structure_get_int(capsStructure, "width", &width);
964
965 fieldType = gst_structure_get_field_type(capsStructure, "height");
966
967 if (fieldType == G_TYPE_INT)
968 gst_structure_get_int(capsStructure, "height", &height);
969
970 QSize size(width, height);
971
972 if (!size.isEmpty() && !supportedFrameSizes.contains(size))
973 supportedFrameSizes << size;
974 }
975
976 // Get supported frame rates
977 if (gst_structure_has_field(capsStructure, "framerate")) {
978 GType fieldType = gst_structure_get_field_type(capsStructure, "framerate");
979
980 if (fieldType == GST_TYPE_FRACTION_RANGE) {
981 } else if (fieldType == GST_TYPE_LIST) {
982 auto framerates = gst_structure_get_value(capsStructure, "framerate");
983
984 for (guint i = 0; i < gst_value_list_get_size(framerates); i++) {
985 auto frate = gst_value_list_get_value(framerates, i);
986 auto num = gst_value_get_fraction_numerator(frate);
987 auto den = gst_value_get_fraction_denominator(frate);
988 AkFrac framerate(num, den);
989 auto fps = QVariant::fromValue(framerate);
990
991 if (!supportedFramerates.contains(fps))
992 supportedFramerates << fps;
993 }
994 } else if (fieldType == GST_TYPE_FRACTION) {
995 gint num = 0;
996 gint den = 0;
997 gst_structure_get_fraction(capsStructure,
998 "framerate",
999 &num,
1000 &den);
1001 AkFrac framerate(num, den);
1002 auto fps = QVariant::fromValue(framerate);
1003
1004 if (!supportedFramerates.contains(fps))
1005 supportedFramerates << fps;
1006 }
1007 }
1008 }
1009
1010 gst_caps_unref(compCaps);
1011 g_free(structureStr);
1012 }
1013
1014 gst_caps_unref(caps);
1015 }
1016 }
1017
1018 auto element =
1019 gst_element_factory_create(GST_ELEMENT_FACTORY(feature),
1020 nullptr);
1021
1022 if (!element) {
1023 gst_object_unref(feature);
1024 gst_object_unref(factory);
1025 gst_caps_unref(rawCaps);
1026
1027 return {};
1028 }
1029
1030 // Read default bitrate
1031 int bitrate = 0;
1032
1033 const char *propBitrate =
1034 QRegExp("vp\\d+enc").exactMatch(codec)?
1035 "target-bitrate": "bitrate";
1036
1037 if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), propBitrate))
1038 g_object_get(G_OBJECT(element), propBitrate, &bitrate, nullptr);
1039
1040 if (codec == "x264enc"
1041 || codec == "x265enc"
1042 || codec == "mpeg2enc"
1043 || codec == "theoraenc")
1044 bitrate *= 1000;
1045
1046 if (bitrate < 1500000)
1047 bitrate = 1500000;
1048
1049 // Read default GOP
1050 int gop = 0;
1051 QStringList gops {"keyframe-max-dist", "gop-size"};
1052
1053 for (auto &g: gops)
1054 if (g_object_class_find_property(G_OBJECT_GET_CLASS(element),
1055 g.toStdString().c_str())) {
1056 g_object_get(G_OBJECT(element),
1057 g.toStdString().c_str(),
1058 &gop,
1059 nullptr);
1060
1061 break;
1062 }
1063
1064 if (gop < 1)
1065 gop = 12;
1066
1067 codecParams["defaultBitRate"] = bitrate;
1068 codecParams["defaultGOP"] = gop;
1069 codecParams["supportedFrameRates"] = supportedFramerates;
1070 codecParams["supportedPixelFormats"] = supportedPixelFormats;
1071 codecParams["supportedFrameSizes"] = QVariant::fromValue(supportedFrameSizes);
1072 codecParams["defaultPixelFormat"] = supportedPixelFormats.isEmpty()?
1073 "yuv420p": supportedPixelFormats.at(0);
1074
1075 gst_object_unref(element);
1076 gst_object_unref(feature);
1077 gst_object_unref(factory);
1078 }
1079 } else if (codecType == "text/x-raw") {
1080 }
1081
1082 gst_caps_unref(rawCaps);
1083
1084 return codecParams;
1085 }
1086
addStream(int streamIndex,const AkCaps & streamCaps)1087 QVariantMap MediaWriterGStreamer::addStream(int streamIndex,
1088 const AkCaps &streamCaps)
1089 {
1090 return this->addStream(streamIndex, streamCaps, {});
1091 }
1092
addStream(int streamIndex,const AkCaps & streamCaps,const QVariantMap & codecParams)1093 QVariantMap MediaWriterGStreamer::addStream(int streamIndex,
1094 const AkCaps &streamCaps,
1095 const QVariantMap &codecParams)
1096 {
1097 QString outputFormat =
1098 this->supportedFormats().contains(this->d->m_outputFormat)?
1099 this->d->m_outputFormat: this->d->guessFormat(this->m_location);
1100
1101 if (outputFormat.isEmpty())
1102 return QVariantMap();
1103
1104 QVariantMap outputParams;
1105
1106 if (codecParams.contains("label"))
1107 outputParams["label"] = codecParams["label"];
1108
1109 outputParams["index"] = streamIndex;
1110 auto codec = codecParams.value("codec").toString();
1111 auto supportedCodecs = this->supportedCodecs(outputFormat, streamCaps.mimeType());
1112
1113 if (codec.isEmpty() || !supportedCodecs.contains(codec))
1114 codec = this->defaultCodec(outputFormat, streamCaps.mimeType());
1115
1116 outputParams["codec"] = codec;
1117 auto codecDefaults = this->defaultCodecParams(codec);
1118
1119 if (streamCaps.mimeType() == "audio/x-raw") {
1120 int bitRate = codecParams.value("bitrate").toInt();
1121 outputParams["bitrate"] = bitRate > 0?
1122 bitRate:
1123 codecDefaults["defaultBitRate"].toInt();
1124 outputParams["caps"] = QVariant::fromValue(streamCaps);
1125 AkAudioCaps audioCaps(streamCaps);
1126 outputParams["timeBase"] = QVariant::fromValue(AkFrac(1, audioCaps.rate()));
1127 } else if (streamCaps.mimeType() == "video/x-raw") {
1128 int bitRate = codecParams.value("bitrate").toInt();
1129 outputParams["bitrate"] = bitRate > 0?
1130 bitRate:
1131 codecDefaults["defaultBitRate"].toInt();
1132 int gop = codecParams.value("gop",
1133 codecDefaults["defaultGOP"]).toInt();
1134 outputParams["gop"] = gop > 0?
1135 gop:
1136 codecDefaults["defaultGOP"].toInt();
1137
1138 outputParams["caps"] = QVariant::fromValue(streamCaps);
1139 AkVideoCaps videoCaps(streamCaps);
1140 outputParams["timeBase"] = QVariant::fromValue(videoCaps.fps().invert());
1141 } else if (streamCaps.mimeType() == "text/x-raw") {
1142 outputParams["caps"] = QVariant::fromValue(streamCaps);
1143 }
1144
1145 this->d->m_streamConfigs << outputParams;
1146 this->streamsChanged(this->streams());
1147
1148 return outputParams;
1149 }
1150
updateStream(int index)1151 QVariantMap MediaWriterGStreamer::updateStream(int index)
1152 {
1153 return this->updateStream(index, {});
1154 }
1155
updateStream(int index,const QVariantMap & codecParams)1156 QVariantMap MediaWriterGStreamer::updateStream(int index,
1157 const QVariantMap &codecParams)
1158 {
1159 QString outputFormat;
1160
1161 if (this->supportedFormats().contains(this->d->m_outputFormat))
1162 outputFormat = this->d->m_outputFormat;
1163 else
1164 outputFormat = this->d->guessFormat(this->m_location);
1165
1166 if (outputFormat.isEmpty())
1167 return {};
1168
1169 if (codecParams.contains("label"))
1170 this->d->m_streamConfigs[index]["label"] = codecParams["label"];
1171
1172 auto streamCaps = this->d->m_streamConfigs[index]["caps"].value<AkCaps>();
1173 QString codec;
1174 bool streamChanged = false;
1175
1176 if (codecParams.contains("codec")) {
1177 if (this->supportedCodecs(outputFormat, streamCaps.mimeType())
1178 .contains(codecParams["codec"].toString())) {
1179 codec = codecParams["codec"].toString();
1180 } else
1181 codec = this->defaultCodec(outputFormat, streamCaps.mimeType());
1182
1183 this->d->m_streamConfigs[index]["codec"] = codec;
1184 streamChanged = true;
1185
1186 // Update sample format.
1187 auto codecDefaults = this->defaultCodecParams(codec);
1188
1189 if (streamCaps.mimeType() == "audio/x-raw") {
1190 AkAudioCaps audioCaps(streamCaps);
1191 this->d->m_streamConfigs[index]["timeBase"] =
1192 QVariant::fromValue(AkFrac(1, audioCaps.rate()));
1193 } else if (streamCaps.mimeType() == "video/x-raw") {
1194 AkVideoCaps videoCaps(streamCaps);
1195 this->d->m_streamConfigs[index]["timeBase"] =
1196 QVariant::fromValue(videoCaps.fps().invert());
1197 }
1198
1199 this->d->m_streamConfigs[index]["caps"] =
1200 QVariant::fromValue(streamCaps);
1201 } else
1202 codec = this->d->m_streamConfigs[index]["codec"].toString();
1203
1204 auto codecDefaults = this->defaultCodecParams(codec);
1205
1206 if ((streamCaps.mimeType() == "audio/x-raw"
1207 || streamCaps.mimeType() == "video/x-raw")
1208 && codecParams.contains("bitrate")) {
1209 int bitRate = codecParams["bitrate"].toInt();
1210 this->d->m_streamConfigs[index]["bitrate"] =
1211 bitRate > 0? bitRate: codecDefaults["defaultBitRate"].toInt();
1212 streamChanged = true;
1213 }
1214
1215 if (streamCaps.mimeType() == "video/x-raw"
1216 && codecParams.contains("gop")) {
1217 int gop = codecParams["gop"].toInt();
1218 this->d->m_streamConfigs[index]["gop"] =
1219 gop > 0? gop: codecDefaults["defaultGOP"].toInt();
1220 streamChanged = true;
1221 }
1222
1223 if (streamChanged)
1224 emit this->streamsChanged(this->streams());
1225
1226 return this->d->m_streamConfigs[index];
1227 }
1228
codecOptions(int index)1229 QVariantList MediaWriterGStreamer::codecOptions(int index)
1230 {
1231 QString outputFormat =
1232 this->supportedFormats().contains(this->d->m_outputFormat)?
1233 this->d->m_outputFormat: this->d->guessFormat(this->m_location);
1234
1235 if (outputFormat.isEmpty())
1236 return {};
1237
1238 auto codec = this->d->m_streamConfigs.value(index).value("codec").toString();
1239
1240 if (codec.isEmpty())
1241 return {};
1242
1243 auto element = gst_element_factory_make(codec.toStdString().c_str(),
1244 nullptr);
1245
1246 if (!element)
1247 return {};
1248
1249 auto optKey = QString("%1/%2/%3").arg(outputFormat).arg(index).arg(codec);
1250 auto options = this->d->parseOptions(element);
1251 gst_object_unref(element);
1252 auto globalCodecOptions = this->d->m_codecOptions.value(optKey);
1253 QVariantList codecOptions;
1254
1255 for (auto &option: options) {
1256 auto optionList = option.toList();
1257 auto key = optionList[0].toString();
1258
1259 if ((codec == "vp8enc" || codec == "vp9enc")
1260 && key == "deadline")
1261 optionList[6] = optionList[7] = 1;
1262 else if ((codec == "x264enc" || codec == "x265enc")
1263 && key == "speed-preset")
1264 optionList[6] = optionList[7] = "ultrafast";
1265
1266 if (globalCodecOptions.contains(key))
1267 optionList[7] = globalCodecOptions[key];
1268
1269 codecOptions << QVariant(optionList);
1270 }
1271
1272 return codecOptions;
1273 }
1274
setOutputFormat(const QString & outputFormat)1275 void MediaWriterGStreamer::setOutputFormat(const QString &outputFormat)
1276 {
1277 if (this->d->m_outputFormat == outputFormat)
1278 return;
1279
1280 this->d->m_outputFormat = outputFormat;
1281 emit this->outputFormatChanged(outputFormat);
1282 }
1283
setFormatOptions(const QVariantMap & formatOptions)1284 void MediaWriterGStreamer::setFormatOptions(const QVariantMap &formatOptions)
1285 {
1286 QString outputFormat = this->d->m_outputFormat.isEmpty()?
1287 this->d->guessFormat(this->m_location):
1288 this->d->m_outputFormat;
1289 bool modified = false;
1290
1291 for (auto it = formatOptions.cbegin(); it != formatOptions.cend(); it++)
1292 if (it.value() != this->d->m_formatOptions.value(outputFormat).value(it.key())) {
1293 this->d->m_formatOptions[outputFormat][it.key()] = it.value();
1294 modified = true;
1295 }
1296
1297 if (modified)
1298 emit this->formatOptionsChanged(this->d->m_formatOptions.value(outputFormat));
1299 }
1300
setCodecOptions(int index,const QVariantMap & codecOptions)1301 void MediaWriterGStreamer::setCodecOptions(int index,
1302 const QVariantMap &codecOptions)
1303 {
1304 auto outputFormat = this->d->m_outputFormat.isEmpty()?
1305 this->d->guessFormat(this->m_location):
1306 this->d->m_outputFormat;
1307
1308 if (outputFormat.isEmpty())
1309 return;
1310
1311 auto codec = this->d->m_streamConfigs.value(index).value("codec").toString();
1312
1313 if (codec.isEmpty())
1314 return;
1315
1316 auto optKey = QString("%1/%2/%3").arg(outputFormat).arg(index).arg(codec);
1317 bool modified = false;
1318
1319 for (auto it = codecOptions.cbegin(); it != codecOptions.cend(); it++)
1320 if (it.value() != this->d->m_codecOptions.value(optKey).value(it.key())) {
1321 this->d->m_codecOptions[optKey][it.key()] = it.value();
1322 modified = true;
1323 }
1324
1325 if (modified)
1326 emit this->codecOptionsChanged(optKey, this->d->m_codecOptions.value(optKey));
1327 }
1328
resetOutputFormat()1329 void MediaWriterGStreamer::resetOutputFormat()
1330 {
1331 this->setOutputFormat("");
1332 }
1333
resetFormatOptions()1334 void MediaWriterGStreamer::resetFormatOptions()
1335 {
1336 QString outputFormat = this->d->m_outputFormat.isEmpty()?
1337 this->d->guessFormat(this->m_location):
1338 this->d->m_outputFormat;
1339
1340 if (this->d->m_formatOptions.value(outputFormat).isEmpty())
1341 return;
1342
1343 this->d->m_formatOptions.remove(outputFormat);
1344 emit this->formatOptionsChanged(QVariantMap());
1345 }
1346
resetCodecOptions(int index)1347 void MediaWriterGStreamer::resetCodecOptions(int index)
1348 {
1349 auto outputFormat = this->d->m_outputFormat.isEmpty()?
1350 this->d->guessFormat(this->m_location):
1351 this->d->m_outputFormat;
1352
1353 if (outputFormat.isEmpty())
1354 return;
1355
1356 auto codec = this->d->m_streamConfigs.value(index).value("codec").toString();
1357
1358 if (codec.isEmpty())
1359 return;
1360
1361 auto optKey = QString("%1/%2/%3").arg(outputFormat).arg(index).arg(codec);
1362
1363 if (this->d->m_codecOptions.value(optKey).isEmpty())
1364 return;
1365
1366 this->d->m_codecOptions.remove(optKey);
1367 emit this->codecOptionsChanged(optKey, QVariantMap());
1368 }
1369
enqueuePacket(const AkPacket & packet)1370 void MediaWriterGStreamer::enqueuePacket(const AkPacket &packet)
1371 {
1372 if (!this->d->m_isRecording)
1373 return;
1374
1375 if (packet.caps().mimeType() == "audio/x-raw") {
1376 this->writeAudioPacket(AkAudioPacket(packet));
1377 } else if (packet.caps().mimeType() == "video/x-raw") {
1378 this->writeVideoPacket(AkVideoPacket(packet));
1379 } else if (packet.caps().mimeType() == "text/x-raw") {
1380 this->writeSubtitlePacket(packet);
1381 }
1382 }
1383
clearStreams()1384 void MediaWriterGStreamer::clearStreams()
1385 {
1386 this->d->m_streamConfigs.clear();
1387 this->streamsChanged(this->streams());
1388 }
1389
init()1390 bool MediaWriterGStreamer::init()
1391 {
1392 QString outputFormat = this->d->m_outputFormat.isEmpty()?
1393 this->d->guessFormat(this->m_location):
1394 this->d->m_outputFormat;
1395
1396 this->d->m_pipeline = gst_pipeline_new(nullptr);
1397 auto muxer = gst_element_factory_make(outputFormat.toStdString().c_str(),
1398 nullptr);
1399
1400 if (!muxer)
1401 return false;
1402
1403 // Set format options.
1404 this->d->setElementOptions(muxer, this->d->m_formatOptions.value(outputFormat));
1405
1406 auto filesink = gst_element_factory_make("filesink", nullptr);
1407 g_object_set(G_OBJECT(filesink),
1408 "location",
1409 this->m_location.toStdString().c_str(),
1410 nullptr);
1411 gst_bin_add_many(GST_BIN(this->d->m_pipeline), muxer, filesink, nullptr);
1412 gst_element_link_many(muxer, filesink, nullptr);
1413
1414 auto streamConfigs = this->d->m_streamConfigs.toVector();
1415
1416 for (int i = 0; i < streamConfigs.count(); i++) {
1417 auto configs = streamConfigs[i];
1418 auto streamCaps = configs["caps"].value<AkCaps>();
1419 auto codec = configs["codec"].toString();
1420
1421 if (codec.startsWith("identity/"))
1422 codec = "identity";
1423
1424 auto optKey = QString("%1/%2/%3").arg(outputFormat).arg(i).arg(codec);
1425 auto codecDefaults = this->defaultCodecParams(codec);
1426
1427 if (streamCaps.mimeType() == "audio/x-raw") {
1428 auto sourceName = QString("audio_%1").arg(i);
1429 auto source = gst_element_factory_make("appsrc", sourceName.toStdString().c_str());
1430 gst_app_src_set_stream_type(GST_APP_SRC(source), GST_APP_STREAM_TYPE_STREAM);
1431 g_object_set(G_OBJECT(source), "format", GST_FORMAT_TIME, nullptr);
1432 g_object_set(G_OBJECT(source), "block", true, nullptr);
1433
1434 AkAudioCaps audioCaps(streamCaps);
1435
1436 auto sampleFormat = AkAudioCaps::sampleFormatToString(audioCaps.format());
1437 auto supportedSampleFormats = codecDefaults["supportedSampleFormats"].toStringList();
1438
1439 if (!supportedSampleFormats.isEmpty() && !supportedSampleFormats.contains(sampleFormat)) {
1440 auto defaultSampleFormat = codecDefaults["defaultSampleFormat"].toString();
1441 auto format = AkAudioCaps::sampleFormatFromString(defaultSampleFormat);
1442 audioCaps.setFormat(format);
1443 }
1444
1445 auto supportedSampleRates = codecDefaults["supportedSampleRates"].toList();
1446 audioCaps = this->d->nearestSampleRate(audioCaps,
1447 supportedSampleRates);
1448 auto channelLayout = AkAudioCaps::channelLayoutToString(audioCaps.layout());
1449 auto supportedChannelLayouts = codecDefaults["supportedChannelLayouts"].toStringList();
1450
1451 if (!supportedChannelLayouts.isEmpty() && !supportedChannelLayouts.contains(channelLayout)) {
1452 auto defaultChannelLayout = codecDefaults["defaultChannelLayout"].toString();
1453 auto layout = AkAudioCaps::channelLayoutFromString(defaultChannelLayout);
1454 audioCaps.setLayout(layout);
1455 }
1456
1457 if (outputFormat == "flvmux") {
1458 audioCaps = this->d->nearestFLVAudioCaps(audioCaps, codec);
1459 QStringList codecs {"speexenc", "avenc_nellymoser"};
1460
1461 if (codecs.contains(codec))
1462 audioCaps.setLayout(AkAudioCaps::Layout_mono);
1463 } else if (outputFormat == "avmux_dv") {
1464 audioCaps.rate() = 48000;
1465 } else if (outputFormat == "avmux_gxf"
1466 || outputFormat == "avmux_mxf"
1467 || outputFormat == "avmux_mxf_d10") {
1468 audioCaps.rate() = qBound(4000, audioCaps.rate(), 96000);
1469 } else if (codec == "avenc_tta") {
1470 audioCaps.rate() = qBound(8000, audioCaps.rate(), 96000);
1471 }
1472
1473 auto gstFormat =
1474 MediaWriterGStreamerPrivate::gstToSampleFormat()
1475 .value(audioCaps.format(), "S16");
1476 auto gstAudioCaps =
1477 gst_caps_new_simple("audio/x-raw",
1478 "format", G_TYPE_STRING, gstFormat.toStdString().c_str(),
1479 "layout", G_TYPE_STRING, "interleaved",
1480 "rate", G_TYPE_INT, audioCaps.rate(),
1481 "channels", G_TYPE_INT, audioCaps.channels(),
1482 nullptr);
1483
1484 gstAudioCaps = gst_caps_fixate(gstAudioCaps);
1485 gst_app_src_set_caps(GST_APP_SRC(source), gstAudioCaps);
1486
1487 auto audioConvert = gst_element_factory_make("audioconvert", nullptr);
1488 auto audioResample = gst_element_factory_make("audioresample", nullptr);
1489 auto audioRate = gst_element_factory_make("audiorate", nullptr);
1490 auto audioCodec = gst_element_factory_make(codec.toStdString().c_str(), nullptr);
1491
1492 if (codec.startsWith("avenc_"))
1493 g_object_set(G_OBJECT(audioCodec), "compliance", -2, nullptr);
1494
1495 // Set codec options.
1496 if (g_object_class_find_property(G_OBJECT_GET_CLASS(audioCodec),
1497 "bitrate")) {
1498 int bitrate = configs["bitrate"].toInt();
1499
1500 if (codec == "lamemp3enc")
1501 bitrate /= 1000;
1502
1503 if (bitrate > 0)
1504 g_object_set(G_OBJECT(audioCodec), "bitrate", bitrate, NULL);
1505 }
1506
1507 auto codecOptions = this->d->m_codecOptions.value(optKey);
1508 this->d->setElementOptions(audioCodec, codecOptions);
1509
1510 auto queue = gst_element_factory_make("queue", nullptr);
1511
1512 gst_bin_add_many(GST_BIN(this->d->m_pipeline),
1513 source,
1514 audioResample,
1515 audioRate,
1516 audioConvert,
1517 audioCodec,
1518 queue,
1519 nullptr);
1520
1521 gst_element_link_many(source,
1522 audioResample,
1523 audioRate,
1524 audioConvert,
1525 nullptr);
1526 gst_element_link_filtered(audioConvert, audioCodec, gstAudioCaps);
1527 gst_caps_unref(gstAudioCaps);
1528 gst_element_link_many(audioCodec, queue, muxer, nullptr);
1529 } else if (streamCaps.mimeType() == "video/x-raw") {
1530 auto sourceName = QString("video_%1").arg(i);
1531 auto source = gst_element_factory_make("appsrc", sourceName.toStdString().c_str());
1532 gst_app_src_set_stream_type(GST_APP_SRC(source), GST_APP_STREAM_TYPE_STREAM);
1533 g_object_set(G_OBJECT(source), "format", GST_FORMAT_TIME, nullptr);
1534 g_object_set(G_OBJECT(source), "block", true, nullptr);
1535
1536 AkVideoCaps videoCaps(streamCaps);
1537
1538 auto pixelFormat = AkVideoCaps::pixelFormatToString(videoCaps.format());
1539 auto supportedPixelFormats = codecDefaults["supportedPixelFormats"].toStringList();
1540
1541 if (!supportedPixelFormats.isEmpty() && !supportedPixelFormats.contains(pixelFormat)) {
1542 auto defaultPixelFormat = codecDefaults["defaultPixelFormat"].toString();
1543 videoCaps.setFormat(AkVideoCaps::pixelFormatFromString(defaultPixelFormat));
1544 }
1545
1546 auto supportedFrameSizes = codecDefaults["supportedFrameSizes"].value<SizeList>();
1547 videoCaps = this->d->nearestFrameSize(videoCaps,
1548 supportedFrameSizes);
1549 auto supportedFrameRates = codecDefaults["supportedFrameRates"].toList();
1550 videoCaps = this->d->nearestFrameRate(videoCaps,
1551 supportedFrameRates);
1552
1553 if (codec == "avenc_dvvideo")
1554 videoCaps = this->d->nearestDVCaps(videoCaps);
1555
1556 auto gstFormat =
1557 MediaWriterGStreamerPrivate::gstToPixelFormat()
1558 .value(videoCaps.format(), "I420");
1559 videoCaps.setWidth(MediaWriterGStreamerPrivate::align(videoCaps.width(), 4));
1560
1561 auto gstVideoCaps =
1562 gst_caps_new_simple("video/x-raw",
1563 "format", G_TYPE_STRING, gstFormat.toStdString().c_str(),
1564 "width", G_TYPE_INT, videoCaps.width(),
1565 "height", G_TYPE_INT, videoCaps.height(),
1566 "framerate", GST_TYPE_FRACTION,
1567 int(videoCaps.fps().num()),
1568 int(videoCaps.fps().den()),
1569 nullptr);
1570
1571 gstVideoCaps = gst_caps_fixate(gstVideoCaps);
1572 gst_app_src_set_caps(GST_APP_SRC(source), gstVideoCaps);
1573
1574 auto videoScale = gst_element_factory_make("videoscale", nullptr);
1575 auto videoRate = gst_element_factory_make("videorate", nullptr);
1576 auto videoConvert = gst_element_factory_make("videoconvert", nullptr);
1577 auto videoCodec = gst_element_factory_make(codec.toStdString().c_str(), nullptr);
1578
1579 if (codec.startsWith("avenc_"))
1580 g_object_set(G_OBJECT(videoCodec), "compliance", -2, nullptr);
1581
1582 // Set codec options.
1583
1584 // Set bitrate
1585 const char *propBitrate =
1586 QRegExp("vp\\d+enc").exactMatch(codec)?
1587 "target-bitrate": "bitrate";
1588
1589 if (g_object_class_find_property(G_OBJECT_GET_CLASS(videoCodec),
1590 propBitrate)) {
1591 int bitrate = configs["bitrate"].toInt();
1592
1593 if (codec == "x264enc"
1594 || codec == "x265enc"
1595 || codec == "mpeg2enc"
1596 || codec == "theoraenc")
1597 bitrate /= 1000;
1598
1599 if (bitrate > 0)
1600 g_object_set(G_OBJECT(videoCodec),
1601 propBitrate,
1602 bitrate,
1603 NULL);
1604 }
1605
1606 // Set GOP
1607 int gop = configs["gop"].toInt();
1608
1609 if (gop > 0) {
1610 QStringList gops {"keyframe-max-dist", "gop-size"};
1611
1612 for (auto &g: gops)
1613 if (g_object_class_find_property(G_OBJECT_GET_CLASS(videoCodec),
1614 g.toStdString().c_str())) {
1615 g_object_set(G_OBJECT(videoCodec),
1616 g.toStdString().c_str(),
1617 gop,
1618 nullptr);
1619
1620 break;
1621 }
1622 }
1623
1624 auto codecOptions = this->d->m_codecOptions.value(optKey);
1625
1626 if ((codec == "vp8enc" || codec == "vp9enc")
1627 && !codecOptions.contains("deadline"))
1628 codecOptions["deadline"] = 1;
1629 else if ((codec == "x264enc" || codec == "x265enc")
1630 && !codecOptions.contains("speed-preset"))
1631 codecOptions["speed-preset"] = "ultrafast";
1632
1633 this->d->setElementOptions(videoCodec, codecOptions);
1634
1635 auto queue = gst_element_factory_make("queue", nullptr);
1636
1637 gst_bin_add_many(GST_BIN(this->d->m_pipeline),
1638 source,
1639 videoScale,
1640 videoRate,
1641 videoConvert,
1642 videoCodec,
1643 queue,
1644 nullptr);
1645
1646 gst_element_link_many(source,
1647 videoScale,
1648 videoRate,
1649 videoConvert,
1650 nullptr);
1651 gst_element_link_filtered(videoConvert, videoCodec, gstVideoCaps);
1652 gst_caps_unref(gstVideoCaps);
1653 gst_element_link_many(videoCodec, queue, muxer, nullptr);
1654 }
1655
1656 this->d->m_streamParams << OutputParams(configs["index"].toInt());
1657 }
1658
1659 // Configure the message bus.
1660 GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(this->d->m_pipeline));
1661 this->d->m_busWatchId = gst_bus_add_watch(bus, this->d->busCallback, this);
1662 gst_object_unref(bus);
1663
1664 // Run the main GStreamer loop.
1665 this->d->m_mainLoop = g_main_loop_new(nullptr, FALSE);
1666 QtConcurrent::run(&this->d->m_threadPool, g_main_loop_run, this->d->m_mainLoop);
1667 gst_element_set_state(this->d->m_pipeline, GST_STATE_PLAYING);
1668 this->d->m_isRecording = true;
1669
1670 return true;
1671 }
1672
uninit()1673 void MediaWriterGStreamer::uninit()
1674 {
1675 this->d->m_isRecording = false;
1676
1677 if (this->d->m_pipeline) {
1678 auto sources = gst_bin_iterate_sources(GST_BIN(this->d->m_pipeline));
1679 GValue sourceItm = G_VALUE_INIT;
1680 gboolean done = FALSE;
1681
1682 while (!done) {
1683 switch (gst_iterator_next(sources, &sourceItm)) {
1684 case GST_ITERATOR_OK: {
1685 auto source = GST_ELEMENT(g_value_get_object(&sourceItm));
1686
1687 if (gst_app_src_end_of_stream(GST_APP_SRC(source)) != GST_FLOW_OK)
1688 qWarning() << "Error sending EOS to "
1689 << gst_element_get_name(source);
1690
1691 g_value_reset(&sourceItm);
1692 }
1693 break;
1694 case GST_ITERATOR_RESYNC:
1695 // Rollback changes to items.
1696 gst_iterator_resync(sources);
1697 break;
1698 case GST_ITERATOR_ERROR:
1699 // Wrong parameters were given.
1700 done = TRUE;
1701 break;
1702 case GST_ITERATOR_DONE:
1703 done = TRUE;
1704 break;
1705 default:
1706 break;
1707 }
1708 }
1709
1710 g_value_unset(&sourceItm);
1711 gst_iterator_free(sources);
1712
1713 gst_element_send_event(this->d->m_pipeline, gst_event_new_eos());
1714
1715 gst_element_set_state(this->d->m_pipeline, GST_STATE_NULL);
1716 this->d->waitState(GST_STATE_NULL);
1717 gst_object_unref(GST_OBJECT(this->d->m_pipeline));
1718 g_source_remove(this->d->m_busWatchId);
1719 this->d->m_pipeline = nullptr;
1720 this->d->m_busWatchId = 0;
1721 }
1722
1723 if (this->d->m_mainLoop) {
1724 g_main_loop_quit(this->d->m_mainLoop);
1725 g_main_loop_unref(this->d->m_mainLoop);
1726 this->d->m_mainLoop = nullptr;
1727 }
1728
1729 this->d->m_streamParams.clear();
1730 }
1731
writeAudioPacket(const AkAudioPacket & packet)1732 void MediaWriterGStreamer::writeAudioPacket(const AkAudioPacket &packet)
1733 {
1734 if (!this->d->m_pipeline)
1735 return;
1736
1737 int streamIndex = -1;
1738
1739 for (int i = 0; i < this->d->m_streamParams.size(); i++)
1740 if (this->d->m_streamParams[i].inputIndex() == packet.index()) {
1741 streamIndex = i;
1742
1743 break;
1744 }
1745
1746 if (streamIndex < 0)
1747 return;
1748
1749 auto souceName = QString("audio_%1").arg(streamIndex);
1750 auto source = gst_bin_get_by_name(GST_BIN(this->d->m_pipeline),
1751 souceName.toStdString().c_str());
1752
1753 if (!source)
1754 return;
1755
1756 auto sourceCaps = gst_app_src_get_caps(GST_APP_SRC(source));
1757 auto iFormat =
1758 MediaWriterGStreamerPrivate::gstToSampleFormat()
1759 .value(packet.caps().format(), "S16LE");
1760
1761 auto inputCaps =
1762 gst_caps_new_simple("audio/x-raw",
1763 "format", G_TYPE_STRING, iFormat.toStdString().c_str(),
1764 "layout", G_TYPE_STRING, "interleaved",
1765 "rate", G_TYPE_INT, packet.caps().rate(),
1766 "channels", G_TYPE_INT, packet.caps().channels(),
1767 nullptr);
1768 inputCaps = gst_caps_fixate(inputCaps);
1769
1770 if (!gst_caps_is_equal(sourceCaps, inputCaps))
1771 gst_app_src_set_caps(GST_APP_SRC(source), inputCaps);
1772
1773 gst_caps_unref(inputCaps);
1774 gst_caps_unref(sourceCaps);
1775
1776 auto size = size_t(packet.buffer().size());
1777
1778 auto buffer = gst_buffer_new_allocate(nullptr, size, nullptr);
1779 GstMapInfo info;
1780 gst_buffer_map(buffer, &info, GST_MAP_WRITE);
1781 memcpy(info.data, packet.buffer().constData(), size);
1782 gst_buffer_unmap(buffer, &info);
1783
1784 auto pts = qint64(packet.pts() * packet.timeBase().value() * GST_SECOND);
1785
1786 #if 0
1787 GST_BUFFER_PTS(buffer) = GST_BUFFER_DTS(buffer) = this->m_streamParams[streamIndex].nextPts(pts, packet.id());
1788 GST_BUFFER_DURATION(buffer) = packet.caps().samples() * packet.timeBase().value() * GST_SECOND;
1789 GST_BUFFER_OFFSET(buffer) = this->m_streamParams[streamIndex].nFrame();
1790 #else
1791 GST_BUFFER_PTS(buffer) = this->d->m_streamParams[streamIndex].nextPts(pts, packet.id());
1792 GST_BUFFER_DTS(buffer) = GST_CLOCK_TIME_NONE;
1793 GST_BUFFER_DURATION(buffer) = GST_CLOCK_TIME_NONE;
1794 GST_BUFFER_OFFSET(buffer) = GST_BUFFER_OFFSET_NONE;
1795 #endif
1796
1797 this->d->m_streamParams[streamIndex].nFrame() += quint64(packet.caps().samples());
1798
1799 if (gst_app_src_push_buffer(GST_APP_SRC(source), buffer) != GST_FLOW_OK)
1800 qWarning() << "Error pushing buffer to GStreamer pipeline";
1801 }
1802
writeVideoPacket(const AkVideoPacket & packet)1803 void MediaWriterGStreamer::writeVideoPacket(const AkVideoPacket &packet)
1804 {
1805 if (!this->d->m_pipeline)
1806 return;
1807
1808 int streamIndex = -1;
1809
1810 for (int i = 0; i < this->d->m_streamParams.size(); i++)
1811 if (this->d->m_streamParams[i].inputIndex() == packet.index()) {
1812 streamIndex = i;
1813
1814 break;
1815 }
1816
1817 if (streamIndex < 0)
1818 return;
1819
1820 auto videoPacket = packet.convert(AkVideoCaps::Format_rgb24, 32);
1821
1822 auto souceName = QString("video_%1").arg(streamIndex);
1823 auto source = gst_bin_get_by_name(GST_BIN(this->d->m_pipeline),
1824 souceName.toStdString().c_str());
1825
1826 if (!source)
1827 return;
1828
1829 auto sourceCaps = gst_app_src_get_caps(GST_APP_SRC(source));
1830 auto iFormat = MediaWriterGStreamerPrivate::gstToPixelFormat()
1831 .value(videoPacket.caps().format(), "BGR");
1832 auto inputCaps =
1833 gst_caps_new_simple("video/x-raw",
1834 "format", G_TYPE_STRING, iFormat.toStdString().c_str(),
1835 "width", G_TYPE_INT, videoPacket.caps().width(),
1836 "height", G_TYPE_INT, videoPacket.caps().height(),
1837 "framerate", GST_TYPE_FRACTION,
1838 int(videoPacket.caps().fps().num()),
1839 int(videoPacket.caps().fps().den()),
1840 nullptr);
1841 inputCaps = gst_caps_fixate(inputCaps);
1842
1843 if (!gst_caps_is_equal(sourceCaps, inputCaps))
1844 gst_app_src_set_caps(GST_APP_SRC(source), inputCaps);
1845
1846 gst_caps_unref(inputCaps);
1847 gst_caps_unref(sourceCaps);
1848
1849 auto size = size_t(videoPacket.buffer().size());
1850 auto buffer = gst_buffer_new_allocate(nullptr, size, nullptr);
1851 GstMapInfo info;
1852 gst_buffer_map(buffer, &info, GST_MAP_WRITE);
1853 memcpy(info.data, videoPacket.buffer().constData(), size);
1854 gst_buffer_unmap(buffer, &info);
1855
1856 auto pts = qint64(videoPacket.pts()
1857 * videoPacket.timeBase().value()
1858 * GST_SECOND);
1859
1860 #if 0
1861 GST_BUFFER_PTS(buffer) = GST_BUFFER_DTS(buffer) = this->m_streamParams[streamIndex].nextPts(pts, packet.id());
1862 GST_BUFFER_DURATION(buffer) = GST_SECOND / packet.caps().fps().value();
1863 GST_BUFFER_OFFSET(buffer) = this->m_streamParams[streamIndex].nFrame();
1864 #else
1865 GST_BUFFER_PTS(buffer) = this->d->m_streamParams[streamIndex].nextPts(pts, videoPacket.id());
1866 GST_BUFFER_DTS(buffer) = GST_CLOCK_TIME_NONE;
1867 GST_BUFFER_DURATION(buffer) = GST_CLOCK_TIME_NONE;
1868 GST_BUFFER_OFFSET(buffer) = GST_BUFFER_OFFSET_NONE;
1869 #endif
1870
1871 this->d->m_streamParams[streamIndex].nFrame()++;
1872
1873 if (gst_app_src_push_buffer(GST_APP_SRC(source), buffer) != GST_FLOW_OK)
1874 qWarning() << "Error pushing buffer to GStreamer pipeline";
1875 }
1876
writeSubtitlePacket(const AkPacket & packet)1877 void MediaWriterGStreamer::writeSubtitlePacket(const AkPacket &packet)
1878 {
1879 Q_UNUSED(packet)
1880 }
1881
MediaWriterGStreamerPrivate(MediaWriterGStreamer * self)1882 MediaWriterGStreamerPrivate::MediaWriterGStreamerPrivate(MediaWriterGStreamer *self):
1883 self(self)
1884 {
1885 }
1886
guessFormat(const QString & fileName)1887 QString MediaWriterGStreamerPrivate::guessFormat(const QString &fileName)
1888 {
1889 auto ext = QFileInfo(fileName).suffix();
1890
1891 for (auto &format: self->supportedFormats())
1892 if (self->fileExtensions(format).contains(ext))
1893 return format;
1894
1895 return {};
1896 }
1897
readCaps(const QString & element)1898 QStringList MediaWriterGStreamerPrivate::readCaps(const QString &element)
1899 {
1900 auto factory = gst_element_factory_find(element.toStdString().c_str());
1901
1902 if (!factory)
1903 return {};
1904
1905 QStringList elementCaps;
1906 auto feature = gst_plugin_feature_load(GST_PLUGIN_FEATURE(factory));
1907
1908 if (!feature) {
1909 gst_object_unref(factory);
1910
1911 return {};
1912 }
1913
1914 auto pads = gst_element_factory_get_static_pad_templates(GST_ELEMENT_FACTORY(feature));
1915
1916 for (auto padItem = pads; padItem; padItem = g_list_next(padItem)) {
1917 auto padtemplate =
1918 reinterpret_cast<GstStaticPadTemplate *>(padItem->data);
1919
1920 if (padtemplate->direction == GST_PAD_SRC
1921 && padtemplate->presence == GST_PAD_ALWAYS) {
1922 auto caps = gst_caps_from_string(padtemplate->static_caps.string);
1923
1924 for (guint i = 0; i < gst_caps_get_size(caps); i++) {
1925 auto capsStructure = gst_caps_get_structure(caps, i);
1926 auto structureCaps = gst_structure_to_string(capsStructure);
1927
1928 elementCaps << structureCaps;
1929
1930 g_free(structureCaps);
1931 }
1932
1933 gst_caps_unref(caps);
1934 }
1935 }
1936
1937 gst_object_unref(feature);
1938 gst_object_unref(factory);
1939
1940 return elementCaps;
1941 }
1942
parseOptions(const GstElement * element) const1943 QVariantList MediaWriterGStreamerPrivate::parseOptions(const GstElement *element) const
1944 {
1945 QVariantList options;
1946 guint nprops = 0;
1947
1948 auto propSpecs =
1949 g_object_class_list_properties(G_OBJECT_GET_CLASS(element),
1950 &nprops);
1951
1952 for (guint i = 0; i < nprops; i++) {
1953 auto param = propSpecs[i];
1954
1955 if ((param->flags & G_PARAM_READWRITE) != G_PARAM_READWRITE)
1956 continue;
1957
1958 #if 0
1959 if (param->flags & G_PARAM_DEPRECATED)
1960 continue;
1961 #endif
1962
1963 auto name = g_param_spec_get_name(param);
1964
1965 if (!strcmp(name, "name")
1966 || !strcmp(name, "bitrate")
1967 || !strcmp(name, "target-bitrate")
1968 || !strcmp(name, "keyframe-max-dist")
1969 || !strcmp(name, "gop-size"))
1970 continue;
1971
1972 QVariant defaultValue;
1973 QVariant value;
1974 qreal min = 0;
1975 qreal max = 0;
1976 qreal step = 0;
1977 QVariantList menu;
1978 auto paramType = MediaWriterGStreamerPrivate::codecGstOptionTypeToStr().value(param->value_type);
1979
1980 GValue gValue;
1981 memset(&gValue, 0, sizeof(GValue));
1982 g_value_init(&gValue, param->value_type);
1983 g_object_get_property(G_OBJECT (element), param->name, &gValue);
1984
1985 switch (param->value_type) {
1986 case G_TYPE_STRING: {
1987 value = g_value_get_string(&gValue);
1988 auto spec = G_PARAM_SPEC_STRING(param);
1989 defaultValue = spec->default_value;
1990 break;
1991 }
1992 case G_TYPE_BOOLEAN: {
1993 value = g_value_get_boolean(&gValue);
1994 auto spec = G_PARAM_SPEC_BOOLEAN(param);
1995 defaultValue = spec->default_value;
1996 break;
1997 }
1998 case G_TYPE_ULONG: {
1999 value = quint64(g_value_get_ulong(&gValue));
2000 auto spec = G_PARAM_SPEC_ULONG(param);
2001 defaultValue = quint64(spec->default_value);
2002 min = spec->minimum;
2003 max = spec->maximum;
2004 step = 1;
2005 break;
2006 }
2007 case G_TYPE_LONG: {
2008 value = qint64(g_value_get_long(&gValue));
2009 auto spec = G_PARAM_SPEC_LONG(param);
2010 defaultValue = qint64(spec->default_value);
2011 min = spec->minimum;
2012 max = spec->maximum;
2013 step = 1;
2014 break;
2015 }
2016 case G_TYPE_UINT: {
2017 value = g_value_get_uint(&gValue);
2018 auto spec = G_PARAM_SPEC_UINT(param);
2019 defaultValue = spec->default_value;
2020 min = spec->minimum;
2021 max = spec->maximum;
2022 step = 1;
2023 break;
2024 }
2025 case G_TYPE_INT: {
2026 value = g_value_get_int(&gValue);
2027 auto spec = G_PARAM_SPEC_INT(param);
2028 defaultValue = spec->default_value;
2029 min = spec->minimum;
2030 max = spec->maximum;
2031 step = 1;
2032 break;
2033 }
2034 case G_TYPE_UINT64: {
2035 value = quint64(g_value_get_uint64(&gValue));
2036 auto spec = G_PARAM_SPEC_UINT64(param);
2037 defaultValue = quint64(spec->default_value);
2038 min = spec->minimum;
2039 max = spec->maximum;
2040 step = 1;
2041 break;
2042 }
2043 case G_TYPE_INT64: {
2044 value = qint64(g_value_get_int64(&gValue));
2045 auto spec = G_PARAM_SPEC_INT64(param);
2046 defaultValue = qint64(spec->default_value);
2047 min = spec->minimum;
2048 max = spec->maximum;
2049 step = 1;
2050 break;
2051 }
2052 case G_TYPE_FLOAT: {
2053 value = g_value_get_float(&gValue);
2054 auto spec = G_PARAM_SPEC_FLOAT(param);
2055 defaultValue = spec->default_value;
2056 min = qreal(spec->minimum);
2057 max = qreal(spec->maximum);
2058 step = 0.01;
2059 break;
2060 }
2061 case G_TYPE_DOUBLE: {
2062 value = g_value_get_double(&gValue);
2063 auto spec = G_PARAM_SPEC_DOUBLE(param);
2064 defaultValue = spec->default_value;
2065 min = qreal(spec->minimum);
2066 max = qreal(spec->maximum);
2067 step = 0.01;
2068 break;
2069 }
2070 case G_TYPE_CHAR: {
2071 value = g_value_get_schar(&gValue);
2072 auto spec = G_PARAM_SPEC_CHAR(param);
2073 defaultValue = spec->default_value;
2074 min = spec->minimum;
2075 max = spec->maximum;
2076 step = 1;
2077 break;
2078 }
2079 case G_TYPE_UCHAR: {
2080 value = g_value_get_uchar(&gValue);
2081 auto spec = G_PARAM_SPEC_UCHAR(param);
2082 defaultValue = spec->default_value;
2083 min = spec->minimum;
2084 max = spec->maximum;
2085 step = 1;
2086 break;
2087 }
2088 default:
2089 if (G_IS_PARAM_SPEC_ENUM(param)) {
2090 auto curValue = g_value_get_enum(&gValue);
2091 value = curValue;
2092 auto spec = G_PARAM_SPEC_ENUM(param);
2093 auto gValue = G_ENUM_CLASS(g_type_class_ref(param->value_type))->values;
2094
2095 if (gValue) {
2096 for (; gValue->value_name; gValue++) {
2097 if (spec->default_value == gValue->value)
2098 defaultValue = gValue->value_nick;
2099
2100 if (curValue == gValue->value)
2101 value = gValue->value_nick;
2102
2103 menu << QVariant(QVariantList {
2104 gValue->value_nick,
2105 gValue->value_name,
2106 gValue->value
2107 });
2108 }
2109
2110 if (!defaultValue.isNull())
2111 defaultValue = menu.first().toList().first();
2112
2113 if (!value.isNull())
2114 value = defaultValue;
2115 }
2116
2117 paramType = "menu";
2118 } else if (G_IS_PARAM_SPEC_FLAGS(param)) {
2119 // flag1+flag2+flags3+...
2120 auto flags = g_value_get_flags(&gValue);
2121 auto spec = G_PARAM_SPEC_FLAGS(param);
2122 auto gValue = spec->flags_class->values;
2123 QStringList defaultFlagList;
2124 QStringList flagList;
2125
2126 if (gValue)
2127 for (; gValue->value_name; gValue++) {
2128 if ((spec->default_value & gValue->value) == gValue->value)
2129 defaultFlagList << gValue->value_nick;
2130
2131 if ((flags & gValue->value) == gValue->value)
2132 flagList << gValue->value_nick;
2133
2134 menu << QVariant(QVariantList {
2135 gValue->value_nick,
2136 gValue->value_name,
2137 gValue->value
2138 });
2139 }
2140
2141 defaultValue = defaultFlagList;
2142 value = flagList;
2143 paramType = "flags";
2144 } else if (GST_IS_PARAM_SPEC_FRACTION(param)) {
2145 auto num = gst_value_get_fraction_numerator(&gValue);
2146 auto den = gst_value_get_fraction_denominator(&gValue);
2147 value = AkFrac(num, den).toString();
2148 defaultValue = value;
2149 paramType = "frac";
2150 } else if (param->value_type == GST_TYPE_CAPS) {
2151 auto caps = gst_caps_to_string(gst_value_get_caps(&gValue));
2152 value = QString(caps);
2153 g_free(caps);
2154 defaultValue = value;
2155 paramType = "caps";
2156 } else
2157 continue;
2158
2159 break;
2160 }
2161
2162 g_value_unset(&gValue);
2163
2164 options << QVariant(QVariantList {
2165 name,
2166 g_param_spec_get_blurb(param),
2167 paramType,
2168 min,
2169 max,
2170 step,
2171 defaultValue,
2172 value,
2173 menu
2174 });
2175 }
2176
2177 g_free(propSpecs);
2178
2179 return options;
2180 }
2181
waitState(GstState state)2182 void MediaWriterGStreamerPrivate::waitState(GstState state)
2183 {
2184 forever {
2185 GstState curState;
2186 auto ret = gst_element_get_state(this->m_pipeline,
2187 &curState,
2188 nullptr,
2189 GST_CLOCK_TIME_NONE);
2190
2191 if (ret == GST_STATE_CHANGE_FAILURE)
2192 break;
2193
2194 if (ret == GST_STATE_CHANGE_SUCCESS
2195 && curState == state)
2196 break;
2197 }
2198 }
2199
busCallback(GstBus * bus,GstMessage * message,gpointer userData)2200 gboolean MediaWriterGStreamerPrivate::busCallback(GstBus *bus,
2201 GstMessage *message,
2202 gpointer userData)
2203 {
2204 Q_UNUSED(bus)
2205 auto self = static_cast<MediaWriterGStreamer *>(userData);
2206
2207 switch (GST_MESSAGE_TYPE(message)) {
2208 case GST_MESSAGE_ERROR: {
2209 GError *err = nullptr;
2210 gchar *debug = nullptr;
2211 gst_message_parse_error(message, &err, &debug);
2212
2213 qDebug() << "ERROR: from element"
2214 << GST_MESSAGE_SRC_NAME(message)
2215 << ":"
2216 << err->message;
2217
2218 if (debug)
2219 qDebug() << "Additional debug info:\n"
2220 << debug;
2221
2222 auto element = GST_ELEMENT(GST_MESSAGE_SRC(message));
2223
2224 for (auto padItem = GST_ELEMENT_PADS(element);
2225 padItem;
2226 padItem = g_list_next(padItem)) {
2227 auto pad = GST_PAD_CAST(padItem->data);
2228 auto curCaps = gst_pad_get_current_caps(pad);
2229 auto curCapsStr = gst_caps_to_string(curCaps);
2230
2231 qDebug() << " Current caps:" << curCapsStr;
2232
2233 g_free(curCapsStr);
2234 gst_caps_unref(curCaps);
2235
2236 auto allCaps = gst_pad_get_allowed_caps(pad);
2237 auto allCapsStr = gst_caps_to_string(allCaps);
2238
2239 qDebug() << " Allowed caps:" << allCapsStr;
2240
2241 g_free(allCapsStr);
2242 gst_caps_unref(allCaps);
2243 }
2244
2245 g_error_free(err);
2246 g_free(debug);
2247 g_main_loop_quit(self->d->m_mainLoop);
2248
2249 break;
2250 }
2251 case GST_MESSAGE_EOS:
2252 g_main_loop_quit(self->d->m_mainLoop);
2253 break;
2254 case GST_MESSAGE_STATE_CHANGED: {
2255 GstState oldstate;
2256 GstState newstate;
2257 GstState pending;
2258 gst_message_parse_state_changed(message, &oldstate, &newstate, &pending);
2259 qDebug() << "State changed from"
2260 << gst_element_state_get_name(oldstate)
2261 << "to"
2262 << gst_element_state_get_name(newstate);
2263
2264 break;
2265 }
2266 case GST_MESSAGE_STREAM_STATUS: {
2267 GstStreamStatusType type;
2268 GstElement *owner = nullptr;
2269 gst_message_parse_stream_status(message, &type, &owner);
2270 qDebug() << "Stream Status:"
2271 << GST_ELEMENT_NAME(owner)
2272 << "is"
2273 << type;
2274
2275 break;
2276 }
2277 case GST_MESSAGE_LATENCY: {
2278 qDebug() << "Recalculating latency";
2279 gst_bin_recalculate_latency(GST_BIN(self->d->m_pipeline));
2280 break;
2281 }
2282 case GST_MESSAGE_STREAM_START: {
2283 qDebug() << "Stream started";
2284 break;
2285 }
2286 case GST_MESSAGE_ASYNC_DONE: {
2287 GstClockTime runningTime;
2288 gst_message_parse_async_done(message, &runningTime);
2289 qDebug() << "ASYNC done";
2290 break;
2291 }
2292 case GST_MESSAGE_NEW_CLOCK: {
2293 GstClock *clock = nullptr;
2294 gst_message_parse_new_clock(message, &clock);
2295 qDebug() << "New clock:" << (clock? GST_OBJECT_NAME(clock): "NULL");
2296 break;
2297 }
2298 case GST_MESSAGE_DURATION_CHANGED: {
2299 GstFormat format;
2300 gint64 duration;
2301 gst_message_parse_duration(message, &format, &duration);
2302 qDebug() << "Duration changed:"
2303 << gst_format_get_name(format)
2304 << ","
2305 << qreal(duration);
2306 break;
2307 }
2308 case GST_MESSAGE_TAG: {
2309 GstTagList *tagList = nullptr;
2310 gst_message_parse_tag(message, &tagList);
2311 gchar *tags = gst_tag_list_to_string(tagList);
2312 // qDebug() << "Tags:" << tags;
2313 g_free(tags);
2314 gst_tag_list_unref(tagList);
2315 break;
2316 }
2317 case GST_MESSAGE_ELEMENT: {
2318 const GstStructure *messageStructure = gst_message_get_structure(message);
2319 gchar *structure = gst_structure_to_string(messageStructure);
2320 // qDebug() << structure;
2321 g_free(structure);
2322 break;
2323 }
2324 case GST_MESSAGE_QOS: {
2325 qDebug() << QString("Received QOS from element %1:")
2326 .arg(GST_MESSAGE_SRC_NAME(message)).toStdString().c_str();
2327
2328 GstFormat format;
2329 guint64 processed;
2330 guint64 dropped;
2331 gst_message_parse_qos_stats(message, &format, &processed, &dropped);
2332 const gchar *formatStr = gst_format_get_name(format);
2333 qDebug() << " Processed" << processed << formatStr;
2334 qDebug() << " Dropped" << dropped << formatStr;
2335
2336 gint64 jitter;
2337 gdouble proportion;
2338 gint quality;
2339 gst_message_parse_qos_values(message, &jitter, &proportion, &quality);
2340 qDebug() << " Jitter =" << jitter;
2341 qDebug() << " Proportion =" << proportion;
2342 qDebug() << " Quality =" << quality;
2343
2344 gboolean live;
2345 guint64 runningTime;
2346 guint64 streamTime;
2347 guint64 timestamp;
2348 guint64 duration;
2349 gst_message_parse_qos(message,
2350 &live,
2351 &runningTime,
2352 &streamTime,
2353 ×tamp,
2354 &duration);
2355 qDebug() << " Is live stream =" << live;
2356 qDebug() << " Runninng time =" << runningTime;
2357 qDebug() << " Stream time =" << streamTime;
2358 qDebug() << " Timestamp =" << timestamp;
2359 qDebug() << " Duration =" << duration;
2360
2361 break;
2362 }
2363 default:
2364 qDebug() << "Unhandled message:" << GST_MESSAGE_TYPE_NAME(message);
2365 break;
2366 }
2367
2368 return TRUE;
2369 }
2370
setElementOptions(GstElement * element,const QVariantMap & options)2371 void MediaWriterGStreamerPrivate::setElementOptions(GstElement *element,
2372 const QVariantMap &options)
2373 {
2374 for (auto it = options.cbegin(); it != options.cend(); it++) {
2375 auto paramSpec =
2376 g_object_class_find_property(G_OBJECT_GET_CLASS(element),
2377 it.key().toStdString().c_str());
2378
2379 if (!paramSpec || !(paramSpec->flags & G_PARAM_WRITABLE))
2380 continue;
2381
2382 GValue gValue;
2383 memset(&gValue, 0, sizeof(GValue));
2384 g_value_init(&gValue, paramSpec->value_type);
2385 QString value;
2386
2387 if (G_IS_PARAM_SPEC_FLAGS(paramSpec)) {
2388 auto flags = it.value().toStringList();
2389 value = flags.join('+');
2390 } else {
2391 value = it.value().toString();
2392 }
2393
2394 if (!gst_value_deserialize(&gValue, value.toStdString().c_str()))
2395 continue;
2396
2397 g_object_set_property(G_OBJECT(element),
2398 it.key().toStdString().c_str(),
2399 &gValue);
2400 }
2401 }
2402
nearestDVCaps(const AkVideoCaps & caps) const2403 AkVideoCaps MediaWriterGStreamerPrivate::nearestDVCaps(const AkVideoCaps &caps) const
2404 {
2405 AkVideoCaps nearestCaps;
2406 qreal q = std::numeric_limits<qreal>::max();
2407
2408 for (auto &sCaps: MediaWriterGStreamerPrivate::dvSupportedCaps()) {
2409 qreal dw = sCaps.width() - caps.width();
2410 qreal dh = sCaps.height() - caps.height();
2411 qreal df = sCaps.fps().value() - caps.fps().value();
2412 qreal k = dw * dw + dh * dh + df * df;
2413
2414 if (k < q) {
2415 nearestCaps = sCaps;
2416 q = k;
2417 } else if (qFuzzyCompare(k, q) && sCaps.format() == caps.format())
2418 nearestCaps = sCaps;
2419 }
2420
2421 return nearestCaps;
2422 }
2423
nearestFLVAudioCaps(const AkAudioCaps & caps,const QString & codec) const2424 AkAudioCaps MediaWriterGStreamerPrivate::nearestFLVAudioCaps(const AkAudioCaps &caps,
2425 const QString &codec) const
2426 {
2427 int nearestSampleRate = caps.rate();
2428 int q = std::numeric_limits<int>::max();
2429 auto &sampleRates = MediaWriterGStreamerPrivate::flvSupportedSampleRates();
2430
2431 for (auto &sampleRate: sampleRates.value(codec)) {
2432 int k = qAbs(sampleRate - caps.rate());
2433
2434 if (k < q) {
2435 nearestSampleRate = sampleRate;
2436 q = k;
2437
2438 if (k == 0)
2439 break;
2440 }
2441 }
2442
2443 AkAudioCaps nearestCaps(caps);
2444 nearestCaps.rate() = nearestSampleRate;
2445
2446 return nearestCaps;
2447 }
2448
nearestSampleRate(const AkAudioCaps & caps,const QVariantList & sampleRates) const2449 AkAudioCaps MediaWriterGStreamerPrivate::nearestSampleRate(const AkAudioCaps &caps,
2450 const QVariantList &sampleRates) const
2451 {
2452 QList<int> rates;
2453
2454 for (auto &rate: sampleRates)
2455 rates << rate.toInt();
2456
2457 return this->nearestSampleRate(caps, rates);
2458 }
2459
nearestSampleRate(const AkAudioCaps & caps,const QList<int> & sampleRates) const2460 AkAudioCaps MediaWriterGStreamerPrivate::nearestSampleRate(const AkAudioCaps &caps,
2461 const QList<int> &sampleRates) const
2462 {
2463 if (sampleRates.isEmpty())
2464 return caps;
2465
2466 auto audioCaps = caps;
2467 int sampleRate = 0;
2468 int maxDiff = std::numeric_limits<int>::max();
2469
2470 for (auto &rate: sampleRates) {
2471 int diff = qAbs(audioCaps.rate() - rate);
2472
2473 if (diff < maxDiff) {
2474 sampleRate = rate;
2475
2476 if (!diff)
2477 break;
2478
2479 maxDiff = diff;
2480 }
2481 }
2482
2483 audioCaps.rate() = sampleRate;
2484
2485 return audioCaps;
2486 }
2487
nearestFrameRate(const AkVideoCaps & caps,const QVariantList & frameRates) const2488 AkVideoCaps MediaWriterGStreamerPrivate::nearestFrameRate(const AkVideoCaps &caps,
2489 const QVariantList &frameRates) const
2490 {
2491 QList<AkFrac> rates;
2492
2493 for (auto &rate: frameRates)
2494 rates << rate.value<AkFrac>();
2495
2496 return this->nearestFrameRate(caps, rates);
2497 }
2498
nearestFrameRate(const AkVideoCaps & caps,const QList<AkFrac> & frameRates) const2499 AkVideoCaps MediaWriterGStreamerPrivate::nearestFrameRate(const AkVideoCaps &caps,
2500 const QList<AkFrac> &frameRates) const
2501 {
2502 if (frameRates.isEmpty())
2503 return caps;
2504
2505 auto videoCaps = caps;
2506 AkFrac frameRate;
2507 qreal maxDiff = std::numeric_limits<qreal>::max();
2508
2509 for (auto &rate: frameRates) {
2510 qreal diff = qAbs(videoCaps.fps().value() - rate.value());
2511
2512 if (diff < maxDiff) {
2513 frameRate = rate;
2514
2515 if (qIsNull(diff))
2516 break;
2517
2518 maxDiff = diff;
2519 }
2520 }
2521
2522 videoCaps.fps() = frameRate;
2523
2524 return videoCaps;
2525 }
2526
nearestFrameSize(const AkVideoCaps & caps,const QList<QSize> & frameSizes) const2527 AkVideoCaps MediaWriterGStreamerPrivate::nearestFrameSize(const AkVideoCaps &caps,
2528 const QList<QSize> &frameSizes) const
2529 {
2530 if (frameSizes.isEmpty())
2531 return caps;
2532
2533 QSize nearestSize;
2534 qreal q = std::numeric_limits<qreal>::max();
2535
2536 for (auto &size: frameSizes) {
2537 qreal dw = size.width() - caps.width();
2538 qreal dh = size.height() - caps.height();
2539 qreal k = dw * dw + dh * dh;
2540
2541 if (k < q) {
2542 nearestSize = size;
2543 q = k;
2544
2545 if (k == 0.)
2546 break;
2547 }
2548 }
2549
2550 AkVideoCaps nearestCaps(caps);
2551 nearestCaps.setWidth(nearestSize.width());
2552 nearestCaps.setHeight(nearestSize.height());
2553
2554 return nearestCaps;
2555 }
2556
2557 #include "moc_mediawritergstreamer.cpp"
2558