5#include <audio/qgstreameraudiodecoder_p.h>
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>
12#include <gst/gstvalue.h>
13#include <gst/base/gstbasesrc.h>
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>
27Q_STATIC_LOGGING_CATEGORY(qLcGstreamerAudioDecoder,
"qt.multimedia.gstreameraudiodecoder");
44 static const auto error = qGstErrorMessageIfElementsNotAvailable(
"audioconvert",
"playbin");
46 return q23::unexpected{ *error };
52 : QPlatformAudioDecoder(parent),
54 QGstPipeline::createFromFactory(
"playbin3",
"playbin"),
57 QGstElement::createFromFactory(
"audioconvert",
"audioconvert"),
61 m_playbin.installMessageFilter(
this);
66 m_outputBin = QGstBin::create(
"audio-output-bin");
67 m_outputBin.add(m_audioConvert);
70 m_outputBin.addGhostPad(m_audioConvert,
"sink");
72 g_object_set(m_playbin.object(),
"audio-sink", m_outputBin.element(), NULL);
76 m_playbin.set(
"volume", volume);
83 m_playbin.removeMessageFilter(
this);
88 qCDebug(qLcGstreamerAudioDecoder) <<
"received bus message:" << message;
90 switch (message.type()) {
91 case GST_MESSAGE_DURATION:
92 return processBusMessageDuration(message);
94 case GST_MESSAGE_ERROR:
95 return processBusMessageError(message);
97 case GST_MESSAGE_WARNING:
98 return processBusMessageWarning(message);
100 case GST_MESSAGE_INFO:
101 return processBusMessageInfo(message);
103 case GST_MESSAGE_EOS:
104 return processBusMessageEOS(message);
106 case GST_MESSAGE_STATE_CHANGED:
107 return processBusMessageStateChanged(message);
109 case GST_MESSAGE_STREAMS_SELECTED:
110 return processBusMessageStreamsSelected(message);
124 qCDebug(qLcGstreamerAudioDecoder) <<
" error" << QCompactGstMessageAdaptor(message);
126 QUniqueGErrorHandle err;
128 gst_message_parse_error(message.message(), &err, &debug);
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>"));
136 processInvalidMedia(QAudioDecoder::ResourceError,
137 QString::fromUtf8(err.get()->message));
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;
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;
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;
167 processInvalidMedia(qerror, QString::fromUtf8(err.get()->message));
181 qCWarning(qLcGstreamerAudioDecoder) <<
"Warning:" << QCompactGstMessageAdaptor(message);
187 if (qLcGstreamerAudioDecoder().isDebugEnabled())
188 qCWarning(qLcGstreamerAudioDecoder) <<
"Info:" << QCompactGstMessageAdaptor(message);
194 m_playbin.setState(GST_STATE_NULL);
201 if (message.source() != m_playbin)
208 gst_message_parse_state_changed(message.message(), &oldState, &newState, &pending);
210 bool isDecoding =
false;
212 case GST_STATE_VOID_PENDING:
214 case GST_STATE_READY:
216 case GST_STATE_PLAYING:
219 case GST_STATE_PAUSED:
225 m_durationQueries = 5;
230 setIsDecoding(isDecoding);
236 using namespace Qt::StringLiterals;
238 QGstStreamCollectionHandle collection;
239 gst_message_parse_streams_selected(
const_cast<GstMessage *>(message.message()), &collection);
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)
249 processInvalidMedia(QAudioDecoder::FormatError, u"No audio track in media"_s);
264 bool isSignalRequired = (mSource != fileName);
266 if (isSignalRequired)
279 bool isSignalRequired = (mDevice != device);
281 if (isSignalRequired)
289 if (!mSource.isEmpty()) {
290 m_playbin.set(
"uri", mSource.toEncoded().constData());
291 }
else if (mDevice) {
293 if (!mDevice->isOpen() || !mDevice->isReadable()) {
294 processInvalidMedia(QAudioDecoder::ResourceError, QLatin1String(
"Unable to read from specified device"));
298 QUrl streamURL = qGstRegisterQIODevice(mDevice);
299 m_playbin.set(
"uri", streamURL.toEncoded().constData());
306 if (mFormat.isValid()) {
307 setAudioFlags(
false);
308 auto caps = QGstUtils::capsForAudioFormat(mFormat);
309 m_appSink.setCaps(caps);
313 m_appSink.setCaps({});
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");
326 m_playbin.setState(GST_STATE_NULL);
327 m_currentSessionId += 1;
331 if (m_buffersAvailable != 0) {
332 m_buffersAvailable = 0;
333 bufferAvailableChanged(
false);
336 if (m_position != invalidPosition) {
337 m_position = invalidPosition;
338 positionChanged(m_position.count());
341 if (m_duration != invalidDuration) {
342 m_duration = invalidDuration;
343 durationChanged(m_duration.count());
346 setIsDecoding(
false);
356 if (mFormat != format) {
358 formatChanged(mFormat);
364 using namespace std::chrono;
366 QAudioBuffer audioBuffer;
368 if (m_buffersAvailable == 0)
371 m_buffersAvailable -= 1;
373 if (m_buffersAvailable == 0)
374 bufferAvailableChanged(
false);
376 QGstSampleHandle sample = m_appSink.pullSample();
377 GstBuffer *buffer = gst_sample_get_buffer(sample.get());
379 gst_buffer_map(buffer, &mapInfo, GST_MAP_READ);
380 const char *bufferData = (
const char *)mapInfo.data;
381 int bufferSize = mapInfo.size;
382 QAudioFormat format = QGstUtils::audioFormatForSample(sample.get());
384 if (format.isValid()) {
387 nanoseconds position = getPositionFromBuffer(buffer);
388 audioBuffer = QAudioBuffer{
389 QByteArray(bufferData, bufferSize),
391 round<microseconds>(position).count(),
393 milliseconds positionInMs = round<milliseconds>(position);
394 if (position != m_position) {
395 m_position = positionInMs;
396 positionChanged(m_position.count());
399 gst_buffer_unmap(buffer, &mapInfo);
406 return m_position.count();
411 return m_duration.count();
414void QGstreamerAudioDecoder::processInvalidMedia(QAudioDecoder::Error errorCode,
const QString& errorString)
417 error(
int(errorCode), errorString);
425 QMetaObject::invokeMethod(
this, [
this, sessionId = m_currentSessionId] {
426 if (sessionId != m_currentSessionId)
429 m_buffersAvailable += 1;
430 bufferAvailableChanged(
true);
440 qCDebug(qLcGstreamerAudioDecoder) <<
"QGstreamerAudioDecoder::new_sample";
441 return decoder->newSample(sink);
446 int flags = m_playbin.getInt(
"flags");
453 m_playbin.set(
"flags", flags);
458 using namespace std::chrono_literals;
463 qCDebug(qLcGstreamerAudioDecoder) <<
"QGstreamerAudioDecoder::addAppSink";
464 m_appSink = QGstAppSink::create(
"decoderAppSink");
465 GstAppSinkCallbacks callbacks{};
466 callbacks.new_sample = new_sample;
467 m_appSink.setCallbacks(callbacks,
this,
nullptr);
469#if GST_CHECK_VERSION(1
, 24
, 0
)
470 static constexpr auto maxBufferTime = 500ms;
471 m_appSink.setMaxBufferTime(maxBufferTime);
473 static constexpr int maxBuffers = 16;
474 m_appSink.setMaxBuffers(maxBuffers);
477 static constexpr bool sync =
false;
478 m_appSink.setSync(sync);
480 m_audioConvert.src().modifyPipelineInIdleProbe([&] {
481 m_outputBin.add(m_appSink);
482 qLinkGstElements(m_audioConvert, m_appSink);
491 qCDebug(qLcGstreamerAudioDecoder) <<
"QGstreamerAudioDecoder::removeAppSink";
493 m_audioConvert.src().modifyPipelineInIdleProbe([&] {
494 qUnlinkGstElements(m_audioConvert, m_appSink);
495 m_outputBin.stopAndRemoveElements(m_appSink);
503 std::optional<
std::chrono::milliseconds> duration = m_playbin.durationInMs();
505 duration = invalidDuration;
507 if (m_duration != duration) {
508 m_duration = *duration;
509 durationChanged(m_duration.count());
512 if (m_duration.count() > 0)
513 m_durationQueries = 0;
515 if (m_durationQueries > 0) {
517 int delay = 25 << (5 - m_durationQueries);
518 QTimer::singleShot(delay,
this, &QGstreamerAudioDecoder::updateDuration);
525 using namespace std::chrono;
526 using namespace std::chrono_literals;
527 nanoseconds position{ GST_BUFFER_TIMESTAMP(buffer) };
531 return invalidPosition;
536#include "moc_qgstreameraudiodecoder_p.cpp"
QAudioFormat audioFormat() const override
~QGstreamerAudioDecoder() override
QIODevice * sourceDevice() const override
qint64 duration() const override
static q23::expected< QPlatformAudioDecoder *, QString > create(QAudioDecoder *parent)
QUrl source() const override
QAudioBuffer read() override
qint64 position() const override
bool processBusMessage(const QGstreamerMessage &message) override
bool canReadQrc() const override
@ GST_PLAY_FLAG_SOFT_VOLUME
@ GST_PLAY_FLAG_BUFFERING
@ GST_PLAY_FLAG_NATIVE_AUDIO
@ GST_PLAY_FLAG_NATIVE_VIDEO