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";
84bool QPulseAudioSinkStream::
start(QIODevice *device)
89 createQIODeviceConnections(device);
91 bool streamStarted = startStream(StreamType::Ringbuffer);
95bool QPulseAudioSinkStream::
start(AudioCallback &&callback)
97 m_audioCallback = std::move(callback);
99 bool streamStarted = startStream(StreamType::Callback);
100 return streamStarted;
105 QIODevice *device = createRingbufferWriterDevice();
108 bool started = start(device);
115void QPulseAudioSinkStream::
stop(ShutdownPolicy policy)
119 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
120 std::lock_guard engineLock{ *pulseEngine };
122 uninstallCallbacks();
124 std::ignore = streamCork(m_stream,
true);
126 if (m_audioCallback) {
128 case ShutdownPolicy::DrainRingbuffer:
129 case ShutdownPolicy::DiscardRingbuffer:
132 Q_UNREACHABLE_RETURN();
136 case ShutdownPolicy::DrainRingbuffer: {
137 bool writeFailed =
false;
139 visitRingbuffer([&](
auto &ringbuffer) {
140 ringbuffer.consumeAll([&](
auto region) {
144 QSpan<
const std::byte> writeRegion = as_bytes(region);
146 pa_stream_write(m_stream.get(), writeRegion.data(), writeRegion.size(),
147 nullptr, 0, PA_SEEK_RELATIVE);
155 case ShutdownPolicy::DiscardRingbuffer: {
159 Q_UNREACHABLE_RETURN();
162 pa_stream_disconnect(m_stream.get());
167 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
168 std::lock_guard engineLock{ *pulseEngine };
170 std::ignore = streamCork(m_stream,
true);
175 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
176 std::lock_guard engineLock{ *pulseEngine };
178 std::ignore = streamCork(m_stream,
false);
181bool QPulseAudioSinkStream::
open()
const
183 return m_stream.isValid();
186void QPulseAudioSinkStream::installCallbacks(StreamType streamType)
188 pa_stream_set_overflow_callback(m_stream.get(), [](pa_stream *stream,
void *data) {
189 auto *self =
reinterpret_cast<QPulseAudioSinkStream *>(data);
190 Q_ASSERT(stream == self->m_stream.get());
191 self->underflowCallback();
194 pa_stream_set_underflow_callback(m_stream.get(), [](pa_stream *stream,
void *data) {
195 auto *self =
reinterpret_cast<QPulseAudioSinkStream *>(data);
196 Q_ASSERT(stream == self->m_stream.get());
197 self->overflowCallback();
200 pa_stream_set_state_callback(m_stream.get(), [](pa_stream *stream,
void *data) {
201 auto *self =
reinterpret_cast<QPulseAudioSinkStream *>(data);
202 Q_ASSERT(stream == self->m_stream.get());
203 self->stateCallback();
206 switch (streamType) {
207 case StreamType::Ringbuffer:
208 pa_stream_set_write_callback(m_stream.get(),
209 [](pa_stream *stream, size_t nbytes,
void *data) {
210 auto *self =
reinterpret_cast<QPulseAudioSinkStream *>(data);
211 Q_ASSERT(stream == self->m_stream.get());
212 self->writeCallbackRingbuffer(nbytes);
215 case StreamType::Callback:
216 pa_stream_set_write_callback(m_stream.get(),
217 [](pa_stream *stream, size_t nbytes,
void *data) {
218 auto *self =
reinterpret_cast<QPulseAudioSinkStream *>(data);
219 Q_ASSERT(stream == self->m_stream.get());
220 self->writeCallbackAudioCallback(nbytes);
225 Q_UNREACHABLE_RETURN();
228 pa_stream_set_latency_update_callback(m_stream.get(), [](pa_stream *stream,
void *data) {
229 auto *self =
reinterpret_cast<QPulseAudioSinkStream *>(data);
230 Q_ASSERT(stream == self->m_stream.get());
231 self->latencyUpdateCallback();
235void QPulseAudioSinkStream::uninstallCallbacks()
237 pa_stream_set_overflow_callback(m_stream.get(),
nullptr,
nullptr);
238 pa_stream_set_underflow_callback(m_stream.get(),
nullptr,
nullptr);
239 pa_stream_set_state_callback(m_stream.get(),
nullptr,
nullptr);
240 pa_stream_set_write_callback(m_stream.get(),
nullptr,
nullptr);
241 pa_stream_set_latency_update_callback(m_stream.get(),
nullptr,
nullptr);
244bool QPulseAudioSinkStream::startStream(StreamType streamType)
247 .maxlength = uint32_t(m_format.bytesForFrames(m_hardwareBufferFrames.value_or(1024))),
248 .tlength = uint32_t(-1),
249 .prebuf = uint32_t(-1),
250 .minreq = uint32_t(-1),
251 .fragsize = uint32_t(-1),
254 installCallbacks(streamType);
256 constexpr pa_stream_flags flags =
257 pa_stream_flags(PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY);
259 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
260 std::lock_guard engineLock{ *pulseEngine };
262 int status = pa_stream_connect_playback(m_stream.get(), m_audioDevice.id().data(), &attr, flags,
266 qCWarning(qLcPulseAudioOut) <<
"pa_stream_connect_playback() failed!";
275 m_parent->updateStreamIdle(idle);
278void QPulseAudioSinkStream::writeCallbackRingbuffer(size_t requestedBytes)
281 uint32_t requestedFrames = m_format.framesForBytes(requestedBytes);
282 size_t nbytes = m_format.bytesForFrames(requestedFrames);
284 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
285 Q_ASSERT(pulseEngine->isInMainLoop());
287 void *dest =
nullptr;
289 int status = pa_stream_begin_write(m_stream.get(), &dest, &nbytes);
291 qCWarning(qLcPulseAudioOut)
292 <<
"pa_stream_begin_write error:" << currentError(pulseEngine->context());
294 QMetaObject::invokeMethod(m_parent, [
this] {
295 handleIOError(m_parent);
298 QSpan<std::byte> hostBuffer{
reinterpret_cast<
std::byte *>(dest), qsizetype(nbytes) };
300 const uint64_t consumedFrames = process(hostBuffer, requestedFrames);
301 if (consumedFrames != requestedFrames) {
302 auto remainder = drop(hostBuffer, m_format.bytesForFrames(consumedFrames));
303 std::fill(remainder.begin(), remainder.end(), std::byte{});
305 status = pa_stream_write(m_stream.get(), hostBuffer.data(), nbytes,
306 nullptr, 0, PA_SEEK_RELATIVE);
308 qCWarning(qLcPulseAudioOut)
309 <<
"pa_stream_begin_write error:" << currentError(pulseEngine->context());
311 QMetaObject::invokeMethod(m_parent, [
this] {
312 handleIOError(m_parent);
317void QPulseAudioSinkStream::writeCallbackAudioCallback(size_t requestedBytes)
320 uint32_t requestedFrames = m_format.framesForBytes(requestedBytes);
321 size_t nbytes = m_format.bytesForFrames(requestedFrames);
323 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
324 Q_ASSERT(pulseEngine->isInMainLoop());
326 void *dest =
nullptr;
328 int status = pa_stream_begin_write(m_stream.get(), &dest, &nbytes);
330 qCWarning(qLcPulseAudioOut)
331 <<
"pa_stream_begin_write error:" << currentError(pulseEngine->context());
333 invokeOnAppThread([
this] {
334 handleIOError(m_parent);
337 QSpan<std::byte> hostBuffer{
reinterpret_cast<
std::byte *>(dest), qsizetype(nbytes) };
338 runAudioCallback(*m_audioCallback, hostBuffer, m_format, volume());
340 status = pa_stream_write(m_stream.get(), hostBuffer.data(), nbytes,
341 nullptr, 0, PA_SEEK_RELATIVE);
343 qCWarning(qLcPulseAudioOut)
344 <<
"pa_stream_begin_write error:" << currentError(pulseEngine->context());
346 invokeOnAppThread([
this] {
347 handleIOError(m_parent);
352QPulseAudioSink::
QPulseAudioSink(QAudioDevice device,
const QAudioFormat &format, QObject *parent)
357bool QPulseAudioSink::validatePulseaudio()
359 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
360 if (!pulseEngine->contextIsGood()) {
361 qWarning() <<
"Invalid PulseAudio context:" << pulseEngine->getContextState();
362 setError(QtAudio::Error::FatalError);
368void QPulseAudioSink::
start(QIODevice *device)
370 if (!validatePulseaudio())
372 return BaseClass::start(device);
375void QPulseAudioSink::
start(AudioCallback &&callback)
377 if (!validatePulseaudio())
379 return BaseClass::start(std::forward<AudioCallback>(callback));
384 if (!validatePulseaudio())
386 return BaseClass::start();