Qt 6.x
The Qt SDK
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
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_pullMode = true;
66 m_audioSource = device;
67 m_nextBuffer = 0;
68 m_processedBytes = 0;
69 m_availableBuffers = BufferCount;
70 setState(QAudio::ActiveState);
71 setError(QAudio::NoError);
72
73 // Attempt to fill buffers first.
74 for (int i = 0; i != BufferCount; ++i) {
75 const int index = i * m_bufferSize;
76 const qint64 readSize = m_audioSource->read(m_buffers + index, m_bufferSize);
77 if (readSize && SL_RESULT_SUCCESS != (*m_bufferQueueItf)->Enqueue(m_bufferQueueItf,
78 m_buffers + index,
79 readSize)) {
80 setError(QAudio::FatalError);
81 destroyPlayer();
82 return;
83 }
84 m_processedBytes += readSize;
85 }
86
87 if (m_processedBytes < 1)
88 onEOSEvent();
89
90 // Change the state to playing.
91 // We need to do this after filling the buffers or processedBytes might get corrupted.
92 startPlayer();
93 connect(m_audioSource, &QIODevice::readyRead, this, &QAndroidAudioSink::readyRead);
94}
95
96void QAndroidAudioSink::readyRead()
97{
98 if (m_pullMode && m_state == QAudio::IdleState) {
99 setState(QAudio::ActiveState);
100 setError(QAudio::NoError);
101 QMetaObject::invokeMethod(this, "bufferAvailable", Qt::QueuedConnection);
102 }
103}
104
106{
107 if (m_state != QAudio::StoppedState)
108 stop();
109
110 if (!preparePlayer())
111 return nullptr;
112
113 m_pullMode = false;
114 m_processedBytes = 0;
115 m_availableBuffers = BufferCount;
116 m_audioSource = new SLIODevicePrivate(this);
118
119 // Change the state to playing
120 startPlayer();
121
122 setState(QAudio::IdleState);
123 return m_audioSource;
124}
125
127{
128 if (m_state == QAudio::StoppedState)
129 return;
130
131 stopPlayer();
132 setError(QAudio::NoError);
133}
134
136{
137 if (m_state != QAudio::ActiveState && m_state != QAudio::IdleState)
138 return 0;
139
140 return m_availableBuffers.loadAcquire() ? m_bufferSize : 0;
141}
142
144{
145 if (m_state != QAudio::StoppedState)
146 return;
147
148 m_startRequiresInit = true;
149 m_bufferSize = value;
150}
151
153{
154 return m_bufferSize;
155}
156
158{
159 if (m_state == QAudio::IdleState || m_state == QAudio::SuspendedState)
160 return m_format.durationForBytes(m_processedBytes);
161
162 SLmillisecond processMSec = 0;
163 if (m_playItf)
164 (*m_playItf)->GetPosition(m_playItf, &processMSec);
165
166 return processMSec * 1000;
167}
168
170{
171 if (m_state != QAudio::SuspendedState)
172 return;
173
174 if (SL_RESULT_SUCCESS != (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_PLAYING)) {
175 setError(QAudio::FatalError);
176 destroyPlayer();
177 return;
178 }
179
180 setState(m_suspendedInState);
181 setError(QAudio::NoError);
182}
183
185{
186 m_startRequiresInit = true;
187 m_format = format;
188}
189
191{
192 return m_format;
193}
194
196{
197 if (m_state != QAudio::ActiveState && m_state != QAudio::IdleState)
198 return;
199
200 if (SL_RESULT_SUCCESS != (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_PAUSED)) {
201 setError(QAudio::FatalError);
202 destroyPlayer();
203 return;
204 }
205
206 m_suspendedInState = m_state;
207 setState(QAudio::SuspendedState);
208 setError(QAudio::NoError);
209}
210
212{
213 destroyPlayer();
214}
215
217{
218 m_volume = qBound(qreal(0.0), vol, qreal(1.0));
219 const SLmillibel newVolume = adjustVolume(m_volume);
220 if (m_volumeItf && SL_RESULT_SUCCESS != (*m_volumeItf)->SetVolumeLevel(m_volumeItf, newVolume))
221 qWarning() << "Unable to change volume";
222}
223
225{
226 return m_volume;
227}
228
229void QAndroidAudioSink::onEOSEvent()
230{
231 if (m_state != QAudio::ActiveState)
232 return;
233
234 SLBufferQueueState state;
235 if (SL_RESULT_SUCCESS != (*m_bufferQueueItf)->GetState(m_bufferQueueItf, &state))
236 return;
237
238 if (state.count > 0)
239 return;
240
241 setState(QAudio::IdleState);
242 setError(QAudio::UnderrunError);
243}
244
245void QAndroidAudioSink::onBytesProcessed(qint64 bytes)
246{
247 m_processedBytes += bytes;
248}
249
250void QAndroidAudioSink::bufferAvailable()
251{
252 if (m_state == QAudio::StoppedState)
253 return;
254
255 if (!m_pullMode) { // We're in push mode.
256 // Signal that there is a new open slot in the buffer and return
257 const int val = m_availableBuffers.fetchAndAddRelease(1) + 1;
258 if (val == BufferCount)
260
261 return;
262 }
263
264 // We're in pull mode.
265 const int index = m_nextBuffer * m_bufferSize;
266 const qint64 readSize = m_audioSource->read(m_buffers + index, m_bufferSize);
267
268 if (readSize < 1) {
270 return;
271 }
272
273
274 if (SL_RESULT_SUCCESS != (*m_bufferQueueItf)->Enqueue(m_bufferQueueItf,
275 m_buffers + index,
276 readSize)) {
277 setError(QAudio::FatalError);
278 destroyPlayer();
279 return;
280 }
281
282 m_nextBuffer = (m_nextBuffer + 1) % BufferCount;
283 QMetaObject::invokeMethod(this, "onBytesProcessed", Qt::QueuedConnection, Q_ARG(qint64, readSize));
284
285 if (m_audioSource->atEnd())
286 setState(QAudio::IdleState);
287}
288
289void QAndroidAudioSink::playCallback(SLPlayItf player, void *ctx, SLuint32 event)
290{
292 QAndroidAudioSink *audioOutput = reinterpret_cast<QAndroidAudioSink *>(ctx);
293 if (event & SL_PLAYEVENT_HEADATEND)
294 QMetaObject::invokeMethod(audioOutput, "onEOSEvent", Qt::QueuedConnection);
295}
296
297void QAndroidAudioSink::bufferQueueCallback(SLBufferQueueItf bufferQueue, void *ctx)
298{
299 Q_UNUSED(bufferQueue);
300 QAndroidAudioSink *audioOutput = reinterpret_cast<QAndroidAudioSink *>(ctx);
301 audioOutput->bufferAvailable();
302}
303
304bool QAndroidAudioSink::preparePlayer()
305{
306 if (m_startRequiresInit)
307 destroyPlayer();
308 else
309 return true;
310
311 if (!QOpenSLESEngine::setAudioOutput(m_deviceName))
312 qWarning() << "Unable to set up Audio Output Device";
313
314 SLEngineItf engine = QOpenSLESEngine::instance()->slEngine();
315 if (!engine) {
316 qWarning() << "No engine";
317 setError(QAudio::FatalError);
318 return false;
319 }
320
321 SLDataLocator_BufferQueue bufferQueueLocator = { SL_DATALOCATOR_BUFFERQUEUE, BufferCount };
322 SLAndroidDataFormat_PCM_EX pcmFormat = QOpenSLESEngine::audioFormatToSLFormatPCM(m_format);
323
324 SLDataSource audioSrc = { &bufferQueueLocator, &pcmFormat };
325
326 // OutputMix
327 if (SL_RESULT_SUCCESS != (*engine)->CreateOutputMix(engine,
328 &m_outputMixObject,
329 0,
330 nullptr,
331 nullptr)) {
332 qWarning() << "Unable to create output mix";
333 setError(QAudio::FatalError);
334 return false;
335 }
336
337 if (SL_RESULT_SUCCESS != (*m_outputMixObject)->Realize(m_outputMixObject, SL_BOOLEAN_FALSE)) {
338 qWarning() << "Unable to initialize output mix";
339 setError(QAudio::FatalError);
340 return false;
341 }
342
343 SLDataLocator_OutputMix outputMixLocator = { SL_DATALOCATOR_OUTPUTMIX, m_outputMixObject };
344 SLDataSink audioSink = { &outputMixLocator, nullptr };
345
346#ifndef ANDROID
347 const int iids = 2;
348 const SLInterfaceID ids[iids] = { SL_IID_BUFFERQUEUE, SL_IID_VOLUME };
349 const SLboolean req[iids] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
350#else
351 const int iids = 3;
352 const SLInterfaceID ids[iids] = { SL_IID_BUFFERQUEUE,
353 SL_IID_VOLUME,
354 SL_IID_ANDROIDCONFIGURATION };
355 const SLboolean req[iids] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
356#endif // ANDROID
357
358 // AudioPlayer
359 if (SL_RESULT_SUCCESS != (*engine)->CreateAudioPlayer(engine,
360 &m_playerObject,
361 &audioSrc,
362 &audioSink,
363 iids,
364 ids,
365 req)) {
366 qWarning() << "Unable to create AudioPlayer";
367 setError(QAudio::OpenError);
368 return false;
369 }
370
371#ifdef ANDROID
372 // Set profile/category
373 SLAndroidConfigurationItf playerConfig;
374 if (SL_RESULT_SUCCESS == (*m_playerObject)->GetInterface(m_playerObject,
375 SL_IID_ANDROIDCONFIGURATION,
376 &playerConfig)) {
377 (*playerConfig)->SetConfiguration(playerConfig,
378 SL_ANDROID_KEY_STREAM_TYPE,
379 &m_streamType,
380 sizeof(SLint32));
381 }
382#endif // ANDROID
383
384 if (SL_RESULT_SUCCESS != (*m_playerObject)->Realize(m_playerObject, SL_BOOLEAN_FALSE)) {
385 qWarning() << "Unable to initialize AudioPlayer";
386 setError(QAudio::OpenError);
387 return false;
388 }
389
390 // Buffer interface
391 if (SL_RESULT_SUCCESS != (*m_playerObject)->GetInterface(m_playerObject,
392 SL_IID_BUFFERQUEUE,
393 &m_bufferQueueItf)) {
394 setError(QAudio::FatalError);
395 return false;
396 }
397
398 if (SL_RESULT_SUCCESS != (*m_bufferQueueItf)->RegisterCallback(m_bufferQueueItf,
399 bufferQueueCallback,
400 this)) {
401 setError(QAudio::FatalError);
402 return false;
403 }
404
405 // Play interface
406 if (SL_RESULT_SUCCESS != (*m_playerObject)->GetInterface(m_playerObject,
407 SL_IID_PLAY,
408 &m_playItf)) {
409 setError(QAudio::FatalError);
410 return false;
411 }
412
413 if (SL_RESULT_SUCCESS != (*m_playItf)->RegisterCallback(m_playItf, playCallback, this)) {
414 setError(QAudio::FatalError);
415 return false;
416 }
417
418 if (SL_RESULT_SUCCESS != (*m_playItf)->SetCallbackEventsMask(m_playItf, m_eventMask)) {
419 setError(QAudio::FatalError);
420 return false;
421 }
422
423 // Volume interface
424 if (SL_RESULT_SUCCESS != (*m_playerObject)->GetInterface(m_playerObject,
425 SL_IID_VOLUME,
426 &m_volumeItf)) {
427 setError(QAudio::FatalError);
428 return false;
429 }
430
431 setVolume(m_volume);
432
433 const int lowLatencyBufferSize = QOpenSLESEngine::getLowLatencyBufferSize(m_format);
435
436 if (defaultBufferSize <= 0) {
437 qWarning() << "Unable to get minimum buffer size, returned" << defaultBufferSize;
438 setError(QAudio::FatalError);
439 return false;
440 }
441
442 // Buffer size
443 if (m_bufferSize <= 0) {
444 m_bufferSize = defaultBufferSize;
446 if (m_bufferSize < lowLatencyBufferSize)
447 m_bufferSize = lowLatencyBufferSize;
448 } else if (m_bufferSize < defaultBufferSize) {
449 m_bufferSize = defaultBufferSize;
450 }
451
452 if (!m_buffers)
453 m_buffers = new char[BufferCount * m_bufferSize];
454
455 setError(QAudio::NoError);
456 m_startRequiresInit = false;
457
458 return true;
459}
460
461void QAndroidAudioSink::destroyPlayer()
462{
463 if (m_state != QAudio::StoppedState)
464 stopPlayer();
465
466 if (m_playerObject) {
467 (*m_playerObject)->Destroy(m_playerObject);
468 m_playerObject = nullptr;
469 }
470
471 if (m_outputMixObject) {
472 (*m_outputMixObject)->Destroy(m_outputMixObject);
473 m_outputMixObject = nullptr;
474 }
475
476 if (!m_pullMode && m_audioSource) {
477 m_audioSource->close();
478 delete m_audioSource;
479 m_audioSource = nullptr;
480 }
481
482 delete [] m_buffers;
483 m_buffers = nullptr;
484 m_processedBytes = 0;
485 m_nextBuffer = 0;
486 m_availableBuffers.storeRelease(BufferCount);
487 m_playItf = nullptr;
488 m_volumeItf = nullptr;
489 m_bufferQueueItf = nullptr;
490 m_startRequiresInit = true;
491}
492
493void QAndroidAudioSink::stopPlayer()
494{
495 setState(QAudio::StoppedState);
496
497 if (m_audioSource) {
498 if (m_pullMode) {
499 disconnect(m_audioSource, &QIODevice::readyRead, this, &QAndroidAudioSink::readyRead);
500 } else {
501 m_audioSource->close();
502 delete m_audioSource;
503 m_audioSource = nullptr;
504 }
505 }
506
507 // We need to change the state manually...
508 if (m_playItf)
509 (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_STOPPED);
510
511 if (m_bufferQueueItf && SL_RESULT_SUCCESS != (*m_bufferQueueItf)->Clear(m_bufferQueueItf))
512 qWarning() << "Unable to clear buffer";
513}
514
515void QAndroidAudioSink::startPlayer()
516{
519
520 if (SL_RESULT_SUCCESS != (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_PLAYING)) {
521 setError(QAudio::FatalError);
522 destroyPlayer();
523 }
524}
525
526qint64 QAndroidAudioSink::writeData(const char *data, qint64 len)
527{
528 if (!len)
529 return 0;
530
531 if (len > m_bufferSize)
532 len = m_bufferSize;
533
534 // Acquire one slot in the buffer
535 const int before = m_availableBuffers.fetchAndAddAcquire(-1);
536
537 // If there where no vacant slots, then we just overdrew the buffer account...
538 if (before < 1) {
539 m_availableBuffers.fetchAndAddRelease(1);
540 return 0;
541 }
542
543 const int index = m_nextBuffer * m_bufferSize;
544 ::memcpy(m_buffers + index, data, len);
545 const SLuint32 res = (*m_bufferQueueItf)->Enqueue(m_bufferQueueItf,
546 m_buffers + index,
547 len);
548
549 // If we where unable to enqueue a new buffer, give back the acquired slot.
550 if (res == SL_RESULT_BUFFER_INSUFFICIENT) {
551 m_availableBuffers.fetchAndAddRelease(1);
552 return 0;
553 }
554
555 if (res != SL_RESULT_SUCCESS) {
556 setError(QAudio::FatalError);
557 destroyPlayer();
558 return -1;
559 }
560
561 m_processedBytes += len;
562 setState(QAudio::ActiveState);
563 setError(QAudio::NoError);
564 m_nextBuffer = (m_nextBuffer + 1) % BufferCount;
565
566 return len;
567}
568
569inline void QAndroidAudioSink::setState(QAudio::State state)
570{
571 if (m_state == state)
572 return;
573
574 m_state = state;
575 Q_EMIT stateChanged(m_state);
576}
577
578inline void QAndroidAudioSink::setError(QAudio::Error error)
579{
580 if (m_error == error)
581 return;
582
583 m_error = error;
584 Q_EMIT errorChanged(m_error);
585}
586
587inline SLmillibel QAndroidAudioSink::adjustVolume(qreal vol)
588{
589 if (qFuzzyIsNull(vol))
590 return SL_MILLIBEL_MIN;
591
592 if (qFuzzyCompare(vol, qreal(1.0)))
593 return 0;
594
596}
597
QMediaPlayer player
Definition audio.cpp:205
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.
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:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
static int getLowLatencyBufferSize(const QAudioFormat &format)
static QOpenSLESEngine * instance()
static int getOutputValue(OutputValue type, int defaultValue=0)
SLEngineItf slEngine() const
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)
void errorChanged(QAudio::Error error)
void stateChanged(QAudio::State state)
EGLContext ctx
else opt state
[0]
State
\value ActiveState Audio data is being processed, this state is set after start() is called and while...
Definition qaudio.h:25
@ StoppedState
Definition qaudio.h:25
@ SuspendedState
Definition qaudio.h:25
@ IdleState
Definition qaudio.h:25
@ ActiveState
Definition qaudio.h:25
Error
\value NoError No errors have occurred \value OpenError An error occurred opening the audio device \v...
Definition qaudio.h:24
@ UnderrunError
Definition qaudio.h:24
@ FatalError
Definition qaudio.h:24
@ OpenError
Definition qaudio.h:24
@ NoError
Definition qaudio.h:24
float convertVolume(float volume, VolumeScale from, VolumeScale to)
Converts an audio volume from a volume scale to another, and returns the result.
Definition qaudio.cpp:91
@ DecibelVolumeScale
Definition qaudio.h:31
@ LinearVolumeScale
Definition qaudio.h:28
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:287
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:303
#define qDebug
[1]
Definition qlogging.h:160
#define qWarning
Definition qlogging.h:162
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:62
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:70
long long qint64
Definition qtypes.h:55
double qreal
Definition qtypes.h:92
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...
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent