4#include <QtCore/qcoreapplication.h>
5#include <QtCore/qdebug.h>
6#include <QtCore/qmath.h>
7#include <private/qaudiohelpers_p.h>
21#define LOW_LATENCY_CATEGORY_NAME "game"
29 pa_threaded_mainloop_signal(pulseEngine->
mainloop(), 0);
35 pa_stream_state_t
state = pa_stream_get_state(
stream);
36 qCDebug(qLcPulseAudioOut) <<
"Stream state callback:" <<
state;
38 case PA_STREAM_CREATING:
40 case PA_STREAM_TERMINATED:
43 case PA_STREAM_FAILED:
47 pa_context_errno(pa_stream_get_context(
stream)))));
49 pa_threaded_mainloop_signal(pulseEngine->
mainloop(), 0);
57 qCDebug(qLcPulseAudioOut) <<
"Buffer underflow";
66 qCDebug(qLcPulseAudioOut) <<
"Buffer overflow";
75 const pa_timing_info *
info = pa_stream_get_timing_info(
stream);
77 qCDebug(qLcPulseAudioOut) <<
"Latency callback:";
78 qCDebug(qLcPulseAudioOut) <<
"\tWrite index corrupt: " <<
info->write_index_corrupt;
79 qCDebug(qLcPulseAudioOut) <<
"\tWrite index: " <<
info->write_index;
80 qCDebug(qLcPulseAudioOut) <<
"\tRead index corrupt: " <<
info->read_index_corrupt;
81 qCDebug(qLcPulseAudioOut) <<
"\tRead index: " <<
info->read_index;
82 qCDebug(qLcPulseAudioOut) <<
"\tSink usec: " <<
info->sink_usec;
83 qCDebug(qLcPulseAudioOut) <<
"\tConfigured sink usec: " <<
info->configured_sink_usec;
92 qCDebug(qLcPulseAudioOut) <<
"Stream successful:" << success;
94 pa_threaded_mainloop_signal(pulseEngine->
mainloop(), 0);
101 qCDebug(qLcPulseAudioOut) <<
"Stream drained:" <<
static_cast<bool>(success) << userdata;
104 pa_threaded_mainloop_signal(pulseEngine->
mainloop(), 0);
106 if (userdata && success)
114 qCDebug(qLcPulseAudioOut) <<
"Stream flushed:" <<
static_cast<bool>(success) << userdata;
123 qCDebug(qLcPulseAudioOut) <<
"Prebuffer adjusted:" <<
static_cast<bool>(success);
139 return m_stateMachine.
error();
144 return m_stateMachine.
state();
149 bool atEnd = m_audioSource && m_audioSource->
atEnd();
151 qCDebug(qLcPulseAudioOut) <<
"Draining stream at end of buffer";
161 if (!exchangeDrainOperation(
nullptr))
173 m_audioSource =
nullptr;
178 gettimeofday(&lastTimingInfo,
nullptr);
179 lastProcessedUSecs = 0;
183 m_stateMachine.
start();
186void QPulseAudioSink::startPulling()
192 m_tickTimer.
start(m_pullingPeriodTime,
this);
195void QPulseAudioSink::stopTimer()
214 gettimeofday(&lastTimingInfo,
nullptr);
215 lastProcessedUSecs = 0;
217 m_stateMachine.
start(
false);
219 return m_audioSource;
222bool QPulseAudioSink::open()
230 || pa_context_get_state(pulseEngine->
context()) != PA_CONTEXT_READY) {
237 Q_ASSERT(spec.channels == channel_map.channels);
239 if (!pa_sample_spec_valid(&spec)) {
245 m_totalTimeValue = 0;
247 if (m_streamName.
isNull())
252 qCDebug(qLcPulseAudioOut) <<
"Opening stream with.";
253 qCDebug(qLcPulseAudioOut) <<
"\tFormat: " << spec.format;
254 qCDebug(qLcPulseAudioOut) <<
"\tRate: " << spec.rate;
255 qCDebug(qLcPulseAudioOut) <<
"\tChannels: " << spec.channels;
256 qCDebug(qLcPulseAudioOut) <<
"\tFrame size: " << pa_frame_size(&spec);
261 pa_proplist *propList = pa_proplist_new();
264 static const char *mediaRoleFromAudioRole[] = {
277 const char *
r = mediaRoleFromAudioRole[m_role];
279 pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE,
r);
282 m_stream = pa_stream_new_with_proplist(pulseEngine->
context(), m_streamName.
constData(),
283 &m_spec, &channel_map, propList);
284 pa_proplist_free(propList);
287 qCWarning(qLcPulseAudioOut) <<
"QAudioSink: pa_stream_new_with_proplist() failed!";
301 pa_buffer_attr requestedBuffer;
303 auto targetBufferSize = m_userBufferSize ? *m_userBufferSize : defaultBufferSize();
304 requestedBuffer.tlength =
305 targetBufferSize ?
static_cast<uint32_t
>(targetBufferSize) : static_cast<uint32_t>(-1);
307 requestedBuffer.fragsize =
static_cast<uint32_t
>(-1);
308 requestedBuffer.maxlength =
static_cast<uint32_t
>(-1);
309 requestedBuffer.minreq =
static_cast<uint32_t
>(-1);
310 requestedBuffer.prebuf =
static_cast<uint32_t
>(-1);
312 pa_stream_flags
flags =
313 pa_stream_flags(PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY);
314 if (pa_stream_connect_playback(m_stream, m_device.
data(), &requestedBuffer,
flags,
nullptr,
317 qCWarning(qLcPulseAudioOut) <<
"pa_stream_connect_playback() failed!";
318 pa_stream_unref(m_stream);
325 while (pa_stream_get_state(m_stream) != PA_STREAM_READY)
326 pa_threaded_mainloop_wait(pulseEngine->
mainloop());
328 const pa_buffer_attr *
buffer = pa_stream_get_buffer_attr(m_stream);
329 m_bufferSize =
buffer->tlength;
334 m_pullingPeriodTime =
336 m_pullingPeriodSize = pa_usec_to_bytes(m_pullingPeriodTime * 1000, &m_spec);
339 m_audioBuffer.resize(
buffer->maxlength);
341 const qint64 streamSize = m_audioSource ? m_audioSource->
size() : 0;
342 if (m_pullMode && streamSize > 0 &&
static_cast<qint64>(
buffer->prebuf) > streamSize) {
343 pa_buffer_attr newBufferAttr;
345 newBufferAttr.prebuf = streamSize;
351 qCDebug(qLcPulseAudioOut) <<
"Buffering info:";
352 qCDebug(qLcPulseAudioOut) <<
"\tMax length: " <<
buffer->maxlength;
353 qCDebug(qLcPulseAudioOut) <<
"\tTarget length: " <<
buffer->tlength;
354 qCDebug(qLcPulseAudioOut) <<
"\tPre-buffering: " <<
buffer->prebuf;
355 qCDebug(qLcPulseAudioOut) <<
"\tMinimum request: " <<
buffer->minreq;
356 qCDebug(qLcPulseAudioOut) <<
"\tFragment size: " <<
buffer->fragsize;
362 &QPulseAudioSink::onPulseContextFailed);
369 m_elapsedTimeOffset = 0;
374void QPulseAudioSink::close()
384 std::lock_guard
lock(*pulseEngine);
386 pa_stream_set_state_callback(m_stream,
nullptr,
nullptr);
387 pa_stream_set_write_callback(m_stream,
nullptr,
nullptr);
388 pa_stream_set_underflow_callback(m_stream,
nullptr,
nullptr);
389 pa_stream_set_overflow_callback(m_stream,
nullptr,
nullptr);
390 pa_stream_set_latency_update_callback(m_stream,
nullptr,
nullptr);
392 if (
auto prevOp = exchangeDrainOperation(
nullptr))
394 pa_operation_cancel(prevOp.get());
398 pa_stream_disconnect(m_stream);
399 pa_stream_unref(m_stream);
404 &QPulseAudioSink::onPulseContextFailed);
409 m_audioSource->
reset();
411 delete m_audioSource;
412 m_audioSource =
nullptr;
417 m_audioBuffer.clear();
422 if (
event->timerId() == m_tickTimer.
timerId() && m_pullMode)
428void QPulseAudioSink::userFeed()
432 if (writableSize == 0) {
439 const int inputSize =
440 std::min({ m_pullingPeriodSize,
static_cast<int>(m_audioBuffer.size()), writableSize });
443 int audioBytesPulled = m_audioSource->
read(m_audioBuffer.data(), inputSize);
444 Q_ASSERT(audioBytesPulled <= inputSize);
446 if (audioBytesPulled > 0) {
447 if (audioBytesPulled > inputSize) {
449 <<
"Invalid audio data size provided by pull source:" << audioBytesPulled
450 <<
"should be less than" << inputSize;
451 audioBytesPulled = inputSize;
453 auto bytesWritten = write(m_audioBuffer.data(), audioBytesPulled);
459 if (inputSize < writableSize)
461 }
else if (audioBytesPulled == 0) {
463 const auto atEnd = m_audioSource->
atEnd();
464 qCDebug(qLcPulseAudioOut) <<
"No more data available, source is done:" << atEnd;
477 void *dest =
nullptr;
479 if (pa_stream_begin_write(m_stream, &dest, &nbytes) < 0) {
489 if (m_volume < 1.0f) {
497 data =
reinterpret_cast<char *
>(dest);
499 if ((pa_stream_write(m_stream,
data,
len,
nullptr, 0, PA_SEEK_RELATIVE)) < 0) {
508 m_totalTimeValue +=
len;
519 std::lock_guard
lock(*pulseEngine);
521 if (
auto prevOp = exchangeDrainOperation(
nullptr))
523 pa_operation_cancel(prevOp.get());
526 pulseEngine->
wait(drainOp.get());
539 return pa_stream_writable_size(m_stream);
544 m_userBufferSize =
value;
552 if (m_userBufferSize)
553 return *m_userBufferSize;
555 return defaultBufferSize();
560 constexpr qint64 secsToUSecs = 1000000;
561 return (
t1.tv_sec -
t2.tv_sec) * secsToUSecs + (
t1.tv_usec -
t2.tv_usec);
570 return lastProcessedUSecs;
572 auto info = pa_stream_get_timing_info(m_stream);
574 return lastProcessedUSecs;
577 if (
info->timestamp - lastTimingInfo > 0) {
578 lastTimingInfo.tv_sec =
info->timestamp.tv_sec;
579 lastTimingInfo.tv_usec =
info->timestamp.tv_usec;
584 if (
info->since_underrun >= 0
585 && pa_bytes_to_usec(
info->since_underrun, &m_spec) >
info->sink_usec) {
590 const int latencyListMaxSize = 10;
591 if (latencyList.
size() > latencyListMaxSize)
593 for (
const auto l : latencyList)
595 averageLatency /= latencyList.
size();
596 if (averageLatency < 0)
601 const qint64 usecsRead =
info->read_index < 0 ? 0 : pa_bytes_to_usec(
info->read_index, &m_spec);
602 const qint64 usecsWritten =
603 info->write_index < 0 ? 0 : pa_bytes_to_usec(
info->write_index, &m_spec);
606 qint64 usecs = usecsRead - averageLatency;
609 gettimeofday(&tv,
nullptr);
612 qint64 timeSinceUpdate = tv -
info->timestamp;
613 if (timeSinceUpdate > 0)
614 usecs += timeSinceUpdate;
617 if (usecs > usecsWritten)
618 usecs = usecsWritten;
621 if (usecs < lastProcessedUSecs)
622 usecs = lastProcessedUSecs;
624 lastProcessedUSecs = usecs;
635 std::lock_guard
lock(*pulseEngine);
639 pulseEngine->
wait(operation.get());
642 pulseEngine->
wait(operation.get());
667 std::lock_guard
lock(*pulseEngine);
671 pulseEngine->
wait(operation.get());
683 m_audioDevice = qobject_cast<QPulseAudioSink *>(audio);
700 while (written <
len) {
701 int chunk = m_audioDevice->write(
data + written, (
len - written));
724void QPulseAudioSink::onPulseContextFailed()
730PAOperationUPtr QPulseAudioSink::exchangeDrainOperation(pa_operation *newOperation)
735qsizetype QPulseAudioSink::defaultBufferSize()
const
741 if (pa_sample_spec_valid(&spec))
749#include "moc_qpulseaudiosink_p.cpp"
DarwinBluetooth::LECBManagerNotifier * notifier
IOBluetoothDevice * device
PulseOutputPrivate(QPulseAudioSink *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...
qint64 writeData(const char *data, qint64 len) override
Writes up to maxSize bytes from data to the device.
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 activateFromIdle()
bool isActiveOrIdle() const
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.
void stop()
Stops the timer.
bool isActive() const noexcept
Returns true if the timer is running and has not been stopped; otherwise returns false.
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
bool isNull() const noexcept
Returns true if this byte array is null; otherwise returns false.
\inmodule QtCore \reentrant
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...
virtual qint64 size() const
For open random-access devices, this function returns the size of the device.
virtual bool reset()
Seeks to the start of input for random-access devices.
virtual bool atEnd() const
Returns true if the current read and write position is at the end of the device (i....
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
qsizetype size() const noexcept
void pop_front() noexcept
void append(parameter_type t)
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
virtual void timerEvent(QTimerEvent *event)
This event handler can be reimplemented in a subclass to receive timer events for the object.
static QPulseAudioEngine * instance()
pa_threaded_mainloop * mainloop()
void wait(pa_operation *op)
void streamDrainedCallback()
void streamUnderflowCallback()
QAudio::Error error() const override
qreal volume() const override
QIODevice * start() override
QPulseAudioSink(const QByteArray &device, QObject *parent)
void setVolume(qreal volume) override
qint64 processedUSecs() const override
void setBufferSize(qsizetype value) override
QAudio::State state() const override
friend class PulseOutputPrivate
void setFormat(const QAudioFormat &format) override
void timerEvent(QTimerEvent *event) override
This event handler can be reimplemented in a subclass to receive timer events for the object.
QAudioFormat format() const override
qsizetype bytesFree() const override
qsizetype bufferSize() const override
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void qMultiplySamples(qreal factor, const QAudioFormat &format, const void *src, void *dest, int len)
QUtf8StringView currentError(const pa_context *context)
pa_channel_map channelMapForAudioFormat(const QAudioFormat &format)
pa_sample_spec audioFormatToSampleSpec(const QAudioFormat &format)
Combined button and popup list for selecting options.
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr const T & qMin(const T &a, const T &b)
constexpr const T & qBound(const T &min, const T &val, const T &max)
GLenum GLsizei GLuint GLint * bytesWritten
GLenum GLuint GLenum GLsizei length
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
[4]
GLint GLsizei GLsizei GLenum format
static void outputStreamStateCallback(pa_stream *stream, void *userdata)
static void outputStreamOverflowCallback(pa_stream *stream, void *userdata)
static QT_BEGIN_NAMESPACE constexpr uint SinkPeriodTimeMs
static void outputStreamSuccessCallback(pa_stream *stream, int success, void *userdata)
static void outputStreamUnderflowCallback(pa_stream *stream, void *userdata)
static void streamAdjustPrebufferCallback(pa_stream *stream, int success, void *userdata)
static void outputStreamDrainComplete(pa_stream *stream, int success, void *userdata)
static qint64 operator-(timeval t1, timeval t2)
static void outputStreamLatencyCallback(pa_stream *stream, void *userdata)
static void outputStreamWriteCallback(pa_stream *stream, size_t length, void *userdata)
static void outputStreamFlushComplete(pa_stream *stream, int success, void *userdata)
static constexpr uint DefaultBufferLengthMs
std::unique_ptr< pa_operation, PAOperationDeleter > PAOperationUPtr
#define QStringLiteral(str)
myObject disconnect()
[26]