6#include <QtMultimedia/private/qpipewire_audiocontextmanager_p.h>
7#include <QtMultimedia/private/qpipewire_audiodevice_p.h>
8#include <QtMultimedia/private/qpipewire_support_p.h>
9#include <QtCore/qcoreapplication.h>
10#include <QtCore/qdebug.h>
11#include <QtCore/qloggingcategory.h>
13#include <pipewire/pipewire.h>
14#include <pipewire/stream.h>
15#include <spa/pod/builder.h>
32 const QAudioFormat &format,
33 std::optional<qsizetype> ringbufferSize,
34 QPipewireAudioSink *parent,
36 std::optional<NativePeriodFrames> nativePeriodFrames,
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_ringbufferDrained.thread() || m_ringbufferDrained.thread()->isCurrentThread());
67 Q_ASSERT(!m_deviceRemovalObserver);
77 createStream(StreamType::Ringbuffer);
79 Q_ASSERT(hasStream());
80 auto sinkNodeSerial = findSinkNodeSerial();
81 if (!sinkNodeSerial) {
89 createQIODeviceConnections(device);
91 bool connected = connectStream(*sinkNodeSerial, SPA_DIRECTION_OUTPUT);
98 m_self = shared_from_this();
99 QAudioContextManager::instance()->registerStreamReference(m_self);
106 QIODevice *device = createRingbufferWriterDevice();
109 bool started = start(device);
118 createStream(StreamType::Callback);
120 Q_ASSERT(hasStream());
121 auto sinkNodeSerial = findSinkNodeSerial();
122 if (!sinkNodeSerial) {
127 m_audioCallback = std::move(audioCallback);
129 bool connected = connectStream(*sinkNodeSerial, SPA_DIRECTION_OUTPUT);
136 m_self = shared_from_this();
137 QAudioContextManager::instance()->registerStreamReference(m_self);
143 m_shutdownPolicy.store(shutdownPolicy, std::memory_order_relaxed);
144 if (shutdownPolicy == ShutdownPolicy::DrainRingbuffer) {
146 m_ringbufferDrained.callOnActivated([
this] {
154 disconnectQIODeviceConnections();
155 if (m_deviceRemovalObserver)
156 unregisterDeviceObserver();
158 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer || m_audioCallback) {
165 m_disconnectSemaphore.acquire();
171 m_parent->updateStreamIdle(idle);
176 const char *roleString = [&] {
178 case AudioEndpointRole::MediaPlayback:
179 case AudioEndpointRole::Other:
181 case AudioEndpointRole::Accessibility:
182 return "Accessibility";
183 case AudioEndpointRole::SoundEffect:
184 return "Notification";
186 Q_UNREACHABLE_RETURN(
"Music");
190 auto extraProperties = std::array{
191 spa_dict_item{ PW_KEY_MEDIA_CATEGORY,
"Playback" },
192 spa_dict_item{ PW_KEY_MEDIA_ROLE, roleString },
195 QString applicationName = qApp->applicationName();
196 if (applicationName.isNull())
197 applicationName = u"QPipewireAudioSink"_s;
199 QPipewireAudioStream::createStream(extraProperties, m_nativePeriodFrames,
200 applicationName.toUtf8().constData(), streamType);
205 const QPipewireAudioDevicePrivate *device =
206 QAudioDevicePrivate::handle<QPipewireAudioDevicePrivate>(m_audioDevice);
208 QByteArray nodeName = device->nodeName();
209 auto ret = QAudioContextManager::deviceMonitor().findSinkNodeSerial(std::string_view{
211 size_t(nodeName.size()),
215 qWarning() <<
"Cannot find device: " << nodeName;
221 if (isStopRequested())
224 disconnectQIODeviceConnections();
225 handleIOError(
std::exchange(m_parent,
nullptr));
230 struct spa_buffer *buf = b->buffer;
231 uint64_t strideBytes = format.bytesPerSample() * format.channelCount();
232 Q_ASSERT(strideBytes > 0);
233 uint64_t totalNumberOfFrames = buf->datas[0].maxsize / strideBytes;
235#if PW_CHECK_VERSION
(0
, 3
, 49
)
236 if (pw_check_library_version(0, 3, 49))
239 totalNumberOfFrames = std::min(b->requested, totalNumberOfFrames);
242 const uint64_t requestedSamples = totalNumberOfFrames * format.channelCount();
244 QSpan<std::byte> writeBuffer{
245 reinterpret_cast<
std::byte *>(buf->datas[0].data),
246 qsizetype(requestedSamples * format.bytesPerSample()),
249 struct HostBufferData
251 QSpan<std::byte> writeBuffer;
252 const uint64_t requestedSamples{};
253 const uint64_t totalNumberOfFrames{};
256 return HostBufferData{
267 qCritical() <<
"pw_stream_dequeue_buffer failed";
284 <<
"QPipewireAudioSinkStream: shutdown with DiscardRingbuffer";
297 <<
"QPipewireAudioSinkStream: shutdown after draining ringbuffer";
311 qCritical() <<
"pw_stream_dequeue_buffer failed";
391 qDebug() <<
"XRuns occurred:" << numberOfXruns;
static constexpr bool pipewireRealtimeTracing
Q_STATIC_LOGGING_CATEGORY(lcPipewireAudioSink, "qt.multimedia.pipewire.audiosink")
static auto resolveHostBuffer(pw_buffer *b, const QAudioFormat &format)
QAudioFormat::SampleFormat SampleFormat
bool start(AudioCallback)
void updateStreamIdle(bool idle) override
bool start(QIODevice *device)
void stop(ShutdownPolicy)
void handleDeviceRemoved() override
QPipewireAudioSinkStream(QAudioDevice, const QAudioFormat &, std::optional< qsizetype > ringbufferSize, QPipewireAudioSink *parent, float volume, std::optional< NativePeriodFrames > nativePeriodFrames, AudioEndpointRole)