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
qandroidaudiosource.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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
6#include "qopenslesengine_p.h"
7#include <private/qaudiohelpers_p.h>
8#include <qbuffer.h>
9#include <qdebug.h>
10#include <qloggingcategory.h>
11
12#ifdef ANDROID
13#include <SLES/OpenSLES_AndroidConfiguration.h>
14#include <QtCore/qcoreapplication.h>
15#include <QtCore/qpermissions.h>
16#endif
17
18static Q_LOGGING_CATEGORY(qLcAndroidAudioSource, "qt.multimedia.android.audiosource")
19
21
22#define NUM_BUFFERS 2
23#define DEFAULT_PERIOD_TIME_MS 50
24#define MINIMUM_PERIOD_TIME_MS 5
25
26#ifdef ANDROID
27static bool hasRecordingPermission()
28{
29 QMicrophonePermission permission;
30
31 const bool permitted = qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
32 if (!permitted)
33 qCWarning(qLcAndroidAudioSource, "Missing microphone permission!");
34
35 return permitted;
36}
37
38static void bufferQueueCallback(SLAndroidSimpleBufferQueueItf, void *context)
39#else
40static void bufferQueueCallback(SLBufferQueueItf, void *context)
41#endif
42{
43 // Process buffer in main thread
44 QMetaObject::invokeMethod(reinterpret_cast<QAndroidAudioSource*>(context), "processBuffer");
45}
46
48 : QPlatformAudioSource(parent)
49 , m_device(device)
50 , m_engine(QOpenSLESEngine::instance())
51 , m_recorderObject(0)
52 , m_recorder(0)
53 , m_bufferQueue(0)
54 , m_pullMode(true)
55 , m_processedBytes(0)
56 , m_audioSource(0)
57 , m_bufferIODevice(0)
58 , m_errorState(QAudio::NoError)
59 , m_deviceState(QAudio::StoppedState)
60 , m_lastNotifyTime(0)
61 , m_volume(1.0)
62 , m_bufferSize(0)
63 , m_buffers(new QByteArray[NUM_BUFFERS])
64 , m_currentBuffer(0)
65{
66#ifdef ANDROID
67 if (qstrcmp(device, QT_ANDROID_PRESET_CAMCORDER) == 0)
68 m_recorderPreset = SL_ANDROID_RECORDING_PRESET_CAMCORDER;
69 else if (qstrcmp(device, QT_ANDROID_PRESET_VOICE_RECOGNITION) == 0)
70 m_recorderPreset = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;
71 else if (qstrcmp(device, QT_ANDROID_PRESET_VOICE_COMMUNICATION) == 0)
72 m_recorderPreset = SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION;
73 else
74 m_recorderPreset = SL_ANDROID_RECORDING_PRESET_GENERIC;
75#endif
76}
77
79{
80 if (m_recorderObject)
81 (*m_recorderObject)->Destroy(m_recorderObject);
82 delete[] m_buffers;
83}
84
86{
87 return m_errorState;
88}
89
91{
92 return m_deviceState;
93}
94
96{
97 if (m_deviceState == QAudio::StoppedState)
98 m_format = format;
99}
100
102{
103 return m_format;
104}
105
107{
108 if (m_deviceState != QAudio::StoppedState)
109 stopRecording();
110
111 if (!m_pullMode && m_bufferIODevice) {
112 m_bufferIODevice->close();
113 delete m_bufferIODevice;
114 m_bufferIODevice = 0;
115 }
116
117 m_pullMode = true;
118 m_audioSource = device;
119
120 if (startRecording()) {
121 m_deviceState = QAudio::ActiveState;
122 } else {
123 m_deviceState = QAudio::StoppedState;
124 Q_EMIT errorChanged(m_errorState);
125 }
126
127 Q_EMIT stateChanged(m_deviceState);
128}
129
131{
132 if (m_deviceState != QAudio::StoppedState)
133 stopRecording();
134
135 m_audioSource = 0;
136
137 if (!m_pullMode && m_bufferIODevice) {
138 m_bufferIODevice->close();
139 delete m_bufferIODevice;
140 }
141
142 m_pullMode = false;
143 m_pushBuffer.clear();
144 m_bufferIODevice = new QBuffer(&m_pushBuffer, this);
145 m_bufferIODevice->open(QIODevice::ReadOnly);
146
147 if (startRecording()) {
148 m_deviceState = QAudio::IdleState;
149 } else {
150 m_deviceState = QAudio::StoppedState;
151 Q_EMIT errorChanged(m_errorState);
152 m_bufferIODevice->close();
153 delete m_bufferIODevice;
154 m_bufferIODevice = 0;
155 }
156
157 Q_EMIT stateChanged(m_deviceState);
158 return m_bufferIODevice;
159}
160
161bool QAndroidAudioSource::startRecording()
162{
163 if (!hasRecordingPermission())
164 return false;
165
166 m_processedBytes = 0;
167 m_lastNotifyTime = 0;
168
169 SLresult result;
170
171 // configure audio source
172 SLDataLocator_IODevice loc_dev = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,
173 SL_DEFAULTDEVICEID_AUDIOINPUT, NULL };
174 SLDataSource audioSrc = { &loc_dev, NULL };
175
176 // configure audio sink
177#ifdef ANDROID
178 SLDataLocator_AndroidSimpleBufferQueue loc_bq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
179 NUM_BUFFERS };
180#else
181 SLDataLocator_BufferQueue loc_bq = { SL_DATALOCATOR_BUFFERQUEUE, NUM_BUFFERS };
182#endif
183
184 SLAndroidDataFormat_PCM_EX format_pcm = QOpenSLESEngine::audioFormatToSLFormatPCM(m_format);
185 SLDataSink audioSnk = { &loc_bq, &format_pcm };
186
187 // create audio recorder
188 // (requires the RECORD_AUDIO permission)
189#ifdef ANDROID
190 const SLInterfaceID id[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION };
191 const SLboolean req[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
192#else
193 const SLInterfaceID id[1] = { SL_IID_BUFFERQUEUE };
194 const SLboolean req[1] = { SL_BOOLEAN_TRUE };
195#endif
196
197 result = (*m_engine->slEngine())->CreateAudioRecorder(m_engine->slEngine(), &m_recorderObject,
198 &audioSrc, &audioSnk,
199 sizeof(req) / sizeof(SLboolean), id, req);
200 if (result != SL_RESULT_SUCCESS) {
201 m_errorState = QAudio::OpenError;
202 return false;
203 }
204
205#ifdef ANDROID
206 // configure recorder source
207 SLAndroidConfigurationItf configItf;
208 result = (*m_recorderObject)->GetInterface(m_recorderObject, SL_IID_ANDROIDCONFIGURATION,
209 &configItf);
210 if (result != SL_RESULT_SUCCESS) {
211 m_errorState = QAudio::OpenError;
212 return false;
213 }
214
215 result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_RECORDING_PRESET,
216 &m_recorderPreset, sizeof(SLuint32));
217
218 SLuint32 presetValue = SL_ANDROID_RECORDING_PRESET_NONE;
219 SLuint32 presetSize = 2*sizeof(SLuint32); // intentionally too big
220 result = (*configItf)->GetConfiguration(configItf, SL_ANDROID_KEY_RECORDING_PRESET,
221 &presetSize, (void*)&presetValue);
222
223 if (result != SL_RESULT_SUCCESS || presetValue == SL_ANDROID_RECORDING_PRESET_NONE) {
224 m_errorState = QAudio::OpenError;
225 return false;
226 }
227#endif
228
229 // realize the audio recorder
230 result = (*m_recorderObject)->Realize(m_recorderObject, SL_BOOLEAN_FALSE);
231 if (result != SL_RESULT_SUCCESS) {
232 m_errorState = QAudio::OpenError;
233 return false;
234 }
235
236 // get the record interface
237 result = (*m_recorderObject)->GetInterface(m_recorderObject, SL_IID_RECORD, &m_recorder);
238 if (result != SL_RESULT_SUCCESS) {
239 m_errorState = QAudio::FatalError;
240 return false;
241 }
242
243 // get the buffer queue interface
244#ifdef ANDROID
245 SLInterfaceID bufferqueueItfID = SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
246#else
247 SLInterfaceID bufferqueueItfID = SL_IID_BUFFERQUEUE;
248#endif
249 result = (*m_recorderObject)->GetInterface(m_recorderObject, bufferqueueItfID, &m_bufferQueue);
250 if (result != SL_RESULT_SUCCESS) {
251 m_errorState = QAudio::FatalError;
252 return false;
253 }
254
255 // register callback on the buffer queue
256 result = (*m_bufferQueue)->RegisterCallback(m_bufferQueue, bufferQueueCallback, this);
257 if (result != SL_RESULT_SUCCESS) {
258 m_errorState = QAudio::FatalError;
259 return false;
260 }
261
262 if (m_bufferSize <= 0) {
263 m_bufferSize = m_format.bytesForDuration(DEFAULT_PERIOD_TIME_MS * 1000);
264 } else {
265 int minimumBufSize = m_format.bytesForDuration(MINIMUM_PERIOD_TIME_MS * 1000);
266 if (m_bufferSize < minimumBufSize)
267 m_bufferSize = minimumBufSize;
268 }
269
270 // enqueue empty buffers to be filled by the recorder
271 for (int i = 0; i < NUM_BUFFERS; ++i) {
272 m_buffers[i].resize(m_bufferSize);
273
274 result = (*m_bufferQueue)->Enqueue(m_bufferQueue, m_buffers[i].data(), m_bufferSize);
275 if (result != SL_RESULT_SUCCESS) {
276 m_errorState = QAudio::FatalError;
277 return false;
278 }
279 }
280
281 // start recording
282 result = (*m_recorder)->SetRecordState(m_recorder, SL_RECORDSTATE_RECORDING);
283 if (result != SL_RESULT_SUCCESS) {
284 m_errorState = QAudio::FatalError;
285 return false;
286 }
287
288 m_errorState = QAudio::NoError;
289
290 return true;
291}
292
294{
295 if (m_deviceState == QAudio::StoppedState)
296 return;
297
298 m_deviceState = QAudio::StoppedState;
299
300 stopRecording();
301
302 m_errorState = QAudio::NoError;
303 Q_EMIT stateChanged(m_deviceState);
304}
305
306void QAndroidAudioSource::stopRecording()
307{
308 flushBuffers();
309
310 (*m_recorder)->SetRecordState(m_recorder, SL_RECORDSTATE_STOPPED);
311 (*m_bufferQueue)->Clear(m_bufferQueue);
312
313 (*m_recorderObject)->Destroy(m_recorderObject);
314 m_recorderObject = 0;
315
316 for (int i = 0; i < NUM_BUFFERS; ++i)
317 m_buffers[i].clear();
318 m_currentBuffer = 0;
319
320 if (!m_pullMode && m_bufferIODevice) {
321 m_bufferIODevice->close();
322 delete m_bufferIODevice;
323 m_bufferIODevice = 0;
324 m_pushBuffer.clear();
325 }
326}
327
329{
330 if (m_deviceState == QAudio::ActiveState) {
331 m_deviceState = QAudio::SuspendedState;
332 emit stateChanged(m_deviceState);
333
334 (*m_recorder)->SetRecordState(m_recorder, SL_RECORDSTATE_PAUSED);
335 }
336}
337
339{
340 if (m_deviceState == QAudio::SuspendedState || m_deviceState == QAudio::IdleState) {
341 (*m_recorder)->SetRecordState(m_recorder, SL_RECORDSTATE_RECORDING);
342
343 m_deviceState = QAudio::ActiveState;
344 emit stateChanged(m_deviceState);
345 }
346}
347
349{
350 if (m_deviceState == QAudio::StoppedState || m_deviceState == QAudio::SuspendedState)
351 return;
352
353 if (m_deviceState != QAudio::ActiveState) {
354 m_errorState = QAudio::NoError;
355 m_deviceState = QAudio::ActiveState;
356 emit stateChanged(m_deviceState);
357 }
358
359 QByteArray *processedBuffer = &m_buffers[m_currentBuffer];
360 writeDataToDevice(processedBuffer->constData(), processedBuffer->size());
361
362 // Make sure that it was not stopped from writeDataToDevice
363 if (m_deviceState == QAudio::StoppedState || m_deviceState == QAudio::SuspendedState)
364 return;
365
366 // Re-enqueue the buffer
367 SLresult result = (*m_bufferQueue)->Enqueue(m_bufferQueue,
368 processedBuffer->data(),
369 processedBuffer->size());
370
371 m_currentBuffer = (m_currentBuffer + 1) % NUM_BUFFERS;
372
373 // If the buffer queue is empty (shouldn't happen), stop recording.
374#ifdef ANDROID
375 SLAndroidSimpleBufferQueueState state;
376#else
377 SLBufferQueueState state;
378#endif
379 result = (*m_bufferQueue)->GetState(m_bufferQueue, &state);
380 if (result != SL_RESULT_SUCCESS || state.count == 0) {
381 stop();
382 m_errorState = QAudio::FatalError;
383 Q_EMIT errorChanged(m_errorState);
384 }
385}
386
387void QAndroidAudioSource::writeDataToDevice(const char *data, int size)
388{
389 m_processedBytes += size;
390
391 QByteArray outData;
392
393 // Apply volume
394 if (m_volume < 1.0f) {
395 outData.resize(size);
396 QAudioHelperInternal::qMultiplySamples(m_volume, m_format, data, outData.data(), size);
397 } else {
398 outData.append(data, size);
399 }
400
401 if (m_pullMode) {
402 // write buffer to the QIODevice
403 if (m_audioSource->write(outData) < 0) {
404 stop();
405 m_errorState = QAudio::IOError;
406 Q_EMIT errorChanged(m_errorState);
407 }
408 } else {
409 // emits readyRead() so user will call read() on QIODevice to get some audio data
410 if (m_bufferIODevice != 0) {
411 m_pushBuffer.append(outData);
412 Q_EMIT m_bufferIODevice->readyRead();
413 }
414 }
415}
416
417void QAndroidAudioSource::flushBuffers()
418{
419 SLmillisecond recorderPos;
420 (*m_recorder)->GetPosition(m_recorder, &recorderPos);
421 qint64 devicePos = processedUSecs();
422
423 qint64 delta = recorderPos * 1000 - devicePos;
424
425 if (delta > 0) {
426 const int writeSize = qMin(m_buffers[m_currentBuffer].size(),
427 m_format.bytesForDuration(delta));
428 writeDataToDevice(m_buffers[m_currentBuffer].constData(), writeSize);
429 }
430}
431
433{
434 if (m_deviceState == QAudio::ActiveState || m_deviceState == QAudio::SuspendedState)
435 return m_bufferIODevice ? m_bufferIODevice->bytesAvailable() : m_bufferSize;
436
437 return 0;
438}
439
441{
442 m_bufferSize = value;
443}
444
446{
447 return m_bufferSize;
448}
449
451{
452 return m_format.durationForBytes(m_processedBytes);
453}
454
456{
457 m_volume = vol;
458}
459
461{
462 return m_volume;
463}
464
466{
467 stop();
468}
469
IOBluetoothDevice * device
void setBufferSize(qsizetype value)
void setFormat(const QAudioFormat &format)
QAudioFormat format() const
QAudio::Error error() const
qsizetype bufferSize() const
void setVolume(qreal volume)
qsizetype bytesReady() const
QAndroidAudioSource(const QByteArray &device, QObject *parent)
QAudio::State state() const
The QAudioFormat class stores audio stream parameter information.
Q_MULTIMEDIA_EXPORT qint32 bytesForDuration(qint64 microseconds) const
Returns the number of bytes required for this audio format for microseconds.
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)
\inmodule QtCore \reentrant
Definition qbuffer.h:16
bool open(OpenMode openMode) override
\reimp
Definition qbuffer.cpp:295
void close() override
\reimp
Definition qbuffer.cpp:315
\inmodule QtCore
Definition qbytearray.h:57
void clear()
Clears the contents of the byte array and makes it null.
void resize(qsizetype size)
Sets the size of the byte array to size bytes.
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
\inmodule QtCore \reentrant
Definition qiodevice.h:34
void readyRead()
This signal is emitted once every time new data is available for reading from the device's current re...
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
virtual qint64 bytesAvailable() const
Returns the number of bytes that are available for reading.
Access the microphone for monitoring or recording sound.
\inmodule QtCore
Definition qobject.h:103
SLEngineItf slEngine() const
static SLAndroidDataFormat_PCM_EX audioFormatToSLFormatPCM(const QAudioFormat &format)
b clear()
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
@ 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.
#define MINIMUM_PERIOD_TIME_MS
#define NUM_BUFFERS
static void * context
#define DEFAULT_PERIOD_TIME_MS
Q_CORE_EXPORT int qstrcmp(const char *str1, const char *str2)
#define qApp
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint id
[7]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLint GLsizei GLsizei GLenum format
GLuint64EXT * result
[6]
@ NoError
Definition main.cpp:34
#define Q_EMIT
#define emit
ptrdiff_t qsizetype
Definition qtypes.h:165
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
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...