20 std::optional<qsizetype> ringbufferSize, QPulseAudioSink *parent,
22 std::optional<int32_t> hardwareBufferSize,
23 AudioEndpointRole role)
31 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
33 pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(format);
34 pa_channel_map channel_map = QPulseAudioInternal::channelMapForAudioFormat(format);
36 if (Q_UNLIKELY(qLcPulseAudioOut().isEnabled(QtDebugMsg))) {
37 qCDebug(qLcPulseAudioOut) <<
"Opening stream with.";
38 qCDebug(qLcPulseAudioOut) <<
"\tFormat: " << spec.format;
39 qCDebug(qLcPulseAudioOut) <<
"\tRate: " << spec.rate;
40 qCDebug(qLcPulseAudioOut) <<
"\tChannels: " << spec.channels;
41 qCDebug(qLcPulseAudioOut) <<
"\tFrame size: " << pa_frame_size(&spec);
44 const QByteArray streamName =
45 QStringLiteral(
"QtmPulseStream-%1-%2").arg(::getpid()).arg(quintptr(
this)).toUtf8();
47 PAProplistHandle propList{
50 const char *roleString = [&]() ->
const char * {
52 case AudioEndpointRole::MediaPlayback:
54 case AudioEndpointRole::SoundEffect:
56 case AudioEndpointRole::Accessibility:
58 case AudioEndpointRole::Other:
61 Q_UNREACHABLE_RETURN(
nullptr);
66 pa_proplist_sets(propList.get(), PA_PROP_MEDIA_ROLE, roleString);
68 std::lock_guard engineLock{ *pulseEngine };
70 m_stream = PAStreamHandle{
71 pa_stream_new_with_proplist(pulseEngine->context(), streamName.constData(), &spec,
72 &channel_map, propList.get()),
73 PAStreamHandle::HasRef,
77 qWarning() <<
"Failed to create PulseAudio stream";
84 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
85 std::lock_guard engineLock{ *pulseEngine };
89bool QPulseAudioSinkStream::
start(QIODevice *device)
94 createQIODeviceConnections(device);
96 bool streamStarted = startStream(StreamType::Ringbuffer);
100bool QPulseAudioSinkStream::
start(AudioCallback &&callback)
102 m_audioCallback = std::move(callback);
104 bool streamStarted = startStream(StreamType::Callback);
105 return streamStarted;
110 QIODevice *device = createRingbufferWriterDevice();
113 bool started = start(device);
120void QPulseAudioSinkStream::
stop(ShutdownPolicy policy)
124 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
125 std::lock_guard engineLock{ *pulseEngine };
127 uninstallCallbacks();
129 std::ignore = streamCork(m_stream,
true);
131 if (m_audioCallback) {
133 case ShutdownPolicy::DrainRingbuffer:
134 case ShutdownPolicy::DiscardRingbuffer:
137 Q_UNREACHABLE_RETURN();
141 case ShutdownPolicy::DrainRingbuffer: {
142 bool writeFailed =
false;
144 visitRingbuffer([&](
auto &ringbuffer) {
145 ringbuffer.consumeAll([&](
auto region) {
149 QSpan<
const std::byte> writeRegion = as_bytes(region);
151 pa_stream_write(m_stream.get(), writeRegion.data(), writeRegion.size(),
152 nullptr, 0, PA_SEEK_RELATIVE);
160 case ShutdownPolicy::DiscardRingbuffer: {
164 Q_UNREACHABLE_RETURN();
167 pa_stream_disconnect(m_stream.get());
172 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
173 std::lock_guard engineLock{ *pulseEngine };
175 std::ignore = streamCork(m_stream,
true);
180 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
181 std::lock_guard engineLock{ *pulseEngine };
183 std::ignore = streamCork(m_stream,
false);
186bool QPulseAudioSinkStream::
open()
const
188 return m_stream.isValid();
191void QPulseAudioSinkStream::installCallbacks(StreamType streamType)
193 pa_stream_set_overflow_callback(m_stream.get(), [](pa_stream *stream,
void *data) {
194 auto *self =
reinterpret_cast<QPulseAudioSinkStream *>(data);
195 Q_ASSERT(stream == self->m_stream.get());
196 self->underflowCallback();
199 pa_stream_set_underflow_callback(m_stream.get(), [](pa_stream *stream,
void *data) {
200 auto *self =
reinterpret_cast<QPulseAudioSinkStream *>(data);
201 Q_ASSERT(stream == self->m_stream.get());
202 self->overflowCallback();
205 pa_stream_set_state_callback(m_stream.get(), [](pa_stream *stream,
void *data) {
206 auto *self =
reinterpret_cast<QPulseAudioSinkStream *>(data);
207 Q_ASSERT(stream == self->m_stream.get());
208 self->stateCallback();
211 switch (streamType) {
212 case StreamType::Ringbuffer:
213 pa_stream_set_write_callback(m_stream.get(),
214 [](pa_stream *stream, size_t nbytes,
void *data) {
215 auto *self =
reinterpret_cast<QPulseAudioSinkStream *>(data);
216 Q_ASSERT(stream == self->m_stream.get());
217 self->writeCallbackRingbuffer(nbytes);
220 case StreamType::Callback:
221 pa_stream_set_write_callback(m_stream.get(),
222 [](pa_stream *stream, size_t nbytes,
void *data) {
223 auto *self =
reinterpret_cast<QPulseAudioSinkStream *>(data);
224 Q_ASSERT(stream == self->m_stream.get());
225 self->writeCallbackAudioCallback(nbytes);
230 Q_UNREACHABLE_RETURN();
233 pa_stream_set_latency_update_callback(m_stream.get(), [](pa_stream *stream,
void *data) {
234 auto *self =
reinterpret_cast<QPulseAudioSinkStream *>(data);
235 Q_ASSERT(stream == self->m_stream.get());
236 self->latencyUpdateCallback();
240void QPulseAudioSinkStream::uninstallCallbacks()
242 pa_stream_set_overflow_callback(m_stream.get(),
nullptr,
nullptr);
243 pa_stream_set_underflow_callback(m_stream.get(),
nullptr,
nullptr);
244 pa_stream_set_state_callback(m_stream.get(),
nullptr,
nullptr);
245 pa_stream_set_write_callback(m_stream.get(),
nullptr,
nullptr);
246 pa_stream_set_latency_update_callback(m_stream.get(),
nullptr,
nullptr);
249bool QPulseAudioSinkStream::startStream(StreamType streamType)
252 .maxlength = uint32_t(m_format.bytesForFrames(m_hardwareBufferFrames.value_or(1024))),
253 .tlength = uint32_t(-1),
254 .prebuf = uint32_t(-1),
255 .minreq = uint32_t(-1),
256 .fragsize = uint32_t(-1),
259 installCallbacks(streamType);
261 constexpr pa_stream_flags flags =
262 pa_stream_flags(PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY);
264 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
265 std::lock_guard engineLock{ *pulseEngine };
267 int status = pa_stream_connect_playback(m_stream.get(), m_audioDevice.id().data(), &attr, flags,
271 qCWarning(qLcPulseAudioOut) <<
"pa_stream_connect_playback() failed!";
280 m_parent->updateStreamIdle(idle);
283void QPulseAudioSinkStream::writeCallbackRingbuffer(size_t requestedBytes)
286 uint32_t requestedFrames = m_format.framesForBytes(requestedBytes);
287 size_t nbytes = m_format.bytesForFrames(requestedFrames);
289 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
290 Q_ASSERT(pulseEngine->isInMainLoop());
292 void *dest =
nullptr;
294 int status = pa_stream_begin_write(m_stream.get(), &dest, &nbytes);
296 qCWarning(qLcPulseAudioOut)
297 <<
"pa_stream_begin_write error:" << currentError(pulseEngine->context());
299 QMetaObject::invokeMethod(m_parent, [
this] {
300 handleIOError(m_parent);
303 QSpan<std::byte> hostBuffer{
reinterpret_cast<
std::byte *>(dest), qsizetype(nbytes) };
305 const uint64_t consumedFrames = process(hostBuffer, requestedFrames);
306 if (consumedFrames != requestedFrames) {
307 auto remainder = drop(hostBuffer, m_format.bytesForFrames(consumedFrames));
308 std::fill(remainder.begin(), remainder.end(), std::byte{});
310 status = pa_stream_write(m_stream.get(), hostBuffer.data(), nbytes,
311 nullptr, 0, PA_SEEK_RELATIVE);
313 qCWarning(qLcPulseAudioOut)
314 <<
"pa_stream_begin_write error:" << currentError(pulseEngine->context());
316 QMetaObject::invokeMethod(m_parent, [
this] {
317 handleIOError(m_parent);
322void QPulseAudioSinkStream::writeCallbackAudioCallback(size_t requestedBytes)
325 uint32_t requestedFrames = m_format.framesForBytes(requestedBytes);
326 size_t nbytes = m_format.bytesForFrames(requestedFrames);
328 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
329 Q_ASSERT(pulseEngine->isInMainLoop());
331 void *dest =
nullptr;
333 int status = pa_stream_begin_write(m_stream.get(), &dest, &nbytes);
335 qCWarning(qLcPulseAudioOut)
336 <<
"pa_stream_begin_write error:" << currentError(pulseEngine->context());
338 invokeOnAppThread([
this] {
339 handleIOError(m_parent);
342 QSpan<std::byte> hostBuffer{
reinterpret_cast<
std::byte *>(dest), qsizetype(nbytes) };
343 runAudioCallback(*m_audioCallback, hostBuffer, m_format, volume());
345 status = pa_stream_write(m_stream.get(), hostBuffer.data(), nbytes,
346 nullptr, 0, PA_SEEK_RELATIVE);
348 qCWarning(qLcPulseAudioOut)
349 <<
"pa_stream_begin_write error:" << currentError(pulseEngine->context());
351 invokeOnAppThread([
this] {
352 handleIOError(m_parent);
357QPulseAudioSink::
QPulseAudioSink(QAudioDevice device,
const QAudioFormat &format, QObject *parent)
365bool QPulseAudioSink::validatePulseaudio()
367 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
368 if (!pulseEngine->contextIsGood()) {
369 qWarning() <<
"Invalid PulseAudio context:" << pulseEngine->getContextState();
370 setError(QtAudio::Error::FatalError);
376void QPulseAudioSink::
start(QIODevice *device)
378 if (!validatePulseaudio())
380 return BaseClass::start(device);
383void QPulseAudioSink::
start(AudioCallback &&callback)
385 if (!validatePulseaudio())
387 return BaseClass::start(std::forward<AudioCallback>(callback));
392 if (!validatePulseaudio())
394 return BaseClass::start();