Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqnxaudiosink.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 Research In Motion
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <private/qaudiohelpers_p.h>
7#include <sys/asoundlib.h>
8#include <sys/asound_common.h>
9
10#include <algorithm>
11#include <limits>
12
13#pragma GCC diagnostic ignored "-Wvla"
14
15QT_BEGIN_NAMESPACE
16
17QQnxAudioSink::QQnxAudioSink(QAudioDevice device, const QAudioFormat &format, QObject *parent)
18 : QPlatformAudioSink(std::move(device), format, parent),
19 m_source(0),
20 m_pushSource(false),
21 m_timer(new QTimer(this)),
22 m_state(QAudio::StoppedState),
23 m_suspendedInState(QAudio::SuspendedState),
24 m_periodSize(0),
25 m_bytesWritten(0),
26 m_requestedBufferSize(0),
27 m_pcmNotifier(0)
28{
29 m_timer->setSingleShot(false);
30 m_timer->setInterval(20);
31 connect(m_timer, &QTimer::timeout, this, &QQnxAudioSink::pullData);
32
33 const std::optional<snd_pcm_channel_info_t> info =
34 QnxAudioUtils::pcmChannelInfo(m_audioDevice.id(), QAudioDevice::Output);
35
36 if (info)
37 m_requestedBufferSize = info->max_fragment_size;
38}
39
44
45void QQnxAudioSink::start(QIODevice *source)
46{
47 if (m_state != QAudio::StoppedState)
48 stop();
49
50 m_source = source;
51 m_pushSource = false;
52
53 if (open()) {
54 changeState(QAudio::ActiveState, QAudio::NoError);
55 m_timer->start();
56 } else {
57 changeState(QAudio::StoppedState, QAudio::OpenError);
58 }
59}
60
62{
63 if (m_state != QAudio::StoppedState)
64 stop();
65
66 m_source = new QnxPushIODevice(this);
67 m_source->open(QIODevice::WriteOnly|QIODevice::Unbuffered);
68 m_pushSource = true;
69
70 if (open()) {
71 changeState(QAudio::IdleState, QAudio::NoError);
72 } else {
73 changeState(QAudio::StoppedState, QAudio::OpenError);
74 }
75
76 return m_source;
77}
78
80{
81 if (m_state == QAudio::StoppedState)
82 return;
83
84 changeState(QAudio::StoppedState, QAudio::NoError);
85
86 close();
87}
88
90{
91 if (m_pcmHandle)
92#if SND_PCM_VERSION < SND_PROTOCOL_VERSION('P',3,0,2)
93 snd_pcm_playback_drain(m_pcmHandle.get());
94#else
95 snd_pcm_channel_drain(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK);
96#endif
97 stop();
98}
99
101{
102 if (!m_pcmHandle)
103 return;
104
105 snd_pcm_playback_pause(m_pcmHandle.get());
106 suspendInternal(QAudio::SuspendedState);
107}
108
110{
111 if (!m_pcmHandle)
112 return;
113
114 snd_pcm_playback_resume(m_pcmHandle.get());
115 resumeInternal();
116}
117
118void QQnxAudioSink::setBufferSize(qsizetype bufferSize)
119{
120 m_requestedBufferSize = std::clamp<qsizetype>(bufferSize, 0, std::numeric_limits<int>::max());
121}
122
124{
125 const std::optional<snd_pcm_channel_setup_t> setup = m_pcmHandle
126 ? QnxAudioUtils::pcmChannelSetup(m_pcmHandle.get(), QAudioDevice::Output)
127 : std::nullopt;
128
129 return setup ? setup->buf.block.frag_size : m_requestedBufferSize;
130}
131
133{
134 if (m_state != QAudio::ActiveState && m_state != QAudio::IdleState)
135 return 0;
136
137 const std::optional<snd_pcm_channel_status_t> status = QnxAudioUtils::pcmChannelStatus(
138 m_pcmHandle.get(), QAudioDevice::Output);
139
140 return status ? status->free : 0;
141}
142
144{
145 return qint64(1000000) * m_format.framesForBytes(m_bytesWritten) / m_format.sampleRate();
146}
147
149{
150 return m_state;
151}
152
153void QQnxAudioSink::updateState()
154{
155 const std::optional<snd_pcm_channel_status_t> status = QnxAudioUtils::pcmChannelStatus(
156 m_pcmHandle.get(), QAudioDevice::Output);
157
158 if (!status)
159 return;
160
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);
165}
166
167void QQnxAudioSink::pullData()
168{
169 if (m_state == QAudio::StoppedState
170 || m_state == QAudio::SuspendedState)
171 return;
172
173 const int bytesAvailable = bytesFree();
174
175 // skip if we have less than 4ms of data
176 if (m_format.durationForBytes(bytesAvailable) < 4000)
177 return;
178
179 const int frames = m_format.framesForBytes(bytesAvailable);
180 // The buffer is placed on the stack so no more than 64K or 1 frame
181 // whichever is larger.
182 const int maxFrames = qMax(m_format.framesForBytes(64 * 1024), 1);
183 const int bytesRequested = m_format.bytesForFrames(qMin(frames, maxFrames));
184
185 char buffer[bytesRequested];
186 const int bytesRead = m_source->read(buffer, bytesRequested);
187
188 // reading can take a while and stream may have been stopped
189 if (!m_pcmHandle)
190 return;
191
192 if (bytesRead > 0) {
193 const qint64 bytesWritten = write(buffer, bytesRead);
194
195 if (bytesWritten <= 0) {
196 close();
197 changeState(QAudio::StoppedState, QAudio::FatalError);
198 } else if (bytesWritten != bytesRead) {
199 m_source->seek(m_source->pos()-(bytesRead-bytesWritten));
200 }
201 } else {
202 // We're done
203 if (bytesRead == 0)
204 changeState(QAudio::IdleState, QAudio::NoError);
205 else
206 changeState(QAudio::IdleState, QAudio::IOError);
207 }
208}
209
210bool QQnxAudioSink::open()
211{
212 m_pcmHandle = QnxAudioUtils::openPcmDevice(m_audioDevice.id(), QAudioDevice::Output);
213
214 if (!m_pcmHandle)
215 return false;
216
217 int errorCode = 0;
218
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);
221 close();
222 return false;
223 }
224
225 addPcmEventFilter();
226
227 // Necessary so that bytesFree() which uses the "free" member of the status struct works
228 snd_pcm_plugin_set_disable(m_pcmHandle.get(), PLUGIN_MMAP);
229
230 const std::optional<snd_pcm_channel_info_t> info = QnxAudioUtils::pcmChannelInfo(
231 m_pcmHandle.get(), QAudioDevice::Output);
232
233 if (!info) {
234 close();
235 return false;
236 }
237
238 const int fragmentSize = std::clamp(m_requestedBufferSize,
239 info->min_fragment_size, info->max_fragment_size);
240
241 snd_pcm_channel_params_t params = QnxAudioUtils::formatToChannelParams(m_format,
242 QAudioDevice::Output, fragmentSize);
243
244 if ((errorCode = snd_pcm_plugin_params(m_pcmHandle.get(), &params)) < 0) {
245 qWarning("QQnxAudioSink: open error, couldn't set channel params (0x%x)", -errorCode);
246 close();
247 return false;
248 }
249
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);
252 close();
253 return false;
254 }
255
256 const std::optional<snd_pcm_channel_setup_t> setup = QnxAudioUtils::pcmChannelSetup(
257 m_pcmHandle.get(), QAudioDevice::Output);
258
259 if (!setup) {
260 close();
261 return false;
262 }
263
264 m_periodSize = qMin(2048, setup->buf.block.frag_size);
265 m_bytesWritten = 0;
266
267 createPcmNotifiers();
268
269 return true;
270}
271
272void QQnxAudioSink::close()
273{
274 if (!m_pushSource)
275 m_timer->stop();
276
277 destroyPcmNotifiers();
278
279 if (m_pcmHandle) {
280#if SND_PCM_VERSION < SND_PROTOCOL_VERSION('P',3,0,2)
281 snd_pcm_plugin_flush(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK);
282#else
283 snd_pcm_plugin_drop(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK);
284#endif
285 m_pcmHandle = nullptr;
286 }
287
288 if (m_pushSource) {
289 delete m_source;
290 m_source = 0;
291 }
292}
293
294void QQnxAudioSink::changeState(QAudio::State state, QAudio::Error error)
295{
296 if (m_state != state) {
297 m_state = state;
298 emit stateChanged(state);
299 }
300
301 setError(error);
302}
303
304qint64 QQnxAudioSink::pushData(const char *data, qint64 len)
305{
306 const QAudio::State s = state();
307
308 if (s == QAudio::StoppedState || s == QAudio::SuspendedState)
309 return 0;
310
311 if (s == QAudio::IdleState)
312 changeState(QAudio::ActiveState, QAudio::NoError);
313
314 qint64 totalWritten = 0;
315
316 int retry = 0;
317
318 constexpr int maxRetries = 10;
319
320 while (totalWritten < len) {
321 const int bytesWritten = write(data + totalWritten, len - totalWritten);
322
323 if (bytesWritten <= 0) {
324 ++retry;
325
326 if (retry >= maxRetries) {
327 close();
328 changeState(QAudio::StoppedState, QAudio::FatalError);
329
330 return totalWritten;
331 } else {
332 continue;
333 }
334 }
335
336 retry = 0;
337
338 totalWritten += bytesWritten;
339 }
340
341 return totalWritten;
342}
343
344qint64 QQnxAudioSink::write(const char *data, qint64 len)
345{
346 if (!m_pcmHandle)
347 return 0;
348
349 // Make sure we're writing (N * frame) worth of bytes
350 const int size = m_format.bytesForFrames(qBound(qint64(0), qint64(bytesFree()), len) / m_format.bytesPerFrame());
351
352 if (size == 0)
353 return 0;
354
355 int written = 0;
356
357 if (volume() < 1.0f) {
358 char out[size];
359 QAudioHelperInternal::qMultiplySamples(volume(), m_format, data, out, size);
360 written = snd_pcm_plugin_write(m_pcmHandle.get(), out, size);
361 } else {
362 written = snd_pcm_plugin_write(m_pcmHandle.get(), data, size);
363 }
364
365 if (written > 0) {
366 m_bytesWritten += written;
367 return written;
368 }
369
370 return 0;
371}
372
373void QQnxAudioSink::suspendInternal(QAudio::State suspendState)
374{
375 if (!m_pushSource)
376 m_timer->stop();
377
378 m_suspendedInState = m_state;
379 changeState(suspendState, QAudio::NoError);
380}
381
382void QQnxAudioSink::resumeInternal()
383{
384 changeState(m_suspendedInState, QAudio::NoError);
385
386 m_timer->start();
387}
388
389QAudio::State suspendState(const snd_pcm_event_t &event)
390{
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;
394}
395
396void QQnxAudioSink::addPcmEventFilter()
397{
398 /* Enable PCM events */
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);
406}
407
408void QQnxAudioSink::createPcmNotifiers()
409{
410 // QSocketNotifier::Read for poll based event dispatcher. Exception for
411 // select based event dispatcher.
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);
417}
418
419void QQnxAudioSink::destroyPcmNotifiers()
420{
421 if (m_pcmNotifier) {
422 delete m_pcmNotifier;
423 m_pcmNotifier = 0;
424 }
425}
426
427void QQnxAudioSink::pcmNotifierActivated(int socket)
428{
429 Q_UNUSED(socket);
430
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)
438 resumeInternal();
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) {
442 updateState();
443 }
444 }
445}
446
447QnxPushIODevice::QnxPushIODevice(QQnxAudioSink *output)
448 : QIODevice(output),
449 m_output(output)
450{
451}
452
456
457qint64 QnxPushIODevice::readData(char *data, qint64 len)
458{
459 Q_UNUSED(data);
460 Q_UNUSED(len);
461 return 0;
462}
463
464qint64 QnxPushIODevice::writeData(const char *data, qint64 len)
465{
466 return m_output->pushData(data, len);
467}
468
470{
471 return true;
472}
473
474QT_END_NAMESPACE
QAudio::State state() const override
qint64 pushData(const char *data, qint64 len)
void setBufferSize(qsizetype) override
void reset() override
qsizetype bufferSize() const override
qsizetype bytesFree() const override
QIODevice * start() override
void resume() override
void stop() override
void suspend() 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)