summaryrefslogtreecommitdiffstats
path: root/src/plugins/multimedia/gstreamer/audio
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/multimedia/gstreamer/audio')
-rw-r--r--src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder.cpp531
-rw-r--r--src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder_p.h111
-rw-r--r--src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice.cpp87
-rw-r--r--src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice_p.h55
4 files changed, 784 insertions, 0 deletions
diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder.cpp b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder.cpp
new file mode 100644
index 000000000..280b43cdb
--- /dev/null
+++ b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder.cpp
@@ -0,0 +1,531 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+//#define DEBUG_DECODER
+
+#include <audio/qgstreameraudiodecoder_p.h>
+
+#include <common/qgstreamermessage_p.h>
+#include <common/qgst_debug_p.h>
+#include <common/qgstutils_p.h>
+
+#include <gst/gstvalue.h>
+#include <gst/base/gstbasesrc.h>
+
+#include <QtCore/qdatetime.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qsize.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qstandardpaths.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qloggingcategory.h>
+
+QT_BEGIN_NAMESPACE
+
+static Q_LOGGING_CATEGORY(qLcGstreamerAudioDecoder, "qt.multimedia.gstreameraudiodecoder");
+
+typedef enum {
+ GST_PLAY_FLAG_VIDEO = 0x00000001,
+ GST_PLAY_FLAG_AUDIO = 0x00000002,
+ GST_PLAY_FLAG_TEXT = 0x00000004,
+ GST_PLAY_FLAG_VIS = 0x00000008,
+ GST_PLAY_FLAG_SOFT_VOLUME = 0x00000010,
+ GST_PLAY_FLAG_NATIVE_AUDIO = 0x00000020,
+ GST_PLAY_FLAG_NATIVE_VIDEO = 0x00000040,
+ GST_PLAY_FLAG_DOWNLOAD = 0x00000080,
+ GST_PLAY_FLAG_BUFFERING = 0x000000100
+} GstPlayFlags;
+
+
+QMaybe<QPlatformAudioDecoder *> QGstreamerAudioDecoder::create(QAudioDecoder *parent)
+{
+ static const auto error = qGstErrorMessageIfElementsNotAvailable("audioconvert", "playbin");
+ if (error)
+ return *error;
+
+ return new QGstreamerAudioDecoder(parent);
+}
+
+QGstreamerAudioDecoder::QGstreamerAudioDecoder(QAudioDecoder *parent)
+ : QPlatformAudioDecoder(parent),
+ m_playbin{
+ QGstPipeline::adopt(GST_PIPELINE_CAST(
+ QGstElement::createFromFactory("playbin", "playbin").element())),
+ },
+ m_audioConvert{
+ QGstElement::createFromFactory("audioconvert", "audioconvert"),
+ }
+{
+ // Sort out messages
+ m_playbin.installMessageFilter(this);
+
+ // Set the rest of the pipeline up
+ setAudioFlags(true);
+
+ m_outputBin = QGstBin::create("audio-output-bin");
+ m_outputBin.add(m_audioConvert);
+
+ // add ghostpad
+ m_outputBin.addGhostPad(m_audioConvert, "sink");
+
+ g_object_set(m_playbin.object(), "audio-sink", m_outputBin.element(), NULL);
+
+ m_deepNotifySourceConnection = m_playbin.connect(
+ "deep-notify::source", (GCallback)&configureAppSrcElement, (gpointer)this);
+
+ // Set volume to 100%
+ gdouble volume = 1.0;
+ m_playbin.set("volume", volume);
+}
+
+QGstreamerAudioDecoder::~QGstreamerAudioDecoder()
+{
+ stop();
+
+ m_playbin.removeMessageFilter(this);
+
+ delete m_appSrc;
+}
+
+void QGstreamerAudioDecoder::configureAppSrcElement([[maybe_unused]] GObject *object, GObject *orig,
+ [[maybe_unused]] GParamSpec *pspec,
+ QGstreamerAudioDecoder *self)
+{
+ // In case we switch from appsrc to not
+ if (!self->m_appSrc)
+ return;
+
+ QGstElementHandle appsrc;
+ g_object_get(orig, "source", &appsrc, NULL);
+
+ auto *qAppSrc = self->m_appSrc;
+ qAppSrc->setExternalAppSrc(QGstAppSrc{
+ qGstSafeCast<GstAppSrc>(appsrc.get()),
+ QGstAppSrc::NeedsRef, // CHECK: can we `release()`?
+ });
+ qAppSrc->setup(self->mDevice);
+}
+
+bool QGstreamerAudioDecoder::processBusMessage(const QGstreamerMessage &message)
+{
+ qCDebug(qLcGstreamerAudioDecoder) << "received bus message:" << message;
+
+ GstMessage *gm = message.message();
+
+ switch (message.type()) {
+ case GST_MESSAGE_DURATION: {
+ updateDuration();
+ return false;
+ }
+
+ case GST_MESSAGE_ERROR: {
+ qCDebug(qLcGstreamerAudioDecoder) << " error" << QCompactGstMessageAdaptor(message);
+
+ QUniqueGErrorHandle err;
+ QGString debug;
+ gst_message_parse_error(gm, &err, &debug);
+
+ if (message.source() == m_playbin) {
+ if (err.get()->domain == GST_STREAM_ERROR
+ && err.get()->code == GST_STREAM_ERROR_CODEC_NOT_FOUND)
+ processInvalidMedia(QAudioDecoder::FormatError,
+ tr("Cannot play stream of type: <unknown>"));
+ else
+ processInvalidMedia(QAudioDecoder::ResourceError,
+ QString::fromUtf8(err.get()->message));
+ } else {
+ QAudioDecoder::Error qerror = QAudioDecoder::ResourceError;
+ if (err.get()->domain == GST_STREAM_ERROR) {
+ switch (err.get()->code) {
+ case GST_STREAM_ERROR_DECRYPT:
+ case GST_STREAM_ERROR_DECRYPT_NOKEY:
+ qerror = QAudioDecoder::AccessDeniedError;
+ break;
+ case GST_STREAM_ERROR_FORMAT:
+ case GST_STREAM_ERROR_DEMUX:
+ case GST_STREAM_ERROR_DECODE:
+ case GST_STREAM_ERROR_WRONG_TYPE:
+ case GST_STREAM_ERROR_TYPE_NOT_FOUND:
+ case GST_STREAM_ERROR_CODEC_NOT_FOUND:
+ qerror = QAudioDecoder::FormatError;
+ break;
+ default:
+ break;
+ }
+ } else if (err.get()->domain == GST_CORE_ERROR) {
+ switch (err.get()->code) {
+ case GST_CORE_ERROR_MISSING_PLUGIN:
+ qerror = QAudioDecoder::FormatError;
+ break;
+ default:
+ break;
+ }
+ }
+
+ processInvalidMedia(qerror, QString::fromUtf8(err.get()->message));
+ }
+ break;
+ }
+
+ default:
+ if (message.source() == m_playbin)
+ return handlePlaybinMessage(message);
+ }
+
+ return false;
+}
+
+bool QGstreamerAudioDecoder::handlePlaybinMessage(const QGstreamerMessage &message)
+{
+ GstMessage *gm = message.message();
+
+ switch (GST_MESSAGE_TYPE(gm)) {
+ case GST_MESSAGE_STATE_CHANGED: {
+ GstState oldState;
+ GstState newState;
+ GstState pending;
+
+ gst_message_parse_state_changed(gm, &oldState, &newState, &pending);
+
+ bool isDecoding = false;
+ switch (newState) {
+ case GST_STATE_VOID_PENDING:
+ case GST_STATE_NULL:
+ case GST_STATE_READY:
+ break;
+ case GST_STATE_PLAYING:
+ isDecoding = true;
+ break;
+ case GST_STATE_PAUSED:
+ isDecoding = true;
+
+ // gstreamer doesn't give a reliable indication the duration
+ // information is ready, GST_MESSAGE_DURATION is not sent by most elements
+ // the duration is queried up to 5 times with increasing delay
+ m_durationQueries = 5;
+ updateDuration();
+ break;
+ }
+
+ setIsDecoding(isDecoding);
+ break;
+ };
+
+ case GST_MESSAGE_EOS:
+ m_playbin.setState(GST_STATE_NULL);
+ finished();
+ break;
+
+ case GST_MESSAGE_ERROR:
+ Q_UNREACHABLE_RETURN(false); // handled in processBusMessage
+
+ case GST_MESSAGE_WARNING:
+ qCWarning(qLcGstreamerAudioDecoder) << "Warning:" << QCompactGstMessageAdaptor(message);
+ break;
+
+ case GST_MESSAGE_INFO: {
+ if (qLcGstreamerAudioDecoder().isDebugEnabled())
+ qCWarning(qLcGstreamerAudioDecoder) << "Info:" << QCompactGstMessageAdaptor(message);
+ break;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+QUrl QGstreamerAudioDecoder::source() const
+{
+ return mSource;
+}
+
+void QGstreamerAudioDecoder::setSource(const QUrl &fileName)
+{
+ stop();
+ mDevice = nullptr;
+ delete m_appSrc;
+ m_appSrc = nullptr;
+
+ bool isSignalRequired = (mSource != fileName);
+ mSource = fileName;
+ if (isSignalRequired)
+ sourceChanged();
+}
+
+QIODevice *QGstreamerAudioDecoder::sourceDevice() const
+{
+ return mDevice;
+}
+
+void QGstreamerAudioDecoder::setSourceDevice(QIODevice *device)
+{
+ stop();
+ mSource.clear();
+ bool isSignalRequired = (mDevice != device);
+ mDevice = device;
+ if (isSignalRequired)
+ sourceChanged();
+}
+
+void QGstreamerAudioDecoder::start()
+{
+ addAppSink();
+
+ if (!mSource.isEmpty()) {
+ m_playbin.set("uri", mSource.toEncoded().constData());
+ } else if (mDevice) {
+ // make sure we can read from device
+ if (!mDevice->isOpen() || !mDevice->isReadable()) {
+ processInvalidMedia(QAudioDecoder::ResourceError, QLatin1String("Unable to read from specified device"));
+ return;
+ }
+
+ if (!m_appSrc) {
+ auto maybeAppSrc = QGstAppSource::create(this);
+ if (maybeAppSrc) {
+ m_appSrc = maybeAppSrc.value();
+ } else {
+ processInvalidMedia(QAudioDecoder::ResourceError, maybeAppSrc.error());
+ return;
+ }
+ }
+
+ m_playbin.set("uri", "appsrc://");
+ } else {
+ return;
+ }
+
+ // Set audio format
+ if (m_appSink) {
+ if (mFormat.isValid()) {
+ setAudioFlags(false);
+ auto caps = QGstUtils::capsForAudioFormat(mFormat);
+ m_appSink.setCaps(caps);
+ } else {
+ // We want whatever the native audio format is
+ setAudioFlags(true);
+ m_appSink.setCaps({});
+ }
+ }
+
+ if (m_playbin.setState(GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
+ qWarning() << "GStreamer; Unable to start decoding process";
+ m_playbin.dumpGraph("failed");
+ return;
+ }
+}
+
+void QGstreamerAudioDecoder::stop()
+{
+ m_playbin.setState(GST_STATE_NULL);
+ m_currentSessionId += 1;
+ removeAppSink();
+
+ // GStreamer thread is stopped. Can safely access m_buffersAvailable
+ if (m_buffersAvailable != 0) {
+ m_buffersAvailable = 0;
+ bufferAvailableChanged(false);
+ }
+
+ if (m_position != invalidPosition) {
+ m_position = invalidPosition;
+ positionChanged(m_position.count());
+ }
+
+ if (m_duration != invalidDuration) {
+ m_duration = invalidDuration;
+ durationChanged(m_duration.count());
+ }
+
+ setIsDecoding(false);
+}
+
+QAudioFormat QGstreamerAudioDecoder::audioFormat() const
+{
+ return mFormat;
+}
+
+void QGstreamerAudioDecoder::setAudioFormat(const QAudioFormat &format)
+{
+ if (mFormat != format) {
+ mFormat = format;
+ formatChanged(mFormat);
+ }
+}
+
+QAudioBuffer QGstreamerAudioDecoder::read()
+{
+ using namespace std::chrono;
+
+ QAudioBuffer audioBuffer;
+
+ if (m_buffersAvailable == 0)
+ return audioBuffer;
+
+ m_buffersAvailable -= 1;
+
+ if (m_buffersAvailable == 0)
+ bufferAvailableChanged(false);
+
+ QGstSampleHandle sample = m_appSink.pullSample();
+ GstBuffer *buffer = gst_sample_get_buffer(sample.get());
+ GstMapInfo mapInfo;
+ gst_buffer_map(buffer, &mapInfo, GST_MAP_READ);
+ const char *bufferData = (const char *)mapInfo.data;
+ int bufferSize = mapInfo.size;
+ QAudioFormat format = QGstUtils::audioFormatForSample(sample.get());
+
+ if (format.isValid()) {
+ // XXX At the moment we have to copy data from GstBuffer into QAudioBuffer.
+ // We could improve performance by implementing QAbstractAudioBuffer for GstBuffer.
+ nanoseconds position = getPositionFromBuffer(buffer);
+ audioBuffer = QAudioBuffer{
+ QByteArray(bufferData, bufferSize),
+ format,
+ round<microseconds>(position).count(),
+ };
+ milliseconds positionInMs = round<milliseconds>(position);
+ if (position != m_position) {
+ m_position = positionInMs;
+ positionChanged(m_position.count());
+ }
+ }
+ gst_buffer_unmap(buffer, &mapInfo);
+
+ return audioBuffer;
+}
+
+qint64 QGstreamerAudioDecoder::position() const
+{
+ return m_position.count();
+}
+
+qint64 QGstreamerAudioDecoder::duration() const
+{
+ return m_duration.count();
+}
+
+void QGstreamerAudioDecoder::processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString)
+{
+ stop();
+ error(int(errorCode), errorString);
+}
+
+GstFlowReturn QGstreamerAudioDecoder::newSample(GstAppSink *)
+{
+ // "Note that the preroll buffer will also be returned as the first buffer when calling
+ // gst_app_sink_pull_buffer()."
+
+ QMetaObject::invokeMethod(this, [this, sessionId = m_currentSessionId] {
+ if (sessionId != m_currentSessionId)
+ return; // stop()ed before message is executed
+
+ m_buffersAvailable += 1;
+ bufferAvailableChanged(true);
+ bufferReady();
+ });
+
+ return GST_FLOW_OK;
+}
+
+GstFlowReturn QGstreamerAudioDecoder::new_sample(GstAppSink *sink, gpointer user_data)
+{
+ QGstreamerAudioDecoder *decoder = reinterpret_cast<QGstreamerAudioDecoder *>(user_data);
+ qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::new_sample";
+ return decoder->newSample(sink);
+}
+
+void QGstreamerAudioDecoder::setAudioFlags(bool wantNativeAudio)
+{
+ int flags = m_playbin.getInt("flags");
+ // make sure not to use GST_PLAY_FLAG_NATIVE_AUDIO unless desired
+ // it prevents audio format conversion
+ flags &= ~(GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_NATIVE_VIDEO | GST_PLAY_FLAG_TEXT | GST_PLAY_FLAG_VIS | GST_PLAY_FLAG_NATIVE_AUDIO);
+ flags |= GST_PLAY_FLAG_AUDIO;
+ if (wantNativeAudio)
+ flags |= GST_PLAY_FLAG_NATIVE_AUDIO;
+ m_playbin.set("flags", flags);
+}
+
+void QGstreamerAudioDecoder::addAppSink()
+{
+ using namespace std::chrono_literals;
+
+ if (m_appSink)
+ return;
+
+ qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::addAppSink";
+ m_appSink = QGstAppSink::create("decoderAppSink");
+ GstAppSinkCallbacks callbacks{};
+ callbacks.new_sample = new_sample;
+ m_appSink.setCallbacks(callbacks, this, nullptr);
+
+#if GST_CHECK_VERSION(1, 24, 0)
+ static constexpr auto maxBufferTime = 500ms;
+ m_appSink.setMaxBufferTime(maxBufferTime);
+#else
+ static constexpr int maxBuffers = 16;
+ m_appSink.setMaxBuffers(maxBuffers);
+#endif
+
+ static constexpr bool sync = false;
+ m_appSink.setSync(sync);
+
+ QGstPipeline::modifyPipelineWhileNotRunning(m_playbin.getPipeline(), [&] {
+ m_outputBin.add(m_appSink);
+ qLinkGstElements(m_audioConvert, m_appSink);
+ });
+}
+
+void QGstreamerAudioDecoder::removeAppSink()
+{
+ if (!m_appSink)
+ return;
+
+ qCDebug(qLcGstreamerAudioDecoder) << "QGstreamerAudioDecoder::removeAppSink";
+
+ QGstPipeline::modifyPipelineWhileNotRunning(m_playbin.getPipeline(), [&] {
+ qUnlinkGstElements(m_audioConvert, m_appSink);
+ m_outputBin.stopAndRemoveElements(m_appSink);
+ });
+ m_appSink = {};
+}
+
+void QGstreamerAudioDecoder::updateDuration()
+{
+ std::optional<std::chrono::milliseconds> duration = m_playbin.durationInMs();
+ if (!duration)
+ duration = invalidDuration;
+
+ if (m_duration != duration) {
+ m_duration = *duration;
+ durationChanged(m_duration.count());
+ }
+
+ if (m_duration.count() > 0)
+ m_durationQueries = 0;
+
+ if (m_durationQueries > 0) {
+ //increase delay between duration requests
+ int delay = 25 << (5 - m_durationQueries);
+ QTimer::singleShot(delay, this, &QGstreamerAudioDecoder::updateDuration);
+ m_durationQueries--;
+ }
+}
+
+std::chrono::nanoseconds QGstreamerAudioDecoder::getPositionFromBuffer(GstBuffer *buffer)
+{
+ using namespace std::chrono;
+ using namespace std::chrono_literals;
+ nanoseconds position{ GST_BUFFER_TIMESTAMP(buffer) };
+ if (position >= 0ns)
+ return position;
+ else
+ return invalidPosition;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qgstreameraudiodecoder_p.cpp"
diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder_p.h b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder_p.h
new file mode 100644
index 000000000..d2d259dde
--- /dev/null
+++ b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodecoder_p.h
@@ -0,0 +1,111 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QGSTREAMERAUDIODECODERCONTROL_H
+#define QGSTREAMERAUDIODECODERCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/private/qmultimediautils_p.h>
+#include <QtMultimedia/private/qplatformaudiodecoder_p.h>
+#include <QtMultimedia/private/qtmultimediaglobal_p.h>
+#include <QtMultimedia/qaudiodecoder.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qurl.h>
+
+#include <common/qgst_p.h>
+#include <common/qgstappsource_p.h>
+#include <common/qgstpipeline_p.h>
+
+#include <gst/app/gstappsink.h>
+
+QT_BEGIN_NAMESPACE
+
+class QGstreamerMessage;
+
+class QGstreamerAudioDecoder final : public QPlatformAudioDecoder, public QGstreamerBusMessageFilter
+{
+ Q_OBJECT
+
+public:
+ static QMaybe<QPlatformAudioDecoder *> create(QAudioDecoder *parent);
+ virtual ~QGstreamerAudioDecoder();
+
+ QUrl source() const override;
+ void setSource(const QUrl &fileName) override;
+
+ QIODevice *sourceDevice() const override;
+ void setSourceDevice(QIODevice *device) override;
+
+ void start() override;
+ void stop() override;
+
+ QAudioFormat audioFormat() const override;
+ void setAudioFormat(const QAudioFormat &format) override;
+
+ QAudioBuffer read() override;
+
+ qint64 position() const override;
+ qint64 duration() const override;
+
+ // GStreamerBusMessageFilter interface
+ bool processBusMessage(const QGstreamerMessage &message) override;
+
+private slots:
+ void updateDuration();
+
+private:
+ explicit QGstreamerAudioDecoder(QAudioDecoder *parent);
+
+ static GstFlowReturn new_sample(GstAppSink *sink, gpointer user_data);
+ GstFlowReturn newSample(GstAppSink *sink);
+
+ static void configureAppSrcElement(GObject *, GObject *, GParamSpec *,
+ QGstreamerAudioDecoder *_this);
+
+ void setAudioFlags(bool wantNativeAudio);
+ void addAppSink();
+ void removeAppSink();
+
+ bool handlePlaybinMessage(const QGstreamerMessage &);
+
+ void processInvalidMedia(QAudioDecoder::Error errorCode, const QString &errorString);
+ static std::chrono::nanoseconds getPositionFromBuffer(GstBuffer *buffer);
+
+ QGstPipeline m_playbin;
+ QGstBin m_outputBin;
+ QGstElement m_audioConvert;
+ QGstAppSink m_appSink;
+ QGstAppSource *m_appSrc = nullptr;
+
+ QUrl mSource;
+ QIODevice *mDevice = nullptr;
+ QAudioFormat mFormat;
+
+ int m_buffersAvailable = 0;
+
+ static constexpr auto invalidDuration = std::chrono::milliseconds{ -1 };
+ static constexpr auto invalidPosition = std::chrono::milliseconds{ -1 };
+ std::chrono::milliseconds m_position{ invalidPosition };
+ std::chrono::milliseconds m_duration{ invalidDuration };
+
+ int m_durationQueries = 0;
+
+ qint32 m_currentSessionId{};
+
+ QGObjectHandlerScopedConnection m_deepNotifySourceConnection;
+};
+
+QT_END_NAMESPACE
+
+#endif // QGSTREAMERPLAYERSESSION_H
diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice.cpp b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice.cpp
new file mode 100644
index 000000000..b22e40118
--- /dev/null
+++ b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice.cpp
@@ -0,0 +1,87 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qgstreameraudiodevice_p.h"
+
+#include <common/qgst_p.h>
+#include <common/qgstutils_p.h>
+#include <private/qplatformmediaintegration_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QGStreamerAudioDeviceInfo::QGStreamerAudioDeviceInfo(GstDevice *d, const QByteArray &device,
+ QAudioDevice::Mode mode)
+ : QAudioDevicePrivate(device, mode),
+ gstDevice{
+ d,
+ QGstDeviceHandle::NeedsRef,
+ }
+{
+ QGString name{
+ gst_device_get_display_name(gstDevice.get()),
+ };
+ description = name.toQString();
+
+ auto caps = QGstCaps(gst_device_get_caps(gstDevice.get()), QGstCaps::HasRef);
+ int size = caps.size();
+ for (int i = 0; i < size; ++i) {
+ auto c = caps.at(i);
+ if (c.name() == "audio/x-raw") {
+ auto rate = c["rate"].toIntRange();
+ if (rate) {
+ minimumSampleRate = rate->min;
+ maximumSampleRate = rate->max;
+ }
+ auto channels = c["channels"].toIntRange();
+ if (channels) {
+ minimumChannelCount = channels->min;
+ maximumChannelCount = channels->max;
+ }
+ supportedSampleFormats = c["format"].getSampleFormats();
+ }
+ }
+
+ preferredFormat.setChannelCount(qBound(minimumChannelCount, 2, maximumChannelCount));
+ preferredFormat.setSampleRate(qBound(minimumSampleRate, 48000, maximumSampleRate));
+ QAudioFormat::SampleFormat f = QAudioFormat::Int16;
+ if (!supportedSampleFormats.contains(f))
+ f = supportedSampleFormats.value(0, QAudioFormat::Unknown);
+ preferredFormat.setSampleFormat(f);
+}
+
+QGStreamerCustomAudioDeviceInfo::QGStreamerCustomAudioDeviceInfo(
+ const QByteArray &gstreamerPipeline, QAudioDevice::Mode mode)
+ : QAudioDevicePrivate{
+ gstreamerPipeline,
+ mode,
+ }
+{
+}
+
+QAudioDevice qMakeCustomGStreamerAudioInput(const QByteArray &gstreamerPipeline)
+{
+ auto deviceInfo = std::make_unique<QGStreamerCustomAudioDeviceInfo>(gstreamerPipeline,
+ QAudioDevice::Mode::Input);
+
+ return deviceInfo.release()->create();
+}
+
+QAudioDevice qMakeCustomGStreamerAudioOutput(const QByteArray &gstreamerPipeline)
+{
+ auto deviceInfo = std::make_unique<QGStreamerCustomAudioDeviceInfo>(gstreamerPipeline,
+ QAudioDevice::Mode::Output);
+
+ return deviceInfo.release()->create();
+}
+
+bool isCustomAudioDevice(const QAudioDevicePrivate *device)
+{
+ return dynamic_cast<const QGStreamerCustomAudioDeviceInfo *>(device);
+}
+
+bool isCustomAudioDevice(const QAudioDevice &device)
+{
+ return isCustomAudioDevice(device.handle());
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice_p.h b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice_p.h
new file mode 100644
index 000000000..403fd5e74
--- /dev/null
+++ b/src/plugins/multimedia/gstreamer/audio/qgstreameraudiodevice_p.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QGSTREAMERAUDIODEVICEINFO_H
+#define QGSTREAMERAUDIODEVICEINFO_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qlist.h>
+
+#include <QtMultimedia/qaudio.h>
+#include <QtMultimedia/qaudiodevice.h>
+#include <QtMultimedia/private/qaudiodevice_p.h>
+
+#include <QtQGstreamerMediaPlugin/private/qgst_handle_types_p.h>
+
+#include <gst/gst.h>
+
+QT_BEGIN_NAMESPACE
+
+class QGStreamerAudioDeviceInfo : public QAudioDevicePrivate
+{
+public:
+ QGStreamerAudioDeviceInfo(GstDevice *gstDevice, const QByteArray &device, QAudioDevice::Mode mode);
+
+ QGstDeviceHandle gstDevice;
+};
+
+class QGStreamerCustomAudioDeviceInfo : public QAudioDevicePrivate
+{
+public:
+ QGStreamerCustomAudioDeviceInfo(const QByteArray &gstreamerPipeline, QAudioDevice::Mode mode);
+};
+
+bool isCustomAudioDevice(const QAudioDevicePrivate *device);
+bool isCustomAudioDevice(const QAudioDevice &device);
+
+QAudioDevice qMakeCustomGStreamerAudioInput(const QByteArray &gstreamerPipeline);
+QAudioDevice qMakeCustomGStreamerAudioOutput(const QByteArray &gstreamerPipeline);
+
+QT_END_NAMESPACE
+
+#endif
+