Qt 6.x
The Qt SDK
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#include <qgstpipeline_p.h>
11#include "qgstreamermessage_p.h"
13#include <qgstappsrc_p.h>
14#include <qaudiodevice.h>
15
16#include <QtCore/qdir.h>
17#include <QtCore/qsocketnotifier.h>
18#include <QtCore/qurl.h>
19#include <QtCore/qdebug.h>
20#include <QtCore/qloggingcategory.h>
21#include <QtNetwork/qnetworkaccessmanager.h>
22#include <QtNetwork/qnetworkreply.h>
23
24#include <sys/types.h>
25#include <sys/stat.h>
26#include <fcntl.h>
27
28static Q_LOGGING_CATEGORY(qLcMediaPlayer, "qt.multimedia.player")
29
31
32QGstreamerMediaPlayer::TrackSelector::TrackSelector(TrackType type, QGstElement selector)
34{
35 selector.set("sync-streams", true);
36 selector.set("sync-mode", 1 /*clock*/);
37
38 if (type == SubtitleStream)
39 selector.set("cache-buffers", true);
40}
41
42QGstPad QGstreamerMediaPlayer::TrackSelector::createInputPad()
43{
44 auto pad = selector.getRequestPad("sink_%u");
45 tracks.append(pad);
46 return pad;
47}
48
49void QGstreamerMediaPlayer::TrackSelector::removeAllInputPads()
50{
51 for (auto &pad : tracks)
52 selector.releaseRequestPad(pad);
53 tracks.clear();
54}
55
56void QGstreamerMediaPlayer::TrackSelector::removeInputPad(QGstPad pad)
57{
58 selector.releaseRequestPad(pad);
59 tracks.removeOne(pad);
60}
61
62QGstPad QGstreamerMediaPlayer::TrackSelector::inputPad(int index)
63{
64 if (index >= 0 && index < tracks.count())
65 return tracks[index];
66 return {};
67}
68
69QGstreamerMediaPlayer::TrackSelector &QGstreamerMediaPlayer::trackSelector(TrackType type)
70{
71 auto &ts = trackSelectors[type];
72 Q_ASSERT(ts.type == type);
73 return ts;
74}
75
77{
78 auto videoOutput = QGstreamerVideoOutput::create();
79 if (!videoOutput)
80 return videoOutput.error();
81
82 QGstElement decodebin("decodebin", nullptr);
83 if (!decodebin)
84 return errorMessageCannotFindElement("decodebin");
85
86 QGstElement videoInputSelector("input-selector", "videoInputSelector");
87 if (!videoInputSelector)
88 return errorMessageCannotFindElement("input-selector");
89
90 QGstElement audioInputSelector("input-selector", "audioInputSelector");
91 if (!audioInputSelector)
92 return errorMessageCannotFindElement("input-selector");
93
94 QGstElement subTitleInputSelector("input-selector", "subTitleInputSelector");
95 if (!subTitleInputSelector)
96 return errorMessageCannotFindElement("input-selector");
97
98 return new QGstreamerMediaPlayer(videoOutput.value(), decodebin, videoInputSelector,
99 audioInputSelector, subTitleInputSelector, parent);
100}
101
102QGstreamerMediaPlayer::QGstreamerMediaPlayer(QGstreamerVideoOutput *videoOutput,
103 QGstElement decodebin,
104 QGstElement videoInputSelector,
105 QGstElement audioInputSelector,
106 QGstElement subTitleInputSelector,
108 : QObject(parent),
110 trackSelectors{ { { VideoStream, videoInputSelector },
111 { AudioStream, audioInputSelector },
112 { SubtitleStream, subTitleInputSelector } } },
113 playerPipeline("playerPipeline"),
114 gstVideoOutput(videoOutput)
115{
116 playerPipeline.setFlushOnConfigChanges(true);
117
118 gstVideoOutput->setParent(this);
119 gstVideoOutput->setPipeline(playerPipeline);
120
121 for (auto &ts : trackSelectors)
122 playerPipeline.add(ts.selector);
123
124 playerPipeline.setState(GST_STATE_NULL);
125 playerPipeline.installMessageFilter(static_cast<QGstreamerBusMessageFilter *>(this));
126 playerPipeline.installMessageFilter(static_cast<QGstreamerSyncMessageFilter *>(this));
127
128 gst_pipeline_use_clock(playerPipeline.pipeline(), gst_system_clock_obtain());
129
130 /* Taken from gstdicoverer.c:
131 * This is ugly. We get the GType of decodebin so we can quickly detect
132 * when a decodebin is added to uridecodebin so we can set the
133 * post-stream-topology setting to TRUE */
134 decodebinType = G_OBJECT_TYPE(decodebin.element());
135 connect(&positionUpdateTimer, &QTimer::timeout, this, &QGstreamerMediaPlayer::updatePosition);
136}
137
139{
140 playerPipeline.removeMessageFilter(static_cast<QGstreamerBusMessageFilter *>(this));
141 playerPipeline.removeMessageFilter(static_cast<QGstreamerSyncMessageFilter *>(this));
142 playerPipeline.setStateSync(GST_STATE_NULL);
143 topology.free();
144}
145
147{
148 if (playerPipeline.isNull() || m_url.isEmpty())
149 return 0;
150
151 return playerPipeline.position()/1e6;
152}
153
155{
156 return m_duration;
157}
158
160{
161 return m_bufferProgress/100.;
162}
163
165{
166 return QMediaTimeRange();
167}
168
170{
171 return playerPipeline.playbackRate();
172}
173
175{
176 if (playerPipeline.setPlaybackRate(rate))
178}
179
181{
182 qint64 currentPos = playerPipeline.position()/1e6;
183 if (pos == currentPos)
184 return;
185 playerPipeline.finishStateChange();
186 playerPipeline.setPosition(pos*1e6);
187 qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << pos << playerPipeline.position()/1e6;
191}
192
194{
195 if (state() == QMediaPlayer::PlayingState || m_url.isEmpty())
196 return;
198
199 playerPipeline.setInStoppedState(false);
201 playerPipeline.setPosition(0);
203 }
204
205 qCDebug(qLcMediaPlayer) << "play().";
206 int ret = playerPipeline.setState(GST_STATE_PLAYING);
207 if (m_requiresSeekOnPlay) {
208 // Flushing the pipeline is required to get track changes
209 // immediately, when they happen while paused.
210 playerPipeline.flush();
211 m_requiresSeekOnPlay = false;
212 }
213 if (ret == GST_STATE_CHANGE_FAILURE)
214 qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the playing state.";
218 positionUpdateTimer.start(100);
219}
220
222{
223 if (state() == QMediaPlayer::PausedState || m_url.isEmpty())
224 return;
225
226 positionUpdateTimer.stop();
227 if (playerPipeline.inStoppedState()) {
228 playerPipeline.setInStoppedState(false);
229 playerPipeline.flush();
230 }
231 int ret = playerPipeline.setState(GST_STATE_PAUSED);
232 if (ret == GST_STATE_CHANGE_FAILURE)
233 qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the paused state.";
235 playerPipeline.setPosition(0);
237 }
240}
241
243{
245 return;
246 stopOrEOS(false);
247}
248
249void QGstreamerMediaPlayer::stopOrEOS(bool eos)
250{
251 positionUpdateTimer.stop();
252 playerPipeline.setInStoppedState(true);
253 bool ret = playerPipeline.setStateSync(GST_STATE_PAUSED);
254 if (!ret)
255 qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the stopped state.";
256 if (!eos)
257 playerPipeline.setPosition(0);
261}
262
264{
265 if (message.isNull())
266 return false;
267
268// qCDebug(qLcMediaPlayer) << "received bus message from" << message.source().name() << message.type() << (message.type() == GST_MESSAGE_TAG);
269
270 GstMessage* gm = message.rawMessage();
271 switch (message.type()) {
272 case GST_MESSAGE_TAG: {
273 // #### This isn't ideal. We shouldn't catch stream specific tags here, rather the global ones
274 GstTagList *tag_list;
275 gst_message_parse_tag(gm, &tag_list);
276 //qCDebug(qLcMediaPlayer) << "Got tags: " << message.source().name() << gst_tag_list_to_string(tag_list);
278 for (auto k : metaData.keys())
279 m_metaData.insert(k, metaData.value(k));
280 break;
281 }
282 case GST_MESSAGE_DURATION_CHANGED: {
283 qint64 d = playerPipeline.duration()/1e6;
284 qCDebug(qLcMediaPlayer) << " duration changed message" << d;
285 if (d != m_duration) {
286 m_duration = d;
288 }
289 return false;
290 }
291 case GST_MESSAGE_EOS:
292 if (doLoop()) {
293 setPosition(0);
294 break;
295 }
296 stopOrEOS(true);
297 break;
298 case GST_MESSAGE_BUFFERING: {
299 qCDebug(qLcMediaPlayer) << " buffering message";
300 int progress = 0;
301 gst_message_parse_buffering(gm, &progress);
302 m_bufferProgress = progress;
304 emit bufferProgressChanged(m_bufferProgress/100.);
305 break;
306 }
307 case GST_MESSAGE_STATE_CHANGED: {
308 if (message.source() != playerPipeline)
309 return false;
310
311 GstState oldState;
312 GstState newState;
313 GstState pending;
314
315 gst_message_parse_state_changed(gm, &oldState, &newState, &pending);
316 qCDebug(qLcMediaPlayer) << " state changed message" << oldState << newState << pending;
317
318#ifdef DEBUG_PLAYBIN
319 static QStringList states = {
320 QStringLiteral("GST_STATE_VOID_PENDING"), QStringLiteral("GST_STATE_NULL"),
321 QStringLiteral("GST_STATE_READY"), QStringLiteral("GST_STATE_PAUSED"),
322 QStringLiteral("GST_STATE_PLAYING") };
323
324 qCDebug(qLcMediaPlayer) << QStringLiteral("state changed: old: %1 new: %2 pending: %3") \
325 .arg(states[oldState]) \
326 .arg(states[newState]) \
327 .arg(states[pending]);
328#endif
329
330 switch (newState) {
331 case GST_STATE_VOID_PENDING:
332 case GST_STATE_NULL:
333 case GST_STATE_READY:
334 break;
335 case GST_STATE_PAUSED:
336 {
337 if (prerolling) {
338 qCDebug(qLcMediaPlayer) << "Preroll done, setting status to Loaded";
339 prerolling = false;
340 GST_DEBUG_BIN_TO_DOT_FILE(playerPipeline.bin(), GST_DEBUG_GRAPH_SHOW_ALL, "playerPipeline");
341
342 qint64 d = playerPipeline.duration()/1e6;
343 if (d != m_duration) {
344 m_duration = d;
345 qCDebug(qLcMediaPlayer) << " duration changed" << d;
347 }
348
349 parseStreamsAndMetadata();
350
353
354 GstQuery *query = gst_query_new_seeking(GST_FORMAT_TIME);
355 gboolean canSeek = false;
356 if (gst_element_query(playerPipeline.element(), query)) {
357 gst_query_parse_seeking(query, NULL, &canSeek, nullptr, nullptr);
358 qCDebug(qLcMediaPlayer) << " pipeline is seekable:" << canSeek;
359 } else {
360 qCDebug(qLcMediaPlayer) << " query for seekable failed.";
361 }
362 gst_query_unref(query);
363 seekableChanged(canSeek);
364 }
365
366 break;
367 }
368 case GST_STATE_PLAYING:
370 break;
371 }
372 break;
373 }
374 case GST_MESSAGE_ERROR: {
375 GError *err;
376 gchar *debug;
377 gst_message_parse_error(gm, &err, &debug);
378 if (err->domain == GST_STREAM_ERROR && err->code == GST_STREAM_ERROR_CODEC_NOT_FOUND)
379 emit error(QMediaPlayer::FormatError, tr("Cannot play stream of type: <unknown>"));
380 else
382 playerPipeline.dumpGraph("error");
384 g_error_free(err);
385 g_free(debug);
386 break;
387 }
388 case GST_MESSAGE_WARNING: {
389 GError *err;
390 gchar *debug;
391 gst_message_parse_warning (gm, &err, &debug);
392 qCWarning(qLcMediaPlayer) << "Warning:" << QString::fromUtf8(err->message);
393 playerPipeline.dumpGraph("warning");
394 g_error_free (err);
395 g_free (debug);
396 break;
397 }
398 case GST_MESSAGE_INFO: {
399 if (qLcMediaPlayer().isDebugEnabled()) {
400 GError *err;
401 gchar *debug;
402 gst_message_parse_info (gm, &err, &debug);
403 qCDebug(qLcMediaPlayer) << "Info:" << QString::fromUtf8(err->message);
404 g_error_free (err);
405 g_free (debug);
406 }
407 break;
408 }
409 case GST_MESSAGE_SEGMENT_START: {
410 qCDebug(qLcMediaPlayer) << " segment start message, updating position";
411 QGstStructure structure(gst_message_get_structure(gm));
412 auto p = structure["position"].toInt64();
413 if (p) {
414 qint64 position = (*p)/1000000;
416 }
417 break;
418 }
419 case GST_MESSAGE_ELEMENT: {
420 QGstStructure structure(gst_message_get_structure(gm));
421 auto type = structure.name();
422 if (type == "stream-topology") {
423 topology.free();
424 topology = structure.copy();
425 }
426 break;
427 }
428
429 default:
430// qCDebug(qLcMediaPlayer) << " default message handler, doing nothing";
431
432 break;
433 }
434
435 return false;
436}
437
439{
440#if QT_CONFIG(gstreamer_gl)
441 if (message.type() != GST_MESSAGE_NEED_CONTEXT)
442 return false;
443 const gchar *type = nullptr;
444 gst_message_parse_context_type (message.rawMessage(), &type);
445 if (strcmp(type, GST_GL_DISPLAY_CONTEXT_TYPE))
446 return false;
447 if (!gstVideoOutput || !gstVideoOutput->gstreamerVideoSink())
448 return false;
449 auto *context = gstVideoOutput->gstreamerVideoSink()->gstGlDisplayContext();
450 if (!context)
451 return false;
452 gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message.rawMessage())), context);
453 playerPipeline.dumpGraph("need_context");
454 return true;
455#else
457 return false;
458#endif
459}
460
462{
463 return m_url;
464}
465
467{
468 return m_stream;
469}
470
471void QGstreamerMediaPlayer::decoderPadAdded(const QGstElement &src, const QGstPad &pad)
472{
473 if (src != decoder)
474 return;
475
476 auto caps = pad.currentCaps();
477 auto type = caps.at(0).name();
478 qCDebug(qLcMediaPlayer) << "Received new pad" << pad.name() << "from" << src.name() << "type" << type;
479 qCDebug(qLcMediaPlayer) << " " << caps.toString();
480
481 TrackType streamType = NTrackTypes;
482 if (type.startsWith("video/x-raw")) {
483 streamType = VideoStream;
484 } else if (type.startsWith("audio/x-raw")) {
485 streamType = AudioStream;
486 } else if (type.startsWith("text/")) {
487 streamType = SubtitleStream;
488 } else {
489 qCWarning(qLcMediaPlayer) << "Ignoring unknown media stream:" << pad.name() << type;
490 return;
491 }
492
493 auto &ts = trackSelector(streamType);
494 QGstPad sinkPad = ts.createInputPad();
495 if (!pad.link(sinkPad)) {
496 qCWarning(qLcMediaPlayer) << "Failed to add track, cannot link pads";
497 return;
498 }
499 qCDebug(qLcMediaPlayer) << "Adding track";
500
501 if (ts.trackCount() == 1) {
502 if (streamType == VideoStream) {
503 connectOutput(ts);
504 ts.setActiveInputPad(sinkPad);
506 }
507 else if (streamType == AudioStream) {
508 connectOutput(ts);
509 ts.setActiveInputPad(sinkPad);
511 }
512 }
513
514 if (!prerolling)
516
517 decoderOutputMap.insert(pad.name(), sinkPad);
518}
519
520void QGstreamerMediaPlayer::decoderPadRemoved(const QGstElement &src, const QGstPad &pad)
521{
522 if (src != decoder)
523 return;
524
525 qCDebug(qLcMediaPlayer) << "Removed pad" << pad.name() << "from" << src.name();
526 auto track = decoderOutputMap.value(pad.name());
527 if (track.isNull())
528 return;
529
530 auto ts = std::find_if(std::begin(trackSelectors), std::end(trackSelectors),
531 [&](TrackSelector &ts){ return ts.selector == track.parent(); });
532 if (ts == std::end(trackSelectors))
533 return;
534
535 qCDebug(qLcMediaPlayer) << " was linked to pad" << track.name() << "from" << ts->selector.name();
536 ts->removeInputPad(track);
537
538 if (ts->trackCount() == 0) {
539 removeOutput(*ts);
540 if (ts->type == AudioStream)
542 else if (ts->type == VideoStream)
544 }
545
546 if (!prerolling)
548}
549
550void QGstreamerMediaPlayer::removeAllOutputs()
551{
552 for (auto &ts : trackSelectors) {
553 removeOutput(ts);
554 ts.removeAllInputPads();
555 }
558}
559
560void QGstreamerMediaPlayer::connectOutput(TrackSelector &ts)
561{
562 if (ts.isConnected)
563 return;
564
566 switch (ts.type) {
567 case AudioStream:
568 e = gstAudioOutput ? gstAudioOutput->gstElement() : QGstElement{};
569 break;
570 case VideoStream:
571 e = gstVideoOutput ? gstVideoOutput->gstElement() : QGstElement{};
572 break;
573 case SubtitleStream:
574 if (gstVideoOutput)
575 gstVideoOutput->linkSubtitleStream(ts.selector);
576 break;
577 default:
578 return;
579 }
580
581 if (!e.isNull()) {
582 qCDebug(qLcMediaPlayer) << "connecting output for track type" << ts.type;
583 playerPipeline.add(e);
584 ts.selector.link(e);
585 e.setState(GST_STATE_PAUSED);
586 }
587
588 ts.isConnected = true;
589}
590
591void QGstreamerMediaPlayer::removeOutput(TrackSelector &ts)
592{
593 if (!ts.isConnected)
594 return;
595
597 switch (ts.type) {
598 case AudioStream:
599 e = gstAudioOutput ? gstAudioOutput->gstElement() : QGstElement{};
600 break;
601 case VideoStream:
602 e = gstVideoOutput ? gstVideoOutput->gstElement() : QGstElement{};
603 break;
604 case SubtitleStream:
605 if (gstVideoOutput)
606 gstVideoOutput->unlinkSubtitleStream();
607 break;
608 default:
609 break;
610 }
611
612 if (!e.isNull()) {
613 qCDebug(qLcMediaPlayer) << "removing output for track type" << ts.type;
614 playerPipeline.remove(e);
615 e.setStateSync(GST_STATE_NULL);
616 }
617
618 ts.isConnected = false;
619}
620
621void QGstreamerMediaPlayer::uridecodebinElementAddedCallback(GstElement */*uridecodebin*/, GstElement *child, QGstreamerMediaPlayer *that)
622{
624 qCDebug(qLcMediaPlayer) << "New element added to uridecodebin:" << c.name();
625
626 if (G_OBJECT_TYPE(child) == that->decodebinType) {
627 qCDebug(qLcMediaPlayer) << " -> setting post-stream-topology property";
628 c.set("post-stream-topology", true);
629 }
630}
631
632void QGstreamerMediaPlayer::sourceSetupCallback(GstElement *uridecodebin, GstElement *source, QGstreamerMediaPlayer *that)
633{
634 Q_UNUSED(uridecodebin)
635 Q_UNUSED(that)
636
637 qCDebug(qLcMediaPlayer) << "Setting up source:" << g_type_name_from_instance((GTypeInstance*)source);
638
639 if (QLatin1String("GstRTSPSrc") == QString::fromUtf8(g_type_name_from_instance((GTypeInstance*)source))) {
641 int latency{40};
642 bool ok{false};
643 int v = QString::fromLocal8Bit(qgetenv("QT_MEDIA_RTSP_LATENCY")).toUInt(&ok);
644 if (ok)
645 latency = v;
646 qCDebug(qLcMediaPlayer) << " -> setting source latency to:" << latency << "ms";
647 s.set("latency", latency);
648
649 bool drop{true};
650 v = QString::fromLocal8Bit(qgetenv("QT_MEDIA_RTSP_DROP_ON_LATENCY")).toUInt(&ok);
651 if (ok && v == 0)
652 drop = false;
653 qCDebug(qLcMediaPlayer) << " -> setting drop-on-latency to:" << drop;
654 s.set("drop-on-latency", drop);
655
656 bool retrans{false};
657 v = QString::fromLocal8Bit(qgetenv("QT_MEDIA_RTSP_DO_RETRANSMISSION")).toUInt(&ok);
658 if (ok && v not_eq 0)
659 retrans = true;
660 qCDebug(qLcMediaPlayer) << " -> setting do-retransmission to:" << retrans;
661 s.set("do-retransmission", retrans);
662 }
663}
664
666{
667 qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << "setting location to" << content;
668
669 prerolling = true;
670
671 bool ret = playerPipeline.setStateSync(GST_STATE_NULL);
672 if (!ret)
673 qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the stopped state.";
674
675 m_url = content;
676 m_stream = stream;
677
678 if (!src.isNull())
679 playerPipeline.remove(src);
680 if (!decoder.isNull())
681 playerPipeline.remove(decoder);
682 src = QGstElement();
683 decoder = QGstElement();
684 removeAllOutputs();
685 seekableChanged(false);
686 playerPipeline.setInStoppedState(true);
687
688 if (m_duration != 0) {
689 m_duration = 0;
691 }
693 if (position() != 0)
696 if (!m_metaData.isEmpty()) {
697 m_metaData.clear();
699 }
700
701 if (content.isEmpty())
702 return;
703
704 if (m_stream) {
705 if (!m_appSrc) {
706 auto maybeAppSrc = QGstAppSrc::create(this);
707 if (maybeAppSrc) {
708 m_appSrc = maybeAppSrc.value();
709 } else {
710 emit error(QMediaPlayer::ResourceError, maybeAppSrc.error());
711 return;
712 }
713 }
714 src = m_appSrc->element();
715 decoder = QGstElement("decodebin", "decoder");
716 if (!decoder) {
718 return;
719 }
720 decoder.set("post-stream-topology", true);
721 playerPipeline.add(src, decoder);
722 src.link(decoder);
723
724 m_appSrc->setup(m_stream);
725 seekableChanged(!stream->isSequential());
726 } else {
727 // use uridecodebin
728 decoder = QGstElement("uridecodebin", "uridecoder");
729 if (!decoder) {
731 return;
732 }
733 playerPipeline.add(decoder);
734 // can't set post-stream-topology to true, as uridecodebin doesn't have the property. Use a hack
735 decoder.connect("element-added", GCallback(QGstreamerMediaPlayer::uridecodebinElementAddedCallback), this);
736 decoder.connect("source-setup", GCallback(QGstreamerMediaPlayer::sourceSetupCallback), this);
737
738 decoder.set("uri", content.toEncoded().constData());
739 if (m_bufferProgress != 0) {
740 m_bufferProgress = 0;
742 }
743 }
744 decoder.onPadAdded<&QGstreamerMediaPlayer::decoderPadAdded>(this);
745 decoder.onPadRemoved<&QGstreamerMediaPlayer::decoderPadRemoved>(this);
746
748
750 int ret = playerPipeline.setState(GST_STATE_PLAYING);
751 if (ret == GST_STATE_CHANGE_FAILURE)
752 qCWarning(qLcMediaPlayer) << "Unable to set the pipeline to the playing state.";
753 } else {
754 int ret = playerPipeline.setState(GST_STATE_PAUSED);
755 if (!ret)
756 qCWarning(qLcMediaPlayer) << "Unable to set the pipeline to the paused state.";
757 }
758
759 playerPipeline.setPosition(0);
761}
762
764{
765 if (gstAudioOutput == output)
766 return;
767
768 auto &ts = trackSelector(AudioStream);
769
770 playerPipeline.beginConfig();
771 if (gstAudioOutput) {
772 removeOutput(ts);
773 gstAudioOutput->setPipeline({});
774 }
775 gstAudioOutput = static_cast<QGstreamerAudioOutput *>(output);
776 if (gstAudioOutput) {
777 gstAudioOutput->setPipeline(playerPipeline);
778 connectOutput(ts);
779 }
780 playerPipeline.endConfig();
781}
782
784{
785 return m_metaData;
786}
787
789{
790 gstVideoOutput->setVideoSink(sink);
791}
792
794{
795 QGstStructure e = s;
796 while (1) {
797 auto next = e["next"].toStructure();
798 if (!next.isNull())
799 e = next;
800 else
801 break;
802 }
803 return e;
804}
805
806void QGstreamerMediaPlayer::parseStreamsAndMetadata()
807{
808 qCDebug(qLcMediaPlayer) << "============== parse topology ============";
809 if (topology.isNull()) {
810 qCDebug(qLcMediaPlayer) << " null topology";
811 return;
812 }
813 auto caps = topology["caps"].toCaps();
814 auto structure = caps.at(0);
816 qCDebug(qLcMediaPlayer) << caps.toString() << fileFormat;
819 m_metaData.insert(QMediaMetaData::Url, m_url);
820 QGValue tags = topology["tags"];
821 if (!tags.isNull()) {
822 GstTagList *tagList = nullptr;
823 gst_structure_get(topology.structure, "tags", GST_TYPE_TAG_LIST, &tagList, nullptr);
824 const auto metaData = QGstreamerMetaData::fromGstTagList(tagList);
825 for (auto k : metaData.keys())
826 m_metaData.insert(k, metaData.value(k));
827 }
828
829 auto demux = endOfChain(topology);
830 auto next = demux["next"];
831 if (!next.isList()) {
832 qCDebug(qLcMediaPlayer) << " no additional streams";
834 return;
835 }
836
837 // collect stream info
838 int size = next.listSize();
839 for (int i = 0; i < size; ++i) {
840 auto val = next.at(i);
841 caps = val.toStructure()["caps"].toCaps();
842 structure = caps.at(0);
843 if (structure.name().startsWith("audio/")) {
846 qCDebug(qLcMediaPlayer) << " audio" << caps.toString() << (int)codec;
847 } else if (structure.name().startsWith("video/")) {
850 qCDebug(qLcMediaPlayer) << " video" << caps.toString() << (int)codec;
851 auto framerate = structure["framerate"].getFraction();
852 if (framerate)
853 m_metaData.insert(QMediaMetaData::VideoFrameRate, *framerate);
854 auto width = structure["width"].toInt();
855 auto height = structure["height"].toInt();
856 if (width && height)
858 }
859 }
860
861 auto sinkPad = trackSelector(VideoStream).activeInputPad();
862 if (!sinkPad.isNull()) {
863 bool hasTags = g_object_class_find_property (G_OBJECT_GET_CLASS (sinkPad.object()), "tags") != NULL;
864
865 GstTagList *tl = nullptr;
866 g_object_get(sinkPad.object(), "tags", &tl, nullptr);
867 qCDebug(qLcMediaPlayer) << " tags=" << hasTags << (tl ? gst_tag_list_to_string(tl) : "(null)");
868 }
869
870
871 qCDebug(qLcMediaPlayer) << "============== end parse topology ============";
873 playerPipeline.dumpGraph("playback");
874}
875
877{
878 return trackSelector(type).trackCount();
879}
880
882{
883 auto track = trackSelector(type).inputPad(index);
884 if (track.isNull())
885 return {};
886
887 GstTagList *tagList = nullptr;
888 g_object_get(track.object(), "tags", &tagList, nullptr);
889
890 return tagList ? QGstreamerMetaData::fromGstTagList(tagList) : QMediaMetaData{};
891}
892
894{
895 return trackSelector(type).activeInputIndex();
896}
897
899{
900 auto &ts = trackSelector(type);
901 auto track = ts.inputPad(index);
902 if (track.isNull() && index != -1) {
903 qCWarning(qLcMediaPlayer) << "Attempt to set an incorrect index" << index
904 << "for the track type" << type;
905 return;
906 }
907
908 qCDebug(qLcMediaPlayer) << "Setting the index" << index << "for the track type" << type;
910 gstVideoOutput->flushSubtitles();
911
912 playerPipeline.beginConfig();
913 if (track.isNull()) {
914 removeOutput(ts);
915 } else {
916 ts.setActiveInputPad(track);
917 connectOutput(ts);
918 }
919 playerPipeline.endConfig();
920
921 // seek to force an immediate change of the stream
922 if (playerPipeline.state() == GST_STATE_PLAYING)
923 playerPipeline.flush();
924 else
925 m_requiresSeekOnPlay = true;
926}
927
929
930#include "moc_qgstreamermediaplayer_p.cpp"
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
bool isNull() const
Definition qgst_p.h:70
QGstElement element()
bool setup(QIODevice *stream=nullptr, qint64 offset=0)
static QMaybe< QGstAppSrc * > create(QObject *parent=nullptr)
GstBin * bin() const
Definition qgst_p.h:564
void remove(const QGstElement &element)
Definition qgst_p.h:561
void add(const QGstElement &element)
Definition qgst_p.h:549
QGstStructure at(int index) const
Definition qgst_p.h:201
bool finishStateChange()
Definition qgst_p.h:476
GstElement * element() const
Definition qgst_p.h:526
void onPadRemoved(T *instance)
Definition qgst_p.h:503
void onPadAdded(T *instance)
Definition qgst_p.h:493
bool setStateSync(GstState state)
Definition qgst_p.h:463
GstState state() const
Definition qgst_p.h:456
bool isNull() const
Definition qgst_p.h:288
const char * name() const
Definition qgst_p.h:316
void set(const char *property, const char *str)
Definition qgst_p.h:290
void connect(const char *name, GCallback callback, gpointer userData)
Definition qgst_p.h:313
GstObject * object() const
Definition qgst_p.h:315
bool link(const QGstPad &sink) const
Definition qgst_p.h:338
QGstCaps currentCaps() const
Definition qgst_p.h:332
GstStateChangeReturn setState(GstState state)
bool inStoppedState() const
void dumpGraph(const char *fileName)
void removeMessageFilter(QGstreamerSyncMessageFilter *filter)
qint64 duration() const
qint64 position() const
double playbackRate() const
bool setPlaybackRate(double rate)
void setInStoppedState(bool stopped)
bool setPosition(qint64 pos)
QGstStructure copy() const
Definition qgst_p.h:157
const GstStructure * structure
Definition qgst_p.h:135
void free()
Definition qgst_p.h:138
QByteArrayView name() const
Definition qgst_p.h:142
bool isNull() const
Definition qgst_p.h:140
void setPipeline(const QGstPipeline &pipeline)
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
static QGstreamerMetaData fromGstTagList(const GstTagList *tags)
QGstreamerVideoSink * gstreamerVideoSink() const
QGstElement gstElement() const
void linkSubtitleStream(QGstElement subtitleSrc)
void setVideoSink(QVideoSink *sink)
static QMaybe< QGstreamerVideoOutput * > create(QObject *parent=nullptr)
GstContext * gstGlDisplayContext() const
T value(const Key &key) const noexcept
Definition qhash.h:1044
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1283
\inmodule QtCore \reentrant
Definition qiodevice.h:34
\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:90
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:311
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
\inmodule QtCore
static QString fromLocal8Bit(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5788
uint toUInt(bool *ok=nullptr, int base=10) const
Returns the string converted to an {unsigned int} using base base, which is 10 by default and must be...
Definition qstring.h:662
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:208
void stop()
Stops the timer.
Definition qtimer.cpp:226
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
\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:2964
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
Definition qurl.cpp:1888
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:531
The QVideoSink class represents a generic sink for video data.
Definition qvideosink.h:22
double e
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.
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
QString errorMessageCannotFindElement(std::string_view element)
Definition qgst_p.h:590
static QGstStructure endOfChain(const QGstStructure &s)
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
return ret
n void setPosition(void) \n\
GLsizei const GLfloat * v
[13]
GLint GLsizei GLsizei height
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLenum src
GLint GLsizei width
GLenum type
GLuint GLsizei const GLchar * message
GLsizei GLsizei GLchar * source
GLenum query
const GLubyte * c
GLuint GLfloat * val
GLuint GLenum * rate
GLsizei GLenum GLboolean sink
GLdouble s
[6]
Definition qopenglext.h:235
GLfloat GLfloat p
[1]
GLuint * states
static void add(QPainterPath &path, const QWingedEdge &list, int edge, QPathEdge::Traversal traversal)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
#define tr(X)
Q_CORE_EXPORT QByteArray qgetenv(const char *varName)
#define emit
#define Q_UNUSED(x)
long long qint64
Definition qtypes.h:55
double qreal
Definition qtypes.h:92
QT_BEGIN_NAMESPACE typedef uchar * output
QFileSelector selector
[1]
QStringList keys
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
QLayoutItem * child
[0]
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent