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 "qgstreameraudiosource_p.h"
10#include "qgstreameraudiodevice_p.h"
11#include <sys/types.h>
12#include <unistd.h>
13
14#include <gst/gst.h>
15Q_DECLARE_OPAQUE_POINTER(GstSample *);
16Q_DECLARE_METATYPE(GstSample *);
17
18QT_BEGIN_NAMESPACE
19
20QGStreamerAudioSource::QGStreamerAudioSource(const QAudioDevice &device, QObject *parent)
21 : QPlatformAudioSource(parent),
22 m_info(device),
23 m_device(device.id())
24{
25 qRegisterMetaType<GstSample *>();
26}
27
28QGStreamerAudioSource::~QGStreamerAudioSource()
29{
30 close();
31}
32
33void QGStreamerAudioSource::setError(QAudio::Error error)
34{
35 if (m_errorState == error)
36 return;
37
38 m_errorState = error;
39 emit errorChanged(error);
40}
41
42QAudio::Error QGStreamerAudioSource::error() const
43{
44 return m_errorState;
45}
46
47void QGStreamerAudioSource::setState(QAudio::State state)
48{
49 if (m_deviceState == state)
50 return;
51
52 m_deviceState = state;
53 emit stateChanged(state);
54}
55
56QAudio::State QGStreamerAudioSource::state() const
57{
58 return m_deviceState;
59}
60
61void QGStreamerAudioSource::setFormat(const QAudioFormat &format)
62{
63 if (m_deviceState == QAudio::StoppedState)
64 m_format = format;
65}
66
67QAudioFormat QGStreamerAudioSource::format() const
68{
69 return m_format;
70}
71
72void QGStreamerAudioSource::start(QIODevice *device)
73{
74 setState(QAudio::StoppedState);
75 setError(QAudio::NoError);
76
77 close();
78
79 if (!open())
80 return;
81
82 m_pullMode = true;
83 m_audioSink = device;
84
85 setState(QAudio::ActiveState);
86}
87
88QIODevice *QGStreamerAudioSource::start()
89{
90 setState(QAudio::StoppedState);
91 setError(QAudio::NoError);
92
93 close();
94
95 if (!open())
96 return nullptr;
97
98 m_pullMode = false;
99 m_audioSink = new GStreamerInputPrivate(this);
100 m_audioSink->open(mode: QIODevice::ReadOnly | QIODevice::Unbuffered);
101
102 setState(QAudio::IdleState);
103
104 return m_audioSink;
105}
106
107void QGStreamerAudioSource::stop()
108{
109 if (m_deviceState == QAudio::StoppedState)
110 return;
111
112 close();
113
114 setError(QAudio::NoError);
115 setState(QAudio::StoppedState);
116}
117
118bool QGStreamerAudioSource::open()
119{
120 if (m_opened)
121 return true;
122
123 const auto *deviceInfo = static_cast<const QGStreamerAudioDeviceInfo *>(m_info.handle());
124 if (!deviceInfo->gstDevice) {
125 setError(QAudio::OpenError);
126 setState(QAudio::StoppedState);
127 return false;
128 }
129
130 gstInput = QGstElement(gst_device_create_element(device: deviceInfo->gstDevice, name: nullptr));
131 if (gstInput.isNull()) {
132 setError(QAudio::OpenError);
133 setState(QAudio::StoppedState);
134 return false;
135 }
136
137 auto gstCaps = QGstUtils::capsForAudioFormat(format: m_format);
138
139 if (gstCaps.isNull()) {
140 setError(QAudio::OpenError);
141 setState(QAudio::StoppedState);
142 return false;
143 }
144
145
146#ifdef DEBUG_AUDIO
147 qDebug() << "Opening input" << QTime::currentTime();
148 qDebug() << "Caps: " << gst_caps_to_string(gstCaps);
149#endif
150
151 gstPipeline = QGstPipeline("pipeline");
152
153 auto *gstBus = gst_pipeline_get_bus(pipeline: gstPipeline.pipeline());
154 gst_bus_add_watch(bus: gstBus, func: &QGStreamerAudioSource::busMessage, user_data: this);
155 gst_object_unref (object: gstBus);
156
157 gstAppSink = createAppSink();
158 gstAppSink.set(property: "caps", c: gstCaps);
159
160 QGstElement conv("audioconvert", "conv");
161 gstVolume = QGstElement("volume", "volume");
162 Q_ASSERT(gstVolume);
163 if (m_volume != 1.)
164 gstVolume.set(property: "volume", d: m_volume);
165
166 gstPipeline.add(e1: gstInput, e2: gstVolume, e3: conv, e4: gstAppSink);
167 gstInput.link(n1: gstVolume, n2: conv, n3: gstAppSink);
168
169 gstPipeline.setState(GST_STATE_PLAYING);
170
171 m_opened = true;
172
173 m_timeStamp.restart();
174 m_elapsedTimeOffset = 0;
175 m_bytesWritten = 0;
176
177 return true;
178}
179
180void QGStreamerAudioSource::close()
181{
182 if (!m_opened)
183 return;
184
185 gstPipeline.setState(GST_STATE_NULL);
186 gstPipeline = {};
187 gstVolume = {};
188 gstAppSink = {};
189 gstInput = {};
190
191 if (!m_pullMode && m_audioSink) {
192 delete m_audioSink;
193 }
194 m_audioSink = nullptr;
195 m_opened = false;
196}
197
198gboolean QGStreamerAudioSource::busMessage(GstBus *, GstMessage *msg, gpointer user_data)
199{
200 QGStreamerAudioSource *input = static_cast<QGStreamerAudioSource *>(user_data);
201 switch (GST_MESSAGE_TYPE (msg)) {
202 case GST_MESSAGE_EOS:
203 input->stop();
204 break;
205 case GST_MESSAGE_ERROR: {
206 input->setError(QAudio::IOError);
207 gchar *debug;
208 GError *error;
209
210 gst_message_parse_error (message: msg, gerror: &error, debug: &debug);
211 g_free (mem: debug);
212
213 qDebug(msg: "Error: %s\n", error->message);
214 g_error_free (error);
215
216 break;
217 }
218 default:
219 break;
220 }
221 return false;
222}
223
224qsizetype QGStreamerAudioSource::bytesReady() const
225{
226 return m_buffer.size();
227}
228
229void QGStreamerAudioSource::resume()
230{
231 if (m_deviceState == QAudio::SuspendedState || m_deviceState == QAudio::IdleState) {
232 gstPipeline.setState(GST_STATE_PLAYING);
233 setState(QAudio::ActiveState);
234 setError(QAudio::NoError);
235 }
236}
237
238void QGStreamerAudioSource::setVolume(qreal vol)
239{
240 if (m_volume == vol)
241 return;
242
243 m_volume = vol;
244 if (!gstVolume.isNull())
245 gstVolume.set(property: "volume", d: vol);
246}
247
248qreal QGStreamerAudioSource::volume() const
249{
250 return m_volume;
251}
252
253void QGStreamerAudioSource::setBufferSize(qsizetype value)
254{
255 m_bufferSize = value;
256}
257
258qsizetype QGStreamerAudioSource::bufferSize() const
259{
260 return m_bufferSize;
261}
262
263qint64 QGStreamerAudioSource::processedUSecs() const
264{
265 return m_format.durationForBytes(byteCount: m_bytesWritten);
266}
267
268void QGStreamerAudioSource::suspend()
269{
270 if (m_deviceState == QAudio::ActiveState) {
271 setError(QAudio::NoError);
272 setState(QAudio::SuspendedState);
273
274 gstPipeline.setState(GST_STATE_PAUSED);
275 }
276}
277
278void QGStreamerAudioSource::reset()
279{
280 stop();
281 m_buffer.clear();
282}
283
284//#define MAX_BUFFERS_IN_QUEUE 4
285
286QGstElement QGStreamerAudioSource::createAppSink()
287{
288 QGstElement sink("appsink", "appsink");
289 GstAppSink *appSink = reinterpret_cast<GstAppSink *>(sink.element());
290
291 GstAppSinkCallbacks callbacks;
292 memset(s: &callbacks, c: 0, n: sizeof(callbacks));
293 callbacks.eos = &eos;
294 callbacks.new_sample = &new_sample;
295 gst_app_sink_set_callbacks(appsink: appSink, callbacks: &callbacks, user_data: this, notify: nullptr);
296// gst_app_sink_set_max_buffers(appSink, MAX_BUFFERS_IN_QUEUE);
297 gst_base_sink_set_sync(GST_BASE_SINK(appSink), FALSE);
298
299 return sink;
300}
301
302void QGStreamerAudioSource::newDataAvailable(GstSample *sample)
303{
304 if (m_audioSink) {
305 GstBuffer *buffer = gst_sample_get_buffer(sample);
306 GstMapInfo mapInfo;
307 gst_buffer_map(buffer, info: &mapInfo, flags: GST_MAP_READ);
308 const char *bufferData = (const char*)mapInfo.data;
309 gsize bufferSize = mapInfo.size;
310
311 if (!m_pullMode) {
312 // need to store that data in the QBuffer
313 m_buffer.append(data: bufferData, size: bufferSize);
314 m_audioSink->readyRead();
315 } else {
316 m_bytesWritten += bufferSize;
317 m_audioSink->write(data: bufferData, len: bufferSize);
318 }
319
320 gst_buffer_unmap(buffer, info: &mapInfo);
321 }
322
323 gst_sample_unref(sample);
324}
325
326GstFlowReturn QGStreamerAudioSource::new_sample(GstAppSink *sink, gpointer user_data)
327{
328 // "Note that the preroll buffer will also be returned as the first buffer when calling gst_app_sink_pull_buffer()."
329 QGStreamerAudioSource *control = static_cast<QGStreamerAudioSource*>(user_data);
330
331 GstSample *sample = gst_app_sink_pull_sample(appsink: sink);
332 QMetaObject::invokeMethod(obj: control, member: "newDataAvailable", c: Qt::AutoConnection, Q_ARG(GstSample *, sample));
333
334 return GST_FLOW_OK;
335}
336
337void QGStreamerAudioSource::eos(GstAppSink *, gpointer user_data)
338{
339 QGStreamerAudioSource *control = static_cast<QGStreamerAudioSource*>(user_data);
340 control->setState(QAudio::StoppedState);
341}
342
343GStreamerInputPrivate::GStreamerInputPrivate(QGStreamerAudioSource *audio)
344{
345 m_audioDevice = qobject_cast<QGStreamerAudioSource*>(object: audio);
346}
347
348qint64 GStreamerInputPrivate::readData(char *data, qint64 len)
349{
350 if (m_audioDevice->state() == QAudio::IdleState)
351 m_audioDevice->setState(QAudio::ActiveState);
352 qint64 bytes = m_audioDevice->m_buffer.read(data, maxLength: len);
353 m_audioDevice->m_bytesWritten += bytes;
354 return bytes;
355}
356
357qint64 GStreamerInputPrivate::writeData(const char *data, qint64 len)
358{
359 Q_UNUSED(data);
360 Q_UNUSED(len);
361 return 0;
362}
363
364qint64 GStreamerInputPrivate::bytesAvailable() const
365{
366 return m_audioDevice->m_buffer.size();
367}
368
369
370QT_END_NAMESPACE
371
372#include "moc_qgstreameraudiosource_p.cpp"
373

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