10#include <QtCore/qcoreapplication.h>
11#include <QtCore/qdebug.h>
12#include <QtCore/qloggingcategory.h>
14#include <pipewire/pipewire.h>
15#include <pipewire/stream.h>
16#include <spa/pod/builder.h>
32 const QAudioFormat &format,
33 std::optional<qsizetype> ringbufferSize,
34 QPipewireAudioSink *parent,
36 std::optional<int32_t> hardwareBufferFrames,
37 AudioEndpointRole role
56 m_xrunNotification = m_xrunOccurred.callOnActivated(&m_xrunOccurred, [
this, parent] {
57 if (isStopRequested())
59 parent->reportXRuns(m_xrunCount.exchange(0));
66 Q_ASSERT(!m_deviceRemovalObserver);
76 createStream(StreamType::Ringbuffer);
78 Q_ASSERT(hasStream());
79 auto sinkNodeSerial = findSinkNodeSerial();
80 if (!sinkNodeSerial) {
88 createQIODeviceConnections(device);
90 bool connected = connectStream(*sinkNodeSerial, SPA_DIRECTION_OUTPUT);
97 m_self = shared_from_this();
105 QIODevice *device = createRingbufferWriterDevice();
108 bool started = start(device);
117 createStream(StreamType::Callback);
119 Q_ASSERT(hasStream());
120 auto sinkNodeSerial = findSinkNodeSerial();
121 if (!sinkNodeSerial) {
126 m_audioCallback = std::move(audioCallback);
128 bool connected = connectStream(*sinkNodeSerial, SPA_DIRECTION_OUTPUT);
135 m_self = shared_from_this();
142 m_shutdownPolicy.store(shutdownPolicy, std::memory_order_relaxed);
143 if (shutdownPolicy == ShutdownPolicy::DrainRingbuffer) {
145 m_ringbufferDrained.callOnActivated([
this] {
153 disconnectQIODeviceConnections();
155 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer || m_audioCallback) {
160 unregisterDeviceObserver();
164 m_disconnectSemaphore.acquire();
169 m_parent->updateStreamIdle(idle);
174 const char *roleString = [&] {
176 case AudioEndpointRole::MediaPlayback:
177 case AudioEndpointRole::Other:
179 case AudioEndpointRole::Accessibility:
180 return "Accessibility";
181 case AudioEndpointRole::SoundEffect:
182 return "Notification";
184 Q_UNREACHABLE_RETURN(
"Music");
188 auto extraProperties = std::array{
189 spa_dict_item{ PW_KEY_MEDIA_CATEGORY,
"Playback" },
190 spa_dict_item{ PW_KEY_MEDIA_ROLE, roleString },
193 QString applicationName = qApp->applicationName();
194 if (applicationName.isNull())
195 applicationName = u"QPipewireAudioSink"_s;
197 QPipewireAudioStream::createStream(extraProperties, m_hardwareBufferFrames,
198 applicationName.toUtf8().constData(), streamType);
203 const QPipewireAudioDevicePrivate *device =
204 QAudioDevicePrivate::handle<QPipewireAudioDevicePrivate>(m_audioDevice);
206 QByteArray nodeName = device->nodeName();
209 size_t(nodeName.size()),
213 qWarning() <<
"Cannot find device: " << nodeName;
219 if (!isStopRequested())
221 handleIOError(m_parent);
226 struct spa_buffer *buf = b->buffer;
227 uint64_t strideBytes = format.bytesPerSample() * format.channelCount();
228 Q_ASSERT(strideBytes > 0);
229 uint64_t totalNumberOfFrames = buf->datas[0].maxsize / strideBytes;
231#if PW_CHECK_VERSION
(0
, 3
, 49
)
232 if (pw_check_library_version(0, 3, 49))
235 totalNumberOfFrames = std::min(b->requested, totalNumberOfFrames);
238 const uint64_t requestedSamples = totalNumberOfFrames * format.channelCount();
240 QSpan<std::byte> writeBuffer{
241 reinterpret_cast<
std::byte *>(buf->datas[0].data),
242 qsizetype(requestedSamples * format.bytesPerSample()),
245 struct HostBufferData
247 QSpan<std::byte> writeBuffer;
248 const uint64_t requestedSamples{};
249 const uint64_t totalNumberOfFrames{};
252 return HostBufferData{
263 qCritical() <<
"pw_stream_dequeue_buffer failed";
280 <<
"QPipewireAudioSinkStream: shutdown with DiscardRingbuffer";
293 <<
"QPipewireAudioSinkStream: shutdown after draining ringbuffer";
307 qCritical() <<
"pw_stream_dequeue_buffer failed";
386 qDebug() <<
"XRuns occurred:" << numberOfXruns;
static QAudioDeviceMonitor & deviceMonitor()
static QAudioContextManager * instance()
void registerStreamReference(std::shared_ptr< QPipewireAudioStream >)
Q_STATIC_LOGGING_CATEGORY(lcPipewireRegistry, "qt.multimedia.pipewire.registry")
static constexpr bool pipewireRealtimeTracing
static auto resolveHostBuffer(pw_buffer *b, const QAudioFormat &format)
StrongIdType< uint64_t, ObjectSerialTag > ObjectSerial
QAudioFormat::SampleFormat SampleFormat
bool start(AudioCallback)
void updateStreamIdle(bool idle) override
QPipewireAudioSinkStream(QAudioDevice, const QAudioFormat &, std::optional< qsizetype > ringbufferSize, QPipewireAudioSink *parent, float volume, std::optional< int32_t > hardwareBufferFrames, AudioEndpointRole)
bool start(QIODevice *device)
void stop(ShutdownPolicy)
void handleDeviceRemoved() override