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
qopenslesengine.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
4#include "qopenslesengine_p.h"
5
8
9#include <QtCore/qcoreapplication.h>
10#include <QtCore/qjniobject.h>
11#include <QtCore/qpermissions.h>
12#include <QtCore/private/qandroidextras_p.h>
13#include <qdebug.h>
14
15#define MINIMUM_PERIOD_TIME_MS 5
16#define DEFAULT_PERIOD_TIME_MS 50
17
18#define CheckError(message) if (result != SL_RESULT_SUCCESS) { qWarning(message); return; }
19
20#define SL_ANDROID_PCM_REPRESENTATION_INVALID 0
21
23
25 : m_engineObject(0)
26 , m_engine(0)
27 , m_checkedInputFormats(false)
28{
29 SLresult result;
30
31 result = slCreateEngine(&m_engineObject, 0, 0, 0, 0, 0);
32 CheckError("Failed to create engine");
33
34 result = (*m_engineObject)->Realize(m_engineObject, SL_BOOLEAN_FALSE);
35 CheckError("Failed to realize engine");
36
37 result = (*m_engineObject)->GetInterface(m_engineObject, SL_IID_ENGINE, &m_engine);
38 CheckError("Failed to get engine interface");
39}
40
42{
43 if (m_engineObject)
44 (*m_engineObject)->Destroy(m_engineObject);
45}
46
48{
49 return openslesEngine();
50}
51
52static SLuint32 getChannelMask(unsigned channelCount)
53{
54 switch (channelCount) {
55 case 1: return SL_SPEAKER_FRONT_CENTER;
56 case 2: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
57 case 3: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER;
58 case 4: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT
59 | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT;
60 case 5: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_BACK_LEFT
61 | SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_FRONT_CENTER;
62 case 6: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_BACK_LEFT
63 | SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY;
64 default: return 0; // Default to 0 for an unsupported or unknown number of channels
65 }
66}
67
69{
70 SLAndroidDataFormat_PCM_EX format_pcm;
71 format_pcm.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
72 format_pcm.numChannels = format.channelCount();
73 format_pcm.sampleRate = format.sampleRate() * 1000;
74 format_pcm.bitsPerSample = format.bytesPerSample() * 8;
75 format_pcm.containerSize = format.bytesPerSample() * 8;
76 format_pcm.channelMask = getChannelMask(format_pcm.numChannels);
77 format_pcm.endianness = (QSysInfo::ByteOrder == QSysInfo::LittleEndian ?
78 SL_BYTEORDER_LITTLEENDIAN :
79 SL_BYTEORDER_BIGENDIAN);
80
81 switch (format.sampleFormat()) {
83 format_pcm.representation = SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT;
84 break;
87 format_pcm.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
88 break;
90 format_pcm.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
91 break;
94 format_pcm.representation = SL_ANDROID_PCM_REPRESENTATION_INVALID;
95 break;
96 }
97
98 return format_pcm;
99}
100
102{
103 QList<QAudioDevice> devices;
104 QJniObject devs;
105 if (mode == QAudioDevice::Input) {
106 devs = QJniObject::callStaticObjectMethod(
107 "org/qtproject/qt/android/multimedia/QtAudioDeviceManager",
108 "getAudioInputDevices",
109 "()[Ljava/lang/String;");
110 } else if (mode == QAudioDevice::Output) {
111 devs = QJniObject::callStaticObjectMethod(
112 "org/qtproject/qt/android/multimedia/QtAudioDeviceManager",
113 "getAudioOutputDevices",
114 "()[Ljava/lang/String;");
115 }
116 if (devs.isValid()) {
117 QJniEnvironment env;
118 jobjectArray devsArray = static_cast<jobjectArray>(devs.object());
119 const jint size = env->GetArrayLength(devsArray);
120 for (int i = 0; i < size; ++i) {
121 QString val = QJniObject(env->GetObjectArrayElement(devsArray, i)).toString();
122 int pos = val.indexOf(QStringLiteral(":"));
124 val.left(pos).toUtf8(), val.mid(pos+1), mode))->create();
125 }
126 }
127 return devices;
128}
129
131{
132 return QJniObject::callStaticMethod<jboolean>(
133 "org/qtproject/qt/android/multimedia/QtAudioDeviceManager",
134 "setAudioOutput",
135 deviceId.toInt());
136}
137
139{
140 return qApp->checkPermission(QMicrophonePermission{}) == Qt::PermissionStatus::Granted;
141}
142
144{
145 if (mode == QAudioDevice::Input) {
146 if (!m_checkedInputFormats)
147 const_cast<QOpenSLESEngine *>(this)->checkSupportedInputFormats();
148 return m_supportedInputChannelCounts;
149 } else {
150 return QList<int>() << 1 << 2;
151 }
152}
153
155{
156 if (mode == QAudioDevice::Input) {
157 if (!m_checkedInputFormats)
158 const_cast<QOpenSLESEngine *>(this)->checkSupportedInputFormats();
159 return m_supportedInputSampleRates;
160 } else {
161 return QList<int>() << 8000 << 11025 << 12000 << 16000 << 22050 << 24000
162 << 32000 << 44100 << 48000 << 64000 << 88200 << 96000 << 192000;
163 }
164}
165
167{
168 static int sampleRate = 0;
169 static int framesPerBuffer = 0;
170
171 if (type == FramesPerBuffer && framesPerBuffer != 0)
172 return framesPerBuffer;
173
174 if (type == SampleRate && sampleRate != 0)
175 return sampleRate;
176
177 QJniObject ctx(QNativeInterface::QAndroidApplication::context());
178 if (!ctx.isValid())
179 return defaultValue;
180
181
182 QJniObject audioServiceString = ctx.getStaticObjectField("android/content/Context",
183 "AUDIO_SERVICE",
184 "Ljava/lang/String;");
185 QJniObject am = ctx.callObjectMethod("getSystemService",
186 "(Ljava/lang/String;)Ljava/lang/Object;",
187 audioServiceString.object());
188 if (!am.isValid())
189 return defaultValue;
190
191 auto sampleRateField = QJniObject::getStaticObjectField("android/media/AudioManager",
192 "PROPERTY_OUTPUT_SAMPLE_RATE",
193 "Ljava/lang/String;");
194 auto framesPerBufferField = QJniObject::getStaticObjectField(
195 "android/media/AudioManager",
196 "PROPERTY_OUTPUT_FRAMES_PER_BUFFER",
197 "Ljava/lang/String;");
198
199 auto sampleRateString = am.callObjectMethod("getProperty",
200 "(Ljava/lang/String;)Ljava/lang/String;",
201 sampleRateField.object());
202 auto framesPerBufferString = am.callObjectMethod("getProperty",
203 "(Ljava/lang/String;)Ljava/lang/String;",
204 framesPerBufferField.object());
205
206 if (!sampleRateString.isValid() || !framesPerBufferString.isValid())
207 return defaultValue;
208
209 framesPerBuffer = framesPerBufferString.toString().toInt();
210 sampleRate = sampleRateString.toString().toInt();
211
212 if (type == FramesPerBuffer)
213 return framesPerBuffer;
214
215 if (type == SampleRate)
216 return sampleRate;
217
218 return defaultValue;
219}
220
222{
223 if (!format.isValid())
224 return 0;
225
226 const int channelConfig = [&format]() -> int
227 {
228 if (format.channelCount() == 1)
229 return 4; /* MONO */
230 else if (format.channelCount() == 2)
231 return 12; /* STEREO */
232 else if (format.channelCount() > 2)
233 return 1052; /* SURROUND */
234 else
235 return 1; /* DEFAULT */
236 }();
237
238 const int audioFormat = [&format]() -> int
239 {
240 const int sdkVersion = QNativeInterface::QAndroidApplication::sdkVersion();
241 if (format.sampleFormat() == QAudioFormat::Float && sdkVersion >= 21)
242 return 4; /* PCM_FLOAT */
243 else if (format.sampleFormat() == QAudioFormat::UInt8)
244 return 3; /* PCM_8BIT */
245 else if (format.sampleFormat() == QAudioFormat::Int16)
246 return 2; /* PCM_16BIT*/
247 else
248 return 1; /* DEFAULT */
249 }();
250
251 const int sampleRate = format.sampleRate();
252 const int minBufferSize = QJniObject::callStaticMethod<jint>("android/media/AudioTrack",
253 "getMinBufferSize",
254 "(III)I",
255 sampleRate,
257 audioFormat);
258 return minBufferSize > 0 ? minBufferSize : format.bytesForDuration(DEFAULT_PERIOD_TIME_MS);
259}
260
266
268{
269 static int isSupported = -1;
270
271 if (isSupported != -1)
272 return (isSupported == 1);
273
274 QJniObject ctx(QNativeInterface::QAndroidApplication::context());
275 if (!ctx.isValid())
276 return false;
277
278 QJniObject pm = ctx.callObjectMethod("getPackageManager", "()Landroid/content/pm/PackageManager;");
279 if (!pm.isValid())
280 return false;
281
282 QJniObject audioFeatureField = QJniObject::getStaticObjectField(
283 "android/content/pm/PackageManager",
284 "FEATURE_AUDIO_LOW_LATENCY",
285 "Ljava/lang/String;");
286 if (!audioFeatureField.isValid())
287 return false;
288
289 isSupported = pm.callMethod<jboolean>("hasSystemFeature",
290 "(Ljava/lang/String;)Z",
291 audioFeatureField.object());
292 return (isSupported == 1);
293}
294
296{
297 return qEnvironmentVariableIsSet("QT_OPENSL_INFO");
298}
299
300void QOpenSLESEngine::checkSupportedInputFormats()
301{
302 m_supportedInputChannelCounts = QList<int>() << 1;
303 m_supportedInputSampleRates.clear();
304
305 SLAndroidDataFormat_PCM_EX defaultFormat;
306 defaultFormat.formatType = SL_DATAFORMAT_PCM;
307 defaultFormat.numChannels = 1;
308 defaultFormat.sampleRate = SL_SAMPLINGRATE_44_1;
309 defaultFormat.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
310 defaultFormat.containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
311 defaultFormat.channelMask = SL_ANDROID_MAKE_INDEXED_CHANNEL_MASK(SL_SPEAKER_FRONT_CENTER);
312 defaultFormat.endianness = SL_BYTEORDER_LITTLEENDIAN;
313 defaultFormat.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
314
315 const SLuint32 rates[13] = { SL_SAMPLINGRATE_8,
316 SL_SAMPLINGRATE_11_025,
317 SL_SAMPLINGRATE_12,
318 SL_SAMPLINGRATE_16,
319 SL_SAMPLINGRATE_22_05,
320 SL_SAMPLINGRATE_24,
321 SL_SAMPLINGRATE_32,
322 SL_SAMPLINGRATE_44_1,
323 SL_SAMPLINGRATE_48,
324 SL_SAMPLINGRATE_64,
325 SL_SAMPLINGRATE_88_2,
326 SL_SAMPLINGRATE_96,
327 SL_SAMPLINGRATE_192 };
328
329
330 // Test sampling rates
331 for (size_t i = 0 ; i < std::size(rates); ++i) {
332 SLAndroidDataFormat_PCM_EX format = defaultFormat;
333 format.sampleRate = rates[i];
334
335 if (inputFormatIsSupported(format))
336 m_supportedInputSampleRates.append(rates[i] / 1000);
337
338 }
339
340 // Test if stereo is supported
341 {
342 SLAndroidDataFormat_PCM_EX format = defaultFormat;
343 format.numChannels = 2;
344 format.channelMask = SL_ANDROID_MAKE_INDEXED_CHANNEL_MASK(SL_SPEAKER_FRONT_LEFT
345 | SL_SPEAKER_FRONT_RIGHT);
346 if (inputFormatIsSupported(format))
347 m_supportedInputChannelCounts.append(2);
348 }
349
350 m_checkedInputFormats = true;
351}
352
353bool QOpenSLESEngine::inputFormatIsSupported(SLAndroidDataFormat_PCM_EX format)
354{
355 SLresult result;
356 SLObjectItf recorder = 0;
357 SLDataLocator_IODevice loc_dev = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,
358 SL_DEFAULTDEVICEID_AUDIOINPUT, NULL };
359 SLDataSource audioSrc = { &loc_dev, NULL };
360
361 SLDataLocator_AndroidSimpleBufferQueue loc_bq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };
362 SLDataSink audioSnk = { &loc_bq, &format };
363
364 // only ask permission when it is about to create the audiorecorder
365 if (!hasRecordPermission())
366 return false;
367
368 result = (*m_engine)->CreateAudioRecorder(m_engine, &recorder, &audioSrc, &audioSnk, 0, 0, 0);
369 if (result == SL_RESULT_SUCCESS)
370 result = (*recorder)->Realize(recorder, false);
371
372 if (result == SL_RESULT_SUCCESS) {
373 (*recorder)->Destroy(recorder);
374 return true;
375 }
376
377 return false;
378}
Mode
Describes the mode of this device.
The QAudioFormat class stores audio stream parameter information.
\inmodule QtCore
Definition qbytearray.h:57
int toInt(bool *ok=nullptr, int base=10) const
Returns the byte array converted to an int using base base, which is ten by default.
\inmodule QtCore
\inmodule QtCore
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
Access the microphone for monitoring or recording sound.
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 QList< QAudioDevice > availableDevices(QAudioDevice::Mode mode)
static bool supportsLowLatency()
static bool printDebugInfo()
QList< int > supportedSampleRates(QAudioDevice::Mode mode) const
QList< int > supportedChannelCounts(QAudioDevice::Mode mode) const
static bool setAudioOutput(const QByteArray &deviceId)
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
@ ByteOrder
Definition qsysinfo.h:34
@ LittleEndian
Definition qsysinfo.h:30
EGLContext ctx
QMediaRecorder * recorder
Definition camera.cpp:20
#define MINIMUM_PERIOD_TIME_MS
#define DEFAULT_PERIOD_TIME_MS
#define qApp
QAudioFormat::ChannelConfig channelConfig
EGLDeviceEXT * devices
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
GLenum mode
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum type
GLint GLsizei GLsizei GLenum format
GLuint GLfloat * val
GLuint64EXT * result
[6]
GLuint GLsizei const GLenum * rates
static bool hasRecordPermission()
static SLuint32 getChannelMask(unsigned channelCount)
#define CheckError(message)
#define SL_ANDROID_PCM_REPRESENTATION_INVALID
#define QStringLiteral(str)
Q_CORE_EXPORT bool qEnvironmentVariableIsSet(const char *varName) noexcept
view create()