Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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
5#include <audio/qgstreameraudiodecoder_p.h>
6
7#include <common/qgst_debug_p.h>
8#include <common/qgstreamermessage_p.h>
9#include <common/qgstutils_p.h>
10#include <uri_handler/qgstreamer_qiodevice_handler_p.h>
11
12#include <gst/gstvalue.h>
13#include <gst/base/gstbasesrc.h>
14
15#include <QtCore/qdatetime.h>
16#include <QtCore/qdebug.h>
17#include <QtCore/qsize.h>
18#include <QtCore/qtimer.h>
19#include <QtCore/qdebug.h>
20#include <QtCore/qdir.h>
21#include <QtCore/qstandardpaths.h>
22#include <QtCore/qurl.h>
23#include <QtCore/qloggingcategory.h>
24
26
27Q_STATIC_LOGGING_CATEGORY(qLcGstreamerAudioDecoder, "qt.multimedia.gstreameraudiodecoder");
28
29typedef enum {
30 GST_PLAY_FLAG_VIDEO = 0x00000001,
31 GST_PLAY_FLAG_AUDIO = 0x00000002,
32 GST_PLAY_FLAG_TEXT = 0x00000004,
33 GST_PLAY_FLAG_VIS = 0x00000008,
39} GstPlayFlags;
40
41
42q23::expected<QPlatformAudioDecoder *, QString> QGstreamerAudioDecoder::create(QAudioDecoder *parent)
43{
44 static const auto error = qGstErrorMessageIfElementsNotAvailable("audioconvert", "playbin");
45 if (error)
46 return q23::unexpected{ *error };
47
48 return new QGstreamerAudioDecoder(parent);
49}
50
51QGstreamerAudioDecoder::QGstreamerAudioDecoder(QAudioDecoder *parent)
52 : QPlatformAudioDecoder(parent),
53 m_playbin{
54 QGstPipeline::createFromFactory("playbin3", "playbin"),
55 },
56 m_audioConvert{
57 QGstElement::createFromFactory("audioconvert", "audioconvert"),
58 }
59{
60 // Sort out messages
61 m_playbin.installMessageFilter(this);
62
63 // Set the rest of the pipeline up
64 setAudioFlags(true);
65
66 m_outputBin = QGstBin::create("audio-output-bin");
67 m_outputBin.add(m_audioConvert);
68
69 // add ghostpad
70 m_outputBin.addGhostPad(m_audioConvert, "sink");
71
72 g_object_set(m_playbin.object(), "audio-sink", m_outputBin.element(), NULL);
73
74 // Set volume to 100%
75 gdouble volume = 1.0;
76 m_playbin.set("volume", volume);
77}
78
80{
81 stop();
82
83 m_playbin.removeMessageFilter(this);
84}
85
87{
88 qCDebug(qLcGstreamerAudioDecoder) << "received bus message:" << message;
89
90 switch (message.type()) {
91 case GST_MESSAGE_DURATION:
92 return processBusMessageDuration(message);
93
94 case GST_MESSAGE_ERROR:
95 return processBusMessageError(message);
96
97 case GST_MESSAGE_WARNING:
98 return processBusMessageWarning(message);
99
100 case GST_MESSAGE_INFO:
101 return processBusMessageInfo(message);
102
103 case GST_MESSAGE_EOS:
104 return processBusMessageEOS(message);
105
106 case GST_MESSAGE_STATE_CHANGED:
107 return processBusMessageStateChanged(message);
108
109 case GST_MESSAGE_STREAMS_SELECTED:
110 return processBusMessageStreamsSelected(message);
111
112 default:
113 return false;
114 }
115}
116
118{
119 return true;
120}
121
122bool QGstreamerAudioDecoder::processBusMessageError(const QGstreamerMessage &message)
123{
124 qCDebug(qLcGstreamerAudioDecoder) << " error" << QCompactGstMessageAdaptor(message);
125
126 QUniqueGErrorHandle err;
127 QGString debug;
128 gst_message_parse_error(message.message(), &err, &debug);
129
130 if (message.source() == m_playbin) {
131 if (err.get()->domain == GST_STREAM_ERROR
132 && err.get()->code == GST_STREAM_ERROR_CODEC_NOT_FOUND)
133 processInvalidMedia(QAudioDecoder::FormatError,
134 tr("Cannot play stream of type: <unknown>"));
135 else
136 processInvalidMedia(QAudioDecoder::ResourceError,
137 QString::fromUtf8(err.get()->message));
138 } else {
139 QAudioDecoder::Error qerror = QAudioDecoder::ResourceError;
140 if (err.get()->domain == GST_STREAM_ERROR) {
141 switch (err.get()->code) {
142 case GST_STREAM_ERROR_DECRYPT:
143 case GST_STREAM_ERROR_DECRYPT_NOKEY:
144 qerror = QAudioDecoder::AccessDeniedError;
145 break;
146 case GST_STREAM_ERROR_FORMAT:
147 case GST_STREAM_ERROR_DEMUX:
148 case GST_STREAM_ERROR_DECODE:
149 case GST_STREAM_ERROR_WRONG_TYPE:
150 case GST_STREAM_ERROR_TYPE_NOT_FOUND:
151 case GST_STREAM_ERROR_CODEC_NOT_FOUND:
152 qerror = QAudioDecoder::FormatError;
153 break;
154 default:
155 break;
156 }
157 } else if (err.get()->domain == GST_CORE_ERROR) {
158 switch (err.get()->code) {
159 case GST_CORE_ERROR_MISSING_PLUGIN:
160 qerror = QAudioDecoder::FormatError;
161 break;
162 default:
163 break;
164 }
165 }
166
167 processInvalidMedia(qerror, QString::fromUtf8(err.get()->message));
168 }
169
170 return false;
171}
172
173bool QGstreamerAudioDecoder::processBusMessageDuration(const QGstreamerMessage &)
174{
175 updateDuration();
176 return false;
177}
178
179bool QGstreamerAudioDecoder::processBusMessageWarning(const QGstreamerMessage &message)
180{
181 qCWarning(qLcGstreamerAudioDecoder) << "Warning:" << QCompactGstMessageAdaptor(message);
182 return false;
183}
184
185bool QGstreamerAudioDecoder::processBusMessageInfo(const QGstreamerMessage &message)
186{
187 if (qLcGstreamerAudioDecoder().isDebugEnabled())
188 qCWarning(qLcGstreamerAudioDecoder) << "Info:" << QCompactGstMessageAdaptor(message);
189 return false;
190}
191
192bool QGstreamerAudioDecoder::processBusMessageEOS(const QGstreamerMessage &)
193{
194 m_playbin.setState(GST_STATE_NULL);
195 finished();
196 return false;
197}
198
199bool QGstreamerAudioDecoder::processBusMessageStateChanged(const QGstreamerMessage &message)
200{
201 if (message.source() != m_playbin)
202 return false;
203
204 GstState oldState;
205 GstState newState;
206 GstState pending;
207
208 gst_message_parse_state_changed(message.message(), &oldState, &newState, &pending);
209
210 bool isDecoding = false;
211 switch (newState) {
212 case GST_STATE_VOID_PENDING:
213 case GST_STATE_NULL:
214 case GST_STATE_READY:
215 break;
216 case GST_STATE_PLAYING:
217 isDecoding = true;
218 break;
219 case GST_STATE_PAUSED:
220 isDecoding = true;
221
222 // gstreamer doesn't give a reliable indication the duration
223 // information is ready, GST_MESSAGE_DURATION is not sent by most elements
224 // the duration is queried up to 5 times with increasing delay
225 m_durationQueries = 5;
226 updateDuration();
227 break;
228 }
229
230 setIsDecoding(isDecoding);
231 return false;
232}
233
234bool QGstreamerAudioDecoder::processBusMessageStreamsSelected(const QGstreamerMessage &message)
235{
236 using namespace Qt::StringLiterals;
237
238 QGstStreamCollectionHandle collection;
239 gst_message_parse_streams_selected(const_cast<GstMessage *>(message.message()), &collection);
240
241 bool hasAudio = false;
242 qForeachStreamInCollection(collection, [&](GstStream *stream) {
243 GstStreamType type = gst_stream_get_stream_type(stream);
244 if (type == GstStreamType::GST_STREAM_TYPE_AUDIO)
245 hasAudio = true;
246 });
247
248 if (!hasAudio)
249 processInvalidMedia(QAudioDecoder::FormatError, u"No audio track in media"_s);
250
251 return false;
252}
253
255{
256 return mSource;
257}
258
259void QGstreamerAudioDecoder::setSource(const QUrl &fileName)
260{
261 stop();
262 mDevice = nullptr;
263
264 bool isSignalRequired = (mSource != fileName);
265 mSource = fileName;
266 if (isSignalRequired)
267 sourceChanged();
268}
269
271{
272 return mDevice;
273}
274
275void QGstreamerAudioDecoder::setSourceDevice(QIODevice *device)
276{
277 stop();
278 mSource.clear();
279 bool isSignalRequired = (mDevice != device);
280 mDevice = device;
281 if (isSignalRequired)
282 sourceChanged();
283}
284
286{
287 addAppSink();
288
289 if (!mSource.isEmpty()) {
290 m_playbin.set("uri", mSource.toEncoded().constData());
291 } else if (mDevice) {
292 // make sure we can read from device
293 if (!mDevice->isOpen() || !mDevice->isReadable()) {
294 processInvalidMedia(QAudioDecoder::ResourceError, QLatin1String("Unable to read from specified device"));
295 return;
296 }
297
298 QUrl streamURL = qGstRegisterQIODevice(mDevice);
299 m_playbin.set("uri", streamURL.toEncoded().constData());
300 } else {
301 return;
302 }
303
304 // Set audio format
305 if (m_appSink) {
306 if (mFormat.isValid()) {
307 setAudioFlags(false);
308 auto caps = QGstUtils::capsForAudioFormat(mFormat);
309 m_appSink.setCaps(caps);
310 } else {
311 // We want whatever the native audio format is
312 setAudioFlags(true);
313 m_appSink.setCaps({});
314 }
315 }
316
317 if (m_playbin.setState(GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
318 qWarning() << "GStreamer; Unable to start decoding process";
319 m_playbin.dumpGraph("failed");
320 return;
321 }
322}
323
325{
326 m_playbin.setState(GST_STATE_NULL);
327 m_currentSessionId += 1;
328 removeAppSink();
329
330 // GStreamer thread is stopped. Can safely access m_buffersAvailable
331 if (m_buffersAvailable != 0) {
332 m_buffersAvailable = 0;
333 bufferAvailableChanged(false);
334 }
335
336 positionChanged(kInvalidPosition);
337 durationChanged(kInvalidDuration);
338
339 setIsDecoding(false);
340}
341
343{
344 return mFormat;
345}
346
347void QGstreamerAudioDecoder::setAudioFormat(const QAudioFormat &format)
348{
349 if (mFormat != format) {
350 mFormat = format;
351 formatChanged(mFormat);
352 }
353}
354
356{
357 using namespace std::chrono;
358
359 QAudioBuffer audioBuffer;
360
361 if (m_buffersAvailable == 0)
362 return audioBuffer;
363
364 m_buffersAvailable -= 1;
365
366 if (m_buffersAvailable == 0)
367 bufferAvailableChanged(false);
368
369 QGstSampleHandle sample = m_appSink.pullSample();
370 GstBuffer *buffer = gst_sample_get_buffer(sample.get());
371 GstMapInfo mapInfo;
372 gst_buffer_map(buffer, &mapInfo, GST_MAP_READ);
373 const char *bufferData = (const char *)mapInfo.data;
374 int bufferSize = mapInfo.size;
375 QAudioFormat format = QGstUtils::audioFormatForSample(sample.get());
376
377 if (format.isValid()) {
378 // XXX At the moment we have to copy data from GstBuffer into QAudioBuffer.
379 // We could improve performance by implementing QAbstractAudioBuffer for GstBuffer.
380 nanoseconds position = getPositionFromBuffer(buffer);
381 // QAudioBuffer expects startTime in microseconds, but the platform
382 // QPlatformAudioDecoder stores/uses milliseconds resolution. Convert
383 // the nanoseconds position to microseconds for the buffer, and to
384 // milliseconds for the stored platform position.
385 const auto startUs = std::chrono::round<std::chrono::microseconds>(position);
386 audioBuffer = QAudioBuffer{ QByteArray(bufferData, bufferSize), format, startUs.count() };
387 positionChanged(std::chrono::round<std::chrono::milliseconds>(position));
388 }
389 gst_buffer_unmap(buffer, &mapInfo);
390
391 return audioBuffer;
392}
393
394void QGstreamerAudioDecoder::processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString)
395{
396 stop();
397 error(errorCode, errorString);
398}
399
400GstFlowReturn QGstreamerAudioDecoder::newSample(GstAppSink *)
401{
402 // "Note that the preroll buffer will also be returned as the first buffer when calling
403 // gst_app_sink_pull_buffer()."
404
405 QMetaObject::invokeMethod(this, [this, sessionId = m_currentSessionId] {
406 if (sessionId != m_currentSessionId)
407 return; // stop()ed before message is executed
408
409 m_buffersAvailable += 1;
410 bufferAvailableChanged(true);
411 bufferReady();
412 });
413
414 return GST_FLOW_OK;
415}
416
417GstFlowReturn QGstreamerAudioDecoder::new_sample(GstAppSink *sink, gpointer user_data)
418{
419 QGstreamerAudioDecoder *decoder = reinterpret_cast<QGstreamerAudioDecoder *>(user_data);
420 qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::new_sample";
421 return decoder->newSample(sink);
422}
423
424void QGstreamerAudioDecoder::setAudioFlags(bool wantNativeAudio)
425{
426 int flags = m_playbin.getInt("flags");
427 // make sure not to use GST_PLAY_FLAG_NATIVE_AUDIO unless desired
428 // it prevents audio format conversion
430 flags |= GST_PLAY_FLAG_AUDIO;
431 if (wantNativeAudio)
433 m_playbin.set("flags", flags);
434}
435
436void QGstreamerAudioDecoder::addAppSink()
437{
438 using namespace std::chrono_literals;
439
440 if (m_appSink)
441 return;
442
443 qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::addAppSink";
444 m_appSink = QGstAppSink::create("decoderAppSink");
445 GstAppSinkCallbacks callbacks{};
446 callbacks.new_sample = new_sample;
447 m_appSink.setCallbacks(callbacks, this, nullptr);
448
449#if GST_CHECK_VERSION(1, 24, 0)
450 static constexpr auto maxBufferTime = 500ms;
451 m_appSink.setMaxBufferTime(maxBufferTime);
452#else
453 static constexpr int maxBuffers = 16;
454 m_appSink.setMaxBuffers(maxBuffers);
455#endif
456
457 static constexpr bool sync = false;
458 m_appSink.setSync(sync);
459
460 m_audioConvert.src().modifyPipelineInIdleProbe([&] {
461 m_outputBin.add(m_appSink);
462 qLinkGstElements(m_audioConvert, m_appSink);
463 });
464}
465
466void QGstreamerAudioDecoder::removeAppSink()
467{
468 if (!m_appSink)
469 return;
470
471 qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::removeAppSink";
472
473 m_audioConvert.src().modifyPipelineInIdleProbe([&] {
474 qUnlinkGstElements(m_audioConvert, m_appSink);
475 m_outputBin.stopAndRemoveElements(m_appSink);
476 });
477
478 m_appSink = {};
479}
480
481void QGstreamerAudioDecoder::updateDuration()
482{
483 std::optional<std::chrono::milliseconds> dur = m_playbin.durationInMs();
484 if (!dur)
485 dur = kInvalidDuration;
486
487 durationChanged(*dur);
488
489 if (dur->count() > 0)
490 m_durationQueries = 0;
491
492 if (m_durationQueries > 0) {
493 //increase delay between duration requests
494 int delay = 25 << (5 - m_durationQueries);
495 QTimer::singleShot(delay, this, &QGstreamerAudioDecoder::updateDuration);
496 m_durationQueries--;
497 }
498}
499
500std::chrono::nanoseconds QGstreamerAudioDecoder::getPositionFromBuffer(GstBuffer *buffer)
501{
502 using namespace std::chrono;
503 using namespace std::chrono_literals;
504 nanoseconds position{ GST_BUFFER_TIMESTAMP(buffer) };
505 if (position >= 0ns)
506 return position;
507 else
508 return std::chrono::nanoseconds{ kInvalidPosition };
509}
510
511QT_END_NAMESPACE
512
513#include "moc_qgstreameraudiodecoder_p.cpp"
The QAudioFormat class stores audio stream parameter information.
QAudioFormat audioFormat() const override
QIODevice * sourceDevice() const override
static q23::expected< QPlatformAudioDecoder *, QString > create(QAudioDecoder *parent)
QAudioBuffer read() override
bool processBusMessage(const QGstreamerMessage &message) override
bool canReadQrc() const override
QGstCaps capsForAudioFormat(const QAudioFormat &format)
Definition qgstutils.cpp:83
Combined button and popup list for selecting options.
@ GST_PLAY_FLAG_AUDIO
@ GST_PLAY_FLAG_SOFT_VOLUME
@ GST_PLAY_FLAG_DOWNLOAD
@ GST_PLAY_FLAG_BUFFERING
@ GST_PLAY_FLAG_NATIVE_AUDIO
@ GST_PLAY_FLAG_NATIVE_VIDEO
@ GST_PLAY_FLAG_VIDEO
QCompactGstMessageAdaptor(const QGstreamerMessage &m)