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
qandroidaudiosink.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include "qopenslesengine_p.h"
6#include <QDebug>
7#include <qmath.h>
8#include <qmediadevices.h>
9
10#ifdef ANDROID
11#include <SLES/OpenSLES_Android.h>
12#include <SLES/OpenSLES_AndroidConfiguration.h>
13#endif // ANDROID
14
16
17static inline void openSlDebugInfo()
18{
20 qDebug() << "======= OpenSL ES Device info ======="
21 << "\nSupports low-latency playback: " << (QOpenSLESEngine::supportsLowLatency() ? "YES" : "NO")
22 << "\nPreferred sample rate: " << QOpenSLESEngine::getOutputValue(QOpenSLESEngine::SampleRate, -1)
24 << "\nPreferred Format: " << format
25 << "\nLow-latency buffer size: " << QOpenSLESEngine::getLowLatencyBufferSize(format)
26 << "\nDefault buffer size: " << QOpenSLESEngine::getDefaultBufferSize(format);
27}
28
30 : QPlatformAudioSink(parent),
31 m_deviceName(device)
32{
33#ifndef ANDROID
34 m_streamType = -1;
35#else
36 m_streamType = SL_ANDROID_STREAM_MEDIA;
37#endif // ANDROID
38}
39
41{
42 destroyPlayer();
43}
44
46{
47 return m_error;
48}
49
51{
52 return m_state;
53}
54
56{
58
59 if (m_state != QAudio::StoppedState)
60 stop();
61
62 if (!preparePlayer())
63 return;
64
65 m_endSound = false;
66 m_pullMode = true;
67 m_audioSource = device;
68 m_nextBuffer = 0;
69 m_processedBytes = 0;
70 m_availableBuffers = BufferCount;
71 setState(QAudio::ActiveState);
72 setError(QAudio::NoError);
73
74 // Attempt to fill buffers first.
75 for (int i = 0; i != BufferCount; ++i) {
76 const int index = i * m_bufferSize;
77 const qint64 readSize = m_audioSource->read(m_buffers + index, m_bufferSize);
78 if (readSize && SL_RESULT_SUCCESS != (*m_bufferQueueItf)->Enqueue(m_bufferQueueItf,
79 m_buffers + index,
80 readSize)) {
81 setError(QAudio::FatalError);
82 destroyPlayer();
83 return;
84 }
85 m_processedBytes += readSize;
86 }
87
88 if (m_processedBytes < 1)
89 onEOSEvent();
90
91 // Change the state to playing.
92 // We need to do this after filling the buffers or processedBytes might get corrupted.
93 startPlayer();
94 connect(m_audioSource, &QIODevice::readyRead, this, &QAndroidAudioSink::readyRead);
95}
96
97void QAndroidAudioSink::readyRead()
98{
99 if (m_pullMode && m_state == QAudio::IdleState) {
100 setState(QAudio::ActiveState);
101 setError(QAudio::NoError);
102 QMetaObject::invokeMethod(this, "bufferAvailable", Qt::QueuedConnection);
103 }
104}
105
107{
108 if (m_state != QAudio::StoppedState)
109 stop();
110
111 if (!preparePlayer())
112 return nullptr;
113
114 m_pullMode = false;
115 m_processedBytes = 0;
116 m_availableBuffers = BufferCount;
117 m_audioSource = new SLIODevicePrivate(this);
119
120 // Change the state to playing
121 startPlayer();
122
123 setState(QAudio::IdleState);
124 return m_audioSource;
125}
126
128{
129 if (m_state == QAudio::StoppedState)
130 return;
131
132 stopPlayer();
133 setError(QAudio::NoError);
134}
135
137{
138 if (m_state != QAudio::ActiveState && m_state != QAudio::IdleState)
139 return 0;
140
141 return m_availableBuffers.loadAcquire() ? m_bufferSize : 0;
142}
143
145{
146 if (m_state != QAudio::StoppedState)
147 return;
148
149 m_startRequiresInit = true;
150 m_bufferSize = value;
151}
152
154{
155 return m_bufferSize;
156}
157
159{
160 if (m_state == QAudio::IdleState || m_state == QAudio::SuspendedState)
161 return m_format.durationForBytes(m_processedBytes);
162
163 SLmillisecond processMSec = 0;
164 if (m_playItf)
165 (*m_playItf)->GetPosition(m_playItf, &processMSec);
166
167 return processMSec * 1000;
168}
169
171{
172 if (m_state != QAudio::SuspendedState)
173 return;
174
175 if (SL_RESULT_SUCCESS != (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_PLAYING)) {
176 setError(QAudio::FatalError);
177 destroyPlayer();
178 return;
179 }
180
181 setState(m_suspendedInState);
182 setError(QAudio::NoError);
183}
184
186{
187 m_startRequiresInit = true;
188 m_format = format;
189}
190
192{
193 return m_format;
194}
195
197{
198 if (m_state != QAudio::ActiveState && m_state != QAudio::IdleState)
199 return;
200
201 if (SL_RESULT_SUCCESS != (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_PAUSED)) {
202 setError(QAudio::FatalError);
203 destroyPlayer();
204 return;
205 }
206
207 m_suspendedInState = m_state;
208 setState(QAudio::SuspendedState);
209 setError(QAudio::NoError);
210}
211
213{
214 destroyPlayer();
215}
216
218{
219 m_volume = qBound(qreal(0.0), vol, qreal(1.0));
220 const SLmillibel newVolume = adjustVolume(m_volume);
221 if (m_volumeItf && SL_RESULT_SUCCESS != (*m_volumeItf)->SetVolumeLevel(m_volumeItf, newVolume))
222 qWarning() << "Unable to change volume";
223}
224
226{
227 return m_volume;
228}
229
230void QAndroidAudioSink::onEOSEvent()
231{
232 if (m_state != QAudio::ActiveState)
233 return;
234
235 SLBufferQueueState state;
236 if (SL_RESULT_SUCCESS != (*m_bufferQueueItf)->GetState(m_bufferQueueItf, &state))
237 return;
238
239 if (state.count > 0)
240 return;
241
242 setState(QAudio::IdleState);
243 setError(QAudio::UnderrunError);
244}
245
246void QAndroidAudioSink::onBytesProcessed(qint64 bytes)
247{
248 m_processedBytes += bytes;
249}
250
251void QAndroidAudioSink::bufferAvailable()
252{
253 if (m_state == QAudio::StoppedState)
254 return;
255
256 if (!m_pullMode) { // We're in push mode.
257 // Signal that there is a new open slot in the buffer and return
258 const int val = m_availableBuffers.fetchAndAddRelease(1) + 1;
259 if (val == BufferCount)
261
262 return;
263 }
264
265 // We're in pull mode.
266 const int index = m_nextBuffer * m_bufferSize;
267 qint64 readSize = 0;
268 if (m_audioSource->atEnd()) {
269 // The whole sound was passed to player buffer, but setting SL_PLAYSTATE_STOPPED state
270 // too quickly will result in cutting of end of the sound. Therefore, we make it a little
271 // longer with empty data to make sure they will be played correctly
272 if (m_endSound) {
273 m_endSound = false;
274 setState(QAudio::IdleState);
275 return;
276 }
277 m_endSound = true;
278 readSize = m_bufferSize;
279 memset(m_buffers + index, 0, readSize);
280 } else {
281 readSize = m_audioSource->read(m_buffers + index, m_bufferSize);
282 }
283
284
285 if (readSize < 1) {
287 return;
288 }
289
290
291 if (SL_RESULT_SUCCESS != (*m_bufferQueueItf)->Enqueue(m_bufferQueueItf,
292 m_buffers + index,
293 readSize)) {
294 setError(QAudio::FatalError);
295 destroyPlayer();
296 return;
297 }
298
299 m_nextBuffer = (m_nextBuffer + 1) % BufferCount;
300 if (!m_endSound) {
301 QMetaObject::invokeMethod(this, "onBytesProcessed", Qt::QueuedConnection, Q_ARG(qint64, readSize));
302 }
303}
304
305void QAndroidAudioSink::playCallback(SLPlayItf player, void *ctx, SLuint32 event)
306{
308 QAndroidAudioSink *audioOutput = reinterpret_cast<QAndroidAudioSink *>(ctx);
309 if (event & SL_PLAYEVENT_HEADATEND)
310 QMetaObject::invokeMethod(audioOutput, "onEOSEvent", Qt::QueuedConnection);
311}
312
313void QAndroidAudioSink::bufferQueueCallback(SLBufferQueueItf bufferQueue, void *ctx)
314{
315 Q_UNUSED(bufferQueue);
316 QAndroidAudioSink *audioOutput = reinterpret_cast<QAndroidAudioSink *>(ctx);
317 QMetaObject::invokeMethod(audioOutput, "bufferAvailable", Qt::QueuedConnection);
318}
319
320bool QAndroidAudioSink::preparePlayer()
321{
322 if (m_startRequiresInit)
323 destroyPlayer();
324 else
325 return true;
326
327 if (!QOpenSLESEngine::setAudioOutput(m_deviceName))
328 qWarning() << "Unable to set up Audio Output Device";
329
330 SLEngineItf engine = QOpenSLESEngine::instance()->slEngine();
331 if (!engine) {
332 qWarning() << "No engine";
333 setError(QAudio::FatalError);
334 return false;
335 }
336
337 SLDataLocator_BufferQueue bufferQueueLocator = { SL_DATALOCATOR_BUFFERQUEUE, BufferCount };
338 SLAndroidDataFormat_PCM_EX pcmFormat = QOpenSLESEngine::audioFormatToSLFormatPCM(m_format);
339
340 SLDataSource audioSrc = { &bufferQueueLocator, &pcmFormat };
341
342 // OutputMix
343 if (SL_RESULT_SUCCESS != (*engine)->CreateOutputMix(engine,
344 &m_outputMixObject,
345 0,
346 nullptr,
347 nullptr)) {
348 qWarning() << "Unable to create output mix";
349 setError(QAudio::FatalError);
350 return false;
351 }
352
353 if (SL_RESULT_SUCCESS != (*m_outputMixObject)->Realize(m_outputMixObject, SL_BOOLEAN_FALSE)) {
354 qWarning() << "Unable to initialize output mix";
355 setError(QAudio::FatalError);
356 return false;
357 }
358
359 SLDataLocator_OutputMix outputMixLocator = { SL_DATALOCATOR_OUTPUTMIX, m_outputMixObject };
360 SLDataSink audioSink = { &outputMixLocator, nullptr };
361
362#ifndef ANDROID
363 const int iids = 2;
364 const SLInterfaceID ids[iids] = { SL_IID_BUFFERQUEUE, SL_IID_VOLUME };
365 const SLboolean req[iids] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
366#else
367 const int iids = 3;
368 const SLInterfaceID ids[iids] = { SL_IID_BUFFERQUEUE,
369 SL_IID_VOLUME,
370 SL_IID_ANDROIDCONFIGURATION };
371 const SLboolean req[iids] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
372#endif // ANDROID
373
374 // AudioPlayer
375 if (SL_RESULT_SUCCESS != (*engine)->CreateAudioPlayer(engine,
376 &m_playerObject,
377 &audioSrc,
378 &audioSink,
379 iids,
380 ids,
381 req)) {
382 qWarning() << "Unable to create AudioPlayer";
383 setError(QAudio::OpenError);
384 return false;
385 }
386
387#ifdef ANDROID
388 // Set profile/category
389 SLAndroidConfigurationItf playerConfig;
390 if (SL_RESULT_SUCCESS == (*m_playerObject)->GetInterface(m_playerObject,
391 SL_IID_ANDROIDCONFIGURATION,
392 &playerConfig)) {
393 (*playerConfig)->SetConfiguration(playerConfig,
394 SL_ANDROID_KEY_STREAM_TYPE,
395 &m_streamType,
396 sizeof(SLint32));
397 }
398#endif // ANDROID
399
400 if (SL_RESULT_SUCCESS != (*m_playerObject)->Realize(m_playerObject, SL_BOOLEAN_FALSE)) {
401 qWarning() << "Unable to initialize AudioPlayer";
402 setError(QAudio::OpenError);
403 return false;
404 }
405
406 // Buffer interface
407 if (SL_RESULT_SUCCESS != (*m_playerObject)->GetInterface(m_playerObject,
408 SL_IID_BUFFERQUEUE,
409 &m_bufferQueueItf)) {
410 setError(QAudio::FatalError);
411 return false;
412 }
413
414 if (SL_RESULT_SUCCESS != (*m_bufferQueueItf)->RegisterCallback(m_bufferQueueItf,
415 bufferQueueCallback,
416 this)) {
417 setError(QAudio::FatalError);
418 return false;
419 }
420
421 // Play interface
422 if (SL_RESULT_SUCCESS != (*m_playerObject)->GetInterface(m_playerObject,
423 SL_IID_PLAY,
424 &m_playItf)) {
425 setError(QAudio::FatalError);
426 return false;
427 }
428
429 if (SL_RESULT_SUCCESS != (*m_playItf)->RegisterCallback(m_playItf, playCallback, this)) {
430 setError(QAudio::FatalError);
431 return false;
432 }
433
434 if (SL_RESULT_SUCCESS != (*m_playItf)->SetCallbackEventsMask(m_playItf, m_eventMask)) {
435 setError(QAudio::FatalError);
436 return false;
437 }
438
439 // Volume interface
440 if (SL_RESULT_SUCCESS != (*m_playerObject)->GetInterface(m_playerObject,
441 SL_IID_VOLUME,
442 &m_volumeItf)) {
443 setError(QAudio::FatalError);
444 return false;
445 }
446
447 setVolume(m_volume);
448
449 const int lowLatencyBufferSize = QOpenSLESEngine::getLowLatencyBufferSize(m_format);
451
452 if (defaultBufferSize <= 0) {
453 qWarning() << "Unable to get minimum buffer size, returned" << defaultBufferSize;
454 setError(QAudio::FatalError);
455 return false;
456 }
457
458 // Buffer size
459 if (m_bufferSize <= 0) {
460 m_bufferSize = defaultBufferSize;
462 if (m_bufferSize < lowLatencyBufferSize)
463 m_bufferSize = lowLatencyBufferSize;
464 } else if (m_bufferSize < defaultBufferSize) {
465 m_bufferSize = defaultBufferSize;
466 }
467
468 if (!m_buffers)
469 m_buffers = new char[BufferCount * m_bufferSize];
470
471 setError(QAudio::NoError);
472 m_startRequiresInit = false;
473
474 return true;
475}
476
477void QAndroidAudioSink::destroyPlayer()
478{
479 if (m_state != QAudio::StoppedState)
480 stopPlayer();
481
482 if (m_playerObject) {
483 (*m_playerObject)->Destroy(m_playerObject);
484 m_playerObject = nullptr;
485 }
486
487 if (m_outputMixObject) {
488 (*m_outputMixObject)->Destroy(m_outputMixObject);
489 m_outputMixObject = nullptr;
490 }
491
492 if (!m_pullMode && m_audioSource) {
493 m_audioSource->close();
494 delete m_audioSource;
495 m_audioSource = nullptr;
496 }
497
498 delete [] m_buffers;
499 m_buffers = nullptr;
500 m_processedBytes = 0;
501 m_nextBuffer = 0;
502 m_availableBuffers.storeRelease(BufferCount);
503 m_playItf = nullptr;
504 m_volumeItf = nullptr;
505 m_bufferQueueItf = nullptr;
506 m_startRequiresInit = true;
507}
508
509void QAndroidAudioSink::stopPlayer()
510{
511 setState(QAudio::StoppedState);
512
513 if (m_audioSource) {
514 if (m_pullMode) {
515 disconnect(m_audioSource, &QIODevice::readyRead, this, &QAndroidAudioSink::readyRead);
516 } else {
517 m_audioSource->close();
518 delete m_audioSource;
519 m_audioSource = nullptr;
520 }
521 }
522
523 // We need to change the state manually...
524 if (m_playItf)
525 (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_STOPPED);
526
527 if (m_bufferQueueItf && SL_RESULT_SUCCESS != (*m_bufferQueueItf)->Clear(m_bufferQueueItf))
528 qWarning() << "Unable to clear buffer";
529}
530
531void QAndroidAudioSink::startPlayer()
532{
535
536 if (SL_RESULT_SUCCESS != (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_PLAYING)) {
537 setError(QAudio::FatalError);
538 destroyPlayer();
539 }
540}
541
542qint64 QAndroidAudioSink::writeData(const char *data, qint64 len)
543{
544 if (!len)
545 return 0;
546
547 if (len > m_bufferSize)
548 len = m_bufferSize;
549
550 // Acquire one slot in the buffer
551 const int before = m_availableBuffers.fetchAndAddAcquire(-1);
552
553 // If there where no vacant slots, then we just overdrew the buffer account...
554 if (before < 1) {
555 m_availableBuffers.fetchAndAddRelease(1);
556 return 0;
557 }
558
559 const int index = m_nextBuffer * m_bufferSize;
560 ::memcpy(m_buffers + index, data, len);
561 const SLuint32 res = (*m_bufferQueueItf)->Enqueue(m_bufferQueueItf,
562 m_buffers + index,
563 len);
564
565 // If we where unable to enqueue a new buffer, give back the acquired slot.
566 if (res == SL_RESULT_BUFFER_INSUFFICIENT) {
567 m_availableBuffers.fetchAndAddRelease(1);
568 return 0;
569 }
570
571 if (res != SL_RESULT_SUCCESS) {
572 setError(QAudio::FatalError);
573 destroyPlayer();
574 return -1;
575 }
576
577 m_processedBytes += len;
578 setState(QAudio::ActiveState);
579 setError(QAudio::NoError);
580 m_nextBuffer = (m_nextBuffer + 1) % BufferCount;
581
582 return len;
583}
584
585inline void QAndroidAudioSink::setState(QAudio::State state)
586{
587 if (m_state == state)
588 return;
589
590 m_state = state;
591 Q_EMIT stateChanged(m_state);
592}
593
594inline void QAndroidAudioSink::setError(QAudio::Error error)
595{
596 if (m_error == error)
597 return;
598
599 m_error = error;
600 Q_EMIT errorChanged(m_error);
601}
602
603inline SLmillibel QAndroidAudioSink::adjustVolume(qreal vol)
604{
605 if (qFuzzyIsNull(vol))
606 return SL_MILLIBEL_MIN;
607
608 if (qFuzzyCompare(vol, qreal(1.0)))
609 return 0;
610
612}
613
QMediaPlayer player
Definition audio.cpp:213
IOBluetoothDevice * device
qsizetype bytesFree() const override
void setVolume(qreal volume) override
qsizetype bufferSize() const override
friend class SLIODevicePrivate
QIODevice * start() override
QAudioFormat format() const override
QAudio::Error error() const override
QAudio::State state() const override
QAndroidAudioSink(const QByteArray &device, QObject *parent)
qint64 processedUSecs() const override
qreal volume() const override
void suspend() override
void setFormat(const QAudioFormat &format) override
void setBufferSize(qsizetype value) override
QAudioFormat preferredFormat() const
Returns the default audio format settings for this device.
The QAudioFormat class stores audio stream parameter information.
Q_MULTIMEDIA_EXPORT qint64 durationForBytes(qint32 byteCount) const
Returns the number of microseconds represented by bytes in this format.
void stateChanged(QAudio::State state)
void errorChanged(QAudio::Error error)
T fetchAndAddAcquire(T valueToAdd) noexcept
T loadAcquire() const noexcept
T fetchAndAddRelease(T valueToAdd) noexcept
void storeRelease(T newValue) noexcept
\inmodule QtCore
Definition qbytearray.h:57
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual bool open(QIODeviceBase::OpenMode mode)
Opens the device and sets its OpenMode to mode.
void readyRead()
This signal is emitted once every time new data is available for reading from the device's current re...
virtual void close()
First emits aboutToClose(), then closes the device and sets its OpenMode to NotOpen.
virtual bool atEnd() const
Returns true if the current read and write position is at the end of the device (i....
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
QAudioDevice defaultAudioOutput
\qmlproperty audioDevice QtMultimedia::MediaDevices::defaultAudioOutput Returns the default audio out...
\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
static int getLowLatencyBufferSize(const QAudioFormat &format)
static QOpenSLESEngine * instance()
static int getOutputValue(OutputValue type, int defaultValue=0)
static int getDefaultBufferSize(const QAudioFormat &format)
static SLAndroidDataFormat_PCM_EX audioFormatToSLFormatPCM(const QAudioFormat &format)
static bool supportsLowLatency()
static bool printDebugInfo()
static bool setAudioOutput(const QByteArray &deviceId)
EGLContext ctx
else opt state
[0]
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
Q_MULTIMEDIA_EXPORT float convertVolume(float volume, VolumeScale from, VolumeScale to)
Converts an audio volume from a volume scale to another, and returns the result.
Definition qtaudio.cpp:93
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
@ DecibelVolumeScale
Definition qaudio.h:35
@ LinearVolumeScale
Definition qaudio.h:32
Combined button and popup list for selecting options.
@ QueuedConnection
static QT_BEGIN_NAMESPACE void openSlDebugInfo()
DBusConnection const char DBusError * error
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:349
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
#define Q_ARG(Type, data)
Definition qobjectdefs.h:63
GLuint index
[2]
GLenum GLenum GLsizei const GLuint * ids
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLint GLsizei GLsizei GLenum format
struct _cl_event * event
GLuint res
GLuint GLfloat * val
GLenum GLsizei len
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static const int defaultBufferSize
#define Q_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
myObject disconnect()
[26]
QJSEngine engine
[0]
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...