Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qgstreamermediaplayer.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
16
17#include <QtMultimedia/qaudiodevice.h>
18#include <QtCore/qdir.h>
19#include <QtCore/qsocketnotifier.h>
20#include <QtCore/qurl.h>
21#include <QtCore/qdebug.h>
22#include <QtCore/qloggingcategory.h>
23#include <QtCore/private/quniquehandle_p.h>
24
25#include <sys/types.h>
26#include <sys/stat.h>
27#include <fcntl.h>
28
29static Q_LOGGING_CATEGORY(qLcMediaPlayer, "qt.multimedia.player")
30
32
33QGstreamerMediaPlayer::TrackSelector::TrackSelector(TrackType type, QGstElement selector)
35{
36 selector.set("sync-streams", true);
37 selector.set("sync-mode", 1 /*clock*/);
38
39 if (type == SubtitleStream)
40 selector.set("cache-buffers", true);
41}
42
43QGstPad QGstreamerMediaPlayer::TrackSelector::createInputPad()
44{
45 auto pad = selector.getRequestPad("sink_%u");
46 tracks.append(pad);
47 return pad;
48}
49
50void QGstreamerMediaPlayer::TrackSelector::removeAllInputPads()
51{
52 for (auto &pad : tracks)
53 selector.releaseRequestPad(pad);
54 tracks.clear();
55}
56
57void QGstreamerMediaPlayer::TrackSelector::removeInputPad(QGstPad pad)
58{
59 selector.releaseRequestPad(pad);
60 tracks.removeOne(pad);
61}
62
63QGstPad QGstreamerMediaPlayer::TrackSelector::inputPad(int index)
64{
65 if (index >= 0 && index < tracks.count())
66 return tracks[index];
67 return {};
68}
69
70QGstreamerMediaPlayer::TrackSelector &QGstreamerMediaPlayer::trackSelector(TrackType type)
71{
72 auto &ts = trackSelectors[type];
73 Q_ASSERT(ts.type == type);
74 return ts;
75}
76
77void QGstreamerMediaPlayer::disconnectDecoderHandlers()
78{
79 auto handlers = std::initializer_list<QGObjectHandlerScopedConnection *>{
80 &padAdded, &padRemoved, &sourceSetup, &uridecodebinElementAdded,
81 &unknownType, &elementAdded, &elementRemoved,
82 };
83 for (QGObjectHandlerScopedConnection *handler : handlers)
84 handler->disconnect();
85
86 decodeBinQueues = 0;
87}
88
89QMaybe<QPlatformMediaPlayer *> QGstreamerMediaPlayer::create(QMediaPlayer *parent)
90{
91 auto videoOutput = QGstreamerVideoOutput::create();
92 if (!videoOutput)
93 return videoOutput.error();
94
95 QGstElement videoInputSelector =
96 QGstElement::createFromFactory("input-selector", "videoInputSelector");
97 if (!videoInputSelector)
98 return errorMessageCannotFindElement("input-selector");
99
100 QGstElement audioInputSelector =
101 QGstElement::createFromFactory("input-selector", "audioInputSelector");
102 if (!audioInputSelector)
103 return errorMessageCannotFindElement("input-selector");
104
105 QGstElement subTitleInputSelector =
106 QGstElement::createFromFactory("input-selector", "subTitleInputSelector");
107 if (!subTitleInputSelector)
108 return errorMessageCannotFindElement("input-selector");
109
110 return new QGstreamerMediaPlayer(videoOutput.value(), videoInputSelector, audioInputSelector,
111 subTitleInputSelector, parent);
112}
113
114QGstreamerMediaPlayer::QGstreamerMediaPlayer(QGstreamerVideoOutput *videoOutput,
115 QGstElement videoInputSelector,
116 QGstElement audioInputSelector,
117 QGstElement subTitleInputSelector,
119 : QObject(parent),
121 trackSelectors{ { { VideoStream, videoInputSelector },
122 { AudioStream, audioInputSelector },
123 { SubtitleStream, subTitleInputSelector } } },
124 playerPipeline(QGstPipeline::create("playerPipeline")),
125 gstVideoOutput(videoOutput)
126{
127 playerPipeline.setFlushOnConfigChanges(true);
128
129 gstVideoOutput->setParent(this);
130 gstVideoOutput->setPipeline(playerPipeline);
131
132 for (auto &ts : trackSelectors)
133 playerPipeline.add(ts.selector);
134
135 playerPipeline.setState(GST_STATE_NULL);
136 playerPipeline.installMessageFilter(static_cast<QGstreamerBusMessageFilter *>(this));
137 playerPipeline.installMessageFilter(static_cast<QGstreamerSyncMessageFilter *>(this));
138
139 QGstClockHandle systemClock{
140 gst_system_clock_obtain(),
141 };
142
143 gst_pipeline_use_clock(playerPipeline.pipeline(), systemClock.get());
144
145 connect(&positionUpdateTimer, &QTimer::timeout, this, [this] {
146 updatePosition();
147 });
148}
149
151{
152 playerPipeline.removeMessageFilter(static_cast<QGstreamerBusMessageFilter *>(this));
153 playerPipeline.removeMessageFilter(static_cast<QGstreamerSyncMessageFilter *>(this));
154 playerPipeline.setStateSync(GST_STATE_NULL);
155 topology.free();
156}
157
159{
160 if (playerPipeline.isNull() || m_url.isEmpty())
161 return 0;
162
163 return playerPipeline.position()/1e6;
164}
165
167{
168 return m_duration;
169}
170
172{
173 return m_bufferProgress/100.;
174}
175
180
182{
183 return playerPipeline.playbackRate();
184}
185
187{
188 bool applyRateToPipeline = state() != QMediaPlayer::StoppedState;
189 if (playerPipeline.setPlaybackRate(rate, applyRateToPipeline))
191}
192
194{
195 qint64 currentPos = playerPipeline.position()/1e6;
196 if (pos == currentPos)
197 return;
198 playerPipeline.finishStateChange();
199 playerPipeline.setPosition(pos*1e6);
200 qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << pos << playerPipeline.position()/1e6;
204}
205
207{
208 if (state() == QMediaPlayer::PlayingState || m_url.isEmpty())
209 return;
211
212 playerPipeline.setInStoppedState(false);
214 playerPipeline.setPosition(0);
216 }
217
218 qCDebug(qLcMediaPlayer) << "play().";
219 int ret = playerPipeline.setState(GST_STATE_PLAYING);
220 if (m_requiresSeekOnPlay) {
221 // Flushing the pipeline is required to get track changes
222 // immediately, when they happen while paused.
223 playerPipeline.flush();
224 m_requiresSeekOnPlay = false;
225 }
226 if (ret == GST_STATE_CHANGE_FAILURE)
227 qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the playing state.";
228
229 positionUpdateTimer.start(100);
231}
232
234{
235 if (state() == QMediaPlayer::PausedState || m_url.isEmpty()
236 || m_resourceErrorState != ResourceErrorState::NoError)
237 return;
238
239 positionUpdateTimer.stop();
240 if (playerPipeline.inStoppedState()) {
241 playerPipeline.setInStoppedState(false);
242 playerPipeline.flush();
243 }
244 int ret = playerPipeline.setState(GST_STATE_PAUSED);
245 if (ret == GST_STATE_CHANGE_FAILURE)
246 qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the paused state.";
248 playerPipeline.setPosition(0);
249 }
252
253 if (m_bufferProgress > 0 || !canTrackProgress())
255 else
257
258 emit bufferProgressChanged(m_bufferProgress / 100.);
259}
260
262{
264 if (position() != 0) {
265 playerPipeline.setPosition(0);
268 }
269 return;
270 }
271 stopOrEOS(false);
272}
273
275{
276 return playerPipeline.pipeline();
277}
278
279void QGstreamerMediaPlayer::stopOrEOS(bool eos)
280{
281 positionUpdateTimer.stop();
282 playerPipeline.setInStoppedState(true);
283 bool ret = playerPipeline.setStateSync(GST_STATE_PAUSED);
284 if (!ret)
285 qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the stopped state.";
286 if (!eos) {
287 playerPipeline.setPosition(0);
289 }
291 if (eos)
293 else
295 m_initialBufferProgressSent = false;
296}
297
298void QGstreamerMediaPlayer::detectPipelineIsSeekable()
299{
300 qCDebug(qLcMediaPlayer) << "detectPipelineIsSeekable";
302 gst_query_new_seeking(GST_FORMAT_TIME),
304 };
305 gboolean canSeek = false;
306 if (gst_element_query(playerPipeline.element(), query.get())) {
307 gst_query_parse_seeking(query.get(), nullptr, &canSeek, nullptr, nullptr);
308 qCDebug(qLcMediaPlayer) << " pipeline is seekable:" << canSeek;
309 } else {
310 qCWarning(qLcMediaPlayer) << " query for seekable failed.";
311 }
312 seekableChanged(canSeek);
313}
314
316{
317 qCDebug(qLcMediaPlayer) << "received bus message:" << message;
318
319 GstMessage* gm = message.message();
320 switch (message.type()) {
321 case GST_MESSAGE_TAG: {
322 // #### This isn't ideal. We shouldn't catch stream specific tags here, rather the global ones
323 QGstTagListHandle tagList;
324 gst_message_parse_tag(gm, &tagList);
325
326 qCDebug(qLcMediaPlayer) << " Got tags: " << tagList.get();
327 auto metaData = taglistToMetaData(tagList);
328 auto keys = metaData.keys();
329 for (auto k : metaData.keys())
330 m_metaData.insert(k, metaData.value(k));
331 if (!keys.isEmpty())
333
334 if (gstVideoOutput) {
335 QVariant rotation = m_metaData.value(QMediaMetaData::Orientation);
336 gstVideoOutput->setRotation(rotation.value<QtVideo::Rotation>());
337 }
338 break;
339 }
340 case GST_MESSAGE_DURATION_CHANGED: {
341 qint64 d = playerPipeline.duration()/1e6;
342 qCDebug(qLcMediaPlayer) << " duration changed message" << d;
343 if (d != m_duration) {
344 m_duration = d;
346 }
347 return false;
348 }
349 case GST_MESSAGE_EOS: {
350 qint64 duration = playerPipeline.duration() / 1e6;
352 if (doLoop()) {
353 setPosition(0);
354 break;
355 }
356 stopOrEOS(true);
357 break;
358 }
359 case GST_MESSAGE_BUFFERING: {
360 int progress = 0;
361 gst_message_parse_buffering(gm, &progress);
362
363 qCDebug(qLcMediaPlayer) << " buffering message: " << progress;
364
365 if (state() != QMediaPlayer::StoppedState && !prerolling) {
366 if (!m_initialBufferProgressSent) {
368 m_initialBufferProgressSent = true;
369 }
370
371 if (m_bufferProgress > 0 && progress == 0)
373 else if (progress >= 50)
374 // QTBUG-124517: rethink buffering
376 else
378 }
379
380 m_bufferProgress = progress;
381
382 emit bufferProgressChanged(m_bufferProgress / 100.);
383 break;
384 }
385 case GST_MESSAGE_STATE_CHANGED: {
386 if (message.source() != playerPipeline)
387 return false;
388
389 GstState oldState;
390 GstState newState;
391 GstState pending;
392
393 gst_message_parse_state_changed(gm, &oldState, &newState, &pending);
394 qCDebug(qLcMediaPlayer) << " state changed message from"
396
397 switch (newState) {
398 case GST_STATE_VOID_PENDING:
399 case GST_STATE_NULL:
400 case GST_STATE_READY:
401 break;
402 case GST_STATE_PAUSED: {
403 if (prerolling) {
404 qCDebug(qLcMediaPlayer) << "Preroll done, setting status to Loaded";
405 prerolling = false;
406 GST_DEBUG_BIN_TO_DOT_FILE(playerPipeline.bin(), GST_DEBUG_GRAPH_SHOW_ALL,
407 "playerPipeline");
408
409 qint64 d = playerPipeline.duration() / 1e6;
410 if (d != m_duration) {
411 m_duration = d;
412 qCDebug(qLcMediaPlayer) << " duration changed" << d;
414 }
415
416 parseStreamsAndMetadata();
417
420
421 if (!playerPipeline.inStoppedState()) {
422 Q_ASSERT(!m_initialBufferProgressSent);
423
424 bool immediatelySendBuffered = !canTrackProgress() || m_bufferProgress > 0;
426 m_initialBufferProgressSent = true;
427 if (immediatelySendBuffered)
429 }
430 }
431
432 break;
433 }
434 case GST_STATE_PLAYING: {
435 if (!m_initialBufferProgressSent) {
436 bool immediatelySendBuffered = !canTrackProgress() || m_bufferProgress > 0;
438 m_initialBufferProgressSent = true;
439 if (immediatelySendBuffered)
441 }
442 break;
443 }
444 }
445 break;
446 }
447 case GST_MESSAGE_ERROR: {
448 qCDebug(qLcMediaPlayer) << " error" << QCompactGstMessageAdaptor(message);
449
452 gst_message_parse_error(gm, &err, &debug);
453 GQuark errorDomain = err.get()->domain;
454 gint errorCode = err.get()->code;
455
456 if (errorDomain == GST_STREAM_ERROR) {
457 if (errorCode == GST_STREAM_ERROR_CODEC_NOT_FOUND)
458 error(QMediaPlayer::FormatError, tr("Cannot play stream of type: <unknown>"));
459 else {
461 }
462 } else if (errorDomain == GST_RESOURCE_ERROR) {
463 if (errorCode == GST_RESOURCE_ERROR_NOT_FOUND) {
464 if (m_resourceErrorState != ResourceErrorState::ErrorReported) {
465 // gstreamer seems to deliver multiple GST_RESOURCE_ERROR_NOT_FOUND events
467 m_resourceErrorState = ResourceErrorState::ErrorReported;
468 m_url.clear();
469 }
470 } else {
472 }
473 } else {
474 playerPipeline.dumpGraph("error");
475 }
477 break;
478 }
479
480 case GST_MESSAGE_WARNING:
481 qCWarning(qLcMediaPlayer) << "Warning:" << QCompactGstMessageAdaptor(message);
482 playerPipeline.dumpGraph("warning");
483 break;
484
485 case GST_MESSAGE_INFO:
486 if (qLcMediaPlayer().isDebugEnabled())
487 qCDebug(qLcMediaPlayer) << "Info:" << QCompactGstMessageAdaptor(message);
488 break;
489
490 case GST_MESSAGE_SEGMENT_START: {
491 qCDebug(qLcMediaPlayer) << " segment start message, updating position";
492 QGstStructure structure(gst_message_get_structure(gm));
493 auto p = structure["position"].toInt64();
494 if (p) {
495 qint64 position = (*p)/1000000;
497 }
498 break;
499 }
500 case GST_MESSAGE_ELEMENT: {
501 QGstStructure structure(gst_message_get_structure(gm));
502 auto type = structure.name();
503 if (type == "stream-topology") {
504 topology.free();
505 topology = structure.copy();
506 }
507 break;
508 }
509
510 case GST_MESSAGE_ASYNC_DONE: {
511 detectPipelineIsSeekable();
512 break;
513 }
514
515 default:
516// qCDebug(qLcMediaPlayer) << " default message handler, doing nothing";
517
518 break;
519 }
520
521 return false;
522}
523
525{
526#if QT_CONFIG(gstreamer_gl)
527 if (message.type() != GST_MESSAGE_NEED_CONTEXT)
528 return false;
529 const gchar *type = nullptr;
530 gst_message_parse_context_type (message.message(), &type);
531 if (strcmp(type, GST_GL_DISPLAY_CONTEXT_TYPE))
532 return false;
533 if (!gstVideoOutput || !gstVideoOutput->gstreamerVideoSink())
534 return false;
535 auto *context = gstVideoOutput->gstreamerVideoSink()->gstGlDisplayContext();
536 if (!context)
537 return false;
538 gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message.message())), context);
539 playerPipeline.dumpGraph("need_context");
540 return true;
541#else
543 return false;
544#endif
545}
546
548{
549 return m_url;
550}
551
553{
554 return m_stream;
555}
556
557void QGstreamerMediaPlayer::decoderPadAdded(const QGstElement &src, const QGstPad &pad)
558{
559 if (src != decoder)
560 return;
561
562 auto caps = pad.currentCaps();
563 auto type = caps.at(0).name();
564 qCDebug(qLcMediaPlayer) << "Received new pad" << pad.name() << "from" << src.name() << "type" << type;
565 qCDebug(qLcMediaPlayer) << " " << caps;
566
567 TrackType streamType = NTrackTypes;
568 if (type.startsWith("video/x-raw")) {
569 streamType = VideoStream;
570 } else if (type.startsWith("audio/x-raw")) {
571 streamType = AudioStream;
572 } else if (type.startsWith("text/")) {
573 streamType = SubtitleStream;
574 } else {
575 qCWarning(qLcMediaPlayer) << "Ignoring unknown media stream:" << pad.name() << type;
576 return;
577 }
578
579 auto &ts = trackSelector(streamType);
580 QGstPad sinkPad = ts.createInputPad();
581 if (!pad.link(sinkPad)) {
582 qCWarning(qLcMediaPlayer) << "Failed to add track, cannot link pads";
583 return;
584 }
585 qCDebug(qLcMediaPlayer) << "Adding track";
586
587 if (ts.trackCount() == 1) {
588 if (streamType == VideoStream) {
589 connectOutput(ts);
590 ts.setActiveInputPad(sinkPad);
592 }
593 else if (streamType == AudioStream) {
594 connectOutput(ts);
595 ts.setActiveInputPad(sinkPad);
597 }
598 }
599
600 if (!prerolling)
602
603 decoderOutputMap.insert(pad.name(), sinkPad);
604}
605
606void QGstreamerMediaPlayer::decoderPadRemoved(const QGstElement &src, const QGstPad &pad)
607{
608 if (src != decoder)
609 return;
610
611 qCDebug(qLcMediaPlayer) << "Removed pad" << pad.name() << "from" << src.name();
612 auto track = decoderOutputMap.value(pad.name());
613 if (track.isNull())
614 return;
615
616 auto ts = std::find_if(std::begin(trackSelectors), std::end(trackSelectors),
617 [&](TrackSelector &ts){ return ts.selector == track.parent(); });
618 if (ts == std::end(trackSelectors))
619 return;
620
621 qCDebug(qLcMediaPlayer) << " was linked to pad" << track.name() << "from" << ts->selector.name();
622 ts->removeInputPad(track);
623
624 if (ts->trackCount() == 0) {
625 removeOutput(*ts);
626 if (ts->type == AudioStream)
628 else if (ts->type == VideoStream)
630 }
631
632 if (!prerolling)
634}
635
636void QGstreamerMediaPlayer::removeAllOutputs()
637{
638 for (auto &ts : trackSelectors) {
639 removeOutput(ts);
640 ts.removeAllInputPads();
641 }
644}
645
646void QGstreamerMediaPlayer::connectOutput(TrackSelector &ts)
647{
648 if (ts.isConnected)
649 return;
650
651 QGstElement e;
652 switch (ts.type) {
653 case AudioStream:
654 e = gstAudioOutput ? gstAudioOutput->gstElement() : QGstElement{};
655 break;
656 case VideoStream:
657 e = gstVideoOutput ? gstVideoOutput->gstElement() : QGstElement{};
658 break;
659 case SubtitleStream:
660 if (gstVideoOutput)
661 gstVideoOutput->linkSubtitleStream(ts.selector);
662 break;
663 default:
664 return;
665 }
666
667 if (!e.isNull()) {
668 qCDebug(qLcMediaPlayer) << "connecting output for track type" << ts.type;
669 playerPipeline.add(e);
670 qLinkGstElements(ts.selector, e);
672 }
673
674 ts.isConnected = true;
675}
676
677void QGstreamerMediaPlayer::removeOutput(TrackSelector &ts)
678{
679 if (!ts.isConnected)
680 return;
681
682 QGstElement e;
683 switch (ts.type) {
684 case AudioStream:
685 e = gstAudioOutput ? gstAudioOutput->gstElement() : QGstElement{};
686 break;
687 case VideoStream:
688 e = gstVideoOutput ? gstVideoOutput->gstElement() : QGstElement{};
689 break;
690 case SubtitleStream:
691 if (gstVideoOutput)
692 gstVideoOutput->unlinkSubtitleStream();
693 break;
694 default:
695 break;
696 }
697
698 if (!e.isNull()) {
699 qCDebug(qLcMediaPlayer) << "removing output for track type" << ts.type;
700 playerPipeline.stopAndRemoveElements(e);
701 }
702
703 ts.isConnected = false;
704}
705
706void QGstreamerMediaPlayer::removeDynamicPipelineElements()
707{
708 for (QGstElement *element : { &src, &decoder }) {
709 if (element->isNull())
710 continue;
711
712 element->setStateSync(GstState::GST_STATE_NULL);
713 playerPipeline.remove(*element);
714 *element = QGstElement{};
715 }
716}
717
718void QGstreamerMediaPlayer::uridecodebinElementAddedCallback(GstElement * /*uridecodebin*/,
719 GstElement *child,
721{
723 qCDebug(qLcMediaPlayer) << "New element added to uridecodebin:" << c.name();
724
725 static const GType decodeBinType = [] {
727 gst_element_factory_find("decodebin"),
728 };
729 return gst_element_factory_get_element_type(factory.get());
730 }();
731
732 if (c.type() == decodeBinType) {
733 qCDebug(qLcMediaPlayer) << " -> setting post-stream-topology property";
734 c.set("post-stream-topology", true);
735 }
736}
737
738void QGstreamerMediaPlayer::sourceSetupCallback(GstElement *uridecodebin, GstElement *source, QGstreamerMediaPlayer *that)
739{
740 Q_UNUSED(uridecodebin)
741 Q_UNUSED(that)
742
743 qCDebug(qLcMediaPlayer) << "Setting up source:" << g_type_name_from_instance((GTypeInstance*)source);
744
745 if (std::string_view("GstRTSPSrc") == g_type_name_from_instance((GTypeInstance *)source)) {
747 int latency{40};
748 bool ok{false};
749 int v = qEnvironmentVariableIntValue("QT_MEDIA_RTSP_LATENCY", &ok);
750 if (ok)
751 latency = v;
752 qCDebug(qLcMediaPlayer) << " -> setting source latency to:" << latency << "ms";
753 s.set("latency", latency);
754
755 bool drop{true};
756 v = qEnvironmentVariableIntValue("QT_MEDIA_RTSP_DROP_ON_LATENCY", &ok);
757 if (ok && v == 0)
758 drop = false;
759 qCDebug(qLcMediaPlayer) << " -> setting drop-on-latency to:" << drop;
760 s.set("drop-on-latency", drop);
761
762 bool retrans{false};
763 v = qEnvironmentVariableIntValue("QT_MEDIA_RTSP_DO_RETRANSMISSION", &ok);
764 if (ok && v != 0)
765 retrans = true;
766 qCDebug(qLcMediaPlayer) << " -> setting do-retransmission to:" << retrans;
767 s.set("do-retransmission", retrans);
768 }
769}
770
771void QGstreamerMediaPlayer::unknownTypeCallback(GstElement *decodebin, GstPad *pad, GstCaps *caps,
773{
774 Q_UNUSED(decodebin)
775 Q_UNUSED(pad)
776 Q_UNUSED(self)
777 qCDebug(qLcMediaPlayer) << "Unknown type:" << caps;
778
779 QMetaObject::invokeMethod(self, [self] {
780 self->stop();
781 });
782}
783
784static bool isQueue(const QGstElement &element)
785{
786 static const GType queueType = [] {
788 gst_element_factory_find("queue"),
789 };
790 return gst_element_factory_get_element_type(factory.get());
791 }();
792
793 static const GType multiQueueType = [] {
795 gst_element_factory_find("multiqueue"),
796 };
797 return gst_element_factory_get_element_type(factory.get());
798 }();
799
800 return element.type() == queueType || element.type() == multiQueueType;
801}
802
803void QGstreamerMediaPlayer::decodebinElementAddedCallback(GstBin * /*decodebin*/,
804 GstBin * /*sub_bin*/, GstElement *child,
806{
808 if (isQueue(c))
809 self->decodeBinQueues += 1;
810}
811
812void QGstreamerMediaPlayer::decodebinElementRemovedCallback(GstBin * /*decodebin*/,
813 GstBin * /*sub_bin*/, GstElement *child,
815{
817 if (isQueue(c))
818 self->decodeBinQueues -= 1;
819}
820
822{
823 qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << "setting location to" << content;
824
825 prerolling = true;
826 m_resourceErrorState = ResourceErrorState::NoError;
827
828 bool ret = playerPipeline.setStateSync(GST_STATE_NULL);
829 if (!ret)
830 qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the stopped state.";
831
832 m_url = content;
833 m_stream = stream;
834
835 removeDynamicPipelineElements();
836 disconnectDecoderHandlers();
837 removeAllOutputs();
838 seekableChanged(false);
839 Q_ASSERT(playerPipeline.inStoppedState());
840
841 if (m_duration != 0) {
842 m_duration = 0;
844 }
846 if (position() != 0)
848 if (!m_metaData.isEmpty()) {
849 m_metaData.clear();
851 }
852
853 if (content.isEmpty() && !stream)
855
856 if (content.isEmpty())
857 return;
858
859 if (m_stream) {
860 if (!m_appSrc) {
861 auto maybeAppSrc = QGstAppSource::create(this);
862 if (maybeAppSrc) {
863 m_appSrc = maybeAppSrc.value();
864 } else {
865 error(QMediaPlayer::ResourceError, maybeAppSrc.error());
866 return;
867 }
868 }
869 src = m_appSrc->element();
870 decoder = QGstElement::createFromFactory("decodebin", "decoder");
871 if (!decoder) {
873 return;
874 }
875 decoder.set("post-stream-topology", true);
876 decoder.set("use-buffering", true);
877 unknownType = decoder.connect("unknown-type", GCallback(unknownTypeCallback), this);
878 elementAdded = decoder.connect("deep-element-added",
879 GCallback(decodebinElementAddedCallback), this);
880 elementRemoved = decoder.connect("deep-element-removed",
881 GCallback(decodebinElementAddedCallback), this);
882
883 playerPipeline.add(src, decoder);
884 qLinkGstElements(src, decoder);
885
886 m_appSrc->setup(m_stream);
887 seekableChanged(!stream->isSequential());
888 } else {
889 // use uridecodebin
890 decoder = QGstElement::createFromFactory("uridecodebin", "decoder");
891 if (!decoder) {
893 return;
894 }
895 playerPipeline.add(decoder);
896
897 constexpr bool hasPostStreamTopology = GST_CHECK_VERSION(1, 22, 0);
898 if constexpr (hasPostStreamTopology) {
899 decoder.set("post-stream-topology", true);
900 } else {
901 // can't set post-stream-topology to true, as uridecodebin doesn't have the property.
902 // Use a hack
903 uridecodebinElementAdded = decoder.connect(
904 "element-added", GCallback(uridecodebinElementAddedCallback), this);
905 }
906
907 sourceSetup = decoder.connect("source-setup", GCallback(sourceSetupCallback), this);
908 unknownType = decoder.connect("unknown-type", GCallback(unknownTypeCallback), this);
909
910 decoder.set("uri", content.toEncoded().constData());
911 decoder.set("use-buffering", true);
912
913 constexpr int mb = 1024 * 1024;
914 decoder.set("ring-buffer-max-size", 2 * mb);
915
916 if (m_bufferProgress != 0) {
917 m_bufferProgress = 0;
919 }
920
921 elementAdded = decoder.connect("deep-element-added",
922 GCallback(decodebinElementAddedCallback), this);
923 elementRemoved = decoder.connect("deep-element-removed",
924 GCallback(decodebinElementAddedCallback), this);
925 }
926 padAdded = decoder.onPadAdded<&QGstreamerMediaPlayer::decoderPadAdded>(this);
927 padRemoved = decoder.onPadRemoved<&QGstreamerMediaPlayer::decoderPadRemoved>(this);
928
930 if (!playerPipeline.setState(GST_STATE_PAUSED)) {
931 qCWarning(qLcMediaPlayer) << "Unable to set the pipeline to the paused state.";
932 // Note: no further error handling: errors will be delivered via a GstMessage
933 return;
934 }
935
936 playerPipeline.setPosition(0);
938}
939
941{
942 if (gstAudioOutput == output)
943 return;
944
945 auto &ts = trackSelector(AudioStream);
946
947 playerPipeline.modifyPipelineWhileNotRunning([&] {
948 if (gstAudioOutput)
949 removeOutput(ts);
950
951 gstAudioOutput = static_cast<QGstreamerAudioOutput *>(output);
952 if (gstAudioOutput)
953 connectOutput(ts);
954 });
955}
956
958{
959 return m_metaData;
960}
961
966
968{
969 QGstStructure e = s;
970 while (1) {
971 auto next = e["next"].toStructure();
972 if (!next.isNull())
973 e = next;
974 else
975 break;
976 }
977 return e;
978}
979
980void QGstreamerMediaPlayer::parseStreamsAndMetadata()
981{
982 qCDebug(qLcMediaPlayer) << "============== parse topology ============";
983 if (topology.isNull()) {
984 qCDebug(qLcMediaPlayer) << " null topology";
985 return;
986 }
987 auto caps = topology["caps"].toCaps();
988 auto structure = caps.at(0);
990 qCDebug(qLcMediaPlayer) << caps << fileFormat;
993 m_metaData.insert(QMediaMetaData::Url, m_url);
994 QGValue tags = topology["tags"];
995 if (!tags.isNull()) {
996 QGstTagListHandle tagList;
997 gst_structure_get(topology.structure, "tags", GST_TYPE_TAG_LIST, &tagList, nullptr);
998
999 const auto metaData = taglistToMetaData(tagList);
1000 for (auto k : metaData.keys())
1001 m_metaData.insert(k, metaData.value(k));
1002 }
1003
1004 auto demux = endOfChain(topology);
1005 auto next = demux["next"];
1006 if (!next.isList()) {
1007 qCDebug(qLcMediaPlayer) << " no additional streams";
1009 return;
1010 }
1011
1012 // collect stream info
1013 int size = next.listSize();
1014 for (int i = 0; i < size; ++i) {
1015 auto val = next.at(i);
1016 caps = val.toStructure()["caps"].toCaps();
1017 structure = caps.at(0);
1018 if (structure.name().startsWith("audio/")) {
1021 qCDebug(qLcMediaPlayer) << " audio" << caps << (int)codec;
1022 } else if (structure.name().startsWith("video/")) {
1025 qCDebug(qLcMediaPlayer) << " video" << caps << (int)codec;
1026 auto framerate = structure["framerate"].getFraction();
1027 if (framerate)
1028 m_metaData.insert(QMediaMetaData::VideoFrameRate, *framerate);
1029
1030 QSize resolution = structure.resolution();
1031 if (resolution.isValid())
1032 m_metaData.insert(QMediaMetaData::Resolution, resolution);
1033
1034 QSize nativeSize = structure.nativeSize();
1035 gstVideoOutput->setNativeSize(nativeSize);
1036 }
1037 }
1038
1039 auto sinkPad = trackSelector(VideoStream).activeInputPad();
1040 if (!sinkPad.isNull()) {
1041 QGstTagListHandle tagList;
1042
1043 g_object_get(sinkPad.object(), "tags", &tagList, nullptr);
1044 if (tagList)
1045 qCDebug(qLcMediaPlayer) << " tags=" << tagList.get();
1046 else
1047 qCDebug(qLcMediaPlayer) << " tags=(null)";
1048 }
1049
1050
1051 qCDebug(qLcMediaPlayer) << "============== end parse topology ============";
1053 playerPipeline.dumpGraph("playback");
1054}
1055
1057{
1058 return trackSelector(type).trackCount();
1059}
1060
1062{
1063 auto track = trackSelector(type).inputPad(index);
1064 if (track.isNull())
1065 return {};
1066
1067 QGstTagListHandle tagList;
1068 g_object_get(track.object(), "tags", &tagList, nullptr);
1069
1070 return taglistToMetaData(tagList);
1071}
1072
1074{
1075 return trackSelector(type).activeInputIndex();
1076}
1077
1079{
1080 auto &ts = trackSelector(type);
1081 auto track = ts.inputPad(index);
1082 if (track.isNull() && index != -1) {
1083 qCWarning(qLcMediaPlayer) << "Attempt to set an incorrect index" << index
1084 << "for the track type" << type;
1085 return;
1086 }
1087
1088 qCDebug(qLcMediaPlayer) << "Setting the index" << index << "for the track type" << type;
1090 gstVideoOutput->flushSubtitles();
1091
1092 playerPipeline.modifyPipelineWhileNotRunning([&] {
1093 if (track.isNull()) {
1094 removeOutput(ts);
1095 } else {
1096 ts.setActiveInputPad(track);
1097 connectOutput(ts);
1098 }
1099 });
1100
1101 // seek to force an immediate change of the stream
1102 if (playerPipeline.state() == GST_STATE_PLAYING)
1103 playerPipeline.flush();
1104 else
1105 m_requiresSeekOnPlay = true;
1106}
1107
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
bool isNull() const
Definition qgst.cpp:75
bool setup(QIODevice *stream=nullptr, qint64 offset=0)
static QMaybe< QGstAppSource * > create(QObject *parent=nullptr)
QGstElement element() const
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void add)(const Ts &...ts)
Definition qgst_p.h:691
GstBin * bin() const
Definition qgst.cpp:1113
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void remove)(const Ts &...ts)
Definition qgst_p.h:700
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void stopAndRemoveElements)(Ts... ts)
Definition qgst_p.h:710
QGstStructure at(int index) const
Definition qgst.cpp:521
QGObjectHandlerConnection onPadRemoved(T *instance)
Definition qgst_p.h:605
GstElement * element() const
Definition qgst.cpp:1018
QGstPad getRequestPad(const char *name) const
Definition qgst.cpp:913
bool finishStateChange(std::chrono::nanoseconds timeout=std::chrono::seconds(5))
Definition qgst.cpp:975
bool setStateSync(GstState state, std::chrono::nanoseconds timeout=std::chrono::seconds(1))
Definition qgst.cpp:947
bool syncStateWithParent()
Definition qgst.cpp:969
QGObjectHandlerConnection onPadAdded(T *instance)
Definition qgst_p.h:591
static QGstElement createFromFactory(const char *factory, const char *name=nullptr)
Definition qgst.cpp:835
GstState state(std::chrono::nanoseconds timeout=std::chrono::seconds(0)) const
Definition qgst.cpp:927
QGObjectHandlerConnection connect(const char *name, GCallback callback, gpointer userData)
Definition qgst.cpp:653
const char * name() const
Definition qgst.cpp:682
void set(const char *property, const char *str)
Definition qgst.cpp:538
GType type() const
Definition qgst.cpp:667
bool link(const QGstPad &sink) const
Definition qgst.cpp:757
QGstCaps currentCaps() const
Definition qgst.cpp:742
GstStateChangeReturn setState(GstState state)
GstPipeline * pipeline() const
bool inStoppedState() const
void dumpGraph(const char *fileName)
void removeMessageFilter(QGstreamerSyncMessageFilter *filter)
qint64 duration() const
qint64 position() const
double playbackRate() const
void setInStoppedState(bool stopped)
bool setPosition(qint64 pos)
bool setPlaybackRate(double rate, bool applyToPipeline=true)
static QGstPipeline create(const char *name)
void modifyPipelineWhileNotRunning(Functor &&fn)
QGstStructure copy() const
Definition qgst.cpp:185
const GstStructure * structure
Definition qgst_p.h:315
void free()
Definition qgst.cpp:163
QByteArrayView name() const
Definition qgst.cpp:175
bool isNull() const
Definition qgst.cpp:170
QGstElement gstElement() const
static QMediaFormat::FileFormat fileFormatForCaps(QGstStructure structure)
static QMediaFormat::VideoCodec videoCodecForCaps(QGstStructure structure)
static QMediaFormat::AudioCodec audioCodecForCaps(QGstStructure structure)
QUrl media() const override
bool processBusMessage(const QGstreamerMessage &message) override
void setPosition(qint64 pos) override
static QMaybe< QPlatformMediaPlayer * > create(QMediaPlayer *parent=nullptr)
qreal playbackRate() const override
void setMedia(const QUrl &, QIODevice *) override
qint64 duration() const override
void setPlaybackRate(qreal rate) override
qint64 position() const override
QMediaTimeRange availablePlaybackRanges() const override
void setVideoSink(QVideoSink *sink) override
int trackCount(TrackType) override
float bufferProgress() const override
bool processSyncMessage(const QGstreamerMessage &message) override
QMediaMetaData trackMetaData(TrackType, int) override
void setActiveTrack(TrackType, int) override
void setAudioOutput(QPlatformAudioOutput *output) override
const QIODevice * mediaStream() const override
int activeTrack(TrackType) override
QMediaMetaData metaData() const override
QGstreamerVideoSink * gstreamerVideoSink() const
QGstElement gstElement() const
void linkSubtitleStream(QGstElement subtitleSrc)
void setVideoSink(QVideoSink *sink)
void setRotation(QtVideo::Rotation)
static QMaybe< QGstreamerVideoOutput * > create(QObject *parent=nullptr)
GstContext * gstGlDisplayContext() const
T value(const Key &key) const noexcept
Definition qhash.h:1054
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1303
\inmodule QtCore \reentrant
Definition qiodevice.h:34
void append(parameter_type t)
Definition qlist.h:458
\inmodule QtMultimedia
Q_INVOKABLE bool isEmpty() const
\qmlmethod bool QtMultimedia::mediaMetaData::isEmpty() Returns true if the meta data contains no item...
Q_INVOKABLE void insert(Key k, const QVariant &value)
\qmlmethod void QtMultimedia::mediaMetaData::insert(Key k, variant value) Inserts a value into a Key:...
Q_INVOKABLE QVariant value(Key k) const
\variable QMediaMetaData::NumMetaData
Q_INVOKABLE QList< Key > keys() const
\qmlmethod list<Key> QtMultimedia::mediaMetaData::keys() Returns a list of MediaMetaData....
Q_INVOKABLE void clear()
\qmlmethod void QtMultimedia::mediaMetaData::clear() Removes all data from the MediaMetaData object.
The QMediaPlayer class allows the playing of a media files.
The QMediaTimeRange class represents a set of zero or more disjoint time intervals.
\inmodule QtCore
Definition qobject.h:103
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
virtual QMediaPlayer::PlaybackState state() const
void positionChanged(qint64 position)
virtual QMediaPlayer::MediaStatus mediaStatus() const
void seekableChanged(bool seekable)
void durationChanged(qint64 duration)
void playbackRateChanged(qreal rate)
void audioAvailableChanged(bool audioAvailable)
void stateChanged(QMediaPlayer::PlaybackState newState)
void videoAvailableChanged(bool videoAvailable)
void mediaStatusChanged(QMediaPlayer::MediaStatus status)
void bufferProgressChanged(float progress)
\inmodule QtCore
Definition qsize.h:25
constexpr bool isValid() const noexcept
Returns true if both the width and height is equal to or greater than 0; otherwise returns false.
Definition qsize.h:127
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
void stop()
Stops the timer.
Definition qtimer.cpp:267
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
Type get() const noexcept
\inmodule QtCore
Definition qurl.h:94
QByteArray toEncoded(FormattingOptions options=FullyEncoded) const
Returns the encoded representation of the URL if it's valid; otherwise an empty QByteArray is returne...
Definition qurl.cpp:2967
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
Definition qurl.cpp:1896
void clear()
Resets the content of the QUrl.
Definition qurl.cpp:1909
\inmodule QtCore
Definition qvariant.h:65
T value() const &
Definition qvariant.h:516
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
Definition qvariant.h:536
The QVideoSink class represents a generic sink for video data.
Definition qvideosink.h:22
cache insert(employee->id(), employee)
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
short next
Definition keywords.cpp:445
Combined button and popup list for selecting options.
QString self
Definition language.cpp:58
static void * context
#define Q_FUNC_INFO
DBusConnection const char DBusError * error
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage return DBusPendingCall * pending
EGLStreamKHR stream
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
QMediaFormat::AudioCodec codec
QMediaFormat::FileFormat fileFormat
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void qLinkGstElements)(const Ts &...ts)
Definition qgst_p.h:644
QString errorMessageCannotFindElement(std::string_view element)
Definition qgst_p.h:817
static bool isQueue(const QGstElement &element)
static QGstStructure endOfChain(const QGstStructure &s)
QMediaMetaData taglistToMetaData(const GstTagList *tagList)
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
return ret
n void setPosition(void) \n\
GLsizei const GLfloat * v
[13]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLenum src
GLenum type
GLuint GLsizei const GLchar * message
GLsizei GLsizei GLchar * source
GLdouble s
[6]
Definition qopenglext.h:235
GLenum query
const GLubyte * c
GLuint GLfloat * val
GLuint GLenum * rate
GLsizei GLenum GLboolean sink
GLfloat GLfloat p
[1]
static void add(QPainterPath &path, const QWingedEdge &list, int edge, QPathEdge::Traversal traversal)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define tr(X)
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
#define emit
#define Q_UNUSED(x)
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
QT_BEGIN_NAMESPACE typedef uchar * output
QFileSelector selector
[1]
QStringList keys
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
myObject disconnect()
[26]
QItemEditorFactory * factory
QLayoutItem * child
[0]
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...