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
9#include "qgstreameraudiosink_p.h"
10#include "qgstreameraudiodevice_p.h"
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
22QT_BEGIN_NAMESPACE
23
24QMaybe<QPlatformAudioSink *> QGStreamerAudioSink::create(const QAudioDevice &device, QObject *parent)
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(element: "audioconvert");
33
34 QGstElement volume("volume", "volume");
35 if (!volume)
36 return errorMessageCannotFindElement(element: "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)
43 : QPlatformAudioSink(parent),
44 m_device(device.id()),
45 gstPipeline("pipeline"),
46 gstVolume(std::move(volume)),
47 m_appSrc(appsrc)
48{
49 gstPipeline.installMessageFilter(filter: this);
50
51 connect(sender: m_appSrc, signal: &QGstAppSrc::bytesProcessed, context: this, slot: &QGStreamerAudioSink::bytesProcessedByAppSrc);
52 connect(sender: m_appSrc, signal: &QGstAppSrc::noMoreData, context: this, slot: &QGStreamerAudioSink::needData);
53 gstAppSrc = m_appSrc->element();
54
55 QGstElement queue("queue", "queue");
56
57 if (m_volume != 1.)
58 gstVolume.set(property: "volume", d: 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(device: audioInfo->gstDevice, name: nullptr);
65
66 gstPipeline.add(e1: gstAppSrc, e2: queue, /*gstDecodeBin, */ e3: audioconvert, e4: gstVolume, e5: gstOutput);
67 gstAppSrc.link(n1: queue, n2: audioconvert, n3: gstVolume, n4: gstOutput);
68}
69
70QGStreamerAudioSink::~QGStreamerAudioSink()
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;
86 emit errorChanged(error);
87}
88
89QAudio::Error QGStreamerAudioSink::error() const
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;
100 emit stateChanged(state);
101}
102
103QAudio::State QGStreamerAudioSink::state() const
104{
105 return m_deviceState;
106}
107
108void QGStreamerAudioSink::start(QIODevice *device)
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
132QIODevice *QGStreamerAudioSink::start()
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);
150 m_audioSource->open(mode: QIODevice::WriteOnly|QIODevice::Unbuffered);
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
173bool QGStreamerAudioSink::processBusMessage(const QGstreamerMessage &message)
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 (message: msg, gerror: &error, debug: &debug);
186 g_free (mem: debug);
187
188 qDebug(msg: "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(stream: m_audioSource, offset: 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, size: len);
248 return len;
249}
250
251void QGStreamerAudioSink::stop()
252{
253 if (m_deviceState == QAudio::StoppedState)
254 return;
255
256 close();
257
258 setError(QAudio::NoError);
259 setState(QAudio::StoppedState);
260}
261
262qsizetype QGStreamerAudioSink::bytesFree() const
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
270void QGStreamerAudioSink::setBufferSize(qsizetype value)
271{
272 m_bufferSize = value;
273 if (!gstAppSrc.isNull())
274 gst_app_src_set_max_bytes(GST_APP_SRC(gstAppSrc.element()), max: value);
275}
276
277qsizetype QGStreamerAudioSink::bufferSize() const
278{
279 return m_bufferSize;
280}
281
282qint64 QGStreamerAudioSink::processedUSecs() const
283{
284 qint64 result = qint64(1000000) * m_bytesProcessed /
285 m_format.bytesPerFrame() /
286 m_format.sampleRate();
287
288 return result;
289}
290
291void QGStreamerAudioSink::resume()
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
302void QGStreamerAudioSink::setFormat(const QAudioFormat &format)
303{
304 m_format = format;
305}
306
307QAudioFormat QGStreamerAudioSink::format() const
308{
309 return m_format;
310}
311
312void QGStreamerAudioSink::suspend()
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
325void QGStreamerAudioSink::reset()
326{
327 stop();
328}
329
330GStreamerOutputPrivate::GStreamerOutputPrivate(QGStreamerAudioSink *audio)
331{
332 m_audioDevice = audio;
333}
334
335qint64 GStreamerOutputPrivate::readData(char *data, qint64 len)
336{
337 Q_UNUSED(data);
338 Q_UNUSED(len);
339
340 return 0;
341}
342
343qint64 GStreamerOutputPrivate::writeData(const char *data, qint64 len)
344{
345 if (m_audioDevice->state() == QAudio::IdleState)
346 m_audioDevice->setState(QAudio::ActiveState);
347 return m_audioDevice->write(data, len);
348}
349
350void QGStreamerAudioSink::setVolume(qreal vol)
351{
352 if (m_volume == vol)
353 return;
354
355 m_volume = vol;
356 if (!gstVolume.isNull())
357 gstVolume.set(property: "volume", d: vol);
358}
359
360qreal QGStreamerAudioSink::volume() const
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{
374 if (state() != QAudio::StoppedState && state() != QAudio::IdleState) {
375 setState(QAudio::IdleState);
376 setError(m_audioSource && m_audioSource->atEnd() ? QAudio::NoError : QAudio::UnderrunError);
377 }
378}
379
380QT_END_NAMESPACE
381
382#include "moc_qgstreameraudiosink_p.cpp"
383

source code of qtmultimedia/src/plugins/multimedia/gstreamer/audio/qgstreameraudiosink.cpp