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 auto op = streamCork(m_stream,
true);
99 pulseEngine->waitForAsyncOperation(op);
101 pa_stream_disconnect(m_stream.get());
103 finalizeQIODevice(shutdownPolicy);
104 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer)
110 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
111 std::lock_guard engineLock{ *pulseEngine };
113 std::ignore = streamCork(m_stream,
true);
118 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
119 std::lock_guard engineLock{ *pulseEngine };
121 std::ignore = streamCork(m_stream,
false);
126 return bool(m_stream);
131 m_parent->updateStreamIdle(idle);
136 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
137 static const bool serverIsPipewire = [&] {
138 return pulseEngine->serverName().contains(u"PulseAudio (on PipeWire");
142 .maxlength = uint32_t(m_format.bytesForFrames(m_hardwareBufferFrames.value_or(1024))),
143 .tlength = uint32_t(-1),
144 .prebuf = uint32_t(-1),
145 .minreq = uint32_t(-1),
149 .fragsize = serverIsPipewire
151 : uint32_t(m_format.bytesForFrames(m_hardwareBufferFrames.value_or(1024))),
154 constexpr pa_stream_flags flags =
155 pa_stream_flags(PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY);
157 std::lock_guard engineLock{ *pulseEngine };
158 installCallbacks(streamType);
160 int status = pa_stream_connect_record(m_stream.get(), m_audioDevice.id().data(), &attr, flags);
162 qCWarning(qLcPulseAudioOut) <<
"pa_stream_connect_record() failed!";
171 switch (streamType) {
172 case StreamType::Ringbuffer: {
173 pa_stream_set_read_callback(m_stream.get(),
174 [](pa_stream *stream, size_t nbytes,
void *data) {
176 Q_ASSERT(stream == self->m_stream.get());
177 self->readCallbackRingbuffer(nbytes);
181 case StreamType::Callback: {
182 pa_stream_set_read_callback(m_stream.get(),
183 [](pa_stream *stream, size_t nbytes,
void *data) {
185 Q_ASSERT(stream == self->m_stream.get());
186 self->readCallbackAudioCallback(nbytes);
195 pa_stream_set_read_callback(m_stream.get(),
nullptr,
nullptr);
202 int status = pa_stream_peek(m_stream.get(), &data, &nBytes);
204 invokeOnAppThread([
this] {
205 handleIOError(m_parent);
210 QSpan<
const std::byte> hostBuffer{
211 reinterpret_cast<
const std::byte *>(data),
215 uint32_t numberOfFrames = m_format.framesForBytes(nBytes);
217 [[maybe_unused]] uint64_t framesWritten =
218 QPlatformAudioSourceStream::process(hostBuffer, numberOfFrames);
219 status = pa_stream_drop(m_stream.get());
221 if (!isStopRequested()) {
222 invokeOnAppThread([
this] {
223 handleIOError(m_parent);
233 int status = pa_stream_peek(m_stream.get(), &data, &nBytes);
235 QMetaObject::invokeMethod(m_parent, [
this] {
236 handleIOError(m_parent);
241 QSpan<
const std::byte> hostBuffer{
242 reinterpret_cast<
const std::byte *>(data),
246 runAudioCallback(*m_audioCallback, hostBuffer, m_format, volume());
248 status = pa_stream_drop(m_stream.get());
250 if (!isStopRequested()) {
251 QMetaObject::invokeMethod(m_parent, [
this] {
252 handleIOError(m_parent);
269bool QPulseAudioSource::validatePulseaudio()
271 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
272 if (!pulseEngine->contextIsGood()) {
273 qWarning() <<
"Invalid PulseAudio context:" << pulseEngine->getContextState();
274 setError(QtAudio::Error::FatalError);
280void QPulseAudioSource::
start(QIODevice *device)
282 if (!validatePulseaudio())
284 return BaseClass::start(device);
287void QPulseAudioSource::
start(AudioCallback &&cb)
289 if (!validatePulseaudio())
291 return BaseClass::start(std::move(cb));
296 if (!validatePulseaudio())
298 return BaseClass::start();
QIODevice * start() override
QPulseAudioSource(QAudioDevice, const QAudioFormat &, QObject *parent)
~QPulseAudioSource() override
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()