summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@qt.io>2021-02-17 17:35:00 +0100
committerLars Knoll <lars.knoll@qt.io>2021-03-02 14:51:13 +0000
commitf5ae5377805c3541fa19fe6c8001113f36f86bc4 (patch)
tree0ad947bedcffa232022b9675cdb904d83d5d22d2
parent5ae52a8e075e002293a29c238bee4608b6596ee3 (diff)
Implement basic metadata support for QMediaPlayer
Unfortunately, GstDiscover has a crappy API that doesn't allow us to retrieve the data from a running decoder session (we'd be forced to create a separate decoder session just to get the data). So let's do things more manually and parse the topology data from decodebin ourselves. Change-Id: I0aca195265763f838d21df6814d4edfb3aace2d2 Reviewed-by: Doris Verria <doris.verria@qt.io> Reviewed-by: Lars Knoll <lars.knoll@qt.io>
-rw-r--r--examples/multimediawidgets/player/player.cpp22
-rw-r--r--src/multimedia/platform/gstreamer/common/qgst_p.h63
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp175
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h6
-rw-r--r--src/multimedia/platform/gstreamer/qgstreamerformatinfo.cpp14
-rw-r--r--src/multimedia/platform/gstreamer/qgstreamerformatinfo_p.h8
-rw-r--r--src/multimedia/qmediametadata.cpp63
-rw-r--r--src/multimedia/qmediametadata.h7
8 files changed, 284 insertions, 74 deletions
diff --git a/examples/multimediawidgets/player/player.cpp b/examples/multimediawidgets/player/player.cpp
index 60745ffe9..6c4df136c 100644
--- a/examples/multimediawidgets/player/player.cpp
+++ b/examples/multimediawidgets/player/player.cpp
@@ -59,6 +59,7 @@
#include <QMediaMetaData>
#include <QMediaDeviceManager>
#include <QAudioDeviceInfo>
+#include <QMediaFormat>
#include <QtWidgets>
Player::Player(QWidget *parent)
@@ -115,7 +116,7 @@ Player::Player(QWidget *parent)
QLabel *metaDataLabel = new QLabel(tr("Metadata for file:"));
QGridLayout *metaDataLayout = new QGridLayout;
int key = QMediaMetaData::Title;
- for (int i = 0; i < 9; i++) {
+ for (int i = 0; i < (QMediaMetaData::NumMetaData + 2)/3; i++) {
for (int j = 0; j < 6; j+=2) {
m_metaDataLabels[key] = new QLabel(QMediaMetaData::metaDataKeyToString
(static_cast<QMediaMetaData::Key>(key)));
@@ -129,6 +130,8 @@ Player::Player(QWidget *parent)
metaDataLayout->addWidget(m_metaDataLabels[key], i, j);
metaDataLayout->addWidget(m_metaDataFields[key], i, j+1);
key++;
+ if (key == QMediaMetaData::NumMetaData)
+ break;
}
}
@@ -303,19 +306,21 @@ void Player::metaDataChanged()
for (auto &key : metaData.keys()) {
int i = int(key);
if (key == QMediaMetaData::CoverArtImage) {
+ QVariant v = metaData.value(key);
if (QLabel *cover = qobject_cast<QLabel*>(m_metaDataFields[key])) {
- QImage coverImage = metaData.value(QMediaMetaData::CoverArtImage).value<QImage>();
+ QImage coverImage = v.value<QImage>();
cover->setPixmap(QPixmap::fromImage(coverImage));
}
- }
- else if (key == QMediaMetaData::ThumbnailImage) {
+ } else if (key == QMediaMetaData::ThumbnailImage) {
+ QVariant v = metaData.value(key);
if (QLabel *thumbnail = qobject_cast<QLabel*>(m_metaDataFields[key])) {
- QImage thumbnailImage = metaData.value(QMediaMetaData::CoverArtImage).value<QImage>();
+ QImage thumbnailImage = v.value<QImage>();
thumbnail->setPixmap(QPixmap::fromImage(thumbnailImage));
}
+ } else if (QLineEdit *field = qobject_cast<QLineEdit*>(m_metaDataFields[key])) {
+ QString stringValue = metaData.stringValue(key);
+ field->setText(stringValue);
}
- else if (QLineEdit *field = qobject_cast<QLineEdit*>(m_metaDataFields[key]))
- field->setText(QString("%1").arg(metaData.value(key).toString()));
m_metaDataFields[i]->setDisabled(false);
m_metaDataLabels[i]->setDisabled(false);
}
@@ -336,7 +341,6 @@ void Player::jump(const QModelIndex &index)
{
if (index.isValid()) {
m_playlist->setCurrentIndex(index.row());
- m_player->play();
}
}
@@ -345,7 +349,6 @@ void Player::playlistPositionChanged(int currentItem)
clearHistogram();
m_playlistView->setCurrentIndex(m_playlistModel->index(currentItem, 0));
m_player->setMedia(m_playlist->currentMedia());
- m_player->play();
}
void Player::seek(int seconds)
@@ -533,4 +536,3 @@ void Player::clearHistogram()
QMetaObject::invokeMethod(m_videoHistogram, "processFrame", Qt::QueuedConnection, Q_ARG(QVideoFrame, QVideoFrame()));
QMetaObject::invokeMethod(m_audioHistogram, "processBuffer", Qt::QueuedConnection, Q_ARG(QAudioBuffer, QAudioBuffer()));
}
-
diff --git a/src/multimedia/platform/gstreamer/common/qgst_p.h b/src/multimedia/platform/gstreamer/common/qgst_p.h
index dab5219b9..3bfa2503a 100644
--- a/src/multimedia/platform/gstreamer/common/qgst_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgst_p.h
@@ -63,6 +63,8 @@
QT_BEGIN_NAMESPACE
class QSize;
+class QGstStructure;
+class QGstCaps;
template <typename T> struct QGRange
{
@@ -127,14 +129,22 @@ public:
return QGRange<int>{ gst_value_get_int_range_min(value), gst_value_get_int_range_max(value) };
}
+ inline QGstStructure toStructure() const;
+ inline QGstCaps toCaps() const;
+
+ inline bool isList() const { return value && GST_VALUE_HOLDS_LIST(value); }
+ inline int listSize() const { return gst_value_list_get_size(value); }
+ inline QGValue at(int index) const { return gst_value_list_get_value(value, index); }
+
Q_MULTIMEDIA_EXPORT QList<QAudioFormat::SampleFormat> getSampleFormats() const;
};
class QGstStructure {
public:
- GstStructure *structure;
- QGstStructure(GstStructure *s) : structure(s) {}
- void free() { gst_structure_free(structure); structure = nullptr; }
+ const GstStructure *structure = nullptr;
+ QGstStructure() = default;
+ QGstStructure(const GstStructure *s) : structure(s) {}
+ void free() { if (structure) gst_structure_free(const_cast<GstStructure *>(structure)); structure = nullptr; }
bool isNull() const { return !structure; }
@@ -148,11 +158,13 @@ public:
Q_MULTIMEDIA_EXPORT QGRange<float> frameRateRange() const;
QByteArray toString() const { return gst_structure_to_string(structure); }
+ QGstStructure copy() const { return gst_structure_copy(structure); }
};
class QGstCaps {
- const GstCaps *caps;
+ const GstCaps *caps = nullptr;
public:
+ QGstCaps() = default;
QGstCaps(const GstCaps *c) : caps(c) {}
bool isNull() const { return !caps; }
@@ -271,6 +283,9 @@ public:
quint64 getUInt64(const char *property) const { guint64 i = 0; g_object_get(m_object, property, &i, nullptr); return i; }
qint64 getInt64(const char *property) const { gint64 i = 0; g_object_get(m_object, property, &i, nullptr); return i; }
double getDouble(const char *property) const { gdouble d = 0; g_object_get(m_object, property, &d, nullptr); return d; }
+ QGstObject getObject(const char *property) const { GstObject *o = nullptr; g_object_get(m_object, property, &o, nullptr); return o; }
+
+ void connect(const char *name, GCallback callback, gpointer userData) { g_signal_connect(m_object, name, callback, userData); }
GstObject *object() const { return m_object; }
const char *name() const { return GST_OBJECT_NAME(m_object); }
@@ -285,7 +300,7 @@ public:
QGstPad(const QGstObject &o)
: QGstPad(GST_PAD(o.object()), NeedsRef)
{}
- QGstPad(GstPad *pad, RefMode mode)
+ QGstPad(GstPad *pad, RefMode mode = NeedsRef)
: QGstObject(&pad->object, mode)
{}
@@ -320,7 +335,7 @@ public:
QGstElement(const QGstObject &o)
: QGstElement(GST_ELEMENT(o.object()), NeedsRef)
{}
- QGstElement(GstElement *element, RefMode mode)
+ QGstElement(GstElement *element, RefMode mode = NeedsRef)
: QGstObject(&element->object, mode)
{}
@@ -386,21 +401,31 @@ public:
void onPadAdded(T *instance) {
struct Impl {
static void callback(GstElement *e, GstPad *pad, gpointer userData) {
- (static_cast<T *>(userData)->*Member)(QGstElement(e, NeedsRef), QGstPad(pad, NeedsRef));
+ (static_cast<T *>(userData)->*Member)(QGstElement(e), QGstPad(pad, NeedsRef));
};
};
- g_signal_connect (element(), "pad-added", G_CALLBACK(Impl::callback), instance);
+ connect("pad-added", G_CALLBACK(Impl::callback), instance);
}
template<auto Member, typename T>
void onPadRemoved(T *instance) {
struct Impl {
static void callback(GstElement *e, GstPad *pad, gpointer userData) {
- (static_cast<T *>(userData)->*Member)(QGstElement(e, NeedsRef), QGstPad(pad, NeedsRef));
+ (static_cast<T *>(userData)->*Member)(QGstElement(e), QGstPad(pad, NeedsRef));
};
};
- g_signal_connect (element(), "pad-removed", G_CALLBACK(Impl::callback), instance);
+ connect("pad-removed", G_CALLBACK(Impl::callback), instance);
+ }
+ template<auto Member, typename T>
+ void onNoMorePads(T *instance) {
+ struct Impl {
+ static void callback(GstElement *e, gpointer userData) {
+ (static_cast<T *>(userData)->*Member)(QGstElement(e));
+ };
+ };
+
+ connect("no-more-pads", G_CALLBACK(Impl::callback), instance);
}
GstElement *element() const { return GST_ELEMENT_CAST(m_object); }
@@ -421,7 +446,7 @@ public:
: QGstElement(gst_bin_new(name), NeedsRef)
{
}
- QGstBin(GstBin *bin, RefMode mode)
+ QGstBin(GstBin *bin, RefMode mode = NeedsRef)
: QGstElement(&bin->element, mode)
{}
@@ -462,7 +487,7 @@ public:
: QGstBin(GST_BIN(gst_pipeline_new(name)), NeedsRef)
{
}
- QGstPipeline(GstPipeline *p, RefMode mode)
+ QGstPipeline(GstPipeline *p, RefMode mode = NeedsRef)
: QGstBin(&p->bin, mode)
{}
@@ -470,6 +495,20 @@ public:
QGstBus bus() { return gst_element_get_bus(element()); }
};
+inline QGstStructure QGValue::toStructure() const
+{
+ if (!value || !GST_VALUE_HOLDS_STRUCTURE(value))
+ return QGstStructure();
+ return QGstStructure(gst_value_get_structure(value));
+}
+
+inline QGstCaps QGValue::toCaps() const
+{
+ if (!value || !GST_VALUE_HOLDS_CAPS(value))
+ return QGstCaps();
+ return QGstCaps(gst_value_get_caps(value));
+}
+
QT_END_NAMESPACE
#endif
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp
index 2e7df3c91..ee8e8e279 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp
+++ b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp
@@ -43,6 +43,7 @@
#include <private/qgstreamervideorenderer_p.h>
#include <private/qgstreamerbushelper_p.h>
#include <private/qgstreamermetadata_p.h>
+#include <private/qgstreamerformatinfo_p.h>
#include <private/qaudiodeviceinfo_gstreamer_p.h>
#include <qaudiodeviceinfo.h>
@@ -89,6 +90,13 @@ QGstreamerMediaPlayer::QGstreamerMediaPlayer(QObject *parent)
busHelper = new QGstreamerBusHelper(playerPipeline.bus().bus(), this);
qRegisterMetaType<QGstreamerMessage>();
connect(busHelper, &QGstreamerBusHelper::message, this, &QGstreamerMediaPlayer::busMessage);
+
+ /* Taken from gstdicoverer.c:
+ * This is ugly. We get the GType of decodebin so we can quickly detect
+ * when a decodebin is added to uridecodebin so we can set the
+ * post-stream-topology setting to TRUE */
+ auto decodebin = QGstElement("decodebin", nullptr);
+ decodebinType = G_OBJECT_TYPE(decodebin.element());
}
QGstreamerMediaPlayer::~QGstreamerMediaPlayer()
@@ -98,6 +106,7 @@ QGstreamerMediaPlayer::~QGstreamerMediaPlayer()
delete m_stream;
if (networkManager)
delete networkManager;
+ topology.free();
}
qint64 QGstreamerMediaPlayer::position() const
@@ -188,9 +197,10 @@ void QGstreamerMediaPlayer::pause()
void QGstreamerMediaPlayer::stop()
{
- int ret = playerPipeline.setState(GST_STATE_NULL);
+ int ret = playerPipeline.setStateSync(GST_STATE_PAUSED);
if (ret == GST_STATE_CHANGE_FAILURE)
qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the stopped state.";
+ playerPipeline.seek(0, m_playbackRate);
m_state = QMediaPlayer::StoppedState;
emit stateChanged(m_state);
}
@@ -218,22 +228,18 @@ void QGstreamerMediaPlayer::busMessage(const QGstreamerMessage &message)
if (message.isNull())
return;
- qCDebug(qLcMediaPlayer) << "received bus message from" << message.source().name() << message.type();
- if (message.source() != playerPipeline && message.source() != decoder)
- return;
+ qCDebug(qLcMediaPlayer) << "received bus message from" << message.source().name() << message.type() << (message.type() == GST_MESSAGE_TAG);
GstMessage* gm = message.rawMessage();
- //tag message comes from elements inside playbin, not from playbin itself
switch (message.type()) {
case GST_MESSAGE_TAG: {
+ // #### This isn't ideal. We shouldn't catch stream specific tags here, rather the global ones
GstTagList *tag_list;
gst_message_parse_tag(gm, &tag_list);
-
- m_metaData = QGstreamerMetaData::fromGstTagList(tag_list);
-
- gst_tag_list_free(tag_list);
-
- emit metaDataChanged();
+ qCDebug(qLcMediaPlayer) << "Got tags: " << message.source().name() << gst_tag_list_to_string(tag_list);
+ auto metaData = QGstreamerMetaData::fromGstTagList(tag_list);
+ for (auto k : metaData.keys())
+ m_metaData.insert(k, metaData.value(k));
break;
}
case GST_MESSAGE_ASYNC_DONE:
@@ -251,6 +257,7 @@ void QGstreamerMediaPlayer::busMessage(const QGstreamerMessage &message)
// anything to do here?
break;
case GST_MESSAGE_BUFFERING: {
+ qCDebug(qLcMediaPlayer) << " buffering message";
int progress = 0;
gst_message_parse_buffering(gm, &progress);
m_bufferProgress = progress;
@@ -258,9 +265,13 @@ void QGstreamerMediaPlayer::busMessage(const QGstreamerMessage &message)
break;
}
case GST_MESSAGE_STATE_CHANGED: {
+ if (message.source() != playerPipeline)
+ return;
+
GstState oldState;
GstState newState;
GstState pending;
+ qCDebug(qLcMediaPlayer) << " state changed message";
gst_message_parse_state_changed(gm, &oldState, &newState, &pending);
@@ -270,7 +281,7 @@ void QGstreamerMediaPlayer::busMessage(const QGstreamerMessage &message)
QStringLiteral("GST_STATE_READY"), QStringLiteral("GST_STATE_PAUSED"),
QStringLiteral("GST_STATE_PLAYING") };
- qDebug() << QStringLiteral("state changed: old: %1 new: %2 pending: %3") \
+ qCDebug(qLcMediaPlayer) << QStringLiteral("state changed: old: %1 new: %2 pending: %3") \
.arg(states[oldState]) \
.arg(states[newState]) \
.arg(states[pending]);
@@ -291,8 +302,7 @@ void QGstreamerMediaPlayer::busMessage(const QGstreamerMessage &message)
//check for seekable
if (oldState == GST_STATE_READY) {
-// getStreamsInfo();
-// updateVideoResolutionTag();
+ parseStreamsAndMetadata();
if (!qFuzzyCompare(m_playbackRate, qreal(1.0)))
playerPipeline.seek(playerPipeline.position(), m_playbackRate);
@@ -348,7 +358,18 @@ void QGstreamerMediaPlayer::busMessage(const QGstreamerMessage &message)
position /= 1000000;
emit positionChanged(position);
}
+ case GST_MESSAGE_ELEMENT: {
+ QGstStructure structure(gst_message_get_structure(gm));
+ auto type = structure.name();
+ if (type == "stream-topology") {
+ topology.free();
+ topology = structure.copy();
+ }
+ }
+
default:
+ qCDebug(qLcMediaPlayer) << " default message handler, doing nothing";
+
break;
}
@@ -450,6 +471,7 @@ void QGstreamerMediaPlayer::decoderPadAdded(const QGstElement &src, const QGstPa
auto caps = pad.currentCaps();
auto type = caps.at(0).name();
qCDebug(qLcMediaPlayer) << "Received new pad" << pad.name() << "from" << src.name() << "type" << type;
+ qCDebug(qLcMediaPlayer) << " " << caps.toString();
QGstElement selector;
if (type.startsWith("video/x-raw")) {
@@ -465,6 +487,7 @@ void QGstreamerMediaPlayer::decoderPadAdded(const QGstElement &src, const QGstPa
QGstPad sinkPad = selector.getRequestPad("sink_%u");
if (!pad.link(sinkPad))
qCWarning(qLcMediaPlayer) << "Failed to link video pads.";
+
decoderOutputMap.insert(pad.name(), sinkPad);
}
@@ -479,6 +502,23 @@ void QGstreamerMediaPlayer::decoderPadRemoved(const QGstElement &src, const QGst
peerParent.releaseRequestPad(peer);
}
+void QGstreamerMediaPlayer::padsDone(const QGstElement &/*src*/)
+{
+ qCDebug(qLcMediaPlayer) << "All pads have been added";
+ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (playerPipeline.bin(), GST_DEBUG_GRAPH_SHOW_ALL, "playerPipeline");
+}
+
+void QGstreamerMediaPlayer::uridecodebinElementAddedCallback(GstElement */*uridecodebin*/, GstElement *child, QGstreamerMediaPlayer *that)
+{
+ QGstElement c(child);
+ qCDebug(qLcMediaPlayer) << "New element added to uridecodebin:" << c.name();
+
+ if (G_OBJECT_TYPE(child) == that->decodebinType) {
+ qCDebug(qLcMediaPlayer) << " -> setting post-stream-topology property";
+ c.set("post-stream-topology", true);
+ }
+}
+
void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
{
qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << "setting location to" << content;
@@ -489,6 +529,7 @@ void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
m_url = content;
m_stream = stream;
+ m_metaData.clear();
if (!src.isNull())
playerPipeline.remove(src);
@@ -502,6 +543,7 @@ void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
m_appSrc = new QGstAppSrc(this);
src = QGstElement("appsrc", "appsrc");
decoder = QGstElement("decodebin", "decoder");
+ decoder.set("post-stream-topology", true);
playerPipeline.add(src, decoder);
src.link(decoder);
@@ -511,6 +553,8 @@ void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
// use uridecodebin
decoder = QGstElement("uridecodebin", "uridecoder");
playerPipeline.add(decoder);
+ // can't set post-stream-topology to true, as uridecodebin doesn't have the property. Use a hack
+ decoder.connect("element-added", GCallback(QGstreamerMediaPlayer::uridecodebinElementAddedCallback), this);
decoder.set("uri", content.toEncoded().constData());
if (m_bufferProgress != -1) {
@@ -520,15 +564,16 @@ void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
}
decoder.onPadAdded<&QGstreamerMediaPlayer::decoderPadAdded>(this);
decoder.onPadRemoved<&QGstreamerMediaPlayer::decoderPadRemoved>(this);
+ decoder.onNoMorePads<&QGstreamerMediaPlayer::padsDone>(this);
- if (m_state == QMediaPlayer::PausedState) {
+ if (m_state == QMediaPlayer::PlayingState) {
+ int ret = playerPipeline.setState(GST_STATE_PLAYING);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ qCWarning(qLcMediaPlayer) << "Unable to set the pipeline to the playing state.";
+ } else {
int ret = playerPipeline.setState(GST_STATE_PAUSED);
if (ret == GST_STATE_CHANGE_FAILURE)
- qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the paused state.";
- } else if (m_state == QMediaPlayer::PlayingState) {
- int ret = playerPipeline.setState(GST_STATE_PLAYING);
- if (ret == GST_STATE_CHANGE_FAILURE)
- qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the playing state.";
+ qCWarning(qLcMediaPlayer) << "Unable to set the pipeline to the paused state.";
}
emit positionChanged(position());
@@ -556,7 +601,7 @@ bool QGstreamerMediaPlayer::changeAudioOutput()
QGstElement newSink;
auto *deviceInfo = static_cast<const QGStreamerAudioDeviceInfo *>(m_audioOutput.handle());
if (deviceInfo && deviceInfo->gstDevice)
- newSink = QGstElement(gst_device_create_element(deviceInfo->gstDevice , "audiosink"), QGstElement::NeedsRef);
+ newSink = gst_device_create_element(deviceInfo->gstDevice , "audiosink");
if (newSink.isNull())
newSink = QGstElement("autoaudiosink", "audiosink");
@@ -583,8 +628,6 @@ void QGstreamerMediaPlayer::prepareAudioOutputChange(const QGstPad &/*pad*/)
audioSink.setStateSync(GST_STATE_PAUSED);
if (state == GST_STATE_PLAYING)
playerPipeline.setStateSync(state);
-
- GST_DEBUG_BIN_TO_DOT_FILE(playerPipeline.bin(), GST_DEBUG_GRAPH_SHOW_ALL, "newAudio.dot");
}
QAudioDeviceInfo QGstreamerMediaPlayer::audioOutput() const
@@ -594,8 +637,7 @@ QAudioDeviceInfo QGstreamerMediaPlayer::audioOutput() const
QMediaMetaData QGstreamerMediaPlayer::metaData() const
{
-// return m_session->metaData();
- return QMediaMetaData();
+ return m_metaData;
}
void QGstreamerMediaPlayer::updateVideoSink()
@@ -604,7 +646,7 @@ void QGstreamerMediaPlayer::updateVideoSink()
QGstElement newSink;
if (m_videoOutput && m_videoOutput->isReady())
- newSink = QGstElement(m_videoOutput->videoSink(), QGstObject::NeedsRef);
+ newSink = m_videoOutput->videoSink();
if (newSink.isNull())
newSink = QGstElement("fakesink", "fakevideosink");
@@ -644,18 +686,14 @@ void QGstreamerMediaPlayer::updateVideoSink()
} else {
// if (m_pendingVideoSink) {
-//#ifdef DEBUG_PLAYBIN
-// qDebug() << "already waiting for pad to be blocked, just change the pending sink";
-//#endif
+// qCDebug(qLcMediaPlayer) << "already waiting for pad to be blocked, just change the pending sink";
// m_pendingVideoSink = videoSink;
// return;
// }
// m_pendingVideoSink = videoSink;
-//#ifdef DEBUG_PLAYBIN
-// qDebug() << "Blocking the video output pad...";
-//#endif
+// qCDebug(qLcMediaPlayer) << "Blocking the video output pad...";
// //block pads, async to avoid locking in paused state
// GstPad *srcPad = gst_element_get_static_pad(m_videoIdentity, "src");
@@ -666,9 +704,7 @@ void QGstreamerMediaPlayer::updateVideoSink()
// //while the sink is paused. The pad will be blocked as soon as the current
// //buffer is processed.
// if (m_state == QMediaPlayer::PausedState) {
-//#ifdef DEBUG_PLAYBIN
-// qDebug() << "Starting video output to avoid blocking in paused state...";
-//#endif
+// qCDebug(qLcMediaPlayer) << "Starting video output to avoid blocking in paused state...";
// gst_element_set_state(m_videoSink, GST_STATE_PLAYING);
// }
}
@@ -683,13 +719,76 @@ void QGstreamerMediaPlayer::setSeekable(bool seekable)
emit seekableChanged(m_seekable);
}
+static QGstStructure endOfChain(const QGstStructure &s)
+{
+ QGstStructure e = s;
+ while (1) {
+ auto next = e["next"].toStructure();
+ if (!next.isNull())
+ e = next;
+ else
+ break;
+ }
+ return e;
+}
+
+void QGstreamerMediaPlayer::parseStreamsAndMetadata()
+{
+ qCDebug(qLcMediaPlayer) << "============== parse topology ============";
+ auto caps = topology["caps"].toCaps();
+ auto structure = caps.at(0);
+ auto fileFormat = QGstreamerFormatInfo::fileFormatForCaps(structure);
+ qCDebug(qLcMediaPlayer) << caps.toString() << fileFormat;
+ m_metaData.insert(QMediaMetaData::FileFormat, QVariant::fromValue(fileFormat));
+ m_metaData.insert(QMediaMetaData::Duration, duration());
+ m_metaData.insert(QMediaMetaData::Url, m_url);
+ QGValue tags = topology["tags"];
+ if (!tags.isNull()) {
+ GstTagList *tagList = nullptr;
+ gst_structure_get(topology.structure, "tags", GST_TYPE_TAG_LIST, &tagList, nullptr);
+ const auto metaData = QGstreamerMetaData::fromGstTagList(tagList);
+ for (auto k : metaData.keys())
+ m_metaData.insert(k, metaData.value(k));
+ }
+
+ auto demux = endOfChain(topology);
+ auto next = demux["next"];
+ if (!next.isList())
+ return;
+
+ // collect stream info
+ int size = next.listSize();
+ for (int i = 0; i < size; ++i) {
+ auto val = next.at(i);
+ caps = val.toStructure()["caps"].toCaps();
+ structure = caps.at(0);
+ if (structure.name().startsWith("audio/")) {
+ auto codec = QGstreamerFormatInfo::audioCodecForCaps(structure);
+ m_metaData.insert(QMediaMetaData::AudioCodec, QVariant::fromValue(codec));
+ qCDebug(qLcMediaPlayer) << " audio" << caps.toString() << (int)codec;
+ } else if (structure.name().startsWith("video/")) {
+ auto codec = QGstreamerFormatInfo::videoCodecForCaps(structure);
+ m_metaData.insert(QMediaMetaData::VideoCodec, QVariant::fromValue(codec));
+ qCDebug(qLcMediaPlayer) << " video" << caps.toString() << (int)codec;
+ auto framerate = structure["framerate"].getFraction();
+ if (framerate)
+ m_metaData.insert(QMediaMetaData::VideoFrameRate, *framerate);
+ auto width = structure["width"].toInt();
+ auto height = structure["height"].toInt();
+ if (width && height)
+ m_metaData.insert(QMediaMetaData::Resolution, QSize(*width, *height));
+ }
+ }
+
+ qCDebug(qLcMediaPlayer) << "============== end parse topology ============";
+ emit metaDataChanged();
+}
+
void QGstreamerMediaPlayer::setVideoSurface(QAbstractVideoSurface *surface)
{
if (!m_videoOutput) {
m_videoOutput = new QGstreamerVideoRenderer;
-#ifdef DEBUG_PLAYBIN
- qDebug() << Q_FUNC_INFO;
-#endif
+ qCDebug(qLcMediaPlayer) << Q_FUNC_INFO;
connect(m_videoOutput, SIGNAL(sinkChanged()),
this, SLOT(updateVideoRenderer()));
}
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h
index 07746293b..89a6fc250 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h
@@ -126,10 +126,13 @@ private:
friend class QGstreamerStreamsControl;
void decoderPadAdded(const QGstElement &src, const QGstPad &pad);
void decoderPadRemoved(const QGstElement &src, const QGstPad &pad);
+ void padsDone(const QGstElement &src);
void prepareAudioOutputChange(const QGstPad &pad);
+ static void uridecodebinElementAddedCallback(GstElement *uridecodebin, GstElement *child, QGstreamerMediaPlayer *that);
bool changeAudioOutput();
void updateVideoSink();
void setSeekable(bool seekable);
+ void parseStreamsAndMetadata();
QGstreamerStreamsControl *m_streamsControl = nullptr;
QMediaMetaData m_metaData;
@@ -156,6 +159,9 @@ private:
QGstreamerBusHelper *busHelper;
QGstAppSrc *m_appSrc;
+ GType decodebinType;
+ QGstStructure topology;
+
// Gst elements
QGstPipeline playerPipeline;
QGstElement src;
diff --git a/src/multimedia/platform/gstreamer/qgstreamerformatinfo.cpp b/src/multimedia/platform/gstreamer/qgstreamerformatinfo.cpp
index 8606c0aea..3e11bd8c3 100644
--- a/src/multimedia/platform/gstreamer/qgstreamerformatinfo.cpp
+++ b/src/multimedia/platform/gstreamer/qgstreamerformatinfo.cpp
@@ -43,7 +43,7 @@
QT_BEGIN_NAMESPACE
-static QMediaFormat::AudioCodec audioCodecForCaps(QGstStructure structure)
+QMediaFormat::AudioCodec QGstreamerFormatInfo::audioCodecForCaps(QGstStructure structure)
{
const char *name = structure.name();
@@ -79,7 +79,7 @@ static QMediaFormat::AudioCodec audioCodecForCaps(QGstStructure structure)
return QMediaFormat::AudioCodec::Unspecified;
}
-static QMediaFormat::VideoCodec videoCodecForCaps(QGstStructure structure)
+QMediaFormat::VideoCodec QGstreamerFormatInfo::videoCodecForCaps(QGstStructure structure)
{
const char *name = structure.name();
@@ -115,7 +115,7 @@ static QMediaFormat::VideoCodec videoCodecForCaps(QGstStructure structure)
return QMediaFormat::VideoCodec::Unspecified;
}
-static QMediaFormat::FileFormat fileFormatForCaps(QGstStructure structure)
+QMediaFormat::FileFormat QGstreamerFormatInfo::fileFormatForCaps(QGstStructure structure)
{
const char *name = structure.name();
@@ -140,7 +140,7 @@ static QMediaFormat::FileFormat fileFormatForCaps(QGstStructure structure)
}
-static QImageEncoderSettings::FileFormat imageFormatForCaps(QGstStructure structure)
+QImageEncoderSettings::FileFormat QGstreamerFormatInfo::imageFormatForCaps(QGstStructure structure)
{
const char *name = structure.name();
@@ -183,10 +183,10 @@ static QPair<QList<QMediaFormat::AudioCodec>, QList<QMediaFormat::VideoCodec>> g
for (int i = 0; i < caps.size(); i++) {
QGstStructure structure = caps.at(i);
- auto a = audioCodecForCaps(structure);
+ auto a = QGstreamerFormatInfo::audioCodecForCaps(structure);
if (a != QMediaFormat::AudioCodec::Unspecified)
audio.append(a);
- auto v = videoCodecForCaps(structure);
+ auto v = QGstreamerFormatInfo::videoCodecForCaps(structure);
if (v != QMediaFormat::VideoCodec::Unspecified)
video.append(v);
}
@@ -288,7 +288,7 @@ static QList<QImageEncoderSettings::FileFormat> getImageFormatList()
for (int i = 0; i < caps.size(); i++) {
QGstStructure structure = caps.at(i);
- auto f = imageFormatForCaps(structure);
+ auto f = QGstreamerFormatInfo::imageFormatForCaps(structure);
if (f != QImageEncoderSettings::UnspecifiedFormat) {
qDebug() << structure.toString() << f;
formats.insert(f);
diff --git a/src/multimedia/platform/gstreamer/qgstreamerformatinfo_p.h b/src/multimedia/platform/gstreamer/qgstreamerformatinfo_p.h
index 754bce203..d198b4fff 100644
--- a/src/multimedia/platform/gstreamer/qgstreamerformatinfo_p.h
+++ b/src/multimedia/platform/gstreamer/qgstreamerformatinfo_p.h
@@ -67,9 +67,11 @@ public:
QGstMutableCaps formatCaps(const QMediaFormat &f) const;
QGstMutableCaps audioCaps(const QMediaFormat &f) const;
QGstMutableCaps videoCaps(const QMediaFormat &f) const;
- // ###
-// QGstCaps audioEncoderCaps(const QMediaEncoderSettings &f) const;
-// QGstCaps videoEncoderCaps(const QMediaEncoderSettings &f) const;
+
+ static QMediaFormat::AudioCodec audioCodecForCaps(QGstStructure structure);
+ static QMediaFormat::VideoCodec videoCodecForCaps(QGstStructure structure);
+ static QMediaFormat::FileFormat fileFormatForCaps(QGstStructure structure);
+ static QImageEncoderSettings::FileFormat imageFormatForCaps(QGstStructure structure);
QList<CodecMap> getMuxerList(bool demuxer, QList<QMediaFormat::AudioCodec> audioCodecs, QList<QMediaFormat::VideoCodec> videoCodecs);
};
diff --git a/src/multimedia/qmediametadata.cpp b/src/multimedia/qmediametadata.cpp
index 11002d96f..2e4f335ce 100644
--- a/src/multimedia/qmediametadata.cpp
+++ b/src/multimedia/qmediametadata.cpp
@@ -40,6 +40,9 @@
#include "qmediametadata.h"
#include <qvariant.h>
#include <qobject.h>
+#include <qdatetime.h>
+#include <qmediaformat.h>
+#include <qsize.h>
QT_BEGIN_NAMESPACE
@@ -78,12 +81,13 @@ QT_BEGIN_NAMESPACE
Media attributes
\row \li Size \li The size in bytes of the media. \li qint64
\row \li MediaType \li The type of the media (audio, video, etc). \li QString
+ \row \li FileFormat \li The file format of the media. \li QMediaFormat::FileFormat
\row \li Duration \li The duration in millseconds of the media. \li qint64
\header \li {3,1}
Audio attributes
\row \li AudioBitRate \li The bit rate of the media's audio stream in bits per second. \li int
- \row \li AudioCodec \li The codec of the media's audio stream. \li QString
+ \row \li AudioCodec \li The codec of the media's audio stream. \li QMediaForma::AudioCodec
\row \li AverageLevel \li The average volume level of the media. \li int
\row \li ChannelCount \li The number of channels in the media's audio stream. \li int
\row \li PeakValue \li The peak volume of the media's audio stream. \li int
@@ -115,7 +119,7 @@ QT_BEGIN_NAMESPACE
Video attributes
\row \li VideoFrameRate \li The frame rate of the media's video stream. \li qreal
\row \li VideoBitRate \li The bit rate of the media's video stream in bits per second. \li int
- \row \li VideoCodec \li The codec of the media's video stream. \li QString
+ \row \li VideoCodec \li The codec of the media's video stream. \li QMediaFormat::VideoCodec
\row \li PosterUrl \li The URL of a poster image. \li QUrl
\row \li PosterImage \li An embedded poster image. \li QImage
@@ -233,6 +237,57 @@ void QMediaMetaData::insert(QMediaMetaData::Key k, const QVariant &value)
data.insert(k, value);
}
+QString QMediaMetaData::stringValue(QMediaMetaData::Key k) const
+{
+ QVariant value = data.value(k);
+ if (value.isNull())
+ return QString();
+
+ switch (k) {
+ // string based or convertible to string
+ case Title:
+ case Author:
+ case Comment:
+ case Description:
+ case Genre:
+ case Year:
+ case Language:
+ case Publisher:
+ case Copyright:
+ case Date:
+ case Url:
+ case MediaType:
+ case AudioBitRate:
+ case VideoBitRate:
+ case VideoFrameRate:
+ case AlbumTitle:
+ case AlbumArtist:
+ case ContributingArtist:
+ case TrackNumber:
+ case Composer:
+ case Orientation:
+ case LeadPerformer:
+ return value.toString();
+ case Duration: {
+ QTime time = QTime::fromMSecsSinceStartOfDay(value.toInt());
+ return time.toString();
+ }
+ case FileFormat:
+ return QMediaFormat::fileFormatName(value.value<QMediaFormat::FileFormat>());
+ case AudioCodec:
+ return QMediaFormat::audioCodecName(value.value<QMediaFormat::AudioCodec>());
+ case VideoCodec:
+ return QMediaFormat::videoCodecName(value.value<QMediaFormat::VideoCodec>());
+ case Resolution: {
+ QSize size = value.toSize();
+ return QString::fromUtf8("%1 x %2").arg(size.width()).arg(size.height());
+ }
+ case ThumbnailImage:
+ case CoverArtImage:
+ return QString();
+ }
+}
+
QString QMediaMetaData::metaDataKeyToString(QMediaMetaData::Key k)
{
switch (k) {
@@ -262,6 +317,8 @@ QString QMediaMetaData::metaDataKeyToString(QMediaMetaData::Key k)
return (QObject::tr("Duration"));
case QMediaMetaData::MediaType:
return (QObject::tr("Media type"));
+ case QMediaMetaData::FileFormat:
+ return (QObject::tr("Container Format"));
case QMediaMetaData::AudioBitRate:
return (QObject::tr("Audio bit rate"));
case QMediaMetaData::AudioCodec:
@@ -270,6 +327,8 @@ QString QMediaMetaData::metaDataKeyToString(QMediaMetaData::Key k)
return (QObject::tr("Video bit rate"));
case QMediaMetaData::VideoCodec:
return (QObject::tr("Video codec"));
+ case QMediaMetaData::VideoFrameRate:
+ return (QObject::tr("Video frame rate"));
case QMediaMetaData::AlbumTitle:
return (QObject::tr("Album title"));
case QMediaMetaData::AlbumArtist:
diff --git a/src/multimedia/qmediametadata.h b/src/multimedia/qmediametadata.h
index 3e552aba8..b659c1e51 100644
--- a/src/multimedia/qmediametadata.h
+++ b/src/multimedia/qmediametadata.h
@@ -70,10 +70,12 @@ public:
Duration,
MediaType,
+ FileFormat,
AudioBitRate,
AudioCodec,
VideoBitRate,
VideoCodec,
+ VideoFrameRate,
AlbumTitle,
AlbumArtist,
@@ -86,10 +88,10 @@ public:
Orientation,
Resolution,
- LeadPerformer,
+ LeadPerformer
};
- static const int NumMetaData = 27;
+ static constexpr int NumMetaData = LeadPerformer + 1;
// QMetaType typeForKey(Key k);
QVariant value(Key k) const;
@@ -101,6 +103,7 @@ public:
void clear() { data.clear(); }
bool isEmpty() const { return data.isEmpty(); }
+ QString stringValue(Key k) const;
static QString metaDataKeyToString(Key k);