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>
39 if (
type == SubtitleStream)
43QGstPad QGstreamerMediaPlayer::TrackSelector::createInputPad()
50void QGstreamerMediaPlayer::TrackSelector::removeAllInputPads()
52 for (
auto &pad : tracks)
57void QGstreamerMediaPlayer::TrackSelector::removeInputPad(
QGstPad pad)
60 tracks.removeOne(pad);
63QGstPad QGstreamerMediaPlayer::TrackSelector::inputPad(
int index)
70QGstreamerMediaPlayer::TrackSelector &QGstreamerMediaPlayer::trackSelector(
TrackType type)
72 auto &ts = trackSelectors[
type];
77void QGstreamerMediaPlayer::disconnectDecoderHandlers()
79 auto handlers = std::initializer_list<QGObjectHandlerScopedConnection *>{
80 &padAdded, &padRemoved, &sourceSetup, &uridecodebinElementAdded,
81 &unknownType, &elementAdded, &elementRemoved,
93 return videoOutput.error();
97 if (!videoInputSelector)
102 if (!audioInputSelector)
107 if (!subTitleInputSelector)
111 subTitleInputSelector,
parent);
121 trackSelectors{ { {
VideoStream, videoInputSelector },
125 gstVideoOutput(videoOutput)
127 playerPipeline.setFlushOnConfigChanges(
true);
129 gstVideoOutput->setParent(
this);
130 gstVideoOutput->setPipeline(playerPipeline);
132 for (
auto &ts : trackSelectors)
135 playerPipeline.setState(GST_STATE_NULL);
140 gst_system_clock_obtain(),
143 gst_pipeline_use_clock(playerPipeline.pipeline(), systemClock.get());
163 return playerPipeline.
position()/1e6;
173 return m_bufferProgress/100.;
196 if (
pos == currentPos)
218 qCDebug(qLcMediaPlayer) <<
"play().";
219 int ret = playerPipeline.
setState(GST_STATE_PLAYING);
220 if (m_requiresSeekOnPlay) {
223 playerPipeline.
flush();
224 m_requiresSeekOnPlay =
false;
226 if (
ret == GST_STATE_CHANGE_FAILURE)
227 qCDebug(qLcMediaPlayer) <<
"Unable to set the pipeline to the playing state.";
229 positionUpdateTimer.
start(100);
236 || m_resourceErrorState != ResourceErrorState::NoError)
239 positionUpdateTimer.
stop();
242 playerPipeline.
flush();
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.";
253 if (m_bufferProgress > 0 || !canTrackProgress())
279void QGstreamerMediaPlayer::stopOrEOS(
bool eos)
281 positionUpdateTimer.
stop();
285 qCDebug(qLcMediaPlayer) <<
"Unable to set the pipeline to the stopped state.";
295 m_initialBufferProgressSent =
false;
298void QGstreamerMediaPlayer::detectPipelineIsSeekable()
300 qCDebug(qLcMediaPlayer) <<
"detectPipelineIsSeekable";
302 gst_query_new_seeking(GST_FORMAT_TIME),
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;
310 qCWarning(qLcMediaPlayer) <<
" query for seekable failed.";
319 GstMessage* gm =
message.message();
321 case GST_MESSAGE_TAG: {
324 gst_message_parse_tag(gm, &tagList);
326 qCDebug(qLcMediaPlayer) <<
" Got tags: " << tagList.get();
334 if (gstVideoOutput) {
340 case GST_MESSAGE_DURATION_CHANGED: {
342 qCDebug(qLcMediaPlayer) <<
" duration changed message" <<
d;
343 if (
d != m_duration) {
349 case GST_MESSAGE_EOS: {
359 case GST_MESSAGE_BUFFERING: {
361 gst_message_parse_buffering(gm, &progress);
363 qCDebug(qLcMediaPlayer) <<
" buffering message: " << progress;
366 if (!m_initialBufferProgressSent) {
368 m_initialBufferProgressSent =
true;
371 if (m_bufferProgress > 0 && progress == 0)
373 else if (progress >= 50)
380 m_bufferProgress = progress;
385 case GST_MESSAGE_STATE_CHANGED: {
386 if (
message.source() != playerPipeline)
394 qCDebug(qLcMediaPlayer) <<
" state changed message from"
398 case GST_STATE_VOID_PENDING:
400 case GST_STATE_READY:
402 case GST_STATE_PAUSED: {
404 qCDebug(qLcMediaPlayer) <<
"Preroll done, setting status to Loaded";
406 GST_DEBUG_BIN_TO_DOT_FILE(playerPipeline.
bin(), GST_DEBUG_GRAPH_SHOW_ALL,
410 if (
d != m_duration) {
412 qCDebug(qLcMediaPlayer) <<
" duration changed" <<
d;
416 parseStreamsAndMetadata();
422 Q_ASSERT(!m_initialBufferProgressSent);
424 bool immediatelySendBuffered = !canTrackProgress() || m_bufferProgress > 0;
426 m_initialBufferProgressSent =
true;
427 if (immediatelySendBuffered)
434 case GST_STATE_PLAYING: {
435 if (!m_initialBufferProgressSent) {
436 bool immediatelySendBuffered = !canTrackProgress() || m_bufferProgress > 0;
438 m_initialBufferProgressSent =
true;
439 if (immediatelySendBuffered)
447 case GST_MESSAGE_ERROR: {
452 gst_message_parse_error(gm, &err, &
debug);
453 GQuark errorDomain = err.
get()->domain;
454 gint errorCode = err.
get()->code;
456 if (errorDomain == GST_STREAM_ERROR) {
457 if (errorCode == GST_STREAM_ERROR_CODEC_NOT_FOUND)
462 }
else if (errorDomain == GST_RESOURCE_ERROR) {
463 if (errorCode == GST_RESOURCE_ERROR_NOT_FOUND) {
464 if (m_resourceErrorState != ResourceErrorState::ErrorReported) {
467 m_resourceErrorState = ResourceErrorState::ErrorReported;
480 case GST_MESSAGE_WARNING:
485 case GST_MESSAGE_INFO:
486 if (qLcMediaPlayer().isDebugEnabled())
490 case GST_MESSAGE_SEGMENT_START: {
491 qCDebug(qLcMediaPlayer) <<
" segment start message, updating position";
493 auto p = structure[
"position"].toInt64();
500 case GST_MESSAGE_ELEMENT: {
503 if (
type ==
"stream-topology") {
505 topology = structure.
copy();
510 case GST_MESSAGE_ASYNC_DONE: {
511 detectPipelineIsSeekable();
526#if QT_CONFIG(gstreamer_gl)
527 if (
message.type() != GST_MESSAGE_NEED_CONTEXT)
529 const gchar *
type =
nullptr;
530 gst_message_parse_context_type (
message.message(), &
type);
531 if (strcmp(
type, GST_GL_DISPLAY_CONTEXT_TYPE))
538 gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(
message.message())),
context);
539 playerPipeline.
dumpGraph(
"need_context");
564 qCDebug(qLcMediaPlayer) <<
"Received new pad" << pad.
name() <<
"from" <<
src.name() <<
"type" <<
type;
565 qCDebug(qLcMediaPlayer) <<
" " << caps;
568 if (
type.startsWith(
"video/x-raw")) {
570 }
else if (
type.startsWith(
"audio/x-raw")) {
572 }
else if (
type.startsWith(
"text/")) {
575 qCWarning(qLcMediaPlayer) <<
"Ignoring unknown media stream:" << pad.
name() <<
type;
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";
585 qCDebug(qLcMediaPlayer) <<
"Adding track";
587 if (ts.trackCount() == 1) {
590 ts.setActiveInputPad(sinkPad);
595 ts.setActiveInputPad(sinkPad);
611 qCDebug(qLcMediaPlayer) <<
"Removed pad" << pad.
name() <<
"from" <<
src.name();
612 auto track = decoderOutputMap.
value(pad.
name());
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))
621 qCDebug(qLcMediaPlayer) <<
" was linked to pad" << track.name() <<
"from" << ts->selector.name();
622 ts->removeInputPad(track);
624 if (ts->trackCount() == 0) {
636void QGstreamerMediaPlayer::removeAllOutputs()
638 for (
auto &ts : trackSelectors) {
640 ts.removeAllInputPads();
646void QGstreamerMediaPlayer::connectOutput(TrackSelector &ts)
668 qCDebug(qLcMediaPlayer) <<
"connecting output for track type" << ts.type;
669 playerPipeline.
add(e);
674 ts.isConnected =
true;
677void QGstreamerMediaPlayer::removeOutput(TrackSelector &ts)
699 qCDebug(qLcMediaPlayer) <<
"removing output for track type" << ts.type;
703 ts.isConnected =
false;
706void QGstreamerMediaPlayer::removeDynamicPipelineElements()
709 if (element->isNull())
712 element->setStateSync(GstState::GST_STATE_NULL);
713 playerPipeline.
remove(*element);
718void QGstreamerMediaPlayer::uridecodebinElementAddedCallback(GstElement * ,
723 qCDebug(qLcMediaPlayer) <<
"New element added to uridecodebin:" <<
c.name();
725 static const GType decodeBinType = [] {
727 gst_element_factory_find(
"decodebin"),
729 return gst_element_factory_get_element_type(
factory.get());
732 if (
c.type() == decodeBinType) {
733 qCDebug(qLcMediaPlayer) <<
" -> setting post-stream-topology property";
734 c.set(
"post-stream-topology",
true);
743 qCDebug(qLcMediaPlayer) <<
"Setting up source:" << g_type_name_from_instance((GTypeInstance*)
source);
745 if (std::string_view(
"GstRTSPSrc") == g_type_name_from_instance((GTypeInstance *)
source)) {
752 qCDebug(qLcMediaPlayer) <<
" -> setting source latency to:" << latency <<
"ms";
753 s.set(
"latency", latency);
759 qCDebug(qLcMediaPlayer) <<
" -> setting drop-on-latency to:" << drop;
760 s.set(
"drop-on-latency", drop);
766 qCDebug(qLcMediaPlayer) <<
" -> setting do-retransmission to:" << retrans;
767 s.set(
"do-retransmission", retrans);
771void QGstreamerMediaPlayer::unknownTypeCallback(GstElement *decodebin, GstPad *pad, GstCaps *caps,
777 qCDebug(qLcMediaPlayer) <<
"Unknown type:" << caps;
786 static const GType queueType = [] {
788 gst_element_factory_find(
"queue"),
790 return gst_element_factory_get_element_type(
factory.get());
793 static const GType multiQueueType = [] {
795 gst_element_factory_find(
"multiqueue"),
797 return gst_element_factory_get_element_type(
factory.get());
800 return element.
type() == queueType || element.
type() == multiQueueType;
803void QGstreamerMediaPlayer::decodebinElementAddedCallback(GstBin * ,
804 GstBin * , GstElement *
child,
809 self->decodeBinQueues += 1;
812void QGstreamerMediaPlayer::decodebinElementRemovedCallback(GstBin * ,
813 GstBin * , GstElement *
child,
818 self->decodeBinQueues -= 1;
826 m_resourceErrorState = ResourceErrorState::NoError;
830 qCDebug(qLcMediaPlayer) <<
"Unable to set the pipeline to the stopped state.";
835 removeDynamicPipelineElements();
836 disconnectDecoderHandlers();
841 if (m_duration != 0) {
863 m_appSrc = maybeAppSrc.value();
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);
883 playerPipeline.
add(
src, decoder);
886 m_appSrc->
setup(m_stream);
895 playerPipeline.
add(decoder);
897 constexpr bool hasPostStreamTopology = GST_CHECK_VERSION(1, 22, 0);
898 if constexpr (hasPostStreamTopology) {
899 decoder.
set(
"post-stream-topology",
true);
903 uridecodebinElementAdded = decoder.
connect(
904 "element-added", GCallback(uridecodebinElementAddedCallback),
this);
907 sourceSetup = decoder.
connect(
"source-setup", GCallback(sourceSetupCallback),
this);
908 unknownType = decoder.
connect(
"unknown-type", GCallback(unknownTypeCallback),
this);
911 decoder.
set(
"use-buffering",
true);
913 constexpr int mb = 1024 * 1024;
914 decoder.
set(
"ring-buffer-max-size", 2 * mb);
916 if (m_bufferProgress != 0) {
917 m_bufferProgress = 0;
921 elementAdded = decoder.
connect(
"deep-element-added",
922 GCallback(decodebinElementAddedCallback),
this);
923 elementRemoved = decoder.
connect(
"deep-element-removed",
924 GCallback(decodebinElementAddedCallback),
this);
926 padAdded = decoder.
onPadAdded<&QGstreamerMediaPlayer::decoderPadAdded>(
this);
927 padRemoved = decoder.
onPadRemoved<&QGstreamerMediaPlayer::decoderPadRemoved>(
this);
930 if (!playerPipeline.
setState(GST_STATE_PAUSED)) {
931 qCWarning(qLcMediaPlayer) <<
"Unable to set the pipeline to the paused state.";
942 if (gstAudioOutput ==
output)
971 auto next = e[
"next"].toStructure();
980void QGstreamerMediaPlayer::parseStreamsAndMetadata()
982 qCDebug(qLcMediaPlayer) <<
"============== parse topology ============";
984 qCDebug(qLcMediaPlayer) <<
" null topology";
987 auto caps = topology[
"caps"].toCaps();
988 auto structure = caps.at(0);
994 QGValue tags = topology[
"tags"];
997 gst_structure_get(topology.
structure,
"tags", GST_TYPE_TAG_LIST, &tagList,
nullptr);
1005 auto next = demux[
"next"];
1006 if (!
next.isList()) {
1007 qCDebug(qLcMediaPlayer) <<
" no additional streams";
1014 for (
int i = 0;
i <
size; ++
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();
1030 QSize resolution = structure.resolution();
1034 QSize nativeSize = structure.nativeSize();
1039 auto sinkPad = trackSelector(
VideoStream).activeInputPad();
1040 if (!sinkPad.isNull()) {
1043 g_object_get(sinkPad.object(),
"tags", &tagList,
nullptr);
1045 qCDebug(qLcMediaPlayer) <<
" tags=" << tagList.get();
1047 qCDebug(qLcMediaPlayer) <<
" tags=(null)";
1051 qCDebug(qLcMediaPlayer) <<
"============== end parse topology ============";
1058 return trackSelector(
type).trackCount();
1063 auto track = trackSelector(
type).inputPad(
index);
1068 g_object_get(track.object(),
"tags", &tagList,
nullptr);
1075 return trackSelector(
type).activeInputIndex();
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;
1088 qCDebug(qLcMediaPlayer) <<
"Setting the index" <<
index <<
"for the track type" <<
type;
1093 if (track.isNull()) {
1096 ts.setActiveInputPad(track);
1102 if (playerPipeline.
state() == GST_STATE_PLAYING)
1103 playerPipeline.
flush();
1105 m_requiresSeekOnPlay =
true;
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
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)
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void remove)(const Ts &...ts)
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void stopAndRemoveElements)(Ts... ts)
QGstStructure at(int index) const
QGObjectHandlerConnection onPadRemoved(T *instance)
GstElement * element() const
QGstPad getRequestPad(const char *name) const
bool finishStateChange(std::chrono::nanoseconds timeout=std::chrono::seconds(5))
bool setStateSync(GstState state, std::chrono::nanoseconds timeout=std::chrono::seconds(1))
bool syncStateWithParent()
QGObjectHandlerConnection onPadAdded(T *instance)
static QGstElement createFromFactory(const char *factory, const char *name=nullptr)
GstState state(std::chrono::nanoseconds timeout=std::chrono::seconds(0)) const
QGObjectHandlerConnection connect(const char *name, GCallback callback, gpointer userData)
const char * name() const
void set(const char *property, const char *str)
bool link(const QGstPad &sink) const
QGstCaps currentCaps() const
GstStateChangeReturn setState(GstState state)
GstPipeline * pipeline() const
bool inStoppedState() const
void dumpGraph(const char *fileName)
void removeMessageFilter(QGstreamerSyncMessageFilter *filter)
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
const GstStructure * structure
QByteArrayView name() const
QGstElement gstElement() const
QGstreamerVideoSink * gstreamerVideoSink() const
QGstElement gstElement() const
void setNativeSize(QSize)
void linkSubtitleStream(QGstElement subtitleSrc)
void setVideoSink(QVideoSink *sink)
void setRotation(QtVideo::Rotation)
static QMaybe< QGstreamerVideoOutput * > create(QObject *parent=nullptr)
void unlinkSubtitleStream()
GstContext * gstGlDisplayContext() const
T value(const Key &key) const noexcept
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
\inmodule QtCore \reentrant
void append(parameter_type t)
QObject * parent() const
Returns a pointer to the parent object.
constexpr bool isValid() const noexcept
Returns true if both the width and height is equal to or greater than 0; otherwise returns false.
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
void stop()
Stops the timer.
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
Type get() const noexcept
QByteArray toEncoded(FormattingOptions options=FullyEncoded) const
Returns the encoded representation of the URL if it's valid; otherwise an empty QByteArray is returne...
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
void clear()
Resets the content of the QUrl.
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 >
The QVideoSink class represents a generic sink for video data.
cache insert(employee->id(), employee)
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
Combined button and popup list for selecting options.
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
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void qLinkGstElements)(const Ts &...ts)
QString errorMessageCannotFindElement(std::string_view element)
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
n void setPosition(void) \n\
GLsizei const GLfloat * v
[13]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint GLsizei const GLchar * message
GLsizei GLsizei GLchar * source
GLsizei GLenum GLboolean sink
static void add(QPainterPath &path, const QWingedEdge &list, int edge, QPathEdge::Traversal traversal)
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
QT_BEGIN_NAMESPACE typedef uchar * output
QFileSelector selector
[1]
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
myObject disconnect()
[26]
QItemEditorFactory * factory