Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qpulseaudiosource.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 "qpulseaudiodevice_p.h"
12#include "qpulsehelpers_p.h"
13#include <sys/types.h>
14#include <unistd.h>
15#include <mutex> // for lock_guard
16
18
19const int SourcePeriodTimeMs = 50;
20
21static void inputStreamReadCallback(pa_stream *stream, size_t length, void *userdata)
22{
23 Q_UNUSED(userdata);
27 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 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(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(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 =
52 qCDebug(qLcPulseAudioIn)
53 << "*** bytes_to_usec: " << pa_bytes_to_usec(buffer_attr->fragsize, &spec);
54 }
55 break;
56 case PA_STREAM_TERMINATED:
57 break;
58 case PA_STREAM_FAILED:
59 default:
60 qWarning() << "Stream error: " << currentError(stream);
62 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
63 break;
64 }
65}
66
67static void inputStreamUnderflowCallback(pa_stream *stream, void *userdata)
68{
69 Q_UNUSED(userdata);
71 qWarning() << "Got a buffer underflow!";
72}
73
74static void inputStreamOverflowCallback(pa_stream *stream, void *userdata)
75{
77 Q_UNUSED(userdata);
78 qWarning() << "Got a buffer overflow!";
79}
80
81static void inputStreamSuccessCallback(pa_stream *stream, int success, void *userdata)
82{
84 Q_UNUSED(userdata);
85 Q_UNUSED(success);
86
87 // if (!success)
88 // TODO: Is cork success? i->operation_success = success;
89
91 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
92}
93
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
111{
112 // TODO: Investigate draining the stream
113 if (auto notifier = m_stateMachine.stop())
114 close();
115}
116
118{
119 return m_stateMachine.error();
120}
121
123{
124 return m_stateMachine.state();
125}
126
128{
129 if (!m_stateMachine.isActiveOrIdle())
131}
132
134{
135 return m_format;
136}
137
139{
140 reset();
141
142 if (!open())
143 return;
144
145 m_pullMode = true;
147
148 m_stateMachine.start();
149}
150
152{
153 reset();
154
155 if (!open())
156 return nullptr;
157
158 m_pullMode = false;
161
162 m_stateMachine.start(false);
163
164 return m_audioSource;
165}
166
168{
169 if (auto notifier = m_stateMachine.stop())
170 close();
171}
172
173bool QPulseAudioSource::open()
174{
175 if (m_opened)
176 return true;
177
179
180 if (!pulseEngine->context()
181 || pa_context_get_state(pulseEngine->context()) != PA_CONTEXT_READY) {
183 return false;
184 }
185
187 pa_channel_map channel_map = QPulseAudioInternal::channelMapForAudioFormat(m_format);
188 Q_ASSERT(spec.channels == channel_map.channels);
189
190 if (!pa_sample_spec_valid(&spec)) {
191 m_stateMachine.stopOrUpdateError(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(::getpid()).arg(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);
211 }
212
213 pulseEngine->lock();
214
215 m_stream = pa_stream_new(pulseEngine->context(), m_streamName.constData(), &spec, &channel_map);
216
217 pa_stream_set_state_callback(m_stream, inputStreamStateCallback, this);
218 pa_stream_set_read_callback(m_stream, inputStreamReadCallback, this);
219
220 pa_stream_set_underflow_callback(m_stream, inputStreamUnderflowCallback, this);
221 pa_stream_set_overflow_callback(m_stream, inputStreamOverflowCallback, this);
222
223 m_periodSize = pa_usec_to_bytes(SourcePeriodTimeMs * 1000, &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(m_stream, m_device.data(), &buffer_attr,
241 static_cast<pa_stream_flags_t>(flags));
242 if (connectionResult < 0) {
243 qWarning() << "pa_stream_connect_record() failed!";
244 pa_stream_unref(m_stream);
245 m_stream = nullptr;
246 pulseEngine->unlock();
247 m_stateMachine.stopOrUpdateError(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(m_stream) != PA_STREAM_READY)
260 pa_threaded_mainloop_wait(pulseEngine->mainloop());
261
262 const pa_buffer_attr *actualBufferAttr = pa_stream_get_buffer_attr(m_stream);
263 m_periodSize = actualBufferAttr->fragsize;
264 m_periodTime = pa_bytes_to_usec(m_periodSize, &spec) / 1000;
265 if (actualBufferAttr->tlength != static_cast<uint32_t>(-1))
266 m_bufferSize = actualBufferAttr->tlength;
267
268 pulseEngine->unlock();
269
270 connect(pulseEngine, &QPulseAudioEngine::contextFailed, this,
271 &QPulseAudioSource::onPulseContextFailed);
272
273 m_opened = true;
274 m_timer.start(m_periodTime, this);
275
276 m_elapsedTimeOffset = 0;
278
279 return true;
280}
281
282void QPulseAudioSource::close()
283{
284 if (!m_opened)
285 return;
286
287 m_timer.stop();
288
290
291 if (m_stream) {
292 std::lock_guard lock(*pulseEngine);
293
294 pa_stream_set_state_callback(m_stream, nullptr, nullptr);
295 pa_stream_set_read_callback(m_stream, nullptr, nullptr);
296 pa_stream_set_underflow_callback(m_stream, nullptr, nullptr);
297 pa_stream_set_overflow_callback(m_stream, nullptr, nullptr);
298
299 pa_stream_disconnect(m_stream);
300 pa_stream_unref(m_stream);
301 m_stream = nullptr;
302 }
303
305 &QPulseAudioSource::onPulseContextFailed);
306
307 if (!m_pullMode && m_audioSource) {
308 delete m_audioSource;
309 m_audioSource = nullptr;
310 }
311 m_opened = false;
312}
313
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(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
333{
334 using namespace QPulseAudioInternal;
335
336 Q_ASSERT(data != nullptr || len == 0);
337
338 m_stateMachine.updateActiveOrIdle(true, QAudio::NoError);
339 int readBytes = 0;
340
341 if (!m_pullMode && !m_tempBuffer.isEmpty()) {
342 readBytes = qMin(static_cast<int>(len), m_tempBuffer.size());
343 if (readBytes)
344 memcpy(data, m_tempBuffer.constData(), readBytes);
345 m_totalTimeValue += readBytes;
346
347 if (readBytes < m_tempBuffer.size()) {
348 m_tempBuffer.remove(0, readBytes);
349 return readBytes;
350 }
351
352 m_tempBuffer.clear();
353 }
354
355 while (pa_stream_readable_size(m_stream) > 0) {
356 size_t readLength = 0;
357
358 if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) {
359 auto readableSize = pa_stream_readable_size(m_stream);
360 qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- " << readableSize
361 << " bytes available from pulse audio";
362 }
363
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(m_stream, &audioBuffer, &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(audioBuffer, adjusted.data(), readLength);
382 actualLength = m_audioSource->write(adjusted);
383
384 if (actualLength < qint64(readLength)) {
385 pulseEngine->unlock();
386 m_stateMachine.updateActiveOrIdle(false, QAudio::UnderrunError);
387 return actualLength;
388 }
389 } else {
390 actualLength = qMin(static_cast<int>(len - readBytes), static_cast<int>(readLength));
391 applyVolume(audioBuffer, data + readBytes, actualLength);
392 }
393
394 qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- wrote " << actualLength
395 << " to client";
396
397 if (actualLength < qint64(readLength)) {
398 int diff = readLength - actualLength;
399 int oldSize = m_tempBuffer.size();
400
401 qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- appending " << diff
402 << " bytes of data to temp buffer";
403
404 m_tempBuffer.resize(m_tempBuffer.size() + diff);
405 applyVolume(static_cast<const char *>(audioBuffer) + actualLength,
406 m_tempBuffer.data() + oldSize, diff);
408 }
409
410 m_totalTimeValue += actualLength;
411 readBytes += actualLength;
412
413 pa_stream_drop(m_stream);
414 pulseEngine->unlock();
415
416 if (!m_pullMode && readBytes >= len)
417 break;
418 }
419
420 qCDebug(qLcPulseAudioIn) << "QPulseAudioSource::read -- returning after reading " << readBytes
421 << " bytes";
422
423 return readBytes;
424}
425
426void QPulseAudioSource::applyVolume(const void *src, void *dest, int len)
427{
428 Q_ASSERT((src && dest) || len == 0);
429 if (m_volume < 1.f)
431 else if (len)
432 memcpy(dest, src, len);
433}
434
436{
437 if (auto notifier = m_stateMachine.resume()) {
438 {
440
441 std::lock_guard lock(*pulseEngine);
442
443 PAOperationUPtr operation(
444 pa_stream_cork(m_stream, 0, inputStreamSuccessCallback, nullptr));
445 pulseEngine->wait(operation.get());
446 }
447
448 m_timer.start(m_periodTime, this);
449 }
450}
451
453{
454 if (qFuzzyCompare(m_volume, vol))
455 return;
456
457 m_volume = qBound(qreal(0), vol, qreal(1));
458}
459
461{
462 return m_volume;
463}
464
466{
467 m_bufferSize = value;
468}
469
471{
472 return m_bufferSize;
473}
474
476{
477 if (!m_stream)
478 return 0;
479 pa_usec_t usecs = 0;
480 int result = pa_stream_get_time(m_stream, &usecs);
482 //if (result != 0)
483 // qWarning() << "no timing info from pulse";
484
485 return usecs;
486}
487
489{
490 if (auto notifier = m_stateMachine.suspend()) {
491 m_timer.stop();
492
494
495 std::lock_guard lock(*pulseEngine);
496
497 PAOperationUPtr operation(pa_stream_cork(m_stream, 1, inputStreamSuccessCallback, nullptr));
498 pulseEngine->wait(operation.get());
499 }
500}
501
503{
504 if (event->timerId() == m_timer.timerId())
505 userFeed();
506
508}
509
510void QPulseAudioSource::userFeed()
511{
512 if (!m_stateMachine.isActiveOrIdle())
513 return;
514
515 //if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg)) {
516 // QTime now(QTime::currentTime());
517 // qCDebug(qLcPulseAudioIn) << now.second() << "s " << now.msec() << "ms :userFeed() IN";
518 //}
519
520 if (m_pullMode) {
521 // reads some audio data and writes it to QIODevice
522 read(nullptr,0);
523 } else if (m_audioSource != nullptr) {
524 // emits readyRead() so user will call read() on QIODevice to get some audio data
525 PulseInputPrivate *a = qobject_cast<PulseInputPrivate*>(m_audioSource);
526 a->trigger();
527 }
528}
529
531{
532 if (auto notifier = m_stateMachine.stopOrUpdateError())
533 close();
534}
535
536void QPulseAudioSource::onPulseContextFailed()
537{
538 if (auto notifier = m_stateMachine.stopOrUpdateError(QAudio::FatalError))
539 close();
540}
541
543{
544 m_audioDevice = qobject_cast<QPulseAudioSource *>(audio);
545}
546
548{
549 return m_audioDevice->read(data, len);
550}
551
553{
554 Q_UNUSED(data);
555 Q_UNUSED(len);
556 return 0;
557}
558
563
565
566#include "moc_qpulseaudiosource_p.cpp"
DarwinBluetooth::LECBManagerNotifier * notifier
IOBluetoothDevice * device
qint64 writeData(const char *data, qint64 len) override
Writes up to maxSize bytes from data to the device.
PulseInputPrivate(QPulseAudioSource *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 QAudioFormat class stores audio stream parameter information.
QAudio::Error error() const
Notifier start(bool isActive=true)
Notifier updateActiveOrIdle(bool isActive, QAudio::Error error=QAudio::NoError)
QAudio::State state() const
Notifier stopOrUpdateError(QAudio::Error error=QAudio::NoError)
Notifier stop(QAudio::Error error=QAudio::NoError, bool shouldDrain=false, bool forceUpdateError=false)
void start(int msec, QObject *obj)
\obsolete Use chrono overload instead.
int timerId() const noexcept
Returns the timer's ID.
Definition qbasictimer.h:35
void stop()
Stops the timer.
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
void clear()
Clears the contents of the byte array and makes it null.
void resize(qsizetype size)
Sets the size of the byte array to size bytes.
QByteArray & remove(qsizetype index, qsizetype len)
Removes len bytes from the array, starting at index position pos, and returns a reference to the arra...
bool isNull() const noexcept
Returns true if this byte array is null; otherwise returns false.
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual bool open(QIODeviceBase::OpenMode mode)
Opens the device and sets its OpenMode to mode.
void readyRead()
This signal is emitted once every time new data is available for reading from the device's current re...
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
virtual void timerEvent(QTimerEvent *event)
This event handler can be reimplemented in a subclass to receive timer events for the object.
Definition qobject.cpp:1470
static QPulseAudioEngine * instance()
pa_threaded_mainloop * mainloop()
void wait(pa_operation *op)
QAudioFormat format() const override
QAudio::State state() const override
QPulseAudioSource(const QByteArray &device, QObject *parent)
void setFormat(const QAudioFormat &format) override
QIODevice * start() override
qint64 read(char *data, qint64 len)
qint64 processedUSecs() const override
qsizetype bytesReady() const override
void timerEvent(QTimerEvent *event) override
This event handler can be reimplemented in a subclass to receive timer events for the object.
void suspend() override
QAudio::Error error() const override
qsizetype bufferSize() const override
void setBufferSize(qsizetype value) override
void setVolume(qreal volume) override
qreal volume() const override
\inmodule QtCore
Definition qcoreevent.h:366
#define this
Definition dialogs.cpp:9
else opt state
[0]
void qMultiplySamples(qreal factor, const QAudioFormat &format, const void *src, void *dest, int len)
State
Definition qaudio.h:29
Error
Definition qaudio.h:28
@ UnderrunError
Definition qaudio.h:28
@ FatalError
Definition qaudio.h:28
@ OpenError
Definition qaudio.h:28
@ NoError
Definition qaudio.h:28
pa_channel_map channelMapForAudioFormat(const QAudioFormat &format)
pa_sample_spec audioFormatToSampleSpec(const QAudioFormat &format)
Combined button and popup list for selecting options.
bool isEnabled()
@ QueuedConnection
constexpr Initialization Uninitialized
#define Q_UNLIKELY(x)
EGLStreamKHR stream
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
@ QtDebugMsg
Definition qlogging.h:30
#define qWarning
Definition qlogging.h:166
#define qCDebug(category,...)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLenum GLsizei length
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum src
GLbitfield flags
GLint GLsizei GLsizei GLenum format
struct _cl_event * event
GLuint64EXT * result
[6]
GLenum GLsizei len
static void inputStreamSuccessCallback(pa_stream *stream, int success, void *userdata)
static void inputStreamReadCallback(pa_stream *stream, size_t length, void *userdata)
static void inputStreamStateCallback(pa_stream *stream, void *userdata)
static void inputStreamOverflowCallback(pa_stream *stream, void *userdata)
QT_BEGIN_NAMESPACE const int SourcePeriodTimeMs
static void inputStreamUnderflowCallback(pa_stream *stream, void *userdata)
std::unique_ptr< pa_operation, PAOperationDeleter > PAOperationUPtr
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define QStringLiteral(str)
#define emit
#define Q_UNUSED(x)
size_t quintptr
Definition qtypes.h:167
ptrdiff_t qsizetype
Definition qtypes.h:165
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
QObject::connect nullptr
myObject disconnect()
[26]
QReadWriteLock lock
[0]
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...