Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
4#include "qqnxaudiosink_p.h"
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
16
18 : QPlatformAudioSink(parent)
19 , m_source(0)
20 , m_pushSource(false)
21 , m_timer(new QTimer(this))
22 , m_error(QAudio::NoError)
23 , m_state(QAudio::StoppedState)
24 , m_suspendedInState(QAudio::SuspendedState)
25 , m_volume(1.0)
26 , m_periodSize(0)
27 , m_bytesWritten(0)
28 , m_requestedBufferSize(0)
29 , m_deviceInfo(deviceInfo)
30 , m_pcmNotifier(0)
31{
32 m_timer->setSingleShot(false);
33 m_timer->setInterval(20);
34 connect(m_timer, &QTimer::timeout, this, &QQnxAudioSink::pullData);
35
36 const std::optional<snd_pcm_channel_info_t> info = QnxAudioUtils::pcmChannelInfo(
37 m_deviceInfo.id(), QAudioDevice::Output);
38
39 if (info)
40 m_requestedBufferSize = info->max_fragment_size;
41}
42
47
49{
50 if (m_state != QAudio::StoppedState)
51 stop();
52
53 m_source = source;
54 m_pushSource = false;
55
56 if (open()) {
58 m_timer->start();
59 } else {
61 }
62}
63
65{
66 if (m_state != QAudio::StoppedState)
67 stop();
68
69 m_source = new QnxPushIODevice(this);
71 m_pushSource = true;
72
73 if (open()) {
75 } else {
77 }
78
79 return m_source;
80}
81
83{
84 if (m_state == QAudio::StoppedState)
85 return;
86
88
89 close();
90}
91
93{
94 if (m_pcmHandle)
95#if SND_PCM_VERSION < SND_PROTOCOL_VERSION('P',3,0,2)
96 snd_pcm_playback_drain(m_pcmHandle.get());
97#else
98 snd_pcm_channel_drain(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK);
99#endif
100 stop();
101}
102
104{
105 if (!m_pcmHandle)
106 return;
107
108 snd_pcm_playback_pause(m_pcmHandle.get());
109 suspendInternal(QAudio::SuspendedState);
110}
111
113{
114 if (!m_pcmHandle)
115 return;
116
117 snd_pcm_playback_resume(m_pcmHandle.get());
118 resumeInternal();
119}
120
122{
123 m_requestedBufferSize = std::clamp<qsizetype>(bufferSize, 0, std::numeric_limits<int>::max());
124}
125
127{
128 const std::optional<snd_pcm_channel_setup_t> setup = m_pcmHandle
130 : std::nullopt;
131
132 return setup ? setup->buf.block.frag_size : m_requestedBufferSize;
133}
134
136{
137 if (m_state != QAudio::ActiveState && m_state != QAudio::IdleState)
138 return 0;
139
140 const std::optional<snd_pcm_channel_status_t> status = QnxAudioUtils::pcmChannelStatus(
141 m_pcmHandle.get(), QAudioDevice::Output);
142
143 return status ? status->free : 0;
144}
145
147{
148 return qint64(1000000) * m_format.framesForBytes(m_bytesWritten) / m_format.sampleRate();
149}
150
152{
153 return m_error;
154}
155
157{
158 return m_state;
159}
160
162{
163 if (m_state == QAudio::StoppedState)
164 m_format = format;
165}
166
168{
169 return m_format;
170}
171
173{
174 m_volume = qBound(qreal(0.0), volume, qreal(1.0));
175}
176
178{
179 return m_volume;
180}
181
182void QQnxAudioSink::updateState()
183{
184 const std::optional<snd_pcm_channel_status_t> status = QnxAudioUtils::pcmChannelStatus(
185 m_pcmHandle.get(), QAudioDevice::Output);
186
187 if (!status)
188 return;
189
190 if (state() == QAudio::ActiveState && status->underrun > 0)
192 else if (state() == QAudio::IdleState && status->underrun == 0)
194}
195
196void QQnxAudioSink::pullData()
197{
198 if (m_state == QAudio::StoppedState
199 || m_state == QAudio::SuspendedState)
200 return;
201
202 const int bytesAvailable = bytesFree();
203
204 // skip if we have less than 4ms of data
205 if (m_format.durationForBytes(bytesAvailable) < 4000)
206 return;
207
208 const int frames = m_format.framesForBytes(bytesAvailable);
209 // The buffer is placed on the stack so no more than 64K or 1 frame
210 // whichever is larger.
211 const int maxFrames = qMax(m_format.framesForBytes(64 * 1024), 1);
212 const int bytesRequested = m_format.bytesForFrames(qMin(frames, maxFrames));
213
214 char buffer[bytesRequested];
215 const int bytesRead = m_source->read(buffer, bytesRequested);
216
217 // reading can take a while and stream may have been stopped
218 if (!m_pcmHandle)
219 return;
220
221 if (bytesRead > 0) {
222 const qint64 bytesWritten = write(buffer, bytesRead);
223
224 if (bytesWritten <= 0) {
225 close();
227 } else if (bytesWritten != bytesRead) {
228 m_source->seek(m_source->pos()-(bytesRead-bytesWritten));
229 }
230 } else {
231 // We're done
232 if (bytesRead == 0)
234 else
236 }
237}
238
239bool QQnxAudioSink::open()
240{
241 if (!m_format.isValid() || m_format.sampleRate() <= 0) {
242 if (!m_format.isValid())
243 qWarning("QQnxAudioSink: open error, invalid format.");
244 else
245 qWarning("QQnxAudioSink: open error, invalid sample rate (%d).", m_format.sampleRate());
246
247 return false;
248 }
249
250
251 m_pcmHandle = QnxAudioUtils::openPcmDevice(m_deviceInfo.id(), QAudioDevice::Output);
252
253 if (!m_pcmHandle)
254 return false;
255
256 int errorCode = 0;
257
258 if ((errorCode = snd_pcm_nonblock_mode(m_pcmHandle.get(), 0)) < 0) {
259 qWarning("QQnxAudioSink: open error, couldn't set non block mode (0x%x)", -errorCode);
260 close();
261 return false;
262 }
263
264 addPcmEventFilter();
265
266 // Necessary so that bytesFree() which uses the "free" member of the status struct works
267 snd_pcm_plugin_set_disable(m_pcmHandle.get(), PLUGIN_MMAP);
268
269 const std::optional<snd_pcm_channel_info_t> info = QnxAudioUtils::pcmChannelInfo(
270 m_pcmHandle.get(), QAudioDevice::Output);
271
272 if (!info) {
273 close();
274 return false;
275 }
276
277 const int fragmentSize = std::clamp(m_requestedBufferSize,
278 info->min_fragment_size, info->max_fragment_size);
279
280 snd_pcm_channel_params_t params = QnxAudioUtils::formatToChannelParams(m_format,
281 QAudioDevice::Output, fragmentSize);
282
283 if ((errorCode = snd_pcm_plugin_params(m_pcmHandle.get(), &params)) < 0) {
284 qWarning("QQnxAudioSink: open error, couldn't set channel params (0x%x)", -errorCode);
285 close();
286 return false;
287 }
288
289 if ((errorCode = snd_pcm_plugin_prepare(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK)) < 0) {
290 qWarning("QQnxAudioSink: open error, couldn't prepare channel (0x%x)", -errorCode);
291 close();
292 return false;
293 }
294
295 const std::optional<snd_pcm_channel_setup_t> setup = QnxAudioUtils::pcmChannelSetup(
296 m_pcmHandle.get(), QAudioDevice::Output);
297
298 if (!setup) {
299 close();
300 return false;
301 }
302
303 m_periodSize = qMin(2048, setup->buf.block.frag_size);
304 m_bytesWritten = 0;
305
306 createPcmNotifiers();
307
308 return true;
309}
310
311void QQnxAudioSink::close()
312{
313 if (!m_pushSource)
314 m_timer->stop();
315
316 destroyPcmNotifiers();
317
318 if (m_pcmHandle) {
319#if SND_PCM_VERSION < SND_PROTOCOL_VERSION('P',3,0,2)
320 snd_pcm_plugin_flush(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK);
321#else
322 snd_pcm_plugin_drop(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK);
323#endif
324 m_pcmHandle = nullptr;
325 }
326
327 if (m_pushSource) {
328 delete m_source;
329 m_source = 0;
330 }
331}
332
333void QQnxAudioSink::changeState(QAudio::State state, QAudio::Error error)
334{
335 if (m_state != state) {
336 m_state = state;
338 }
339
340 if (m_error != error) {
341 m_error = error;
343 }
344}
345
347{
348 const QAudio::State s = state();
349
351 return 0;
352
353 if (s == QAudio::IdleState)
355
356 qint64 totalWritten = 0;
357
358 int retry = 0;
359
360 constexpr int maxRetries = 10;
361
362 while (totalWritten < len) {
363 const int bytesWritten = write(data + totalWritten, len - totalWritten);
364
365 if (bytesWritten <= 0) {
366 ++retry;
367
368 if (retry >= maxRetries) {
369 close();
371
372 return totalWritten;
373 } else {
374 continue;
375 }
376 }
377
378 retry = 0;
379
380 totalWritten += bytesWritten;
381 }
382
383 return totalWritten;
384}
385
386qint64 QQnxAudioSink::write(const char *data, qint64 len)
387{
388 if (!m_pcmHandle)
389 return 0;
390
391 // Make sure we're writing (N * frame) worth of bytes
392 const int size = m_format.bytesForFrames(qBound(qint64(0), qint64(bytesFree()), len) / m_format.bytesPerFrame());
393
394 if (size == 0)
395 return 0;
396
397 int written = 0;
398
399 if (m_volume < 1.0f) {
400 char out[size];
402 written = snd_pcm_plugin_write(m_pcmHandle.get(), out, size);
403 } else {
404 written = snd_pcm_plugin_write(m_pcmHandle.get(), data, size);
405 }
406
407 if (written > 0) {
408 m_bytesWritten += written;
409 return written;
410 }
411
412 return 0;
413}
414
415void QQnxAudioSink::suspendInternal(QAudio::State suspendState)
416{
417 if (!m_pushSource)
418 m_timer->stop();
419
420 m_suspendedInState = m_state;
421 changeState(suspendState, QAudio::NoError);
422}
423
424void QQnxAudioSink::resumeInternal()
425{
426 changeState(m_suspendedInState, QAudio::NoError);
427
428 m_timer->start();
429}
430
431QAudio::State suspendState(const snd_pcm_event_t &event)
432{
433 Q_ASSERT(event.type == SND_PCM_EVENT_AUDIOMGMT_STATUS);
434 Q_ASSERT(event.data.audiomgmt_status.new_status == SND_PCM_STATUS_SUSPENDED);
436}
437
438void QQnxAudioSink::addPcmEventFilter()
439{
440 /* Enable PCM events */
441 snd_pcm_filter_t filter;
442 memset(&filter, 0, sizeof(filter));
443 filter.enable = (1<<SND_PCM_EVENT_AUDIOMGMT_STATUS) |
444 (1<<SND_PCM_EVENT_AUDIOMGMT_MUTE) |
445 (1<<SND_PCM_EVENT_OUTPUTCLASS) |
446 (1<<SND_PCM_EVENT_UNDERRUN);
447 snd_pcm_set_filter(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK, &filter);
448}
449
450void QQnxAudioSink::createPcmNotifiers()
451{
452 // QSocketNotifier::Read for poll based event dispatcher. Exception for
453 // select based event dispatcher.
454 m_pcmNotifier = new QSocketNotifier(snd_pcm_file_descriptor(m_pcmHandle.get(),
455 SND_PCM_CHANNEL_PLAYBACK),
457 connect(m_pcmNotifier, &QSocketNotifier::activated,
458 this, &QQnxAudioSink::pcmNotifierActivated);
459}
460
461void QQnxAudioSink::destroyPcmNotifiers()
462{
463 if (m_pcmNotifier) {
464 delete m_pcmNotifier;
465 m_pcmNotifier = 0;
466 }
467}
468
469void QQnxAudioSink::pcmNotifierActivated(int socket)
470{
472
473 snd_pcm_event_t pcm_event;
474 memset(&pcm_event, 0, sizeof(pcm_event));
475 while (snd_pcm_channel_read_event(m_pcmHandle.get(), SND_PCM_CHANNEL_PLAYBACK, &pcm_event) == 0) {
476 if (pcm_event.type == SND_PCM_EVENT_AUDIOMGMT_STATUS) {
477 if (pcm_event.data.audiomgmt_status.new_status == SND_PCM_STATUS_SUSPENDED)
478 suspendInternal(suspendState(pcm_event));
479 else if (pcm_event.data.audiomgmt_status.new_status == SND_PCM_STATUS_RUNNING)
480 resumeInternal();
481 else if (pcm_event.data.audiomgmt_status.new_status == SND_PCM_STATUS_PAUSED)
482 suspendInternal(QAudio::SuspendedState);
483 } else if (pcm_event.type == SND_PCM_EVENT_UNDERRUN) {
484 updateState();
485 }
486 }
487}
488
494
498
500{
501 Q_UNUSED(data);
502 Q_UNUSED(len);
503 return 0;
504}
505
507{
508 return m_output->pushData(data, len);
509}
510
512{
513 return true;
514}
515
The QAudioDevice class provides an information about audio devices and their functionality.
QByteArray id
\qmlproperty string QtMultimedia::audioDevice::id
The QAudioFormat class stores audio stream parameter information.
Q_MULTIMEDIA_EXPORT qint32 bytesForFrames(qint32 frameCount) const
Returns the number of bytes required for frameCount frames of this format.
constexpr int sampleRate() const noexcept
Returns the current sample rate in Hertz.
constexpr int bytesPerFrame() const
Returns the number of bytes required to represent one frame (a sample in each channel) in this format...
Q_MULTIMEDIA_EXPORT qint64 durationForBytes(qint32 byteCount) const
Returns the number of microseconds represented by bytes in this format.
constexpr bool isValid() const noexcept
Returns true if all of the parameters are valid.
Q_MULTIMEDIA_EXPORT qint32 framesForBytes(qint32 byteCount) const
Returns the number of frames represented by byteCount in this format.
void stateChanged(QAudio::State state)
void errorChanged(QAudio::Error error)
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual bool open(QIODeviceBase::OpenMode mode)
Opens the device and sets its OpenMode to mode.
virtual qint64 pos() const
For random-access devices, this function returns the position that data is written to or read from.
virtual bool seek(qint64 pos)
For random-access devices, this function sets the current position to pos, returning true on success,...
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
QAudio::State state() const override
qint64 pushData(const char *data, qint64 len)
void setBufferSize(qsizetype) override
qreal volume() const override
void reset() override
void setFormat(const QAudioFormat &format) override
qsizetype bufferSize() const override
qsizetype bytesFree() const override
QIODevice * start() override
QQnxAudioSink(const QAudioDevice &deviceInfo, QObject *parent)
QAudioFormat format() const override
void resume() override
void stop() override
void setVolume(qreal volume) override
QAudio::Error error() const override
void suspend() override
qint64 processedUSecs() const override
\inmodule QtCore
void activated(QSocketDescriptor socket, QSocketNotifier::Type activationEvent, QPrivateSignal)
\inmodule QtCore
Definition qtimer.h:20
void setSingleShot(bool singleShot)
Definition qtimer.cpp:552
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
void setInterval(int msec)
Definition qtimer.cpp:579
void stop()
Stops the timer.
Definition qtimer.cpp:267
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
QnxPushIODevice(QQnxAudioSink *output)
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...
#define this
Definition dialogs.cpp:9
else opt state
[0]
void qMultiplySamples(qreal factor, const QAudioFormat &format, const void *src, void *dest, int len)
State
Definition qaudio.h:29
@ StoppedState
Definition qaudio.h:29
@ SuspendedState
Definition qaudio.h:29
@ IdleState
Definition qaudio.h:29
@ ActiveState
Definition qaudio.h:29
Error
Definition qaudio.h:28
@ UnderrunError
Definition qaudio.h:28
@ FatalError
Definition qaudio.h:28
@ OpenError
Definition qaudio.h:28
@ NoError
Definition qaudio.h:28
@ IOError
Definition qaudio.h:28
Combined button and popup list for selecting options.
std::optional< snd_pcm_channel_info_t > pcmChannelInfo(snd_pcm_t *handle, QAudioDevice::Mode mode)
std::optional< snd_pcm_channel_setup_t > pcmChannelSetup(snd_pcm_t *handle, QAudioDevice::Mode mode)
HandleUniquePtr openPcmDevice(const QByteArray &id, QAudioDevice::Mode mode)
std::optional< snd_pcm_channel_status_t > pcmChannelStatus(snd_pcm_t *handle, QAudioDevice::Mode mode)
snd_pcm_channel_params_t formatToChannelParams(const QAudioFormat &format, QAudioDevice::Mode mode, int fragmentSize)
DBusConnection const char DBusError * error
#define qWarning
Definition qlogging.h:166
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLenum GLsizei GLuint GLint * bytesWritten
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
GLint GLsizei GLsizei GLenum format
GLsizei GLsizei GLchar * source
void ** params
struct _cl_event * event
GLdouble s
[6]
Definition qopenglext.h:235
GLenum GLsizei len
QAudio::State suspendState(const snd_pcm_event_t &event)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
@ NoError
Definition main.cpp:34
#define emit
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:165
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
QT_BEGIN_NAMESPACE typedef uchar * output
QTextStream out(stdout)
[7]
QTcpSocket * socket
[1]
QHostInfo info
[0]