6#include <QtMultimedia/private/qaudiosystem_platform_stream_support_p.h>
7#include <QtMultimedia/private/qpulseaudio_contextmanager_p.h>
8#include <QtMultimedia/private/qpulsehelpers_p.h>
20 std::optional<qsizetype> ringbufferSize,
21 QPulseAudioSource *parent,
23 std::optional<int32_t> hardwareBufferSize)
29 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
30 pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(format);
31 pa_channel_map channel_map = QPulseAudioInternal::channelMapForAudioFormat(format);
33 if (!pa_sample_spec_valid(&spec))
36 const QByteArray streamName =
37 QStringLiteral(
"QtmPulseStream-%1-%2").arg(::getpid()).arg(quintptr(
this)).toUtf8();
39 if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) {
40 qCDebug(qLcPulseAudioIn) <<
"Format: " << spec.format;
41 qCDebug(qLcPulseAudioIn) <<
"Rate: " << spec.rate;
42 qCDebug(qLcPulseAudioIn) <<
"Channels: " << spec.channels;
43 qCDebug(qLcPulseAudioIn) <<
"Frame size: " << pa_frame_size(&spec);
46 std::lock_guard engineLock{ *pulseEngine };
48 m_stream = PAStreamHandle{
49 pa_stream_new(pulseEngine->context(), streamName.constData(), &spec, &channel_map),
50 PAStreamHandle::HasRef,
60 createQIODeviceConnections(device);
62 return startStream(StreamType::Ringbuffer);
67 m_audioCallback = std::move(audioCallback);
68 return startStream(StreamType::Callback);
73 QIODevice *device = createRingbufferReaderDevice();
74 bool started = start(device);
85 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
86 std::lock_guard engineLock{ *pulseEngine };
89 disconnectQIODeviceConnections();
91 if (shutdownPolicy == ShutdownPolicy::DrainRingbuffer) {
92 size_t bytesToRead = pa_stream_readable_size(m_stream.get());
93 if (bytesToRead != size_t(-1))
94 readCallbackRingbuffer(bytesToRead);
98 std::ignore = streamCork(m_stream,
true);
100 pa_stream_disconnect(m_stream.get());
102 finalizeQIODevice(shutdownPolicy);
103 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer)
109 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
110 std::lock_guard engineLock{ *pulseEngine };
112 std::ignore = streamCork(m_stream,
true);
117 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
118 std::lock_guard engineLock{ *pulseEngine };
120 std::ignore = streamCork(m_stream,
false);
125 return bool(m_stream);
130 m_parent->updateStreamIdle(idle);
135 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
136 static const bool serverIsPipewire = [&] {
137 return pulseEngine->serverName().contains(u"PulseAudio (on PipeWire");
141 .maxlength = uint32_t(m_format.bytesForFrames(m_hardwareBufferFrames.value_or(1024))),
142 .tlength = uint32_t(-1),
143 .prebuf = uint32_t(-1),
144 .minreq = uint32_t(-1),
148 .fragsize = serverIsPipewire
150 : uint32_t(m_format.bytesForFrames(m_hardwareBufferFrames.value_or(1024))),
153 constexpr pa_stream_flags flags =
154 pa_stream_flags(PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY);
156 std::lock_guard engineLock{ *pulseEngine };
157 installCallbacks(streamType);
159 int status = pa_stream_connect_record(m_stream.get(), m_audioDevice.id().data(), &attr, flags);
161 qCWarning(qLcPulseAudioOut) <<
"pa_stream_connect_record() failed!";
170 pa_stream_set_overflow_callback(m_stream.get(), [](pa_stream *stream,
void *data) {
171 auto *self =
reinterpret_cast<QPulseAudioSourceStream *>(data);
172 Q_ASSERT(stream == self->m_stream.get());
173 self->underflowCallback();
176 pa_stream_set_underflow_callback(m_stream.get(), [](pa_stream *stream,
void *data) {
177 auto *self =
reinterpret_cast<QPulseAudioSourceStream *>(data);
178 Q_ASSERT(stream == self->m_stream.get());
179 self->overflowCallback();
182 pa_stream_set_state_callback(m_stream.get(), [](pa_stream *stream,
void *data) {
183 auto *self =
reinterpret_cast<QPulseAudioSourceStream *>(data);
184 Q_ASSERT(stream == self->m_stream.get());
185 self->stateCallback();
188 switch (streamType) {
189 case StreamType::Ringbuffer: {
190 pa_stream_set_read_callback(m_stream.get(),
191 [](pa_stream *stream, size_t nbytes,
void *data) {
192 auto *self =
reinterpret_cast<QPulseAudioSourceStream *>(data);
193 Q_ASSERT(stream == self->m_stream.get());
194 self->readCallbackRingbuffer(nbytes);
198 case StreamType::Callback: {
199 pa_stream_set_read_callback(m_stream.get(),
200 [](pa_stream *stream, size_t nbytes,
void *data) {
201 auto *self =
reinterpret_cast<QPulseAudioSourceStream *>(data);
202 Q_ASSERT(stream == self->m_stream.get());
203 self->readCallbackAudioCallback(nbytes);
209 pa_stream_set_latency_update_callback(m_stream.get(), [](pa_stream *stream,
void *data) {
210 auto *self =
reinterpret_cast<QPulseAudioSourceStream *>(data);
211 Q_ASSERT(stream == self->m_stream.get());
212 self->latencyUpdateCallback();
218 pa_stream_set_overflow_callback(m_stream.get(),
nullptr,
nullptr);
219 pa_stream_set_underflow_callback(m_stream.get(),
nullptr,
nullptr);
220 pa_stream_set_state_callback(m_stream.get(),
nullptr,
nullptr);
221 pa_stream_set_read_callback(m_stream.get(),
nullptr,
nullptr);
222 pa_stream_set_latency_update_callback(m_stream.get(),
nullptr,
nullptr);
229 int status = pa_stream_peek(m_stream.get(), &data, &nBytes);
231 invokeOnAppThread([
this] {
232 handleIOError(m_parent);
237 QSpan<
const std::byte> hostBuffer{
238 reinterpret_cast<
const std::byte *>(data),
242 uint32_t numberOfFrames = m_format.framesForBytes(nBytes);
244 [[maybe_unused]] uint64_t framesWritten =
245 QPlatformAudioSourceStream::process(hostBuffer, numberOfFrames);
246 status = pa_stream_drop(m_stream.get());
248 if (!isStopRequested()) {
249 invokeOnAppThread([
this] {
250 handleIOError(m_parent);
260 int status = pa_stream_peek(m_stream.get(), &data, &nBytes);
262 QMetaObject::invokeMethod(m_parent, [
this] {
263 handleIOError(m_parent);
268 QSpan<
const std::byte> hostBuffer{
269 reinterpret_cast<
const std::byte *>(data),
273 runAudioCallback(*m_audioCallback, hostBuffer, m_format, volume());
275 status = pa_stream_drop(m_stream.get());
277 if (!isStopRequested()) {
278 QMetaObject::invokeMethod(m_parent, [
this] {
279 handleIOError(m_parent);
293bool QPulseAudioSource::validatePulseaudio()
295 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
296 if (!pulseEngine->contextIsGood()) {
297 qWarning() <<
"Invalid PulseAudio context:" << pulseEngine->getContextState();
298 setError(QtAudio::Error::FatalError);
304void QPulseAudioSource::
start(QIODevice *device)
306 if (!validatePulseaudio())
308 return BaseClass::start(device);
311void QPulseAudioSource::
start(AudioCallback &&cb)
313 if (!validatePulseaudio())
315 return BaseClass::start(std::move(cb));
320 if (!validatePulseaudio())
322 return BaseClass::start();
QIODevice * start() override
QPulseAudioSource(QAudioDevice, const QAudioFormat &, QObject *parent)
void start(QIODevice *device) override
void start(AudioCallback &&) override
void updateStreamIdle(bool idle) override
bool start(AudioCallback &&)
bool start(QIODevice *device)
void stop(ShutdownPolicy)
QPulseAudioSourceStream(QAudioDevice, const QAudioFormat &, std::optional< qsizetype > ringbufferSize, QPulseAudioSource *parent, float volume, std::optional< int32_t > hardwareBufferSize)
~QPulseAudioSourceStream()