Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qgstreameraudiosink.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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
4#include <QtCore/qcoreapplication.h>
5#include <QtCore/qdebug.h>
6#include <QtCore/qmath.h>
7#include <private/qaudiohelpers_p.h>
8
11#include <sys/types.h>
12#include <unistd.h>
13
14#include <qgstpipeline_p.h>
15#include <qgstappsrc_p.h>
16
17#include <qgstutils_p.h>
18#include <qgstreamermessage_p.h>
19
20#include <utility>
21
23
25{
26 auto maybeAppSrc = QGstAppSrc::create();
27 if (!maybeAppSrc)
28 return maybeAppSrc.error();
29
30 QGstElement audioconvert("audioconvert", "conv");
31 if (!audioconvert)
32 return errorMessageCannotFindElement("audioconvert");
33
34 QGstElement volume("volume", "volume");
35 if (!volume)
36 return errorMessageCannotFindElement("volume");
37
38 return new QGStreamerAudioSink(device, maybeAppSrc.value(), audioconvert, volume, parent);
39}
40
41QGStreamerAudioSink::QGStreamerAudioSink(const QAudioDevice &device, QGstAppSrc *appsrc,
42 QGstElement audioconvert, QGstElement volume, QObject *parent)
44 m_device(device.id()),
45 gstPipeline("pipeline"),
46 gstVolume(std::move(volume)),
47 m_appSrc(appsrc)
48{
49 gstPipeline.installMessageFilter(this);
50
51 connect(m_appSrc, &QGstAppSrc::bytesProcessed, this, &QGStreamerAudioSink::bytesProcessedByAppSrc);
52 connect(m_appSrc, &QGstAppSrc::noMoreData, this, &QGStreamerAudioSink::needData);
53 gstAppSrc = m_appSrc->element();
54
55 QGstElement queue("queue", "queue");
56
57 if (m_volume != 1.)
58 gstVolume.set("volume", m_volume);
59
60 // link decodeBin to audioconvert in a callback once we get a pad from the decoder
61 // g_signal_connect (gstDecodeBin, "pad-added", (GCallback) padAdded, conv);
62
63 const auto *audioInfo = static_cast<const QGStreamerAudioDeviceInfo *>(device.handle());
64 gstOutput = gst_device_create_element(audioInfo->gstDevice, nullptr);
65
66 gstPipeline.add(gstAppSrc, queue, /*gstDecodeBin, */ audioconvert, gstVolume, gstOutput);
67 gstAppSrc.link(queue, audioconvert, gstVolume, gstOutput);
68}
69
71{
72 close();
73 gstPipeline = {};
74 gstVolume = {};
75 gstAppSrc = {};
76 delete m_appSrc;
77 m_appSrc = nullptr;
78}
79
80void QGStreamerAudioSink::setError(QAudio::Error error)
81{
82 if (m_errorState == error)
83 return;
84
85 m_errorState = error;
87}
88
90{
91 return m_errorState;
92}
93
94void QGStreamerAudioSink::setState(QAudio::State state)
95{
96 if (m_deviceState == state)
97 return;
98
99 m_deviceState = state;
101}
102
104{
105 return m_deviceState;
106}
107
109{
110 setState(QAudio::StoppedState);
111 setError(QAudio::NoError);
112
113 close();
114
115 if (!m_format.isValid()) {
116 setError(QAudio::OpenError);
117 return;
118 }
119
120 m_pullMode = true;
121 m_audioSource = device;
122
123 if (!open()) {
124 m_audioSource = nullptr;
125 setError(QAudio::OpenError);
126 return;
127 }
128
129 setState(QAudio::ActiveState);
130}
131
133{
134 setState(QAudio::StoppedState);
135 setError(QAudio::NoError);
136
137 close();
138
139 if (!m_format.isValid()) {
140 setError(QAudio::OpenError);
141 return nullptr;
142 }
143
144 m_pullMode = false;
145
146 if (!open())
147 return nullptr;
148
149 m_audioSource = new GStreamerOutputPrivate(this);
151
152 setState(QAudio::IdleState);
153
154 return m_audioSource;
155}
156
157#if 0
158static void padAdded(GstElement *element, GstPad *pad, gpointer data)
159{
160 GstElement *other = static_cast<GstElement *>(data);
161
162 gchar *name = gst_pad_get_name(pad);
163 qDebug("A new pad %s was created for %s\n", name, gst_element_get_name(element));
164 g_free(name);
165
166 qDebug("element %s will be linked to %s\n",
167 gst_element_get_name(element),
168 gst_element_get_name(other));
169 gst_element_link(element, other);
170}
171#endif
172
174{
175 auto *msg = message.rawMessage();
176 switch (GST_MESSAGE_TYPE (msg)) {
177 case GST_MESSAGE_EOS:
178 setState(QAudio::IdleState);
179 break;
180 case GST_MESSAGE_ERROR: {
181 setError(QAudio::IOError);
182 gchar *debug;
183 GError *error;
184
185 gst_message_parse_error (msg, &error, &debug);
186 g_free (debug);
187
188 qDebug("Error: %s\n", error->message);
189 g_error_free (error);
190
191 break;
192 }
193 default:
194 break;
195 }
196
197 return true;
198}
199
200bool QGStreamerAudioSink::open()
201{
202 if (m_opened)
203 return true;
204
205 if (gstOutput.isNull()) {
206 setError(QAudio::OpenError);
207 setState(QAudio::StoppedState);
208 return false;
209 }
210
211// qDebug() << "GST caps:" << gst_caps_to_string(caps);
212 m_appSrc->setup(m_audioSource, m_audioSource ? m_audioSource->pos() : 0);
213 m_appSrc->setAudioFormat(m_format);
214
215 /* run */
216 gstPipeline.setState(GST_STATE_PLAYING);
217
218 m_opened = true;
219
220 m_timeStamp.restart();
221 m_bytesProcessed = 0;
222
223 return true;
224}
225
226void QGStreamerAudioSink::close()
227{
228 if (!m_opened)
229 return;
230
231 if (!gstPipeline.setStateSync(GST_STATE_NULL))
232 qWarning() << "failed to close the audio output stream";
233
234 if (!m_pullMode && m_audioSource)
235 delete m_audioSource;
236 m_audioSource = nullptr;
237 m_opened = false;
238}
239
240qint64 QGStreamerAudioSink::write(const char *data, qint64 len)
241{
242 if (!len)
243 return 0;
244 if (m_errorState == QAudio::UnderrunError)
245 m_errorState = QAudio::NoError;
246
247 m_appSrc->write(data, len);
248 return len;
249}
250
252{
253 if (m_deviceState == QAudio::StoppedState)
254 return;
255
256 close();
257
258 setError(QAudio::NoError);
259 setState(QAudio::StoppedState);
260}
261
263{
264 if (m_deviceState != QAudio::ActiveState && m_deviceState != QAudio::IdleState)
265 return 0;
266
267 return m_appSrc->canAcceptMoreData() ? 4096*4 : 0;
268}
269
271{
272 m_bufferSize = value;
273 if (!gstAppSrc.isNull())
274 gst_app_src_set_max_bytes(GST_APP_SRC(gstAppSrc.element()), value);
275}
276
278{
279 return m_bufferSize;
280}
281
283{
284 qint64 result = qint64(1000000) * m_bytesProcessed /
285 m_format.bytesPerFrame() /
286 m_format.sampleRate();
287
288 return result;
289}
290
292{
293 if (m_deviceState == QAudio::SuspendedState) {
294 m_appSrc->resume();
295 gstPipeline.setState(GST_STATE_PLAYING);
296
297 setState(m_suspendedInState);
298 setError(QAudio::NoError);
299 }
300}
301
303{
304 m_format = format;
305}
306
308{
309 return m_format;
310}
311
313{
314 if (m_deviceState == QAudio::ActiveState || m_deviceState == QAudio::IdleState) {
315 m_suspendedInState = m_deviceState;
316 setError(QAudio::NoError);
317 setState(QAudio::SuspendedState);
318
319 gstPipeline.setState(GST_STATE_PAUSED);
320 m_appSrc->suspend();
321 // ### elapsed time
322 }
323}
324
326{
327 stop();
328}
329
331{
332 m_audioDevice = audio;
333}
334
336{
337 Q_UNUSED(data);
338 Q_UNUSED(len);
339
340 return 0;
341}
342
344{
345 if (m_audioDevice->state() == QAudio::IdleState)
346 m_audioDevice->setState(QAudio::ActiveState);
347 return m_audioDevice->write(data, len);
348}
349
351{
352 if (m_volume == vol)
353 return;
354
355 m_volume = vol;
356 if (!gstVolume.isNull())
357 gstVolume.set("volume", vol);
358}
359
361{
362 return m_volume;
363}
364
365void QGStreamerAudioSink::bytesProcessedByAppSrc(int bytes)
366{
367 m_bytesProcessed += bytes;
368 setState(QAudio::ActiveState);
369 setError(QAudio::NoError);
370}
371
372void QGStreamerAudioSink::needData()
373{
375 setState(QAudio::IdleState);
376 setError(m_audioSource && m_audioSource->atEnd() ? QAudio::NoError : QAudio::UnderrunError);
377 }
378}
379
381
382#include "moc_qgstreameraudiosink_p.cpp"
IOBluetoothDevice * device
qint64 writeData(const char *data, qint64 len) override
Writes up to maxSize bytes from data to the device.
GStreamerOutputPrivate(QGStreamerAudioSink *audio)
qint64 readData(char *data, qint64 len) override
Reads up to maxSize bytes from the device into data, and returns the number of bytes read or -1 if an...
The QAudioDevice class provides an information about audio devices and their functionality.
The QAudioFormat class stores audio stream parameter information.
constexpr int sampleRate() const noexcept
Returns the current sample rate in Hertz.
constexpr int bytesPerFrame() const
Returns the number of bytes required to represent one frame (a sample in each channel) in this format...
constexpr bool isValid() const noexcept
Returns true if all of the parameters are valid.
qint64 restart() noexcept
Restarts the timer and returns the number of milliseconds elapsed since the previous start.
static QMaybe< QPlatformAudioSink * > create(const QAudioDevice &device, QObject *parent)
void setFormat(const QAudioFormat &format) override
friend class GStreamerOutputPrivate
QIODevice * start() override
QAudioFormat format() const override
void setVolume(qreal volume) override
QAudio::State state() const override
bool processBusMessage(const QGstreamerMessage &message) override
qsizetype bufferSize() const override
qsizetype bytesFree() const override
qint64 processedUSecs() const override
qreal volume() const override
void setBufferSize(qsizetype value) override
QAudio::Error error() const override
void setAudioFormat(const QAudioFormat &f)
bool canAcceptMoreData()
void suspend()
void bytesProcessed(int bytes)
void resume()
QGstElement element()
void noMoreData()
bool setup(QIODevice *stream=nullptr, qint64 offset=0)
static QMaybe< QGstAppSrc * > create(QObject *parent=nullptr)
void write(const char *data, qsizetype size)
void add(const QGstElement &element)
Definition qgst_p.h:549
GstElement * element() const
Definition qgst_p.h:526
bool setStateSync(GstState state)
Definition qgst_p.h:463
bool link(const QGstElement &next)
Definition qgst_p.h:429
bool isNull() const
Definition qgst_p.h:288
void set(const char *property, const char *str)
Definition qgst_p.h:290
GstStateChangeReturn setState(GstState state)
void installMessageFilter(QGstreamerSyncMessageFilter *filter)
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual bool open(QIODeviceBase::OpenMode mode)
Opens the device and sets its OpenMode to mode.
virtual qint64 pos() const
For random-access devices, this function returns the position that data is written to or read from.
virtual bool atEnd() const
Returns true if the current read and write position is at the end of the device (i....
\inmodule QtCore
Definition qobject.h:90
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:311
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
void errorChanged(QAudio::Error error)
void stateChanged(QAudio::State state)
else opt state
[0]
The QAudio namespace contains enums used by the audio classes.
State
\value ActiveState Audio data is being processed, this state is set after start() is called and while...
Definition qaudio.h:25
@ StoppedState
Definition qaudio.h:25
@ SuspendedState
Definition qaudio.h:25
@ IdleState
Definition qaudio.h:25
@ ActiveState
Definition qaudio.h:25
Error
\value NoError No errors have occurred \value OpenError An error occurred opening the audio device \v...
Definition qaudio.h:24
@ UnderrunError
Definition qaudio.h:24
@ OpenError
Definition qaudio.h:24
@ NoError
Definition qaudio.h:24
@ IOError
Definition qaudio.h:24
Combined button and popup list for selecting options.
DBusConnection const char DBusError * error
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
QString errorMessageCannotFindElement(std::string_view element)
Definition qgst_p.h:590
#define qDebug
[1]
Definition qlogging.h:160
#define qWarning
Definition qlogging.h:162
GLenum GLuint id
[7]
GLuint GLsizei const GLchar * message
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint name
GLint GLsizei GLsizei GLenum format
GLenum GLsizei len
GLuint64EXT * result
[6]
#define emit
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:70
long long qint64
Definition qtypes.h:55
double qreal
Definition qtypes.h:92
QQueue< int > queue
[0]
QSharedPointer< T > other(t)
[5]
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent