13#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
16#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
22#include <QtCore/QDateTime>
24#include <QtQuick/QQuickItem>
27#include <gst/video/video.h>
31#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH) || defined(QGC_HAS_GST_D3D11_GPU_PATH) || defined(QGC_HAS_GST_D3D12_GPU_PATH)
34GstBusSyncReply _contextSyncDispatch(GstBus * , GstMessage *message, gpointer )
36 return GstContextBridgeRegistry::dispatchBridges(message);
45 qCDebug(GstVideoReceiverLog) <<
this;
48 (void) connect(&
_watchdogTimer, &QTimer::timeout,
this, &GstVideoReceiver::_watchdog);
56 qCDebug(GstVideoReceiverLog) <<
this;
61 if (_needDispatch()) {
67 qCDebug(GstVideoReceiverLog) <<
"Already running!" <<
_uri;
73 qCDebug(GstVideoReceiverLog) <<
"Failed because URI is not specified";
81 qCDebug(GstVideoReceiverLog) <<
"Starting" <<
_uri <<
", lowLatency" <<
lowLatency() <<
", timeout" <<
_timeout;
86 bool pipelineUp =
false;
92 _tee = gst_element_factory_make(
"tee",
nullptr);
94 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make('tee') failed";
98 GstPad *pad = gst_element_get_static_pad(_tee,
"sink");
100 qCCritical(GstVideoReceiverLog) <<
"gst_element_get_static_pad() failed";
106 _teeProbeId = gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, _teeProbe,
this,
nullptr);
107 gst_clear_object(&pad);
108 if (_teeProbeId == 0) {
110 qCCritical(GstVideoReceiverLog) <<
"gst_pad_add_probe(_teeProbe) failed";
114 decoderQueue = gst_element_factory_make(
"queue",
nullptr);
116 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make('queue') failed";
120 _decoderValve = gst_element_factory_make(
"valve",
nullptr);
121 if (!_decoderValve) {
122 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make('valve') failed";
126 g_object_set(_decoderValve,
130 recorderQueue = gst_element_factory_make(
"queue",
nullptr);
131 if (!recorderQueue) {
132 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make('queue') failed";
136 _recorderValve = gst_element_factory_make(
"valve",
nullptr);
137 if (!_recorderValve) {
138 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make('valve') failed";
142 g_object_set(_recorderValve,
146 _pipeline = gst_pipeline_new(
"receiver");
148 qCCritical(GstVideoReceiverLog) <<
"gst_pipeline_new() failed";
152 g_object_set(_pipeline,
153 "message-forward", TRUE,
156 _source = _makeSource(
_uri);
158 qCCritical(GstVideoReceiverLog) <<
"_makeSource() failed";
162 gst_bin_add_many(GST_BIN(_pipeline), _source, _tee, decoderQueue, _decoderValve, recorderQueue, _recorderValve,
nullptr);
166 GstPad *srcPad =
nullptr;
167 GstIterator *it = gst_element_iterate_src_pads(_source);
168 GValue vpad = G_VALUE_INIT;
169 switch (gst_iterator_next(it, &vpad)) {
170 case GST_ITERATOR_OK:
171 srcPad = GST_PAD(g_value_get_object(&vpad));
172 (void) gst_object_ref(srcPad);
173 (void) g_value_reset(&vpad);
175 case GST_ITERATOR_RESYNC:
176 gst_iterator_resync(it);
181 g_value_unset(&vpad);
182 gst_iterator_free(it);
185 _onNewSourcePad(srcPad);
186 gst_clear_object(&srcPad);
188 (void) g_signal_connect(_source,
"pad-added", G_CALLBACK(_onNewPad),
this);
191 if (!gst_element_link_many(_tee, decoderQueue, _decoderValve,
nullptr)) {
192 qCCritical(GstVideoReceiverLog) <<
"Unable to link decoder queue";
196 if (!gst_element_link_many(_tee, recorderQueue, _recorderValve,
nullptr)) {
197 qCCritical(GstVideoReceiverLog) <<
"Unable to link recorder queue";
201 GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(_pipeline));
203 gst_bus_enable_sync_message_emission(bus);
204 (void) g_signal_connect(bus,
"sync-message", G_CALLBACK(_onBusMessage),
this);
205#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH) || defined(QGC_HAS_GST_D3D11_GPU_PATH) || defined(QGC_HAS_GST_D3D12_GPU_PATH)
211 gst_bus_set_sync_handler(bus, _contextSyncDispatch,
nullptr,
nullptr);
213 gst_clear_object(&bus);
216 GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL,
"pipeline-initial");
217 running = (gst_element_set_state(_pipeline, GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE);
221 qCCritical(GstVideoReceiverLog) <<
"Failed";
224 (void) gst_element_set_state(_pipeline, GST_STATE_NULL);
225 (void) gst_element_get_state(_pipeline,
nullptr,
nullptr, GST_CLOCK_TIME_NONE);
226 gst_clear_object(&_pipeline);
230 gst_clear_object(&_recorderValve);
231 gst_clear_object(&recorderQueue);
232 gst_clear_object(&_decoderValve);
233 gst_clear_object(&decoderQueue);
234 gst_clear_object(&_tee);
235 gst_clear_object(&_source);
242 GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL,
"pipeline-started");
243 qCDebug(GstVideoReceiverLog) <<
"Started" <<
_uri;
247 QMetaObject::invokeMethod(
this, [
this]() {
_watchdogTimer.start(1000); }, Qt::QueuedConnection);
254 if (_needDispatch()) {
259 if (
_uri.isEmpty()) {
260 qCDebug(GstVideoReceiverLog) <<
"Stop called on empty URI (no-op)";
264 qCDebug(GstVideoReceiverLog) <<
"Stopping" <<
_uri;
266 QMetaObject::invokeMethod(
this, [
this]() {
_watchdogTimer.stop(); }, Qt::QueuedConnection);
268 if (_teeProbeId != 0) {
270 GstPad *sinkpad = gst_element_get_static_pad(_tee,
"sink");
272 gst_pad_remove_probe(sinkpad, _teeProbeId);
273 gst_clear_object(&sinkpad);
280 GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(_pipeline));
282 gst_bus_disable_sync_message_emission(bus);
283 (void) g_signal_handlers_disconnect_by_data(bus,
this);
285 gboolean recordingValveClosed = TRUE;
286 g_object_get(_recorderValve,
"drop", &recordingValveClosed,
nullptr);
288 if (!recordingValveClosed) {
289 (void) gst_element_send_event(_pipeline, gst_event_new_eos());
291 GstMessage *msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, (GstMessageType)(GST_MESSAGE_EOS | GST_MESSAGE_ERROR));
293 switch (GST_MESSAGE_TYPE(msg)) {
294 case GST_MESSAGE_EOS:
295 qCDebug(GstVideoReceiverLog) <<
"End of stream received!";
297 case GST_MESSAGE_ERROR:
298 qCCritical(GstVideoReceiverLog) <<
"Error stopping pipeline!";
304 gst_clear_message(&msg);
306 qCCritical(GstVideoReceiverLog) <<
"gst_bus_timed_pop_filtered() failed";
310 gst_clear_object(&bus);
312 qCCritical(GstVideoReceiverLog) <<
"gst_pipeline_get_bus() failed";
315 (void) gst_element_set_state(_pipeline, GST_STATE_NULL);
316 (void) gst_element_get_state(_pipeline,
nullptr,
nullptr, GST_CLOCK_TIME_NONE);
320 _shutdownRecordingBranch();
324 _shutdownDecodingBranch();
327 GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL,
"pipeline-stopped");
329 gst_clear_object(&_pipeline);
332 _recorderValve =
nullptr;
333 _decoderValve =
nullptr;
341 qCDebug(GstVideoReceiverLog) <<
"Streaming stopped" <<
_uri;
344 qCDebug(GstVideoReceiverLog) <<
"Streaming did not start" <<
_uri;
348 qCDebug(GstVideoReceiverLog) <<
"Stopped" <<
_uri;
356 qCCritical(GstVideoReceiverLog) <<
"VideoSink is NULL" <<
_uri;
360 if (_needDispatch()) {
365 qCDebug(GstVideoReceiverLog) <<
"Starting decoding" <<
_uri;
368 qCDebug(GstVideoReceiverLog) <<
"Video Widget is NULL" <<
_uri;
374 gst_clear_object(&_videoSink);
378 qCDebug(GstVideoReceiverLog) <<
"Already decoding!" <<
_uri;
384 GstPad *pad = gst_element_get_static_pad(videoSink,
"sink");
386 qCCritical(GstVideoReceiverLog) <<
"Unable to find sink pad of video sink" <<
_uri;
394 _videoSinkProbeId = gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, _videoSinkProbe,
this,
nullptr);
395 gst_clear_object(&pad);
397 _videoSink = videoSink;
398 gst_object_ref(_videoSink);
407 _ensureVideoSinkInPipeline();
409 if (!_addDecoder(_decoderValve)) {
410 qCCritical(GstVideoReceiverLog) <<
"_addDecoder() failed" <<
_uri;
411 _shutdownDecodingBranch();
416 g_object_set(_decoderValve,
420 qCDebug(GstVideoReceiverLog) <<
"Decoding started" <<
_uri;
427 if (_needDispatch()) {
432 qCDebug(GstVideoReceiverLog) <<
"Stopping decoding" <<
_uri;
438 if (!_pipeline || !_videoSink) {
439 qCDebug(GstVideoReceiverLog) <<
"Not decoding!" <<
_uri;
444 g_object_set(_decoderValve,
450 const bool ret = _unlinkBranch(_decoderValve);
459 if (_needDispatch()) {
460 const QString cachedVideoFile = videoFile;
465 qCDebug(GstVideoReceiverLog) <<
"Starting recording" <<
_uri;
468 qCDebug(GstVideoReceiverLog) <<
"Streaming is not active!" <<
_uri;
474 qCDebug(GstVideoReceiverLog) <<
"Already recording!" <<
_uri;
479 qCDebug(GstVideoReceiverLog) <<
"New video file:" << videoFile <<
_uri;
481 _fileSink = _makeFileSink(videoFile, format);
483 qCCritical(GstVideoReceiverLog) <<
"_makeFileSink() failed" <<
_uri;
490 (void) gst_object_ref(_fileSink);
492 gst_bin_add(GST_BIN(_pipeline), _fileSink);
494 if (!gst_element_link(_recorderValve, _fileSink)) {
495 qCCritical(GstVideoReceiverLog) <<
"Failed to link valve and file sink" <<
_uri;
500 (void) gst_element_sync_state_with_parent(_fileSink);
502 GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL,
"pipeline-with-filesink");
507 GstPad *probepad = gst_element_get_static_pad(_recorderValve,
"src");
509 qCCritical(GstVideoReceiverLog) <<
"gst_element_get_static_pad() failed" <<
_uri;
514 _keyframeWatchId = gst_pad_add_probe(probepad, GST_PAD_PROBE_TYPE_BUFFER, _keyframeWatch,
this,
nullptr);
515 gst_clear_object(&probepad);
517 g_object_set(_recorderValve,
523 qCDebug(GstVideoReceiverLog) <<
"Recording started" <<
_uri;
524 _dispatchSignal([
this]() {
532 if (_needDispatch()) {
537 qCDebug(GstVideoReceiverLog) <<
"Stopping recording" <<
_uri;
540 qCDebug(GstVideoReceiverLog) <<
"Not recording!" <<
_uri;
545 g_object_set(_recorderValve,
551 if (!_unlinkBranch(_recorderValve)) {
559 _recordingStopRequested =
true;
564 if (_needDispatch()) {
565 const QString cachedImageFile = imageFile;
570 qCDebug(GstVideoReceiverLog) <<
"taking screenshot" <<
_uri;
576void GstVideoReceiver::_watchdog()
583 const qint64 now = QDateTime::currentSecsSinceEpoch();
590 qCDebug(GstVideoReceiverLog) <<
"Stream timeout, no frames for" << elapsed <<
_uri;
591 GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL,
"pipeline-watchdog-timeout");
592 _dispatchSignal([
this]() { emit
timeout(); });
603 qCDebug(GstVideoReceiverLog) <<
"Video decoder timeout, no frames for" << elapsed <<
_uri;
604 GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL,
"pipeline-watchdog-timeout");
605 _dispatchSignal([
this]() { emit
timeout(); });
612void GstVideoReceiver::_handleEOS()
621 _shutdownDecodingBranch();
623 _shutdownRecordingBranch();
630#if !defined(QGC_GST_BUILD_VERSION_MAJOR) || (QGC_GST_BUILD_VERSION_MAJOR == 1 && QGC_GST_BUILD_VERSION_MINOR < 28)
631gboolean GstVideoReceiver::_filterParserCaps(
GstElement *bin, GstPad *pad,
GstElement *element, GstQuery *query, gpointer data)
633 Q_UNUSED(bin); Q_UNUSED(pad); Q_UNUSED(element); Q_UNUSED(data)
635 if (GST_QUERY_TYPE(query) != GST_QUERY_CAPS) {
640 gst_query_parse_caps(query, &srcCaps);
641 if (!srcCaps || gst_caps_is_any(srcCaps)) {
645 GstCaps *sinkCaps =
nullptr;
646 GstCaps *filter =
nullptr;
647 GstStructure *structure = gst_caps_get_structure(srcCaps, 0);
648 if (gst_structure_has_name(structure,
"video/x-h265")) {
649 filter = gst_caps_from_string(
"video/x-h265");
650 if (gst_caps_can_intersect(srcCaps, filter)) {
651 sinkCaps = gst_caps_from_string(
"video/x-h265,stream-format=hvc1");
653 gst_clear_caps(&filter);
654 }
else if (gst_structure_has_name(structure,
"video/x-h264")) {
655 filter = gst_caps_from_string(
"video/x-h264");
656 if (gst_caps_can_intersect(srcCaps, filter)) {
657 sinkCaps = gst_caps_from_string(
"video/x-h264,stream-format=avc");
659 gst_clear_caps(&filter);
663 gst_query_set_caps_result(query, sinkCaps);
664 gst_clear_caps(&sinkCaps);
672GstElement *GstVideoReceiver::_makeSource(
const QString &input)
674 if (input.isEmpty()) {
675 qCCritical(GstVideoReceiverLog) <<
"Failed because URI is not specified";
679 const QUrl sourceUrl(input);
681 const bool isRtsp = sourceUrl.scheme().startsWith(
"rtsp", Qt::CaseInsensitive);
682 const bool isUdp264 = input.contains(
"udp://", Qt::CaseInsensitive);
683 const bool isUdp265 = input.contains(
"udp265://", Qt::CaseInsensitive);
684 const bool isUdpMPEGTS = input.contains(
"mpegts://", Qt::CaseInsensitive);
685 const bool isTcpMPEGTS = input.contains(
"tcp://", Qt::CaseInsensitive);
697 qCCritical(GstVideoReceiverLog) <<
"Invalid RTSP URI:" << input;
701 source = gst_element_factory_make(
"rtspsrc",
"source");
703 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make('rtspsrc') failed";
707 const QString rtspUserInfo = sourceUrl.userInfo();
708 QString rtspUser, rtspPassword;
709 if (!rtspUserInfo.isEmpty()) {
710 const int colonIdx = rtspUserInfo.indexOf(QLatin1Char(
':'));
712 rtspUser = rtspUserInfo.left(colonIdx);
713 rtspPassword = rtspUserInfo.mid(colonIdx + 1);
715 rtspUser = rtspUserInfo;
718 QUrl cleanUrl(sourceUrl);
719 cleanUrl.setUserInfo(QString());
720 const QByteArray cleanLocation = cleanUrl.toString().toUtf8();
723 "location", cleanLocation.constData(),
726 "tcp-timeout", G_GUINT64_CONSTANT(5000000),
727 "udp-reconnect", TRUE,
728 "drop-on-latency", TRUE,
732 if (!rtspUser.isEmpty()) {
734 "user-id", rtspUser.toUtf8().constData(),
735 "user-pw", rtspPassword.toUtf8().constData(),
738 }
else if (isTcpMPEGTS) {
739 source = gst_element_factory_make(
"tcpclientsrc",
"source");
741 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make('tcpclientsrc') failed";
745 const QString host = sourceUrl.host();
746 const quint16 port = sourceUrl.port();
748 "host", host.toUtf8().constData(),
751 }
else if (isUdp264 || isUdp265 || isUdpMPEGTS) {
752 source = gst_element_factory_make(
"udpsrc",
"source");
754 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make('udpsrc') failed";
758 const QString
uri = QStringLiteral(
"udp://%1:%2").arg(sourceUrl.host(), QString::number(sourceUrl.port()));
760 "uri",
uri.toUtf8().constData(),
761 "buffer-size", 8 * 1024 * 1024,
764 GstCaps *caps =
nullptr;
766 caps = gst_caps_from_string(
"application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H264");
768 qCCritical(GstVideoReceiverLog) <<
"gst_caps_from_string() failed";
771 }
else if (isUdp265) {
772 caps = gst_caps_from_string(
"application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H265");
774 qCCritical(GstVideoReceiverLog) <<
"gst_caps_from_string() failed";
783 gst_clear_caps(&caps);
786 qCDebug(GstVideoReceiverLog) <<
"URI is not recognized";
790 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make() for data source failed";
794 bin = gst_bin_new(
"sourcebin");
796 qCCritical(GstVideoReceiverLog) <<
"gst_bin_new('sourcebin') failed";
800 parser = gst_element_factory_make(
"parsebin",
"parser");
802 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make('parsebin') failed";
810#if !defined(QGC_GST_BUILD_VERSION_MAJOR) || (QGC_GST_BUILD_VERSION_MAJOR == 1 && QGC_GST_BUILD_VERSION_MINOR < 28)
811 (void) g_signal_connect(parser,
"autoplug-query", G_CALLBACK(_filterParserCaps),
nullptr);
814 gst_bin_add_many(GST_BIN(bin), source, parser,
nullptr);
818 if (isTcpMPEGTS || isUdpMPEGTS) {
819 tsdemux = gst_element_factory_make(
"tsdemux",
nullptr);
821 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make('tsdemux') failed";
825 (void) gst_bin_add(GST_BIN(bin), tsdemux);
827 if (!gst_element_link(source, tsdemux)) {
828 qCCritical(GstVideoReceiverLog) <<
"gst_element_link() failed";
837 (void) gst_element_foreach_src_pad(source, _padProbe, &probeRes);
840 if ((probeRes & 2) && (
_buffer >= 0)) {
841 buffer = gst_element_factory_make(
"rtpjitterbuffer",
nullptr);
843 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make('rtpjitterbuffer') failed";
849 "drop-on-latency",
_buffer == 0 ? TRUE : FALSE,
852 (void) gst_bin_add(GST_BIN(bin), buffer);
854 if (!gst_element_link_many(source, buffer, parser,
nullptr)) {
855 qCCritical(GstVideoReceiverLog) <<
"gst_element_link() failed";
859 if (!gst_element_link(source, parser)) {
860 qCCritical(GstVideoReceiverLog) <<
"gst_element_link() failed";
865 (void) g_signal_connect(source,
"pad-added", G_CALLBACK(_linkPad), parser);
868 (void) g_signal_connect(parser,
"pad-added", G_CALLBACK(_wrapWithGhostPad),
nullptr);
870 source = tsdemux = buffer = parser =
nullptr;
876 gst_clear_object(&bin);
877 gst_clear_object(&parser);
878 gst_clear_object(&tsdemux);
879 gst_clear_object(&buffer);
880 gst_clear_object(&source);
887 GstElement *decoder = gst_element_factory_make(
"decodebin3",
nullptr);
889 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make('decodebin3') failed";
894GstElement *GstVideoReceiver::_makeFileSink(
const QString &videoFile, FILE_FORMAT format)
900 bool releaseElements =
true;
904 qCCritical(GstVideoReceiverLog) <<
"Unsupported file format";
908 mux = gst_element_factory_make(_kFileMux[format],
nullptr);
910 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make('" << _kFileMux[
format] <<
"') failed";
919 "reserved-moov-update-period", G_GUINT64_CONSTANT(1000000000),
923 sink = gst_element_factory_make(
"filesink",
nullptr);
925 qCCritical(GstVideoReceiverLog) <<
"gst_element_factory_make('filesink') failed";
930 "location", qPrintable(videoFile),
933 bin = gst_bin_new(
"sinkbin");
935 qCCritical(GstVideoReceiverLog) <<
"gst_bin_new('sinkbin') failed";
939 GstPadTemplate *padTemplate = gst_element_class_get_pad_template(GST_ELEMENT_GET_CLASS(mux),
"video_%u");
941 qCCritical(GstVideoReceiverLog) <<
"gst_element_class_get_pad_template(mux) failed";
946 GstPad *pad = gst_element_request_pad(mux, padTemplate,
nullptr,
nullptr);
948 qCCritical(GstVideoReceiverLog) <<
"gst_element_request_pad(mux) failed";
952 gst_bin_add_many(GST_BIN(bin), mux,
sink,
nullptr);
954 releaseElements =
false;
956 GstPad *ghostpad = gst_ghost_pad_new(
"sink", pad);
957 (void) gst_element_add_pad(bin, ghostpad);
958 gst_clear_object(&pad);
960 if (!gst_element_link(mux,
sink)) {
961 qCCritical(GstVideoReceiverLog) <<
"gst_element_link() failed";
969 if (releaseElements) {
970 gst_clear_object(&
sink);
971 gst_clear_object(&mux);
974 gst_clear_object(&bin);
978void GstVideoReceiver::_onNewSourcePad(GstPad *pad)
981 if (!gst_element_link(_source, _tee)) {
982 qCCritical(GstVideoReceiverLog) <<
"Unable to link source";
988 qCDebug(GstVideoReceiverLog) <<
"Streaming started" <<
_uri;
992 _eosProbeId = gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, _eosProbe,
this,
nullptr);
993 if (_eosProbeId != 0) {
995 _eosProbePad = GST_PAD_CAST(gst_object_ref(pad));
1001 GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL,
"pipeline-with-new-source-pad");
1003 _ensureVideoSinkInPipeline();
1005 if (!_addDecoder(_decoderValve)) {
1006 qCCritical(GstVideoReceiverLog) <<
"_addDecoder() failed";
1007 _shutdownDecodingBranch();
1011 g_object_set(_decoderValve,
1015 qCDebug(GstVideoReceiverLog) <<
"Decoding started" <<
_uri;
1018void GstVideoReceiver::_logDecodebin3SelectedCodec(
GstElement *decodebin3)
1020 GValue value = G_VALUE_INIT;
1021 GstIterator *iter = gst_bin_iterate_elements(GST_BIN(decodebin3));
1024 while (gst_iterator_next(iter, &value) == GST_ITERATOR_OK) {
1025 child = GST_ELEMENT(g_value_get_object(&value));
1026 GstElementFactory *factory = gst_element_get_factory(child);
1029 gboolean is_decoder = gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_DECODER);
1031 const gchar *decoderKlass = gst_element_factory_get_klass(factory);
1032 GstPluginFeature *feature = GST_PLUGIN_FEATURE(factory);
1033 const gchar *featureName = gst_plugin_feature_get_name(feature);
1034 const guint rank = gst_plugin_feature_get_rank(feature);
1037 QString pluginName = featureName;
1038 GstPlugin *plugin = gst_plugin_feature_get_plugin(feature);
1040 pluginName = gst_plugin_get_name(plugin);
1041 gst_object_unref(plugin);
1043 qCDebug(GstVideoReceiverLog) <<
"Decodebin3 selected codec:rank -" << pluginName <<
"/" << featureName <<
"-" << decoderKlass << (isHardwareDecoder ?
"(HW)" :
"(SW)") <<
":" << rank;
1045 const QString newName = QString::fromUtf8(featureName);
1046 if (newName != _decoderName) {
1047 _decoderName = newName;
1055 g_object_set(child,
"qos", FALSE,
nullptr);
1056 qCDebug(GstVideoReceiverLog) <<
"Disabled QoS on internal decoder" << featureName;
1059 g_value_reset(&value);
1061 g_value_unset(&value);
1062 gst_iterator_free(iter);
1066void GstVideoReceiver::_onNewDecoderPad(GstPad *pad)
1068 qCDebug(GstVideoReceiverLog) <<
"_onNewDecoderPad" <<
_uri;
1070 GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL,
"pipeline-with-new-decoder-pad");
1073 _logDecodebin3SelectedCodec(_decoder);
1075 if (!_addVideoSink(pad)) {
1076 qCCritical(GstVideoReceiverLog) <<
"_addVideoSink() failed";
1080bool GstVideoReceiver::_addDecoder(
GstElement *src)
1082 _decoder = _makeDecoder();
1084 qCCritical(GstVideoReceiverLog) <<
"_makeDecoder() failed";
1088 (void) gst_object_ref(_decoder);
1090 (void) gst_bin_add(GST_BIN(_pipeline), _decoder);
1091 (void) gst_element_sync_state_with_parent(_decoder);
1093 GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL,
"pipeline-with-decoder");
1095 if (!gst_element_link(src, _decoder)) {
1096 qCCritical(GstVideoReceiverLog) <<
"Unable to link decoder";
1097 gst_element_set_state(_decoder, GST_STATE_NULL);
1098 (void) gst_element_get_state(_decoder,
nullptr,
nullptr, GST_CLOCK_TIME_NONE);
1099 (void) gst_bin_remove(GST_BIN(_pipeline), _decoder);
1100 gst_clear_object(&_decoder);
1104 GstPad *srcPad =
nullptr;
1105 GstIterator *it = gst_element_iterate_src_pads(_decoder);
1106 GValue vpad = G_VALUE_INIT;
1107 switch (gst_iterator_next(it, &vpad)) {
1108 case GST_ITERATOR_OK:
1109 srcPad = GST_PAD(g_value_get_object(&vpad));
1110 (void) gst_object_ref(srcPad);
1111 (void) g_value_reset(&vpad);
1113 case GST_ITERATOR_RESYNC:
1114 gst_iterator_resync(it);
1119 g_value_unset(&vpad);
1120 gst_iterator_free(it);
1123 _onNewDecoderPad(srcPad);
1125 (void) g_signal_connect(_decoder,
"pad-added", G_CALLBACK(_onNewPad),
this);
1128 gst_clear_object(&srcPad);
1132void GstVideoReceiver::_ensureVideoSinkInPipeline()
1134 if (!_videoSink || !_pipeline) {
1138 GstObject *parent = gst_element_get_parent(_videoSink);
1140 gst_object_unref(parent);
1144 g_object_set(_videoSink,
1148 (void) gst_object_ref(_videoSink);
1149 (void) gst_bin_add(GST_BIN(_pipeline), _videoSink);
1152 (void) gst_element_set_state(_videoSink, GST_STATE_PAUSED);
1155bool GstVideoReceiver::_addVideoSink(GstPad *pad)
1157 GstCaps *caps = gst_pad_query_caps(pad,
nullptr);
1159 _ensureVideoSinkInPipeline();
1161 GstPad *sinkPad = gst_element_get_static_pad(_videoSink,
"sink");
1162 GstPadLinkReturn linkRet = sinkPad ? gst_pad_link(pad, sinkPad) : GST_PAD_LINK_WRONG_HIERARCHY;
1163 if (linkRet != GST_PAD_LINK_OK) {
1164 qCCritical(GstVideoReceiverLog) <<
"Unable to link decoder pad to video sink, result:" << linkRet;
1167 GstObject *parent = gst_element_get_parent(_videoSink);
1169 (void) gst_element_set_state(_videoSink, GST_STATE_NULL);
1170 (void) gst_element_get_state(_videoSink,
nullptr,
nullptr, GST_CLOCK_TIME_NONE);
1171 (void) gst_bin_remove(GST_BIN(_pipeline), _videoSink);
1172 gst_clear_object(&parent);
1175 gst_clear_object(&sinkPad);
1176 gst_clear_caps(&caps);
1179 gst_clear_object(&sinkPad);
1181 (void) gst_element_sync_state_with_parent(_videoSink);
1184 g_object_set(_videoSink,
"sync", FALSE,
"max-lateness", G_GINT64_CONSTANT(-1),
nullptr);
1186 GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL,
"pipeline-with-videosink");
1191 if (!_decoderValve) {
1192 qCCritical(GstVideoReceiverLog) <<
"Unable to determine video size - _decoderValve is NULL" <<
_uri;
1196 GstPad *valveSrcPad = gst_element_get_static_pad(_decoderValve,
"src");
1198 qCCritical(GstVideoReceiverLog) <<
"gst_element_get_static_pad() failed";
1202 GstCaps *valveSrcPadCaps = gst_pad_query_caps(valveSrcPad,
nullptr);
1203 if (!valveSrcPadCaps) {
1204 qCCritical(GstVideoReceiverLog) <<
"gst_pad_query_caps() failed";
1205 gst_clear_object(&valveSrcPad);
1209 const GstStructure *structure = gst_caps_get_structure(valveSrcPadCaps, 0);
1211 qCCritical(GstVideoReceiverLog) <<
"Unable to determine video size - structure is NULL" <<
_uri;
1212 gst_clear_object(&valveSrcPad);
1218 (void) gst_structure_get_int(structure,
"width", &width);
1219 (void) gst_structure_get_int(structure,
"height", &height);
1222 gint orientation = 0;
1223 if (gst_structure_get_int(structure,
"video-orientation", &orientation)
1224 && (orientation == GST_VIDEO_ORIENTATION_90R
1225 || orientation == GST_VIDEO_ORIENTATION_90L
1226 || orientation == GST_VIDEO_ORIENTATION_UL_LR
1227 || orientation == GST_VIDEO_ORIENTATION_UR_LL)) {
1228 videoSize.setWidth(height);
1229 videoSize.setHeight(width);
1231 videoSize.setWidth(width);
1232 videoSize.setHeight(height);
1235 gst_clear_caps(&valveSrcPadCaps);
1236 gst_clear_object(&valveSrcPad);
1238 _dispatchSignal([
this, videoSize]() { emit
videoSizeChanged(videoSize); });
1240 gst_clear_caps(&caps);
1244void GstVideoReceiver::_noteTeeFrame()
1249void GstVideoReceiver::_noteVideoSinkFrame()
1254 qCDebug(GstVideoReceiverLog) <<
"Decoding started";
1259void GstVideoReceiver::_noteEndOfStream()
1264bool GstVideoReceiver::_unlinkBranch(
GstElement *from)
1266 GstPad *src = gst_element_get_static_pad(from,
"src");
1268 qCCritical(GstVideoReceiverLog) <<
"gst_element_get_static_pad() failed";
1272 GstPad *
sink = gst_pad_get_peer(src);
1274 gst_clear_object(&src);
1275 qCCritical(GstVideoReceiverLog) <<
"gst_pad_get_peer() failed";
1279 if (!gst_pad_unlink(src,
sink)) {
1280 gst_clear_object(&src);
1281 gst_clear_object(&
sink);
1282 qCCritical(GstVideoReceiverLog) <<
"gst_pad_unlink() failed";
1286 gst_clear_object(&src);
1289 const gboolean ret = gst_pad_send_event(
sink, gst_event_new_eos());
1291 gst_clear_object(&
sink);
1294 qCCritical(GstVideoReceiverLog) <<
"Branch EOS was NOT sent";
1298 qCDebug(GstVideoReceiverLog) <<
"Branch EOS was sent";
1303void GstVideoReceiver::_shutdownDecodingBranch()
1306 GstObject *parent = gst_element_get_parent(_decoder);
1308 (void) gst_bin_remove(GST_BIN(_pipeline), _decoder);
1309 (void) gst_element_set_state(_decoder, GST_STATE_NULL);
1310 (void) gst_element_get_state(_decoder,
nullptr,
nullptr, GST_CLOCK_TIME_NONE);
1311 gst_clear_object(&parent);
1314 gst_clear_object(&_decoder);
1317 if (_videoSinkProbeId != 0 && _videoSink) {
1318 GstPad *sinkpad = gst_element_get_static_pad(_videoSink,
"sink");
1320 gst_pad_remove_probe(sinkpad, _videoSinkProbeId);
1321 gst_clear_object(&sinkpad);
1324 _videoSinkProbeId = 0;
1326 if (_eosProbeId != 0 && _eosProbePad) {
1328 gst_pad_remove_probe(_eosProbePad, _eosProbeId);
1331 gst_clear_object(&_eosProbePad);
1336 GstObject *parent = gst_element_get_parent(_videoSink);
1338 (void) gst_bin_remove(GST_BIN(_pipeline), _videoSink);
1339 (void) gst_element_set_state(_videoSink, GST_STATE_NULL);
1340 (void) gst_element_get_state(_videoSink,
nullptr,
nullptr, GST_CLOCK_TIME_NONE);
1341 gst_clear_object(&parent);
1343 gst_clear_object(&_videoSink);
1350 qCDebug(GstVideoReceiverLog) <<
"Decoding stopped";
1354 GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL,
"pipeline-decoding-stopped");
1357void GstVideoReceiver::_shutdownRecordingBranch()
1359 if (_keyframeWatchId != 0 && _recorderValve) {
1360 GstPad *probepad = gst_element_get_static_pad(_recorderValve,
"src");
1362 gst_pad_remove_probe(probepad, _keyframeWatchId);
1363 gst_clear_object(&probepad);
1365 _keyframeWatchId = 0;
1368 gst_bin_remove(GST_BIN(_pipeline), _fileSink);
1369 gst_element_set_state(_fileSink, GST_STATE_NULL);
1370 (void) gst_element_get_state(_fileSink,
nullptr,
nullptr, GST_CLOCK_TIME_NONE);
1371 gst_clear_object(&_fileSink);
1377 qCDebug(GstVideoReceiverLog) <<
"Recording stopped";
1381 if (_recordingStopRequested) {
1382 _recordingStopRequested =
false;
1386 GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(_pipeline), GST_DEBUG_GRAPH_SHOW_ALL,
"pipeline-recording-stopped");
1389bool GstVideoReceiver::_needDispatch()
1394void GstVideoReceiver::_dispatchSignal(
Task emitter)
1401gboolean GstVideoReceiver::_onBusMessage(GstBus * , GstMessage *msg, gpointer data)
1403 if (!msg || !data) {
1404 qCCritical(GstVideoReceiverLog) <<
"Invalid parameters in _onBusMessage: msg=" << msg <<
"data=" << data;
1410 switch (GST_MESSAGE_TYPE(msg)) {
1411 case GST_MESSAGE_ERROR: {
1414 gst_message_parse_error(msg, &
error, &debug);
1417 qCDebug(GstVideoReceiverLog) <<
"GStreamer debug:" << debug;
1418 g_clear_pointer(&debug, g_free);
1422 qCCritical(GstVideoReceiverLog) <<
"GStreamer error:" <<
error->message;
1423 g_clear_error(&
error);
1426 if (pThis->_pipeline) {
1427 GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(pThis->_pipeline), GST_DEBUG_GRAPH_SHOW_ALL,
"pipeline-error");
1430#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH) || defined(QGC_HAS_GST_D3D11_GPU_PATH) || defined(QGC_HAS_GST_D3D12_GPU_PATH)
1438 GstContextBridgeRegistry::resetAllBridges();
1441 pThis->_worker->
dispatch([pThis]() {
1442 qCDebug(GstVideoReceiverLog) <<
"Stopping because of error";
1447 case GST_MESSAGE_WARNING: {
1450 gchar *debug =
nullptr;
1451 GError *
error =
nullptr;
1452 gst_message_parse_warning(msg, &
error, &debug);
1453 qCWarning(GstVideoReceiverLog) <<
"GStreamer warning:"
1455 <<
"debug:" << (debug ? debug :
"(none)");
1456 g_clear_error(&
error);
1457 g_clear_pointer(&debug, g_free);
1460 case GST_MESSAGE_EOS:
1461 pThis->_worker->
dispatch([pThis]() {
1462 qCDebug(GstVideoReceiverLog) <<
"Received EOS";
1463 pThis->_handleEOS();
1466 case GST_MESSAGE_STREAM_COLLECTION: {
1467 GstStreamCollection *collection =
nullptr;
1468 gst_message_parse_stream_collection(msg, &collection);
1473 GList *selectedIds =
nullptr;
1474 const guint nStreams = gst_stream_collection_get_size(collection);
1475 for (guint i = 0; i < nStreams; ++i) {
1476 GstStream *stream = gst_stream_collection_get_stream(collection, i);
1477 const GstStreamType type = gst_stream_get_stream_type(stream);
1478 if (type & GST_STREAM_TYPE_VIDEO) {
1479 selectedIds = g_list_append(selectedIds,
1480 g_strdup(gst_stream_get_stream_id(stream)));
1484 GstEvent *
event = gst_event_new_select_streams(selectedIds);
1485 gst_element_send_event(GST_ELEMENT(GST_MESSAGE_SRC(msg)), event);
1486 g_list_free_full(selectedIds, g_free);
1488 gst_object_unref(collection);
1491 case GST_MESSAGE_QOS: {
1492 guint64 processed = 0, dropped = 0;
1493 gst_message_parse_qos_stats(msg,
nullptr, &processed, &dropped);
1496 gdouble proportion = 0;
1498 gst_message_parse_qos_values(msg, &jitter, &proportion, &quality);
1500 pThis->_processedFrames = processed;
1501 pThis->_droppedFrames = dropped;
1502 pThis->_currentJitterNs = jitter;
1503 pThis->_qosProportion = proportion;
1504 pThis->_qosQuality = quality;
1508 case GST_MESSAGE_ELEMENT: {
1509 const GstStructure *structure = gst_message_get_structure(msg);
1510 if (!gst_structure_has_name(structure,
"GstBinForwarded")) {
1514 GstMessage *forward_msg =
nullptr;
1515 gst_structure_get(structure,
"message", GST_TYPE_MESSAGE, &forward_msg, NULL);
1520 if (GST_MESSAGE_TYPE(forward_msg) == GST_MESSAGE_EOS) {
1521 pThis->_worker->
dispatch([pThis]() {
1522 qCDebug(GstVideoReceiverLog) <<
"Received branch EOS";
1523 pThis->_handleEOS();
1527 gst_clear_message(&forward_msg);
1530 case GST_MESSAGE_STATE_CHANGED: {
1531 if (GST_MESSAGE_SRC(msg) != GST_OBJECT(pThis->_pipeline)) {
1534 GstState oldState = GST_STATE_NULL, newState = GST_STATE_NULL;
1535 gst_message_parse_state_changed(msg, &oldState, &newState,
nullptr);
1536 if (newState == GST_STATE_PLAYING && oldState != GST_STATE_PLAYING) {
1537 GstClockTime min = 0, max = 0;
1538 GstQuery *q = gst_query_new_latency();
1539 if (gst_element_query(pThis->_pipeline, q)) {
1540 gboolean live = FALSE;
1541 gst_query_parse_latency(q, &live, &min, &max);
1544 qCDebug(GstVideoReceiverLog).noquote()
1545 <<
"Pipeline PLAYING:" << pThis->
_uri
1546 <<
"decoder:" << (pThis->_decoderName.isEmpty() ? QStringLiteral(
"(pending)") : pThis->_decoderName)
1547 <<
"min-latency:" << (min / 1000000) <<
"ms"
1548 <<
"max-latency:" << (max / 1000000) <<
"ms";
1552 case GST_MESSAGE_LATENCY:
1553 pThis->_worker->
dispatch([pThis]() {
1554 if (pThis->_pipeline) {
1555 (void) gst_bin_recalculate_latency(GST_BIN(pThis->_pipeline));
1558 pThis->_dispatchSignal([pThis]() { emit pThis->
latencyChanged(); });
1567void GstVideoReceiver::_onNewPad(
GstElement *element, GstPad *pad, gpointer data)
1571 if (element == self->_source) {
1572 self->_onNewSourcePad(pad);
1573 }
else if (element == self->_decoder) {
1574 self->_onNewDecoderPad(pad);
1576 qCDebug(GstVideoReceiverLog) <<
"Unexpected call!";
1580void GstVideoReceiver::_wrapWithGhostPad(
GstElement *element, GstPad *pad, gpointer data)
1584 gchar *
name = gst_pad_get_name(pad);
1586 qCCritical(GstVideoReceiverLog) <<
"gst_pad_get_name() failed";
1590 GstPad *ghostpad = gst_ghost_pad_new(
name, pad);
1592 qCCritical(GstVideoReceiverLog) <<
"gst_ghost_pad_new() failed";
1593 g_clear_pointer(&
name, g_free);
1597 g_clear_pointer(&
name, g_free);
1599 (void) gst_pad_set_active(ghostpad, TRUE);
1601 if (!gst_element_add_pad(GST_ELEMENT_PARENT(element), ghostpad)) {
1602 qCCritical(GstVideoReceiverLog) <<
"gst_element_add_pad() failed";
1606void GstVideoReceiver::_linkPad(
GstElement *element, GstPad *pad, gpointer data)
1608 gchar *
name = gst_pad_get_name(pad);
1610 qCCritical(GstVideoReceiverLog) <<
"gst_pad_get_name() failed";
1614 if (!gst_element_link_pads(element,
name, GST_ELEMENT(data),
"sink")) {
1615 qCCritical(GstVideoReceiverLog) <<
"gst_element_link_pads() failed";
1618 g_clear_pointer(&
name, g_free);
1621gboolean GstVideoReceiver::_padProbe(
GstElement *element, GstPad *pad, gpointer user_data)
1625 int *probeRes =
static_cast<int*
>(user_data);
1628 GstCaps *filter = gst_caps_from_string(
"application/x-rtp");
1630 GstCaps *caps = gst_pad_query_caps(pad,
nullptr);
1632 if (!gst_caps_is_any(caps) && gst_caps_can_intersect(caps, filter)) {
1636 gst_clear_caps(&caps);
1639 gst_clear_caps(&filter);
1645GstPadProbeReturn GstVideoReceiver::_teeProbe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
1647 Q_UNUSED(pad); Q_UNUSED(info)
1651 pThis->_noteTeeFrame();
1654 return GST_PAD_PROBE_OK;
1657GstPadProbeReturn GstVideoReceiver::_videoSinkProbe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
1659 Q_UNUSED(pad); Q_UNUSED(info)
1668 gst_pad_send_event(pad, gst_event_new_flush_start());
1669 gst_pad_send_event(pad, gst_event_new_flush_stop(TRUE));
1673 if ((buf = gst_pad_probe_info_get_buffer(info)) !=
nullptr) {
1676 if ((seg = gst_segment_new()) !=
nullptr) {
1677 gst_segment_init(seg, GST_FORMAT_TIME);
1679 seg->start = buf->pts;
1681 gst_pad_send_event(pad, gst_event_new_segment(seg));
1683 gst_segment_free(seg);
1687 gst_pad_set_offset(pad, -
static_cast<gint64
>(buf->pts));
1692 pThis->_noteVideoSinkFrame();
1695 return GST_PAD_PROBE_OK;
1698GstPadProbeReturn GstVideoReceiver::_eosProbe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
1701 Q_ASSERT(user_data);
1704 const GstEvent *
event = gst_pad_probe_info_get_event(info);
1705 if (GST_EVENT_TYPE(event) == GST_EVENT_EOS) {
1707 pThis->_noteEndOfStream();
1711 return GST_PAD_PROBE_OK;
1714GstPadProbeReturn GstVideoReceiver::_keyframeWatch(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
1716 if (!info || !user_data) {
1717 qCCritical(GstVideoReceiverLog) <<
"Invalid arguments";
1718 return GST_PAD_PROBE_DROP;
1721 GstBuffer *buf = gst_pad_probe_info_get_buffer(info);
1722 if (GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
1724 return GST_PAD_PROBE_DROP;
1728 gst_pad_set_offset(pad, -
static_cast<gint64
>(buf->pts));
1730 qCDebug(GstVideoReceiverLog) <<
"Got keyframe, stop dropping buffers";
1735 return GST_PAD_PROBE_REMOVE;
1741 qCDebug(GstVideoReceiverLog) <<
this;
1746 qCDebug(GstVideoReceiverLog) <<
this;
1751 return (QThread::currentThread() !=
this);
1756 QMutexLocker lock(&_taskQueueSync);
1757 _taskQueue.enqueue(task);
1758 _taskQueueUpdate.wakeOne();
1764 dispatch([
this]() { _shutdown =
true; });
1765 (void) QThread::wait(2000);
1771void GstVideoWorker::run()
1773 while (!_shutdown) {
1774 _taskQueueSync.lock();
1776 while (_taskQueue.isEmpty()) {
1777 _taskQueueUpdate.wait(&_taskQueueSync);
1780 const Task task = _taskQueue.dequeue();
1782 _taskQueueSync.unlock();
std::function< void()> Task
struct _GstElement GstElement
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void stopDecoding() override
void takeScreenshot(const QString &imageFile) override
void decoderStatsChanged()
GstVideoReceiver(QObject *parent=nullptr)
void start(uint32_t timeout) override
void stopRecording() override
void startRecording(const QString &videoFile, FILE_FORMAT format) override
void startDecoding(void *sink) override
bool needDispatch() const
GstVideoWorker(QObject *parent=nullptr)
void recordingStarted(const QString &filename)
void videoSizeChanged(QSize size)
void streamingChanged(bool active)
qint64 _lastSourceFrameTime
qint64 _lastVideoFrameTime
void recordingChanged(bool active)
void onStartRecordingComplete(STATUS status)
void onTakeScreenshotComplete(STATUS status)
void decodingChanged(bool active)
void onStartComplete(STATUS status)
static bool isValidFileFormat(FILE_FORMAT format)
void onStopDecodingComplete(STATUS status)
void onStopRecordingComplete(STATUS status)
QString recordingOutput() const
void onStartDecodingComplete(STATUS status)
void onStopComplete(STATUS status)
bool isHardwareDecoderFactory(GstElementFactory *factory)
gboolean isValidRtspUri(const gchar *uri_str)