21 std::optional<qsizetype> ringbufferSize, QPulseAudioSink *parent,
23 std::optional<NativePeriodFrames> nativePeriodFrames,
24 AudioEndpointRole role)
32 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
34 pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(format);
35 pa_channel_map channel_map = QPulseAudioInternal::channelMapForAudioFormat(format);
37 if (Q_UNLIKELY(qLcPulseAudioOut().isEnabled(QtDebugMsg))) {
38 qCDebug(qLcPulseAudioOut) <<
"Opening stream with.";
39 qCDebug(qLcPulseAudioOut) <<
"\tFormat: " << spec.format;
40 qCDebug(qLcPulseAudioOut) <<
"\tRate: " << spec.rate;
41 qCDebug(qLcPulseAudioOut) <<
"\tChannels: " << spec.channels;
42 qCDebug(qLcPulseAudioOut) <<
"\tFrame size: " << pa_frame_size(&spec);
45 const QByteArray streamName =
46 QStringLiteral(
"QtmPulseStream-%1-%2").arg(::getpid()).arg(quintptr(
this)).toUtf8();
48 PAProplistHandle propList{
51 const char *roleString = [&]() ->
const char * {
53 case AudioEndpointRole::MediaPlayback:
55 case AudioEndpointRole::SoundEffect:
57 case AudioEndpointRole::Accessibility:
59 case AudioEndpointRole::Other:
62 Q_UNREACHABLE_RETURN(
nullptr);
67 pa_proplist_sets(propList.get(), PA_PROP_MEDIA_ROLE, roleString);
69 std::lock_guard engineLock{ *pulseEngine };
71 m_stream = PAStreamHandle{
72 pa_stream_new_with_proplist(pulseEngine->context(), streamName.constData(), &spec,
73 &channel_map, propList.get()),
74 PAStreamHandle::HasRef,
78 qWarning() <<
"Failed to create PulseAudio stream";
85 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
86 std::lock_guard engineLock{ *pulseEngine };
90bool QPulseAudioSinkStream::
start(QIODevice *device)
95 createQIODeviceConnections(device);
97 bool streamStarted = startStream(StreamType::Ringbuffer);
101bool QPulseAudioSinkStream::
start(AudioCallback &&callback)
103 m_audioCallback = std::move(callback);
105 bool streamStarted = startStream(StreamType::Callback);
106 return streamStarted;
111 QIODevice *device = createRingbufferWriterDevice();
114 bool started = start(device);
121void QPulseAudioSinkStream::
stop(ShutdownPolicy policy)
125 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
126 std::lock_guard engineLock{ *pulseEngine };
128 uninstallCallbacks();
130 auto op = streamCork(m_stream,
true);
131 pulseEngine->waitForAsyncOperation(op);
133 if (m_audioCallback) {
135 case ShutdownPolicy::DrainRingbuffer:
136 case ShutdownPolicy::DiscardRingbuffer:
139 Q_UNREACHABLE_RETURN();
143 case ShutdownPolicy::DrainRingbuffer: {
144 bool writeFailed =
false;
146 visitRingbuffer([&](
auto &ringbuffer) {
147 ringbuffer.consumeAll([&](
auto region) {
151 QSpan<
const std::byte> writeRegion = as_bytes(region);
153 pa_stream_write(m_stream.get(), writeRegion.data(), writeRegion.size(),
154 nullptr, 0, PA_SEEK_RELATIVE);
162 case ShutdownPolicy::DiscardRingbuffer: {
166 Q_UNREACHABLE_RETURN();
169 pa_stream_disconnect(m_stream.get());
174 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
175 std::lock_guard engineLock{ *pulseEngine };
177 std::ignore = streamCork(m_stream,
true);
182 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
183 std::lock_guard engineLock{ *pulseEngine };
185 std::ignore = streamCork(m_stream,
false);
188bool QPulseAudioSinkStream::
open()
const
190 return m_stream.isValid();
193void QPulseAudioSinkStream::installCallbacks(StreamType streamType)
195 switch (streamType) {
196 case StreamType::Ringbuffer:
197 pa_stream_set_write_callback(m_stream.get(),
198 [](pa_stream *stream, size_t nbytes,
void *data) {
199 auto *self =
reinterpret_cast<QPulseAudioSinkStream *>(data);
200 Q_ASSERT(stream == self->m_stream.get());
201 self->writeCallbackRingbuffer(nbytes);
204 case StreamType::Callback:
205 pa_stream_set_write_callback(m_stream.get(),
206 [](pa_stream *stream, size_t nbytes,
void *data) {
207 auto *self =
reinterpret_cast<QPulseAudioSinkStream *>(data);
208 Q_ASSERT(stream == self->m_stream.get());
209 self->writeCallbackAudioCallback(nbytes);
214 Q_UNREACHABLE_RETURN();
218void QPulseAudioSinkStream::uninstallCallbacks()
220 pa_stream_set_write_callback(m_stream.get(),
nullptr,
nullptr);
223bool QPulseAudioSinkStream::startStream(StreamType streamType)
226 .maxlength = uint32_t(m_format.bytesForFrames(
227 m_nativePeriodFrames ? qToUnderlying(*m_nativePeriodFrames) : 1024)),
228 .tlength = uint32_t(-1),
229 .prebuf = uint32_t(-1),
230 .minreq = uint32_t(-1),
231 .fragsize = uint32_t(-1),
234 constexpr pa_stream_flags flags =
235 pa_stream_flags(PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY);
237 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
238 std::unique_lock engineLock{ *pulseEngine };
239 installCallbacks(streamType);
243 pa_stream_state state{};
249 pa_stream_set_state_callback(m_stream.get(), [](pa_stream *stream,
void *data) {
250 auto *state =
reinterpret_cast<state_t *>(data);
251 switch (pa_stream_get_state(stream)) {
252 case PA_STREAM_READY:
253 case PA_STREAM_FAILED:
254 case PA_STREAM_TERMINATED:
255 state->state = pa_stream_get_state(stream);
256 state->sem.release();
263 const auto id = m_audioDevice.id();
265 pa_stream_connect_playback(m_stream.get(), id.data(), &attr, flags,
nullptr,
nullptr);
268 qCWarning(qLcPulseAudioOut) <<
"pa_stream_connect_playback() failed!";
274 if (state.state != PA_STREAM_READY) {
275 qCWarning(qLcPulseAudioOut) <<
"Stream failed to connect:" << state.state;
281 pa_stream_set_state_callback(m_stream.get(),
nullptr,
nullptr);
288 m_parent->updateStreamIdle(idle);
291void QPulseAudioSinkStream::writeCallbackRingbuffer(size_t requestedBytes)
294 uint32_t requestedFrames = m_format.framesForBytes(requestedBytes);
295 size_t nbytes = m_format.bytesForFrames(requestedFrames);
297 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
298 Q_ASSERT(pulseEngine->isInMainLoop());
300 void *dest =
nullptr;
302 int status = pa_stream_begin_write(m_stream.get(), &dest, &nbytes);
304 qCWarning(qLcPulseAudioOut)
305 <<
"pa_stream_begin_write error:" << currentError(pulseEngine->context());
307 QMetaObject::invokeMethod(m_parent, [
this] {
308 handleIOError(m_parent);
311 QSpan<std::byte> hostBuffer{
reinterpret_cast<
std::byte *>(dest), qsizetype(nbytes) };
313 const uint64_t consumedFrames = process(hostBuffer, requestedFrames);
314 if (consumedFrames != requestedFrames) {
315 auto remainder = drop(hostBuffer, m_format.bytesForFrames(consumedFrames));
316 QAudioHelperInternal::fillSilence(remainder, m_format);
318 status = pa_stream_write(m_stream.get(), hostBuffer.data(), nbytes,
319 nullptr, 0, PA_SEEK_RELATIVE);
321 qCWarning(qLcPulseAudioOut)
322 <<
"pa_stream_begin_write error:" << currentError(pulseEngine->context());
324 QMetaObject::invokeMethod(m_parent, [
this] {
325 handleIOError(m_parent);
330void QPulseAudioSinkStream::writeCallbackAudioCallback(size_t requestedBytes)
333 uint32_t requestedFrames = m_format.framesForBytes(requestedBytes);
334 size_t nbytes = m_format.bytesForFrames(requestedFrames);
336 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
337 Q_ASSERT(pulseEngine->isInMainLoop());
339 void *dest =
nullptr;
341 int status = pa_stream_begin_write(m_stream.get(), &dest, &nbytes);
343 qCWarning(qLcPulseAudioOut)
344 <<
"pa_stream_begin_write error:" << currentError(pulseEngine->context());
346 invokeOnAppThread([
this] {
347 handleIOError(m_parent);
350 QSpan<std::byte> hostBuffer{
reinterpret_cast<
std::byte *>(dest), qsizetype(nbytes) };
351 runAudioCallback(*m_audioCallback, hostBuffer, m_format, volume());
353 status = pa_stream_write(m_stream.get(), hostBuffer.data(), nbytes,
354 nullptr, 0, PA_SEEK_RELATIVE);
356 qCWarning(qLcPulseAudioOut)
357 <<
"pa_stream_begin_write error:" << currentError(pulseEngine->context());
359 invokeOnAppThread([
this] {
360 handleIOError(m_parent);
365QPulseAudioSink::
QPulseAudioSink(QAudioDevice device,
const QAudioFormat &format, QObject *parent)
372bool QPulseAudioSink::validatePulseaudio()
374 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
375 if (!pulseEngine->contextIsGood()) {
376 qWarning() <<
"Invalid PulseAudio context:" << pulseEngine->getContextState();
377 setError(QtAudio::Error::FatalError);
383void QPulseAudioSink::
start(QIODevice *device)
385 if (!validatePulseaudio())
387 return BaseClass::start(device);
390void QPulseAudioSink::
start(AudioCallback &&callback)
392 if (!validatePulseaudio())
394 return BaseClass::start(std::forward<AudioCallback>(callback));
399 if (!validatePulseaudio())
401 return BaseClass::start();