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> |
15 | Q_DECLARE_OPAQUE_POINTER(GstSample *); |
16 | Q_DECLARE_METATYPE(GstSample *); |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | QGStreamerAudioSource::QGStreamerAudioSource(const QAudioDevice &device, QObject *parent) |
21 | : QPlatformAudioSource(parent), |
22 | m_info(device), |
23 | m_device(device.id()) |
24 | { |
25 | qRegisterMetaType<GstSample *>(); |
26 | } |
27 | |
28 | QGStreamerAudioSource::~QGStreamerAudioSource() |
29 | { |
30 | close(); |
31 | } |
32 | |
33 | void QGStreamerAudioSource::setError(QAudio::Error error) |
34 | { |
35 | if (m_errorState == error) |
36 | return; |
37 | |
38 | m_errorState = error; |
39 | emit errorChanged(error); |
40 | } |
41 | |
42 | QAudio::Error QGStreamerAudioSource::error() const |
43 | { |
44 | return m_errorState; |
45 | } |
46 | |
47 | void QGStreamerAudioSource::setState(QAudio::State state) |
48 | { |
49 | if (m_deviceState == state) |
50 | return; |
51 | |
52 | m_deviceState = state; |
53 | emit stateChanged(state); |
54 | } |
55 | |
56 | QAudio::State QGStreamerAudioSource::state() const |
57 | { |
58 | return m_deviceState; |
59 | } |
60 | |
61 | void QGStreamerAudioSource::setFormat(const QAudioFormat &format) |
62 | { |
63 | if (m_deviceState == QAudio::StoppedState) |
64 | m_format = format; |
65 | } |
66 | |
67 | QAudioFormat QGStreamerAudioSource::format() const |
68 | { |
69 | return m_format; |
70 | } |
71 | |
72 | void 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 | |
88 | QIODevice *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 | |
107 | void 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 | |
118 | bool 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 | |
180 | void 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 | |
198 | gboolean 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 | |
224 | qsizetype QGStreamerAudioSource::bytesReady() const |
225 | { |
226 | return m_buffer.size(); |
227 | } |
228 | |
229 | void 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 | |
238 | void 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 | |
248 | qreal QGStreamerAudioSource::volume() const |
249 | { |
250 | return m_volume; |
251 | } |
252 | |
253 | void QGStreamerAudioSource::setBufferSize(qsizetype value) |
254 | { |
255 | m_bufferSize = value; |
256 | } |
257 | |
258 | qsizetype QGStreamerAudioSource::bufferSize() const |
259 | { |
260 | return m_bufferSize; |
261 | } |
262 | |
263 | qint64 QGStreamerAudioSource::processedUSecs() const |
264 | { |
265 | return m_format.durationForBytes(byteCount: m_bytesWritten); |
266 | } |
267 | |
268 | void 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 | |
278 | void QGStreamerAudioSource::reset() |
279 | { |
280 | stop(); |
281 | m_buffer.clear(); |
282 | } |
283 | |
284 | //#define MAX_BUFFERS_IN_QUEUE 4 |
285 | |
286 | QGstElement 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 | |
302 | void 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 | |
326 | GstFlowReturn 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 | |
337 | void QGStreamerAudioSource::eos(GstAppSink *, gpointer user_data) |
338 | { |
339 | QGStreamerAudioSource *control = static_cast<QGStreamerAudioSource*>(user_data); |
340 | control->setState(QAudio::StoppedState); |
341 | } |
342 | |
343 | GStreamerInputPrivate::GStreamerInputPrivate(QGStreamerAudioSource *audio) |
344 | { |
345 | m_audioDevice = qobject_cast<QGStreamerAudioSource*>(object: audio); |
346 | } |
347 | |
348 | qint64 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 | |
357 | qint64 GStreamerInputPrivate::writeData(const char *data, qint64 len) |
358 | { |
359 | Q_UNUSED(data); |
360 | Q_UNUSED(len); |
361 | return 0; |
362 | } |
363 | |
364 | qint64 GStreamerInputPrivate::bytesAvailable() const |
365 | { |
366 | return m_audioDevice->m_buffer.size(); |
367 | } |
368 | |
369 | |
370 | QT_END_NAMESPACE |
371 | |
372 | #include "moc_qgstreameraudiosource_p.cpp" |
373 | |