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));
65 Q_ASSERT(!m_deviceRemovalObserver);
75 createStream(StreamType::Ringbuffer);
77 Q_ASSERT(hasStream());
78 auto sinkNodeSerial = findSinkNodeSerial();
79 if (!sinkNodeSerial) {
87 createQIODeviceConnections(device);
89 bool connected = connectStream(*sinkNodeSerial, SPA_DIRECTION_OUTPUT);
96 m_self = shared_from_this();
97 QAudioContextManager::instance()->registerStreamReference(m_self);
104 QIODevice *device = createRingbufferWriterDevice();
107 bool started = start(device);
116 createStream(StreamType::Callback);
118 Q_ASSERT(hasStream());
119 auto sinkNodeSerial = findSinkNodeSerial();
120 if (!sinkNodeSerial) {
125 m_audioCallback = std::move(audioCallback);
127 bool connected = connectStream(*sinkNodeSerial, SPA_DIRECTION_OUTPUT);
134 m_self = shared_from_this();
135 QAudioContextManager::instance()->registerStreamReference(m_self);
141 m_shutdownPolicy.store(shutdownPolicy, std::memory_order_relaxed);
142 if (shutdownPolicy == ShutdownPolicy::DrainRingbuffer) {
144 m_ringbufferDrained.callOnActivated([
this] {
152 disconnectQIODeviceConnections();
154 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer || m_audioCallback) {
159 unregisterDeviceObserver();
163 m_disconnectSemaphore.acquire();
168 m_parent->updateStreamIdle(idle);
173 const char *roleString = [&] {
175 case AudioEndpointRole::MediaPlayback:
176 case AudioEndpointRole::Other:
178 case AudioEndpointRole::Accessibility:
179 return "Accessibility";
180 case AudioEndpointRole::SoundEffect:
181 return "Notification";
183 Q_UNREACHABLE_RETURN(
"Music");
187 auto extraProperties = std::array{
188 spa_dict_item{ PW_KEY_MEDIA_CATEGORY,
"Playback" },
189 spa_dict_item{ PW_KEY_MEDIA_ROLE, roleString },
192 QString applicationName = qApp->applicationName();
193 if (applicationName.isNull())
194 applicationName = u"QPipewireAudioSink"_s;
196 QPipewireAudioStream::createStream(extraProperties, m_hardwareBufferFrames,
197 applicationName.toUtf8().constData(), streamType);
202 const QPipewireAudioDevicePrivate *device =
203 QAudioDevicePrivate::handle<QPipewireAudioDevicePrivate>(m_audioDevice);
205 QByteArray nodeName = device->nodeName();
206 auto ret = QAudioContextManager::deviceMonitor().findSinkNodeSerial(std::string_view{
208 size_t(nodeName.size()),
212 qWarning() <<
"Cannot find device: " << nodeName;
218 if (!isStopRequested())
220 handleIOError(m_parent);
225 struct spa_buffer *buf = b->buffer;
226 uint64_t strideBytes = format.bytesPerSample() * format.channelCount();
227 Q_ASSERT(strideBytes > 0);
228 uint64_t totalNumberOfFrames = buf->datas[0].maxsize / strideBytes;
230#if PW_CHECK_VERSION
(0
, 3
, 49
)
231 if (pw_check_library_version(0, 3, 49))
234 totalNumberOfFrames = std::min(b->requested, totalNumberOfFrames);
237 const uint64_t requestedSamples = totalNumberOfFrames * format.channelCount();
239 QSpan<std::byte> writeBuffer{
240 reinterpret_cast<
std::byte *>(buf->datas[0].data),
241 qsizetype(requestedSamples * format.bytesPerSample()),
244 struct HostBufferData
246 QSpan<std::byte> writeBuffer;
247 const uint64_t requestedSamples{};
248 const uint64_t totalNumberOfFrames{};
251 return HostBufferData{
262 qCritical() <<
"pw_stream_dequeue_buffer failed";
279 <<
"QPipewireAudioSinkStream: shutdown with DiscardRingbuffer";
292 <<
"QPipewireAudioSinkStream: shutdown after draining ringbuffer";
306 qCritical() <<
"pw_stream_dequeue_buffer failed";
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