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>
9#include <QtCore/qsemaphore.h>
21 std::optional<qsizetype> ringbufferSize,
22 QPulseAudioSource *parent,
24 std::optional<NativePeriodFrames> nativePeriodFrames)
30 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
31 pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(format);
32 pa_channel_map channel_map = QPulseAudioInternal::channelMapForAudioFormat(format);
34 if (!pa_sample_spec_valid(&spec))
37 const QByteArray streamName =
38 QStringLiteral(
"QtmPulseStream-%1-%2").arg(::getpid()).arg(quintptr(
this)).toUtf8();
40 if (Q_UNLIKELY(qLcPulseAudioIn().isEnabled(QtDebugMsg))) {
41 qCDebug(qLcPulseAudioIn) <<
"Format: " << spec.format;
42 qCDebug(qLcPulseAudioIn) <<
"Rate: " << spec.rate;
43 qCDebug(qLcPulseAudioIn) <<
"Channels: " << spec.channels;
44 qCDebug(qLcPulseAudioIn) <<
"Frame size: " << pa_frame_size(&spec);
47 std::lock_guard engineLock{ *pulseEngine };
49 m_stream = PAStreamHandle{
50 pa_stream_new(pulseEngine->context(), streamName.constData(), &spec, &channel_map),
51 PAStreamHandle::HasRef,
61 createQIODeviceConnections(device);
63 return startStream(StreamType::Ringbuffer);
68 m_audioCallback = std::move(audioCallback);
69 return startStream(StreamType::Callback);
74 QIODevice *device = createRingbufferReaderDevice();
75 bool started = start(device);
86 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
87 std::lock_guard engineLock{ *pulseEngine };
90 disconnectQIODeviceConnections();
92 if (shutdownPolicy == ShutdownPolicy::DrainRingbuffer) {
93 size_t bytesToRead = pa_stream_readable_size(m_stream.get());
94 if (bytesToRead != size_t(-1))
95 readCallbackRingbuffer(bytesToRead);
99 auto op = streamCork(m_stream,
true);
100 pulseEngine->waitForAsyncOperation(op);
102 pa_stream_disconnect(m_stream.get());
104 finalizeQIODevice(shutdownPolicy);
105 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer)
111 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
112 std::lock_guard engineLock{ *pulseEngine };
114 std::ignore = streamCork(m_stream,
true);
119 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
120 std::lock_guard engineLock{ *pulseEngine };
122 std::ignore = streamCork(m_stream,
false);
127 return bool(m_stream);
132 m_parent->updateStreamIdle(idle);
137 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
138 static const bool serverIsPipewire = [&] {
139 return pulseEngine->serverName().contains(u"PulseAudio (on PipeWire");
143 .maxlength = uint32_t(m_format.bytesForFrames(
144 m_nativePeriodFrames ? qToUnderlying(*m_nativePeriodFrames) : 1024)),
145 .tlength = uint32_t(-1),
146 .prebuf = uint32_t(-1),
147 .minreq = uint32_t(-1),
151 .fragsize = serverIsPipewire
153 : uint32_t(m_format.bytesForFrames(
154 m_nativePeriodFrames ? qToUnderlying(*m_nativePeriodFrames)
158 constexpr pa_stream_flags flags =
159 pa_stream_flags(PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY);
161 std::unique_lock engineLock{ *pulseEngine };
162 installCallbacks(streamType);
166 pa_stream_state state{};
172 pa_stream_set_state_callback(m_stream.get(), [](pa_stream *stream,
void *data) {
173 auto *state =
reinterpret_cast<state_t *>(data);
174 switch (pa_stream_get_state(stream)) {
175 case PA_STREAM_READY:
176 case PA_STREAM_FAILED:
177 case PA_STREAM_TERMINATED:
178 state->state = pa_stream_get_state(stream);
179 state->sem.release();
186 const auto id = m_audioDevice.id();
187 int status = pa_stream_connect_record(m_stream.get(), id.data(), &attr, flags);
189 qCWarning(qLcPulseAudioOut) <<
"pa_stream_connect_record() failed!";
196 if (state.state != PA_STREAM_READY) {
197 qCWarning(qLcPulseAudioOut) <<
"Stream failed to connect:" << state.state;
203 pa_stream_set_state_callback(m_stream.get(),
nullptr,
nullptr);
210 switch (streamType) {
211 case StreamType::Ringbuffer: {
212 pa_stream_set_read_callback(m_stream.get(),
213 [](pa_stream *stream, size_t nbytes,
void *data) {
215 Q_ASSERT(stream == self->m_stream.get());
216 self->readCallbackRingbuffer(nbytes);
220 case StreamType::Callback: {
221 pa_stream_set_read_callback(m_stream.get(),
222 [](pa_stream *stream, size_t nbytes,
void *data) {
224 Q_ASSERT(stream == self->m_stream.get());
225 self->readCallbackAudioCallback(nbytes);
234 pa_stream_set_read_callback(m_stream.get(),
nullptr,
nullptr);
241 int status = pa_stream_peek(m_stream.get(), &data, &nBytes);
243 invokeOnAppThread([
this] {
244 handleIOError(m_parent);
249 QSpan<
const std::byte> hostBuffer{
250 reinterpret_cast<
const std::byte *>(data),
254 uint32_t numberOfFrames = m_format.framesForBytes(nBytes);
256 [[maybe_unused]] uint64_t framesWritten =
257 QPlatformAudioSourceStream::process(hostBuffer, numberOfFrames);
258 status = pa_stream_drop(m_stream.get());
260 if (!isStopRequested()) {
261 invokeOnAppThread([
this] {
262 handleIOError(m_parent);
272 int status = pa_stream_peek(m_stream.get(), &data, &nBytes);
274 QMetaObject::invokeMethod(m_parent, [
this] {
275 handleIOError(m_parent);
280 QSpan<
const std::byte> hostBuffer{
281 reinterpret_cast<
const std::byte *>(data),
285 runAudioCallback(*m_audioCallback, hostBuffer, m_format, volume());
287 status = pa_stream_drop(m_stream.get());
289 if (!isStopRequested()) {
290 QMetaObject::invokeMethod(m_parent, [
this] {
291 handleIOError(m_parent);
308bool QPulseAudioSource::validatePulseaudio()
310 QPulseAudioContextManager *pulseEngine = QPulseAudioContextManager::instance();
311 if (!pulseEngine->contextIsGood()) {
312 qWarning() <<
"Invalid PulseAudio context:" << pulseEngine->getContextState();
313 setError(QtAudio::Error::FatalError);
319void QPulseAudioSource::
start(QIODevice *device)
321 if (!validatePulseaudio())
323 return BaseClass::start(device);
326void QPulseAudioSource::
start(AudioCallback &&cb)
328 if (!validatePulseaudio())
330 return BaseClass::start(std::move(cb));
335 if (!validatePulseaudio())
337 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< NativePeriodFrames > nativePeriodFrames)
~QPulseAudioSourceStream()