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 "qpulseaudiosource_p.h"
10#include "qaudioengine_pulse_p.h"
11#include "qpulseaudiodevice_p.h"
12#include "qpulsehelpers_p.h"
13#include <sys/types.h>
14#include <unistd.h>
15#include <mutex> // for lock_guard
16
17QT_BEGIN_NAMESPACE
18
19const int SourcePeriodTimeMs = 50;
20
21static void inputStreamReadCallback(pa_stream *stream, size_t length, void *userdata)
22{
23 Q_UNUSED(userdata);
24 Q_UNUSED(length);
25 Q_UNUSED(stream);
26 QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
27 pa_threaded_mainloop_signal(m: pulseEngine->mainloop(), wait_for_accept: 0);
28}
29
30static void inputStreamStateCallback(pa_stream *stream, void *userdata)
31{
32 using namespace QPulseAudioInternal;
33
34 Q_UNUSED(userdata);
35 pa_stream_state_t state = pa_stream_get_state(p: stream);
36 qCDebug(qLcPulseAudioIn) << "Stream state: " << state;
37 switch (state) {
38 case PA_STREAM_CREATING:
39 break;
40 case PA_STREAM_READY:
41 if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) {
42 QPulseAudioSource *audioInput = static_cast<QPulseAudioSource *>(userdata);
43 const pa_buffer_attr *buffer_attr = pa_stream_get_buffer_attr(s: stream);
44 qCDebug(qLcPulseAudioIn) << "*** maxlength: " << buffer_attr->maxlength;
45 qCDebug(qLcPulseAudioIn) << "*** prebuf: " << buffer_attr->prebuf;
46 qCDebug(qLcPulseAudioIn) << "*** fragsize: " << buffer_attr->fragsize;
47 qCDebug(qLcPulseAudioIn) << "*** minreq: " << buffer_attr->minreq;
48 qCDebug(qLcPulseAudioIn) << "*** tlength: " << buffer_attr->tlength;
49
50 pa_sample_spec spec =
51 QPulseAudioInternal::audioFormatToSampleSpec(format: audioInput->format());
52 qCDebug(qLcPulseAudioIn)
53 << "*** bytes_to_usec: " << pa_bytes_to_usec(length: buffer_attr->fragsize, spec: &spec);
54 }
55 break;
56 case PA_STREAM_TERMINATED:
57 break;
58 case PA_STREAM_FAILED:
59 default:
60 qWarning() << "Stream error: " << currentError(stream);
61 QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
62 pa_threaded_mainloop_signal(m: pulseEngine->mainloop(), wait_for_accept: 0);
63 break;
64 }
65}
66
67static void inputStreamUnderflowCallback(pa_stream *stream, void *userdata)
68{
69 Q_UNUSED(userdata);
70 Q_UNUSED(stream);
71 qWarning() << "Got a buffer underflow!";
72}
73
74static void inputStreamOverflowCallback(pa_stream *stream, void *userdata)
75{
76 Q_UNUSED(stream);
77 Q_UNUSED(userdata);
78 qWarning() << "Got a buffer overflow!";
79}
80
81static void inputStreamSuccessCallback(pa_stream *stream, int success, void *userdata)
82{
83 Q_UNUSED(stream);
84 Q_UNUSED(userdata);
85 Q_UNUSED(success);
86
87 // if (!success)
88 // TODO: Is cork success? i->operation_success = success;
89
90 QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
91 pa_threaded_mainloop_signal(m: pulseEngine->mainloop(), wait_for_accept: 0);
92}
93
94QPulseAudioSource::QPulseAudioSource(const QByteArray &device, QObject *parent)
95 : QPlatformAudioSource(parent),
96 m_totalTimeValue(0),
97 m_audioSource(nullptr),
98 m_volume(qreal(1.0f)),
99 m_pullMode(true),
100 m_opened(false),
101 m_bufferSize(0),
102 m_periodSize(0),
103 m_periodTime(SourcePeriodTimeMs),
104 m_stream(nullptr),
105 m_device(device),
106 m_stateMachine(*this)
107{
108}
109
110QPulseAudioSource::~QPulseAudioSource()
111{
112 // TODO: Investigate draining the stream
113 if (auto notifier = m_stateMachine.stop())
114 close();
115}
116
117QAudio::Error QPulseAudioSource::error() const
118{
119 return m_stateMachine.error();
120}
121
122QAudio::State QPulseAudioSource::state() const
123{
124 return m_stateMachine.state();
125}
126
127void QPulseAudioSource::setFormat(const QAudioFormat &format)
128{
129 if (!m_stateMachine.isActiveOrIdle())
130 m_format = format;
131}
132
133QAudioFormat QPulseAudioSource::format() const
134{
135 return m_format;
136}
137
138void QPulseAudioSource::start(QIODevice *device)
139{
140 reset();
141
142 if (!open())
143 return;
144
145 m_pullMode = true;
146 m_audioSource = device;
147
148 m_stateMachine.start();
149}
150
151QIODevice *QPulseAudioSource::start()
152{
153 reset();
154
155 if (!open())
156 return nullptr;
157
158 m_pullMode = false;
159 m_audioSource = new PulseInputPrivate(this);
160 m_audioSource->open(mode: QIODevice::ReadOnly | QIODevice::Unbuffered);
161
162 m_stateMachine.start(activeOrIdle: QAudioStateMachine::RunningState::Idle);
163
164 return m_audioSource;
165}
166
167void QPulseAudioSource::stop()
168{
169 if (auto notifier = m_stateMachine.stop())
170 close();
171}
172
173bool QPulseAudioSource::open()
174{
175 if (m_opened)
176 return true;
177
178 QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
179
180 if (!pulseEngine->context()
181 || pa_context_get_state(c: pulseEngine->context()) != PA_CONTEXT_READY) {
182 m_stateMachine.stopOrUpdateError(error: QAudio::FatalError);
183 return false;
184 }
185
186 pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(format: m_format);
187 pa_channel_map channel_map = QPulseAudioInternal::channelMapForAudioFormat(format: m_format);
188 Q_ASSERT(spec.channels == channel_map.channels);
189
190 if (!pa_sample_spec_valid(spec: &spec)) {
191 m_stateMachine.stopOrUpdateError(error: QAudio::OpenError);
192 return false;
193 }
194
195 m_spec = spec;
196
197 //if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg)) {
198 // QTime now(QTime::currentTime());
199 // qCDebug(qLcPulseAudioIn) << now.second() << "s " << now.msec() << "ms :open()";
200 //}
201
202 if (m_streamName.isNull())
203 m_streamName =
204 QStringLiteral("QtmPulseStream-%1-%2").arg(a: ::getpid()).arg(a: quintptr(this)).toUtf8();
205
206 if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) {
207 qCDebug(qLcPulseAudioIn) << "Format: " << spec.format;
208 qCDebug(qLcPulseAudioIn) << "Rate: " << spec.rate;
209 qCDebug(qLcPulseAudioIn) << "Channels: " << spec.channels;
210 qCDebug(qLcPulseAudioIn) << "Frame size: " << pa_frame_size(spec: &spec);
211 }
212
213 pulseEngine->lock();
214
215 m_stream = pa_stream_new(c: pulseEngine->context(), name: m_streamName.constData(), ss: &spec, map: &channel_map);
216
217 pa_stream_set_state_callback(s: m_stream, cb: inputStreamStateCallback, userdata: this);
218 pa_stream_set_read_callback(p: m_stream, cb: inputStreamReadCallback, userdata: this);
219
220 pa_stream_set_underflow_callback(p: m_stream, cb: inputStreamUnderflowCallback, userdata: this);
221 pa_stream_set_overflow_callback(p: m_stream, cb: inputStreamOverflowCallback, userdata: this);
222
223 m_periodSize = pa_usec_to_bytes(t: SourcePeriodTimeMs * 1000, spec: &spec);
224
225 int flags = 0;
226 pa_buffer_attr buffer_attr;
227 buffer_attr.maxlength = static_cast<uint32_t>(-1);
228 buffer_attr.prebuf = static_cast<uint32_t>(-1);
229 buffer_attr.tlength = static_cast<uint32_t>(-1);
230 buffer_attr.minreq = static_cast<uint32_t>(-1);
231 flags |= PA_STREAM_ADJUST_LATENCY;
232
233 if (m_bufferSize > 0)
234 buffer_attr.fragsize = static_cast<uint32_t>(m_bufferSize);
235 else
236 buffer_attr.fragsize = static_cast<uint32_t>(m_periodSize);
237
238 flags |= PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING;
239
240 int connectionResult = pa_stream_connect_record(s: m_stream, dev: m_device.data(), attr: &buffer_attr,
241 flags: static_cast<pa_stream_flags_t>(flags));
242 if (connectionResult < 0) {
243 qWarning() << "pa_stream_connect_record() failed!";
244 pa_stream_unref(s: m_stream);
245 m_stream = nullptr;
246 pulseEngine->unlock();
247 m_stateMachine.stopOrUpdateError(error: QAudio::OpenError);
248 return false;
249 }
250
251 //if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) {
252 // auto *ss = pa_stream_get_sample_spec(m_stream);
253 // qCDebug(qLcPulseAudioIn) << "connected stream:";
254 // qCDebug(qLcPulseAudioIn) << " channels" << ss->channels << spec.channels;
255 // qCDebug(qLcPulseAudioIn) << " format" << ss->format << spec.format;
256 // qCDebug(qLcPulseAudioIn) << " rate" << ss->rate << spec.rate;
257 //}
258
259 while (pa_stream_get_state(p: m_stream) != PA_STREAM_READY)
260 pa_threaded_mainloop_wait(m: pulseEngine->mainloop());
261
262 const pa_buffer_attr *actualBufferAttr = pa_stream_get_buffer_attr(s: m_stream);
263 m_periodSize = actualBufferAttr->fragsize;
264 m_periodTime = pa_bytes_to_usec(length: m_periodSize, spec: &spec) / 1000;
265 if (actualBufferAttr->tlength != static_cast<uint32_t>(-1))
266 m_bufferSize = actualBufferAttr->tlength;
267
268 pulseEngine->unlock();
269
270 connect(sender: pulseEngine, signal: &QPulseAudioEngine::contextFailed, context: this,
271 slot: &QPulseAudioSource::onPulseContextFailed);
272
273 m_opened = true;
274 m_timer.start(msec: m_periodTime, obj: this);
275
276 m_elapsedTimeOffset = 0;
277 m_totalTimeValue = 0;
278
279 return true;
280}
281
282void QPulseAudioSource::close()
283{
284 if (!m_opened)
285 return;
286
287 m_timer.stop();
288
289 QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
290
291 if (m_stream) {
292 std::lock_guard lock(*pulseEngine);
293
294 pa_stream_set_state_callback(s: m_stream, cb: nullptr, userdata: nullptr);
295 pa_stream_set_read_callback(p: m_stream, cb: nullptr, userdata: nullptr);
296 pa_stream_set_underflow_callback(p: m_stream, cb: nullptr, userdata: nullptr);
297 pa_stream_set_overflow_callback(p: m_stream, cb: nullptr, userdata: nullptr);
298
299 pa_stream_disconnect(s: m_stream);
300 pa_stream_unref(s: m_stream);
301 m_stream = nullptr;
302 }
303
304 disconnect(sender: pulseEngine, signal: &QPulseAudioEngine::contextFailed, receiver: this,
305 slot: &QPulseAudioSource::onPulseContextFailed);
306
307 if (!m_pullMode && m_audioSource) {
308 delete m_audioSource;
309 m_audioSource = nullptr;
310 }
311 m_opened = false;
312}
313
314qsizetype QPulseAudioSource::bytesReady() const
315{
316 using namespace QPulseAudioInternal;
317
318 if (!m_stateMachine.isActiveOrIdle())
319 return 0;
320
321 std::lock_guard lock(*QPulseAudioEngine::instance());
322
323 int bytes = pa_stream_readable_size(p: m_stream);
324 if (bytes < 0) {
325 qWarning() << "pa_stream_readable_size() failed:" << currentError(m_stream);
326 return 0;
327 }
328
329 return static_cast<qsizetype>(bytes);
330}
331
332qint64 QPulseAudioSource::read(char *data, qint64 len)
333{
334 using namespace QPulseAudioInternal;
335
336 Q_ASSERT(data != nullptr || len == 0);
337
338 m_stateMachine.updateActiveOrIdle(activeOrIdle: QAudioStateMachine::RunningState::Active, error: QAudio::NoError);
339 int readBytes = 0;
340
341 if (!m_pullMode && !m_tempBuffer.isEmpty()) {
342 readBytes = qMin(a: static_cast<int>(len), b: m_tempBuffer.size());
343 if (readBytes)
344 memcpy(dest: data, src: m_tempBuffer.constData(), n: readBytes);
345 m_totalTimeValue += readBytes;
346
347 if (readBytes < m_tempBuffer.size()) {
348 m_tempBuffer.remove(index: 0, len: readBytes);
349 return readBytes;
350 }
351
352 m_tempBuffer.clear();
353 }
354
355 while (pa_stream_readable_size(p: m_stream) > 0) {
356 size_t readLength = 0;
357
358 if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) {
359 auto readableSize = pa_stream_readable_size(p: m_stream);
360 qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- " << readableSize
361 << " bytes available from pulse audio";
362 }
363
364 QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
365 pulseEngine->lock();
366
367 const void *audioBuffer;
368
369 // Second and third parameters (audioBuffer and length) to pa_stream_peek are output
370 // parameters, the audioBuffer pointer is set to point to the actual pulse audio data, and
371 // the length is set to the length of this data.
372 if (pa_stream_peek(p: m_stream, data: &audioBuffer, nbytes: &readLength) < 0) {
373 qWarning() << "pa_stream_peek() failed:" << currentError(m_stream);
374 pulseEngine->unlock();
375 return 0;
376 }
377
378 qint64 actualLength = 0;
379 if (m_pullMode) {
380 QByteArray adjusted(readLength, Qt::Uninitialized);
381 applyVolume(src: audioBuffer, dest: adjusted.data(), len: readLength);
382 actualLength = m_audioSource->write(data: adjusted);
383
384 if (actualLength < qint64(readLength)) {
385 pulseEngine->unlock();
386 m_stateMachine.updateActiveOrIdle(activeOrIdle: QAudioStateMachine::RunningState::Idle,
387 error: QAudio::UnderrunError);
388 return actualLength;
389 }
390 } else {
391 actualLength = qMin(a: static_cast<int>(len - readBytes), b: static_cast<int>(readLength));
392 applyVolume(src: audioBuffer, dest: data + readBytes, len: actualLength);
393 }
394
395 qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- wrote " << actualLength
396 << " to client";
397
398 if (actualLength < qint64(readLength)) {
399 int diff = readLength - actualLength;
400 int oldSize = m_tempBuffer.size();
401
402 qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- appending " << diff
403 << " bytes of data to temp buffer";
404
405 m_tempBuffer.resize(size: m_tempBuffer.size() + diff);
406 applyVolume(src: static_cast<const char *>(audioBuffer) + actualLength,
407 dest: m_tempBuffer.data() + oldSize, len: diff);
408 QMetaObject::invokeMethod(obj: this, member: "userFeed", c: Qt::QueuedConnection);
409 }
410
411 m_totalTimeValue += actualLength;
412 readBytes += actualLength;
413
414 pa_stream_drop(p: m_stream);
415 pulseEngine->unlock();
416
417 if (!m_pullMode && readBytes >= len)
418 break;
419 }
420
421 qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- returning after reading " << readBytes
422 << " bytes";
423
424 return readBytes;
425}
426
427void QPulseAudioSource::applyVolume(const void *src, void *dest, int len)
428{
429 Q_ASSERT((src && dest) || len == 0);
430 if (m_volume < 1.f)
431 QAudioHelperInternal::qMultiplySamples(factor: m_volume, format: m_format, src, dest, len);
432 else if (len)
433 memcpy(dest: dest, src: src, n: len);
434}
435
436void QPulseAudioSource::resume()
437{
438 if (auto notifier = m_stateMachine.resume()) {
439 {
440 QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
441
442 std::lock_guard lock(*pulseEngine);
443
444 PAOperationUPtr operation(
445 pa_stream_cork(s: m_stream, b: 0, cb: inputStreamSuccessCallback, userdata: nullptr));
446 pulseEngine->wait(op: operation.get());
447 }
448
449 m_timer.start(msec: m_periodTime, obj: this);
450 }
451}
452
453void QPulseAudioSource::setVolume(qreal vol)
454{
455 if (qFuzzyCompare(p1: m_volume, p2: vol))
456 return;
457
458 m_volume = qBound(min: qreal(0), val: vol, max: qreal(1));
459}
460
461qreal QPulseAudioSource::volume() const
462{
463 return m_volume;
464}
465
466void QPulseAudioSource::setBufferSize(qsizetype value)
467{
468 m_bufferSize = value;
469}
470
471qsizetype QPulseAudioSource::bufferSize() const
472{
473 return m_bufferSize;
474}
475
476qint64 QPulseAudioSource::processedUSecs() const
477{
478 if (!m_stream)
479 return 0;
480 pa_usec_t usecs = 0;
481 int result = pa_stream_get_time(s: m_stream, r_usec: &usecs);
482 Q_UNUSED(result);
483 //if (result != 0)
484 // qWarning() << "no timing info from pulse";
485
486 return usecs;
487}
488
489void QPulseAudioSource::suspend()
490{
491 if (auto notifier = m_stateMachine.suspend()) {
492 m_timer.stop();
493
494 QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
495
496 std::lock_guard lock(*pulseEngine);
497
498 PAOperationUPtr operation(pa_stream_cork(s: m_stream, b: 1, cb: inputStreamSuccessCallback, userdata: nullptr));
499 pulseEngine->wait(op: operation.get());
500 }
501}
502
503void QPulseAudioSource::timerEvent(QTimerEvent *event)
504{
505 if (event->timerId() == m_timer.timerId())
506 userFeed();
507
508 QPlatformAudioSource::timerEvent(event);
509}
510
511void QPulseAudioSource::userFeed()
512{
513 if (!m_stateMachine.isActiveOrIdle())
514 return;
515
516 //if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg)) {
517 // QTime now(QTime::currentTime());
518 // qCDebug(qLcPulseAudioIn) << now.second() << "s " << now.msec() << "ms :userFeed() IN";
519 //}
520
521 if (m_pullMode) {
522 // reads some audio data and writes it to QIODevice
523 read(data: nullptr,len: 0);
524 } else if (m_audioSource != nullptr) {
525 // emits readyRead() so user will call read() on QIODevice to get some audio data
526 PulseInputPrivate *a = qobject_cast<PulseInputPrivate*>(object: m_audioSource);
527 a->trigger();
528 }
529}
530
531void QPulseAudioSource::reset()
532{
533 if (auto notifier = m_stateMachine.stopOrUpdateError())
534 close();
535}
536
537void QPulseAudioSource::onPulseContextFailed()
538{
539 if (auto notifier = m_stateMachine.stopOrUpdateError(error: QAudio::FatalError))
540 close();
541}
542
543PulseInputPrivate::PulseInputPrivate(QPulseAudioSource *audio)
544{
545 m_audioDevice = qobject_cast<QPulseAudioSource *>(object: audio);
546}
547
548qint64 PulseInputPrivate::readData(char *data, qint64 len)
549{
550 return m_audioDevice->read(data, len);
551}
552
553qint64 PulseInputPrivate::writeData(const char *data, qint64 len)
554{
555 Q_UNUSED(data);
556 Q_UNUSED(len);
557 return 0;
558}
559
560void PulseInputPrivate::trigger()
561{
562 emit readyRead();
563}
564
565QT_END_NAMESPACE
566
567#include "moc_qpulseaudiosource_p.cpp"
568

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtmultimedia/src/multimedia/pulseaudio/qpulseaudiosource.cpp