6#include <private/qaudiohelpers_p.h>
7#include <sys/asoundlib.h>
8#include <sys/asound_common.h>
13#pragma GCC diagnostic ignored "-Wvla"
17QQnxAudioSink::QQnxAudioSink(QAudioDevice device,
const QAudioFormat &format, QObject *parent)
18 : QPlatformAudioSink(std::move(device), format, parent),
21 m_timer(
new QTimer(
this)),
22 m_state(QAudio::StoppedState),
23 m_suspendedInState(QAudio::SuspendedState),
26 m_requestedBufferSize(0),
29 m_timer->setSingleShot(
false);
30 m_timer->setInterval(20);
31 connect(m_timer, &QTimer::timeout,
this, &QQnxAudioSink::pullData);
33 const std::optional<snd_pcm_channel_info_t> info =
34 QnxAudioUtils::pcmChannelInfo(m_audioDevice.id(), QAudioDevice::Output);
37 m_requestedBufferSize = info->max_fragment_size;
47 if (m_state != QAudio::StoppedState)
54 changeState(QAudio::ActiveState, QAudio::NoError);
57 changeState(QAudio::StoppedState, QAudio::OpenError);
63 if (m_state != QAudio::StoppedState)
66 m_source =
new QnxPushIODevice(
this);
67 m_source->open(QIODevice::WriteOnly|QIODevice::Unbuffered);
71 changeState(QAudio::IdleState, QAudio::NoError);
73 changeState(QAudio::StoppedState, QAudio::OpenError);
81 if (m_state == QAudio::StoppedState)
84 changeState(QAudio::StoppedState, QAudio::NoError);
92#if SND_PCM_VERSION < SND_PROTOCOL_VERSION('P',3
,0
,2
)
93 snd_pcm_playback_drain(m_pcmHandle.get());
95 snd_pcm_channel_drain(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK);
105 snd_pcm_playback_pause(m_pcmHandle.get());
106 suspendInternal(QAudio::SuspendedState);
114 snd_pcm_playback_resume(m_pcmHandle.get());
120 m_requestedBufferSize = std::clamp<qsizetype>(bufferSize, 0, std::numeric_limits<
int>::max());
125 const std::optional<snd_pcm_channel_setup_t> setup = m_pcmHandle
126 ? QnxAudioUtils::pcmChannelSetup(m_pcmHandle.get(), QAudioDevice::Output)
129 return setup ? setup->buf.block.frag_size : m_requestedBufferSize;
134 if (m_state != QAudio::ActiveState && m_state != QAudio::IdleState)
137 const std::optional<snd_pcm_channel_status_t> status = QnxAudioUtils::pcmChannelStatus(
138 m_pcmHandle.get(), QAudioDevice::Output);
140 return status ? status->free : 0;
145 return qint64(1000000) * m_format.framesForBytes(m_bytesWritten) / m_format.sampleRate();
155 const std::optional<snd_pcm_channel_status_t> status = QnxAudioUtils::pcmChannelStatus(
156 m_pcmHandle.get(), QAudioDevice::Output);
161 if (state() == QAudio::ActiveState && status->underrun > 0)
162 changeState(QAudio::IdleState, QAudio::UnderrunError);
163 else if (state() == QAudio::IdleState && status->underrun == 0)
164 changeState(QAudio::ActiveState, QAudio::NoError);
169 if (m_state == QAudio::StoppedState
170 || m_state == QAudio::SuspendedState)
173 const int bytesAvailable = bytesFree();
176 if (m_format.durationForBytes(bytesAvailable) < 4000)
179 const int frames = m_format.framesForBytes(bytesAvailable);
182 const int maxFrames = qMax(m_format.framesForBytes(64 * 1024), 1);
183 const int bytesRequested = m_format.bytesForFrames(qMin(frames, maxFrames));
185 char buffer[bytesRequested];
186 const int bytesRead = m_source->read(buffer, bytesRequested);
193 const qint64 bytesWritten = write(buffer, bytesRead);
195 if (bytesWritten <= 0) {
197 changeState(QAudio::StoppedState, QAudio::FatalError);
198 }
else if (bytesWritten != bytesRead) {
199 m_source->seek(m_source->pos()-(bytesRead-bytesWritten));
204 changeState(QAudio::IdleState, QAudio::NoError);
206 changeState(QAudio::IdleState, QAudio::IOError);
212 m_pcmHandle = QnxAudioUtils::openPcmDevice(m_audioDevice.id(), QAudioDevice::Output);
219 if ((errorCode = snd_pcm_nonblock_mode(m_pcmHandle.get(), 0)) < 0) {
220 qWarning(
"QQnxAudioSink: open error, couldn't set non block mode (0x%x)", -errorCode);
228 snd_pcm_plugin_set_disable(m_pcmHandle.get(), PLUGIN_MMAP);
230 const std::optional<snd_pcm_channel_info_t> info = QnxAudioUtils::pcmChannelInfo(
231 m_pcmHandle.get(), QAudioDevice::Output);
238 const int fragmentSize =
std::clamp(m_requestedBufferSize,
239 info->min_fragment_size, info->max_fragment_size);
241 snd_pcm_channel_params_t params = QnxAudioUtils::formatToChannelParams(m_format,
242 QAudioDevice::Output, fragmentSize);
244 if ((errorCode = snd_pcm_plugin_params(m_pcmHandle.get(), ¶ms)) < 0) {
245 qWarning(
"QQnxAudioSink: open error, couldn't set channel params (0x%x)", -errorCode);
250 if ((errorCode = snd_pcm_plugin_prepare(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK)) < 0) {
251 qWarning(
"QQnxAudioSink: open error, couldn't prepare channel (0x%x)", -errorCode);
256 const std::optional<snd_pcm_channel_setup_t> setup = QnxAudioUtils::pcmChannelSetup(
257 m_pcmHandle.get(), QAudioDevice::Output);
264 m_periodSize = qMin(2048, setup->buf.block.frag_size);
267 createPcmNotifiers();
277 destroyPcmNotifiers();
280#if SND_PCM_VERSION < SND_PROTOCOL_VERSION('P',3
,0
,2
)
281 snd_pcm_plugin_flush(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK);
283 snd_pcm_plugin_drop(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK);
285 m_pcmHandle =
nullptr;
294void QQnxAudioSink::changeState(QAudio::State state, QAudio::Error error)
296 if (m_state != state) {
298 emit stateChanged(state);
306 const QAudio::State s = state();
308 if (s == QAudio::StoppedState || s == QAudio::SuspendedState)
311 if (s == QAudio::IdleState)
312 changeState(QAudio::ActiveState, QAudio::NoError);
314 qint64 totalWritten = 0;
318 constexpr int maxRetries = 10;
320 while (totalWritten < len) {
321 const int bytesWritten = write(data + totalWritten, len - totalWritten);
323 if (bytesWritten <= 0) {
326 if (retry >= maxRetries) {
328 changeState(QAudio::StoppedState, QAudio::FatalError);
338 totalWritten += bytesWritten;
350 const int size = m_format.bytesForFrames(qBound(qint64(0), qint64(bytesFree()), len) / m_format.bytesPerFrame());
357 if (volume() < 1.0f) {
359 QAudioHelperInternal::qMultiplySamples(volume(), m_format, data, out, size);
360 written = snd_pcm_plugin_write(m_pcmHandle.get(), out, size);
362 written = snd_pcm_plugin_write(m_pcmHandle.get(), data, size);
366 m_bytesWritten += written;
373void QQnxAudioSink::suspendInternal(QAudio::State suspendState)
378 m_suspendedInState = m_state;
379 changeState(suspendState, QAudio::NoError);
384 changeState(m_suspendedInState, QAudio::NoError);
391 Q_ASSERT(event.type == SND_PCM_EVENT_AUDIOMGMT_STATUS);
392 Q_ASSERT(event.data.audiomgmt_status.new_status == SND_PCM_STATUS_SUSPENDED);
393 return QAudio::SuspendedState;
399 snd_pcm_filter_t filter;
400 memset(&filter, 0,
sizeof(filter));
401 filter.enable = (1<<SND_PCM_EVENT_AUDIOMGMT_STATUS) |
402 (1<<SND_PCM_EVENT_AUDIOMGMT_MUTE) |
403 (1<<SND_PCM_EVENT_OUTPUTCLASS) |
404 (1<<SND_PCM_EVENT_UNDERRUN);
405 snd_pcm_set_filter(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK, &filter);
412 m_pcmNotifier =
new QSocketNotifier(snd_pcm_file_descriptor(m_pcmHandle.get(),
413 SND_PCM_CHANNEL_PLAYBACK),
414 QSocketNotifier::Read,
this);
415 connect(m_pcmNotifier, &QSocketNotifier::activated,
416 this, &QQnxAudioSink::pcmNotifierActivated);
422 delete m_pcmNotifier;
431 snd_pcm_event_t pcm_event;
432 memset(&pcm_event, 0,
sizeof(pcm_event));
433 while (snd_pcm_channel_read_event(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK, &pcm_event) == 0) {
434 if (pcm_event.type == SND_PCM_EVENT_AUDIOMGMT_STATUS) {
435 if (pcm_event.data.audiomgmt_status.new_status == SND_PCM_STATUS_SUSPENDED)
436 suspendInternal(suspendState(pcm_event));
437 else if (pcm_event.data.audiomgmt_status.new_status == SND_PCM_STATUS_RUNNING)
439 else if (pcm_event.data.audiomgmt_status.new_status == SND_PCM_STATUS_PAUSED)
440 suspendInternal(QAudio::SuspendedState);
441 }
else if (pcm_event.type == SND_PCM_EVENT_UNDERRUN) {
466 return m_output->pushData(data, len);
QAudio::State state() const override
qint64 pushData(const char *data, qint64 len)
void setBufferSize(qsizetype) override
qsizetype bufferSize() const override
qsizetype bytesFree() const override
QIODevice * start() override
void start(QIODevice *source) override
qint64 processedUSecs() const override
qint64 writeData(const char *data, qint64 len)
Writes up to maxSize bytes from data to the device.
bool isSequential() const override
Returns true if this device is sequential; otherwise returns false.
qint64 readData(char *data, qint64 len)
Reads up to maxSize bytes from the device into data, and returns the number of bytes read or -1 if an...
QAudio::State suspendState(const snd_pcm_event_t &event)