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
qgstreameraudiodecoder.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 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//#define DEBUG_DECODER
4
6
10
11#include <gst/gstvalue.h>
12#include <gst/base/gstbasesrc.h>
13
14#include <QtCore/qdatetime.h>
15#include <QtCore/qdebug.h>
16#include <QtCore/qsize.h>
17#include <QtCore/qtimer.h>
18#include <QtCore/qdebug.h>
19#include <QtCore/qdir.h>
20#include <QtCore/qstandardpaths.h>
21#include <QtCore/qurl.h>
22#include <QtCore/qloggingcategory.h>
23
25
26static Q_LOGGING_CATEGORY(qLcGstreamerAudioDecoder, "qt.multimedia.gstreameraudiodecoder");
27
39
40
41QMaybe<QPlatformAudioDecoder *> QGstreamerAudioDecoder::create(QAudioDecoder *parent)
42{
43 QGstElement audioconvert = QGstElement::createFromFactory("audioconvert", "audioconvert");
44 if (!audioconvert)
45 return errorMessageCannotFindElement("audioconvert");
46
48 GST_PIPELINE_CAST(QGstElement::createFromFactory("playbin", "playbin").element()));
49 if (!playbin)
50 return errorMessageCannotFindElement("playbin");
51
52 return new QGstreamerAudioDecoder(playbin, audioconvert, parent);
53}
54
55QGstreamerAudioDecoder::QGstreamerAudioDecoder(QGstPipeline playbin, QGstElement audioconvert,
56 QAudioDecoder *parent)
57 : QPlatformAudioDecoder(parent),
58 m_playbin(std::move(playbin)),
59 m_audioConvert(std::move(audioconvert))
60{
61 // Sort out messages
62 m_playbin.installMessageFilter(this);
63
64 // Set the rest of the pipeline up
65 setAudioFlags(true);
66
67 m_outputBin = QGstBin::create("audio-output-bin");
68 m_outputBin.add(m_audioConvert);
69
70 // add ghostpad
71 m_outputBin.addGhostPad(m_audioConvert, "sink");
72
73 g_object_set(m_playbin.object(), "audio-sink", m_outputBin.element(), NULL);
74
75 m_deepNotifySourceConnection = m_playbin.connect(
76 "deep-notify::source", (GCallback)&configureAppSrcElement, (gpointer)this);
77
78 // Set volume to 100%
79 gdouble volume = 1.0;
80 m_playbin.set("volume", volume);
81}
82
84{
85 stop();
86
87 m_playbin.removeMessageFilter(this);
88
89#if QT_CONFIG(gstreamer_app)
90 delete m_appSrc;
91#endif
92}
93
94#if QT_CONFIG(gstreamer_app)
95void QGstreamerAudioDecoder::configureAppSrcElement([[maybe_unused]] GObject *object, GObject *orig,
96 [[maybe_unused]] GParamSpec *pspec,
98{
99 // In case we switch from appsrc to not
100 if (!self->m_appSrc)
101 return;
102
103 QGstElementHandle appsrc;
104 g_object_get(orig, "source", &appsrc, NULL);
105
106 auto *qAppSrc = self->m_appSrc;
107 qAppSrc->setExternalAppSrc(QGstAppSrc{
108 qGstSafeCast<GstAppSrc>(appsrc.get()),
109 QGstAppSrc::NeedsRef, // CHECK: can we `release()`?
110 });
111 qAppSrc->setup(self->mDevice);
112}
113#endif
114
116{
117 qCDebug(qLcGstreamerAudioDecoder) << "received bus message:" << message;
118
119 GstMessage *gm = message.message();
120
121 switch (message.type()) {
122 case GST_MESSAGE_DURATION: {
123 updateDuration();
124 return false;
125 }
126
127 case GST_MESSAGE_ERROR: {
128 qCDebug(qLcGstreamerAudioDecoder) << " error" << QCompactGstMessageAdaptor(message);
129
132 gst_message_parse_error(gm, &err, &debug);
133
134 if (message.source() == m_playbin) {
135 if (err.get()->domain == GST_STREAM_ERROR
136 && err.get()->code == GST_STREAM_ERROR_CODEC_NOT_FOUND)
137 processInvalidMedia(QAudioDecoder::FormatError,
138 tr("Cannot play stream of type: <unknown>"));
139 else
140 processInvalidMedia(QAudioDecoder::ResourceError,
141 QString::fromUtf8(err.get()->message));
142 } else {
144 if (err.get()->domain == GST_STREAM_ERROR) {
145 switch (err.get()->code) {
146 case GST_STREAM_ERROR_DECRYPT:
147 case GST_STREAM_ERROR_DECRYPT_NOKEY:
149 break;
150 case GST_STREAM_ERROR_FORMAT:
151 case GST_STREAM_ERROR_DEMUX:
152 case GST_STREAM_ERROR_DECODE:
153 case GST_STREAM_ERROR_WRONG_TYPE:
154 case GST_STREAM_ERROR_TYPE_NOT_FOUND:
155 case GST_STREAM_ERROR_CODEC_NOT_FOUND:
157 break;
158 default:
159 break;
160 }
161 } else if (err.get()->domain == GST_CORE_ERROR) {
162 switch (err.get()->code) {
163 case GST_CORE_ERROR_MISSING_PLUGIN:
165 break;
166 default:
167 break;
168 }
169 }
170
171 processInvalidMedia(qerror, QString::fromUtf8(err.get()->message));
172 }
173 break;
174 }
175
176 default:
177 if (message.source() == m_playbin)
178 return handlePlaybinMessage(message);
179 }
180
181 return false;
182}
183
184bool QGstreamerAudioDecoder::handlePlaybinMessage(const QGstreamerMessage &message)
185{
186 GstMessage *gm = message.message();
187
188 switch (GST_MESSAGE_TYPE(gm)) {
189 case GST_MESSAGE_STATE_CHANGED: {
190 GstState oldState;
191 GstState newState;
192 GstState pending;
193
194 gst_message_parse_state_changed(gm, &oldState, &newState, &pending);
195
196 bool isDecoding = false;
197 switch (newState) {
198 case GST_STATE_VOID_PENDING:
199 case GST_STATE_NULL:
200 case GST_STATE_READY:
201 break;
202 case GST_STATE_PLAYING:
203 isDecoding = true;
204 break;
205 case GST_STATE_PAUSED:
206 isDecoding = true;
207
208 // gstreamer doesn't give a reliable indication the duration
209 // information is ready, GST_MESSAGE_DURATION is not sent by most elements
210 // the duration is queried up to 5 times with increasing delay
211 m_durationQueries = 5;
212 updateDuration();
213 break;
214 }
215
217 break;
218 };
219
220 case GST_MESSAGE_EOS:
221 m_playbin.setState(GST_STATE_NULL);
222 finished();
223 break;
224
225 case GST_MESSAGE_ERROR:
226 Q_UNREACHABLE_RETURN(false); // handled in processBusMessage
227
228 case GST_MESSAGE_WARNING:
229 qCWarning(qLcGstreamerAudioDecoder) << "Warning:" << QCompactGstMessageAdaptor(message);
230 break;
231
232 case GST_MESSAGE_INFO: {
233 if (qLcGstreamerAudioDecoder().isDebugEnabled())
234 qCWarning(qLcGstreamerAudioDecoder) << "Info:" << QCompactGstMessageAdaptor(message);
235 break;
236 }
237 default:
238 break;
239 }
240
241 return false;
242}
243
245{
246 return mSource;
247}
248
250{
251 stop();
252 mDevice = nullptr;
253 delete m_appSrc;
254 m_appSrc = nullptr;
255
256 bool isSignalRequired = (mSource != fileName);
257 mSource = fileName;
258 if (isSignalRequired)
260}
261
263{
264 return mDevice;
265}
266
268{
269 stop();
270 mSource.clear();
271 bool isSignalRequired = (mDevice != device);
272 mDevice = device;
273 if (isSignalRequired)
275}
276
278{
279 addAppSink();
280
281 if (!mSource.isEmpty()) {
282 m_playbin.set("uri", mSource.toEncoded().constData());
283 } else if (mDevice) {
284 // make sure we can read from device
285 if (!mDevice->isOpen() || !mDevice->isReadable()) {
286 processInvalidMedia(QAudioDecoder::ResourceError, QLatin1String("Unable to read from specified device"));
287 return;
288 }
289
290 if (!m_appSrc) {
291 auto maybeAppSrc = QGstAppSource::create(this);
292 if (maybeAppSrc) {
293 m_appSrc = maybeAppSrc.value();
294 } else {
295 processInvalidMedia(QAudioDecoder::ResourceError, maybeAppSrc.error());
296 return;
297 }
298 }
299
300 m_playbin.set("uri", "appsrc://");
301 } else {
302 return;
303 }
304
305 // Set audio format
306 if (m_appSink) {
307 if (mFormat.isValid()) {
308 setAudioFlags(false);
309 auto caps = QGstUtils::capsForAudioFormat(mFormat);
310 m_appSink.setCaps(caps);
311 } else {
312 // We want whatever the native audio format is
313 setAudioFlags(true);
314 m_appSink.setCaps({});
315 }
316 }
317
318 if (m_playbin.setState(GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
319 qWarning() << "GStreamer; Unable to start decoding process";
320 m_playbin.dumpGraph("failed");
321 return;
322 }
323}
324
326{
327 m_playbin.setState(GST_STATE_NULL);
328 m_currentSessionId += 1;
329 removeAppSink();
330
331 // GStreamer thread is stopped. Can safely access m_buffersAvailable
332 if (m_buffersAvailable != 0) {
333 m_buffersAvailable = 0;
335 }
336
337 if (m_position != -1) {
338 m_position = -1;
339 positionChanged(m_position);
340 }
341
342 if (m_duration != -1) {
343 m_duration = -1;
344 durationChanged(m_duration);
345 }
346
347 setIsDecoding(false);
348}
349
351{
352 return mFormat;
353}
354
356{
357 if (mFormat != format) {
358 mFormat = format;
359 formatChanged(mFormat);
360 }
361}
362
364{
365 QAudioBuffer audioBuffer;
366
367 if (m_buffersAvailable == 0)
368 return audioBuffer;
369
370 m_buffersAvailable -= 1;
371
372 if (m_buffersAvailable == 0)
374
375 QGstSampleHandle sample = m_appSink.pullSample();
376 GstBuffer *buffer = gst_sample_get_buffer(sample.get());
377 GstMapInfo mapInfo;
378 gst_buffer_map(buffer, &mapInfo, GST_MAP_READ);
379 const char *bufferData = (const char *)mapInfo.data;
380 int bufferSize = mapInfo.size;
382
383 if (format.isValid()) {
384 // XXX At the moment we have to copy data from GstBuffer into QAudioBuffer.
385 // We could improve performance by implementing QAbstractAudioBuffer for GstBuffer.
386 qint64 position = getPositionFromBuffer(buffer);
387 audioBuffer = QAudioBuffer(QByteArray(bufferData, bufferSize), format, position);
388 position /= 1000; // convert to milliseconds
389 if (position != m_position) {
390 m_position = position;
391 positionChanged(m_position);
392 }
393 }
394 gst_buffer_unmap(buffer, &mapInfo);
395
396 return audioBuffer;
397}
398
400{
401 return m_position;
402}
403
405{
406 return m_duration;
407}
408
409void QGstreamerAudioDecoder::processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString)
410{
411 stop();
412 error(int(errorCode), errorString);
413}
414
415GstFlowReturn QGstreamerAudioDecoder::newSample(GstAppSink *)
416{
417 // "Note that the preroll buffer will also be returned as the first buffer when calling
418 // gst_app_sink_pull_buffer()."
419
420 QMetaObject::invokeMethod(this, [this, sessionId = m_currentSessionId] {
421 if (sessionId != m_currentSessionId)
422 return; // stop()ed before message is executed
423
424 m_buffersAvailable += 1;
426 bufferReady();
427 });
428
429 return GST_FLOW_OK;
430}
431
432GstFlowReturn QGstreamerAudioDecoder::new_sample(GstAppSink *sink, gpointer user_data)
433{
434 QGstreamerAudioDecoder *decoder = reinterpret_cast<QGstreamerAudioDecoder *>(user_data);
435 qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::new_sample";
436 return decoder->newSample(sink);
437}
438
439void QGstreamerAudioDecoder::setAudioFlags(bool wantNativeAudio)
440{
441 int flags = m_playbin.getInt("flags");
442 // make sure not to use GST_PLAY_FLAG_NATIVE_AUDIO unless desired
443 // it prevents audio format conversion
446 if (wantNativeAudio)
448 m_playbin.set("flags", flags);
449}
450
451void QGstreamerAudioDecoder::addAppSink()
452{
453 using namespace std::chrono_literals;
454
455 if (m_appSink)
456 return;
457
458 qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::addAppSink";
459 m_appSink = QGstAppSink::create("decoderAppSink");
460 GstAppSinkCallbacks callbacks{};
461 callbacks.new_sample = new_sample;
462 m_appSink.setCallbacks(callbacks, this, nullptr);
463
464#if GST_CHECK_VERSION(1, 24, 0)
465 static constexpr auto maxBufferTime = 500ms;
466 m_appSink.setMaxBufferTime(maxBufferTime);
467#else
468 static constexpr int maxBuffers = 16;
469 m_appSink.setMaxBuffers(maxBuffers);
470#endif
471
472 static constexpr bool sync = false;
473 m_appSink.setSync(sync);
474
476 m_outputBin.add(m_appSink);
477 qLinkGstElements(m_audioConvert, m_appSink);
478 });
479}
480
481void QGstreamerAudioDecoder::removeAppSink()
482{
483 if (!m_appSink)
484 return;
485
486 qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::removeAppSink";
487
489 qUnlinkGstElements(m_audioConvert, m_appSink);
490 m_outputBin.stopAndRemoveElements(m_appSink);
491 });
492 m_appSink = {};
493}
494
495void QGstreamerAudioDecoder::updateDuration()
496{
497 int duration = m_playbin.duration() / 1000000;
498
499 if (m_duration != duration) {
500 m_duration = duration;
501 durationChanged(m_duration);
502 }
503
504 if (m_duration > 0)
505 m_durationQueries = 0;
506
507 if (m_durationQueries > 0) {
508 //increase delay between duration requests
509 int delay = 25 << (5 - m_durationQueries);
510 QTimer::singleShot(delay, this, &QGstreamerAudioDecoder::updateDuration);
511 m_durationQueries--;
512 }
513}
514
515qint64 QGstreamerAudioDecoder::getPositionFromBuffer(GstBuffer* buffer)
516{
517 qint64 position = GST_BUFFER_TIMESTAMP(buffer);
518 if (position >= 0)
519 position = position / G_GINT64_CONSTANT(1000); // microseconds
520 else
521 position = -1;
522 return position;
523}
524
526
527#include "moc_qgstreameraudiodecoder_p.cpp"
IOBluetoothDevice * device
\inmodule QtMultimedia
The QAudioDecoder class implements decoding audio.
Error
Defines a media player error condition.
The QAudioFormat class stores audio stream parameter information.
constexpr bool isValid() const noexcept
Returns true if all of the parameters are valid.
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
static QMaybe< QGstAppSource * > create(QObject *parent=nullptr)
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void add)(const Ts &...ts)
Definition qgst_p.h:691
static QGstBin create(const char *name)
Definition qgst.cpp:1060
void addGhostPad(const QGstElement &child, const char *name)
Definition qgst.cpp:1118
GstElement * element() const
Definition qgst.cpp:1018
static QGstElement createFromFactory(const char *factory, const char *name=nullptr)
Definition qgst.cpp:835
QGstPipeline getPipeline() const
Definition qgst.cpp:1031
QGObjectHandlerConnection connect(const char *name, GCallback callback, gpointer userData)
Definition qgst.cpp:653
int getInt(const char *property) const
Definition qgst.cpp:611
void set(const char *property, const char *str)
Definition qgst.cpp:538
GstObject * object() const
Definition qgst.cpp:677
GstStateChangeReturn setState(GstState state)
void installMessageFilter(QGstreamerSyncMessageFilter *filter)
void dumpGraph(const char *fileName)
void removeMessageFilter(QGstreamerSyncMessageFilter *filter)
qint64 duration() const
void modifyPipelineWhileNotRunning(Functor &&fn)
static QGstPipeline adopt(GstPipeline *)
void setSourceDevice(QIODevice *device) override
QAudioFormat audioFormat() const override
QIODevice * sourceDevice() const override
void setSource(const QUrl &fileName) override
qint64 duration() const override
void setAudioFormat(const QAudioFormat &format) override
static QMaybe< QPlatformAudioDecoder * > create(QAudioDecoder *parent)
QAudioBuffer read() override
qint64 position() const override
bool processBusMessage(const QGstreamerMessage &message) override
\inmodule QtCore \reentrant
Definition qiodevice.h:34
bool isOpen() const
Returns true if the device is open; otherwise returns false.
bool isReadable() const
Returns true if data can be read from the device; otherwise returns false.
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
void durationChanged(qint64 duration)
void positionChanged(qint64 position)
void bufferAvailableChanged(bool available)
QAudioDecoder::Error error() const
void formatChanged(const QAudioFormat &format)
void setIsDecoding(bool running=true)
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
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
bool singleShot
whether the timer is a single-shot timer
Definition qtimer.h:22
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
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
QGstCaps capsForAudioFormat(const QAudioFormat &format)
Definition qgstutils.cpp:83
QAudioFormat audioFormatForSample(GstSample *sample)
Definition qgstutils.cpp:48
Combined button and popup list for selecting options.
QString self
Definition language.cpp:58
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void * user_data
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
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
QString errorMessageCannotFindElement(std::string_view element)
Definition qgst_p.h:817
@ GST_PLAY_FLAG_NATIVE_AUDIO
@ GST_PLAY_FLAG_BUFFERING
@ GST_PLAY_FLAG_AUDIO
@ GST_PLAY_FLAG_VIDEO
@ GST_PLAY_FLAG_DOWNLOAD
@ GST_PLAY_FLAG_NATIVE_VIDEO
@ GST_PLAY_FLAG_SOFT_VOLUME
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
GLenum GLuint buffer
GLbitfield flags
GLuint GLsizei const GLchar * message
GLint maxBuffers
GLint GLsizei GLsizei GLenum format
GLsizei GLenum GLboolean sink
PromiseCallbacks callbacks
Definition qstdweb.cpp:275
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define tr(X)
long long qint64
Definition qtypes.h:60
Type get() const noexcept
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...