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
24#define MAX_BUFFERS_IN_QUEUE 4
25
27
28static Q_LOGGING_CATEGORY(qLcGstreamerAudioDecoder, "qt.multimedia.gstreameraudiodecoder");
29
41
42
43QMaybe<QPlatformAudioDecoder *> QGstreamerAudioDecoder::create(QAudioDecoder *parent)
44{
45 QGstElement audioconvert = QGstElement::createFromFactory("audioconvert", "audioconvert");
46 if (!audioconvert)
47 return errorMessageCannotFindElement("audioconvert");
48
50 GST_PIPELINE_CAST(QGstElement::createFromFactory("playbin", "playbin").element()));
51 if (!playbin)
52 return errorMessageCannotFindElement("playbin");
53
54 return new QGstreamerAudioDecoder(playbin, audioconvert, parent);
55}
56
57QGstreamerAudioDecoder::QGstreamerAudioDecoder(QGstPipeline playbin, QGstElement audioconvert,
58 QAudioDecoder *parent)
59 : QPlatformAudioDecoder(parent),
60 m_playbin(std::move(playbin)),
61 m_audioConvert(std::move(audioconvert))
62{
63 // Sort out messages
64 m_playbin.installMessageFilter(this);
65
66 // Set the rest of the pipeline up
67 setAudioFlags(true);
68
69 m_outputBin = QGstBin::create("audio-output-bin");
70 m_outputBin.add(m_audioConvert);
71
72 // add ghostpad
73 m_outputBin.addGhostPad(m_audioConvert, "sink");
74
75 g_object_set(m_playbin.object(), "audio-sink", m_outputBin.element(), NULL);
76
77 m_deepNotifySourceConnection = m_playbin.connect(
78 "deep-notify::source", (GCallback)&configureAppSrcElement, (gpointer)this);
79
80 // Set volume to 100%
81 gdouble volume = 1.0;
82 m_playbin.set("volume", volume);
83}
84
86{
87 stop();
88
89 m_playbin.removeMessageFilter(this);
90
91#if QT_CONFIG(gstreamer_app)
92 delete m_appSrc;
93#endif
94}
95
96#if QT_CONFIG(gstreamer_app)
97void QGstreamerAudioDecoder::configureAppSrcElement([[maybe_unused]] GObject *object, GObject *orig,
98 [[maybe_unused]] GParamSpec *pspec,
100{
101 // In case we switch from appsrc to not
102 if (!self->m_appSrc)
103 return;
104
105 QGstElementHandle appsrc;
106 g_object_get(orig, "source", &appsrc, NULL);
107
108 auto *qAppSrc = self->m_appSrc;
109 qAppSrc->setExternalAppSrc(QGstAppSrc{
110 qGstSafeCast<GstAppSrc>(appsrc.get()),
111 QGstAppSrc::NeedsRef, // CHECK: can we `release()`?
112 });
113 qAppSrc->setup(self->mDevice);
114}
115#endif
116
118{
119 qCDebug(qLcGstreamerAudioDecoder) << "received bus message:" << message;
120
121 GstMessage *gm = message.message();
122
123 switch (message.type()) {
124 case GST_MESSAGE_DURATION: {
125 updateDuration();
126 return false;
127 }
128
129 case GST_MESSAGE_ERROR: {
130 qCDebug(qLcGstreamerAudioDecoder) << " error" << QCompactGstMessageAdaptor(message);
131
134 gst_message_parse_error(gm, &err, &debug);
135
136 if (message.source() == m_playbin) {
137 if (err.get()->domain == GST_STREAM_ERROR
138 && err.get()->code == GST_STREAM_ERROR_CODEC_NOT_FOUND)
139 processInvalidMedia(QAudioDecoder::FormatError,
140 tr("Cannot play stream of type: <unknown>"));
141 else
142 processInvalidMedia(QAudioDecoder::ResourceError,
143 QString::fromUtf8(err.get()->message));
144 } else {
146 if (err.get()->domain == GST_STREAM_ERROR) {
147 switch (err.get()->code) {
148 case GST_STREAM_ERROR_DECRYPT:
149 case GST_STREAM_ERROR_DECRYPT_NOKEY:
151 break;
152 case GST_STREAM_ERROR_FORMAT:
153 case GST_STREAM_ERROR_DEMUX:
154 case GST_STREAM_ERROR_DECODE:
155 case GST_STREAM_ERROR_WRONG_TYPE:
156 case GST_STREAM_ERROR_TYPE_NOT_FOUND:
157 case GST_STREAM_ERROR_CODEC_NOT_FOUND:
159 break;
160 default:
161 break;
162 }
163 } else if (err.get()->domain == GST_CORE_ERROR) {
164 switch (err.get()->code) {
165 case GST_CORE_ERROR_MISSING_PLUGIN:
167 break;
168 default:
169 break;
170 }
171 }
172
173 processInvalidMedia(qerror, QString::fromUtf8(err.get()->message));
174 }
175 break;
176 }
177
178 default:
179 if (message.source() == m_playbin)
180 return handlePlaybinMessage(message);
181 }
182
183 return false;
184}
185
186bool QGstreamerAudioDecoder::handlePlaybinMessage(const QGstreamerMessage &message)
187{
188 GstMessage *gm = message.message();
189
190 switch (GST_MESSAGE_TYPE(gm)) {
191 case GST_MESSAGE_STATE_CHANGED: {
192 GstState oldState;
193 GstState newState;
194 GstState pending;
195
196 gst_message_parse_state_changed(gm, &oldState, &newState, &pending);
197
198 bool isDecoding = false;
199 switch (newState) {
200 case GST_STATE_VOID_PENDING:
201 case GST_STATE_NULL:
202 case GST_STATE_READY:
203 break;
204 case GST_STATE_PLAYING:
205 isDecoding = true;
206 break;
207 case GST_STATE_PAUSED:
208 isDecoding = true;
209
210 // gstreamer doesn't give a reliable indication the duration
211 // information is ready, GST_MESSAGE_DURATION is not sent by most elements
212 // the duration is queried up to 5 times with increasing delay
213 m_durationQueries = 5;
214 updateDuration();
215 break;
216 }
217
219 break;
220 };
221
222 case GST_MESSAGE_EOS:
223 m_playbin.setState(GST_STATE_NULL);
224 finished();
225 break;
226
227 case GST_MESSAGE_ERROR:
228 Q_UNREACHABLE_RETURN(false); // handled in processBusMessage
229
230 case GST_MESSAGE_WARNING:
231 qCWarning(qLcGstreamerAudioDecoder) << "Warning:" << QCompactGstMessageAdaptor(message);
232 break;
233
234 case GST_MESSAGE_INFO: {
235 if (qLcGstreamerAudioDecoder().isDebugEnabled())
236 qCWarning(qLcGstreamerAudioDecoder) << "Info:" << QCompactGstMessageAdaptor(message);
237 break;
238 }
239 default:
240 break;
241 }
242
243 return false;
244}
245
247{
248 return mSource;
249}
250
252{
253 stop();
254 mDevice = nullptr;
255 delete m_appSrc;
256 m_appSrc = nullptr;
257
258 bool isSignalRequired = (mSource != fileName);
259 mSource = fileName;
260 if (isSignalRequired)
262}
263
265{
266 return mDevice;
267}
268
270{
271 stop();
272 mSource.clear();
273 bool isSignalRequired = (mDevice != device);
274 mDevice = device;
275 if (isSignalRequired)
277}
278
280{
281 addAppSink();
282
283 if (!mSource.isEmpty()) {
284 m_playbin.set("uri", mSource.toEncoded().constData());
285 } else if (mDevice) {
286 // make sure we can read from device
287 if (!mDevice->isOpen() || !mDevice->isReadable()) {
288 processInvalidMedia(QAudioDecoder::ResourceError, QLatin1String("Unable to read from specified device"));
289 return;
290 }
291
292 if (!m_appSrc) {
293 auto maybeAppSrc = QGstAppSource::create(this);
294 if (maybeAppSrc) {
295 m_appSrc = maybeAppSrc.value();
296 } else {
297 processInvalidMedia(QAudioDecoder::ResourceError, maybeAppSrc.error());
298 return;
299 }
300 }
301
302 m_playbin.set("uri", "appsrc://");
303 } else {
304 return;
305 }
306
307 // Set audio format
308 if (m_appSink) {
309 if (mFormat.isValid()) {
310 setAudioFlags(false);
311 auto caps = QGstUtils::capsForAudioFormat(mFormat);
312 m_appSink.setCaps(caps);
313 } else {
314 // We want whatever the native audio format is
315 setAudioFlags(true);
316 m_appSink.setCaps({});
317 }
318 }
319
320 if (m_playbin.setState(GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
321 qWarning() << "GStreamer; Unable to start decoding process";
322 m_playbin.dumpGraph("failed");
323 return;
324 }
325}
326
328{
329 m_playbin.setState(GST_STATE_NULL);
330 m_currentSessionId += 1;
331 removeAppSink();
332
333 // GStreamer thread is stopped. Can safely access m_buffersAvailable
334 if (m_buffersAvailable != 0) {
335 m_buffersAvailable = 0;
337 }
338
339 if (m_position != -1) {
340 m_position = -1;
341 positionChanged(m_position);
342 }
343
344 if (m_duration != -1) {
345 m_duration = -1;
346 durationChanged(m_duration);
347 }
348
349 setIsDecoding(false);
350}
351
353{
354 return mFormat;
355}
356
358{
359 if (mFormat != format) {
360 mFormat = format;
361 formatChanged(mFormat);
362 }
363}
364
366{
367 QAudioBuffer audioBuffer;
368
369 if (m_buffersAvailable == 0)
370 return audioBuffer;
371
372 m_buffersAvailable -= 1;
373
374 if (m_buffersAvailable == 0)
376
377 QGstSampleHandle sample = m_appSink.pullSample();
378 GstBuffer *buffer = gst_sample_get_buffer(sample.get());
379 GstMapInfo mapInfo;
380 gst_buffer_map(buffer, &mapInfo, GST_MAP_READ);
381 const char *bufferData = (const char *)mapInfo.data;
382 int bufferSize = mapInfo.size;
384
385 if (format.isValid()) {
386 // XXX At the moment we have to copy data from GstBuffer into QAudioBuffer.
387 // We could improve performance by implementing QAbstractAudioBuffer for GstBuffer.
388 qint64 position = getPositionFromBuffer(buffer);
389 audioBuffer = QAudioBuffer(QByteArray(bufferData, bufferSize), format, position);
390 position /= 1000; // convert to milliseconds
391 if (position != m_position) {
392 m_position = position;
393 positionChanged(m_position);
394 }
395 }
396 gst_buffer_unmap(buffer, &mapInfo);
397
398 return audioBuffer;
399}
400
402{
403 return m_position;
404}
405
407{
408 return m_duration;
409}
410
411void QGstreamerAudioDecoder::processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString)
412{
413 stop();
414 error(int(errorCode), errorString);
415}
416
417GstFlowReturn QGstreamerAudioDecoder::newSample(GstAppSink *)
418{
419 // "Note that the preroll buffer will also be returned as the first buffer when calling
420 // gst_app_sink_pull_buffer()."
421
422 QMetaObject::invokeMethod(this, [this, sessionId = m_currentSessionId] {
423 if (sessionId != m_currentSessionId)
424 return; // stop()ed before message is executed
425
426 m_buffersAvailable += 1;
428 bufferReady();
429 });
430
431 return GST_FLOW_OK;
432}
433
434GstFlowReturn QGstreamerAudioDecoder::new_sample(GstAppSink *sink, gpointer user_data)
435{
436 QGstreamerAudioDecoder *decoder = reinterpret_cast<QGstreamerAudioDecoder *>(user_data);
437 qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::new_sample";
438 return decoder->newSample(sink);
439}
440
441void QGstreamerAudioDecoder::setAudioFlags(bool wantNativeAudio)
442{
443 int flags = m_playbin.getInt("flags");
444 // make sure not to use GST_PLAY_FLAG_NATIVE_AUDIO unless desired
445 // it prevents audio format conversion
448 if (wantNativeAudio)
450 m_playbin.set("flags", flags);
451}
452
453void QGstreamerAudioDecoder::addAppSink()
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 gst_app_sink_set_max_buffers(m_appSink.appSink(), MAX_BUFFERS_IN_QUEUE);
464 gst_base_sink_set_sync(m_appSink.baseSink(), FALSE);
465
467 m_outputBin.add(m_appSink);
468 qLinkGstElements(m_audioConvert, m_appSink);
469 });
470}
471
472void QGstreamerAudioDecoder::removeAppSink()
473{
474 if (!m_appSink)
475 return;
476
477 qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::removeAppSink";
478
480 qUnlinkGstElements(m_audioConvert, m_appSink);
481 m_outputBin.stopAndRemoveElements(m_appSink);
482 });
483 m_appSink = {};
484}
485
486void QGstreamerAudioDecoder::updateDuration()
487{
488 int duration = m_playbin.duration() / 1000000;
489
490 if (m_duration != duration) {
491 m_duration = duration;
492 durationChanged(m_duration);
493 }
494
495 if (m_duration > 0)
496 m_durationQueries = 0;
497
498 if (m_durationQueries > 0) {
499 //increase delay between duration requests
500 int delay = 25 << (5 - m_durationQueries);
501 QTimer::singleShot(delay, this, &QGstreamerAudioDecoder::updateDuration);
502 m_durationQueries--;
503 }
504}
505
506qint64 QGstreamerAudioDecoder::getPositionFromBuffer(GstBuffer* buffer)
507{
508 qint64 position = GST_BUFFER_TIMESTAMP(buffer);
509 if (position >= 0)
510 position = position / G_GINT64_CONSTANT(1000); // microseconds
511 else
512 position = -1;
513 return position;
514}
515
517
518#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:690
static QGstBin create(const char *name)
Definition qgst.cpp:1058
void addGhostPad(const QGstElement &child, const char *name)
Definition qgst.cpp:1116
GstElement * element() const
Definition qgst.cpp:1026
static QGstElement createFromFactory(const char *factory, const char *name=nullptr)
Definition qgst.cpp:835
QGstPipeline getPipeline() const
Definition qgst.cpp:1039
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:804
@ 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 MAX_BUFFERS_IN_QUEUE
#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 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...