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                               &timestamp,
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