7#include <QtCore/qspan.h>
8#include <QtCore/qthread.h>
9#include <QLoggingCategory>
17using QtMultimediaPrivate::QPlatformAudioSinkStream;
18using QtMultimediaPrivate::QPlatformAudioIOStream;
19using QtMultimediaPrivate::runAudioCallback;
20using QtMultimediaPrivate::withTemporaryBuffer;
23 std::optional<qsizetype> ringbufferSize,
24 QQnxSndAudioSink *parent,
float volume,
25 std::optional<NativePeriodFrames> nativePeriodFrames,
50 return openPcmDevice();
61 .direction = SND_PCM_STREAM_PLAYBACK,
62 .deviceId = m_audioDevice.id(),
64 .category = lcQnxSndOutput(),
65 .streamLabel =
"Playback",
66 .periodCountEnvVar =
"QT_QNXSND_OUTPUT_PERIODS",
67 .periodFrames = m_nativePeriodFrames
68 ? std::optional<uint32_t>{ qToUnderlying(*m_nativePeriodFrames) }
77 m_nativeFormat = r.nativeFormat;
86 Q_ASSERT(!m_workerThread || !m_workerThread->isRunning());
91 if (
const int err = snd_pcm_close(m_handle); err < 0) {
92 qCWarning(lcQnxSndOutput) <<
"snd_pcm_close failed:"
93 << snd_strerror(err) <<
"(" << err <<
")";
101 setQIODevice(ioDevice);
102 createQIODeviceConnections(ioDevice);
111 QIODevice *ioDevice = createRingbufferWriterDevice();
112 setQIODevice(ioDevice);
113 createQIODeviceConnections(ioDevice);
121 m_audioCallback = std::move(audioCallback);
132 m_suspended.store(
true, std::memory_order_release);
141 m_suspended.store(
false, std::memory_order_release);
150 if (m_suspended.load(std::memory_order_acquire))
151 shutdownPolicy = ShutdownPolicy::DiscardRingbuffer;
153 m_shutdownPolicy.store(shutdownPolicy, std::memory_order_release);
159 disconnectQIODeviceConnections();
161 switch (shutdownPolicy) {
162 case ShutdownPolicy::DiscardRingbuffer:
167 m_parent.store(
nullptr, std::memory_order_release);
169 case ShutdownPolicy::DrainRingbuffer:
177 m_parent.store(
nullptr, std::memory_order_release);
178 m_ringbufferDrained.callOnActivated([self = shared_from_this()]()
mutable {
179 self->joinWorkerThread();
180 self->closePcmDevice();
181 self->m_parent.store(
nullptr, std::memory_order_release);
194 if (
auto *parent = m_parent.load(std::memory_order_acquire))
195 parent->updateStreamIdle(streamIsIdle);
202 if (!m_wakePipe.open())
203 qCWarning(lcQnxSndOutput) <<
"wake pipe creation failed; worker wakeups degraded";
205 m_workerThread.reset(QThread::create([
this, streamType] {
206 runProcessLoop(streamType);
208 m_workerThread->setObjectName(u"QQnxSndAudioSinkStream");
214 m_workerThread->start();
226 if (
const int err = snd_pcm_drop(m_handle); err < 0) {
227 qCWarning(lcQnxSndOutput) <<
"snd_pcm_drop failed:"
228 << snd_strerror(err) <<
"(" << err <<
")";
231 if (m_workerThread) {
232 m_workerThread->wait();
245 if (!processOnePeriod(streamType) && isStopRequested(
std::memory_order_acquire))
248 handleSndPcmError(err);
253 if (isStopRequested(
std::memory_order_acquire)) {
254 switch (m_shutdownPolicy.load(std::memory_order_acquire)) {
255 case ShutdownPolicy::DiscardRingbuffer:
257 case ShutdownPolicy::DrainRingbuffer: {
258 const bool bufferDrained = visitRingbuffer([](
const auto &ringbuffer) {
259 return ringbuffer.used() == 0;
265 if (bufferDrained || m_suspended.load(std::memory_order_acquire)) {
270 m_ringbufferDrained.set();
278 if (m_suspended.load(std::memory_order_acquire)) {
282 m_wakePipe.waitForWake();
297 if (!processOnePeriod(streamType)) {
298 if (!isStopRequested(
std::memory_order_acquire))
313 const snd_pcm_sframes_t avail = snd_pcm_avail_update(m_handle);
315 if (isStopRequested(
std::memory_order_acquire))
318 if (
const int err = recoverFromXrun(
static_cast<
int>(avail)); err < 0) {
319 handleSndPcmError(err);
327 const snd_pcm_uframes_t framesToWrite =
328 std::min<snd_pcm_uframes_t>(
static_cast<snd_pcm_uframes_t>(avail), m_periodFrames);
332 const size_t nativeBytesPerFrame =
333 m_format.channelCount() * QAudioHelperInternal::bytesPerSample(m_nativeFormat);
334 const size_t hostBytes =
static_cast<size_t>(framesToWrite) * nativeBytesPerFrame;
336 return withTemporaryBuffer(hostBytes, [&](QSpan<std::byte> hostBufferSpan) ->
bool {
337 switch (streamType) {
338 case StreamType::Ringbuffer:
339 QPlatformAudioSinkStream::process(hostBufferSpan, framesToWrite, m_nativeFormat);
344 Q_ASSERT(m_audioCallback);
345 runAudioCallback(*m_audioCallback, hostBufferSpan, m_format, volume(), m_nativeFormat);
349 snd_pcm_uframes_t framesPending = framesToWrite;
350 auto *cursor = hostBufferSpan.data();
351 while (framesPending > 0) {
352 if (isStopRequested(
std::memory_order_acquire))
355 snd_pcm_sframes_t written = snd_pcm_writei(m_handle, cursor, framesPending);
356 if (written == -EAGAIN || written == 0) {
368 if (
const int err = recoverFromXrun(
static_cast<
int>(written)); err < 0) {
369 handleSndPcmError(err);
374 framesPending -=
static_cast<snd_pcm_uframes_t>(written);
375 cursor += written * nativeBytesPerFrame;
389 invokeOnAppThread([self = shared_from_this(), err] {
390 qCWarning(lcQnxSndOutput) <<
"audio output error, stopping stream:"
391 << (err ? snd_strerror(err) :
"I/O error");
392 if (
auto *parent = self->m_parent.load(std::memory_order_acquire))
393 self->handleIOError(parent);
399QQnxSndAudioSink::
QQnxSndAudioSink(QAudioDevice device,
const QAudioFormat &format, QObject *parent)
414 m_stream->stop(ShutdownPolicy::DiscardRingbuffer);
~QQnxSndAudioSink() override
QQnxSndAudioSink(QAudioDevice, const QAudioFormat &, QObject *parent)
void setWorkerRealtimePriority(const QLoggingCategory &category)
int startPcm(snd_pcm_t *handle)
int recoverFromXrun(snd_pcm_t *handle, int err)
PollOutcome pollPcm(snd_pcm_t *handle, const WakePipe &wake)
PcmOpenResult openConfiguredPcm(const PcmOpenConfig &config)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
bool start(AudioCallback)
QtMultimediaPrivate::QPlatformAudioSinkStream::AudioCallback AudioCallback
void updateStreamIdle(bool) override
void stop(ShutdownPolicy)
QQnxSndAudioSinkStream(QAudioDevice, const QAudioFormat &, std::optional< qsizetype > ringbufferSize, QQnxSndAudioSink *parent, float volume, std::optional< NativePeriodFrames > nativePeriodFrames, AudioEndpointRole)
snd_pcm_uframes_t periodFrames