Qt 6.x
The Qt SDK
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
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 // Re-enqueue the buffer
363 SLresult result = (*m_bufferQueue)->Enqueue(m_bufferQueue,
364 processedBuffer->data(),
365 processedBuffer->size());
366
367 m_currentBuffer = (m_currentBuffer + 1) % NUM_BUFFERS;
368
369 // If the buffer queue is empty (shouldn't happen), stop recording.
370#ifdef ANDROID
371 SLAndroidSimpleBufferQueueState state;
372#else
373 SLBufferQueueState state;
374#endif
375 result = (*m_bufferQueue)->GetState(m_bufferQueue, &state);
376 if (result != SL_RESULT_SUCCESS || state.count == 0) {
377 stop();
378 m_errorState = QAudio::FatalError;
379 Q_EMIT errorChanged(m_errorState);
380 }
381}
382
383void QAndroidAudioSource::writeDataToDevice(const char *data, int size)
384{
385 m_processedBytes += size;
386
387 QByteArray outData;
388
389 // Apply volume
390 if (m_volume < 1.0f) {
391 outData.resize(size);
392 QAudioHelperInternal::qMultiplySamples(m_volume, m_format, data, outData.data(), size);
393 } else {
394 outData.append(data, size);
395 }
396
397 if (m_pullMode) {
398 // write buffer to the QIODevice
399 if (m_audioSource->write(outData) < 0) {
400 stop();
401 m_errorState = QAudio::IOError;
402 Q_EMIT errorChanged(m_errorState);
403 }
404 } else {
405 // emits readyRead() so user will call read() on QIODevice to get some audio data
406 if (m_bufferIODevice != 0) {
407 m_pushBuffer.append(outData);
408 Q_EMIT m_bufferIODevice->readyRead();
409 }
410 }
411}
412
413void QAndroidAudioSource::flushBuffers()
414{
415 SLmillisecond recorderPos;
416 (*m_recorder)->GetPosition(m_recorder, &recorderPos);
417 qint64 devicePos = processedUSecs();
418
419 qint64 delta = recorderPos * 1000 - devicePos;
420
421 if (delta > 0) {
422 const int writeSize = qMin(m_buffers[m_currentBuffer].size(),
423 m_format.bytesForDuration(delta));
424 writeDataToDevice(m_buffers[m_currentBuffer].constData(), writeSize);
425 }
426}
427
429{
430 if (m_deviceState == QAudio::ActiveState || m_deviceState == QAudio::SuspendedState)
431 return m_bufferIODevice ? m_bufferIODevice->bytesAvailable() : m_bufferSize;
432
433 return 0;
434}
435
437{
438 m_bufferSize = value;
439}
440
442{
443 return m_bufferSize;
444}
445
447{
448 return m_format.durationForBytes(m_processedBytes);
449}
450
452{
453 m_volume = vol;
454}
455
457{
458 return m_volume;
459}
460
462{
463 stop();
464}
465
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.
\inmodule QtCore \reentrant
Definition qbuffer.h:16
bool open(OpenMode openMode) override
\reimp
Definition qbuffer.cpp:296
void close() override
\reimp
Definition qbuffer.cpp:316
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:534
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
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:90
SLEngineItf slEngine() const
static SLAndroidDataFormat_PCM_EX audioFormatToSLFormatPCM(const QAudioFormat &format)
void errorChanged(QAudio::Error error)
void stateChanged(QAudio::State state)
b clear()
void qMultiplySamples(qreal factor, const QAudioFormat &format, const void *src, void *dest, int len)
The QAudio namespace contains enums used by the audio classes.
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
@ FatalError
Definition qaudio.h:24
@ OpenError
Definition qaudio.h:24
@ NoError
Definition qaudio.h:24
@ IOError
Definition qaudio.h:24
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:70
long long qint64
Definition qtypes.h:55
double qreal
Definition qtypes.h:92
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