Qt 6.x
The Qt SDK
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
53{
54 SLAndroidDataFormat_PCM_EX format_pcm;
55 format_pcm.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
56 format_pcm.numChannels = format.channelCount();
57 format_pcm.sampleRate = format.sampleRate() * 1000;
58 format_pcm.bitsPerSample = format.bytesPerSample() * 8;
59 format_pcm.containerSize = format.bytesPerSample() * 8;
60 format_pcm.channelMask = (format.channelCount() == 1 ?
61 SL_SPEAKER_FRONT_CENTER :
62 SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT);
63 format_pcm.endianness = (QSysInfo::ByteOrder == QSysInfo::LittleEndian ?
64 SL_BYTEORDER_LITTLEENDIAN :
65 SL_BYTEORDER_BIGENDIAN);
66
67 switch (format.sampleFormat()) {
69 format_pcm.representation = SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT;
70 break;
73 format_pcm.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
74 break;
76 format_pcm.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
77 break;
80 format_pcm.representation = SL_ANDROID_PCM_REPRESENTATION_INVALID;
81 break;
82 }
83
84 return format_pcm;
85}
86
88{
90 QJniObject devs;
92 devs = QJniObject::callStaticObjectMethod(
93 "org/qtproject/qt/android/multimedia/QtAudioDeviceManager",
94 "getAudioInputDevices",
95 "()[Ljava/lang/String;");
96 } else if (mode == QAudioDevice::Output) {
97 devs = QJniObject::callStaticObjectMethod(
98 "org/qtproject/qt/android/multimedia/QtAudioDeviceManager",
99 "getAudioOutputDevices",
100 "()[Ljava/lang/String;");
101 }
102 if (devs.isValid()) {
103 QJniEnvironment env;
104 jobjectArray devsArray = static_cast<jobjectArray>(devs.object());
105 const jint size = env->GetArrayLength(devsArray);
106 for (int i = 0; i < size; ++i) {
107 QString val = QJniObject(env->GetObjectArrayElement(devsArray, i)).toString();
108 int pos = val.indexOf(QStringLiteral(":"));
110 val.left(pos).toUtf8(), val.mid(pos+1), mode))->create();
111 }
112 }
113 return devices;
114}
115
117{
118 return QJniObject::callStaticMethod<jboolean>(
119 "org/qtproject/qt/android/multimedia/QtAudioDeviceManager",
120 "setAudioOutput",
121 deviceId.toInt());
122}
123
125{
126 return qApp->checkPermission(QMicrophonePermission{}) == Qt::PermissionStatus::Granted;
127}
128
130{
131 if (mode == QAudioDevice::Input) {
132 if (!m_checkedInputFormats)
133 const_cast<QOpenSLESEngine *>(this)->checkSupportedInputFormats();
134 return m_supportedInputChannelCounts;
135 } else {
136 return QList<int>() << 1 << 2;
137 }
138}
139
141{
142 if (mode == QAudioDevice::Input) {
143 if (!m_checkedInputFormats)
144 const_cast<QOpenSLESEngine *>(this)->checkSupportedInputFormats();
145 return m_supportedInputSampleRates;
146 } else {
147 return QList<int>() << 8000 << 11025 << 12000 << 16000 << 22050 << 24000
148 << 32000 << 44100 << 48000 << 64000 << 88200 << 96000 << 192000;
149 }
150}
151
153{
154 static int sampleRate = 0;
155 static int framesPerBuffer = 0;
156
157 if (type == FramesPerBuffer && framesPerBuffer != 0)
158 return framesPerBuffer;
159
160 if (type == SampleRate && sampleRate != 0)
161 return sampleRate;
162
163 QJniObject ctx(QNativeInterface::QAndroidApplication::context());
164 if (!ctx.isValid())
165 return defaultValue;
166
167
168 QJniObject audioServiceString = ctx.getStaticObjectField("android/content/Context",
169 "AUDIO_SERVICE",
170 "Ljava/lang/String;");
171 QJniObject am = ctx.callObjectMethod("getSystemService",
172 "(Ljava/lang/String;)Ljava/lang/Object;",
173 audioServiceString.object());
174 if (!am.isValid())
175 return defaultValue;
176
177 auto sampleRateField = QJniObject::getStaticObjectField("android/media/AudioManager",
178 "PROPERTY_OUTPUT_SAMPLE_RATE",
179 "Ljava/lang/String;");
180 auto framesPerBufferField = QJniObject::getStaticObjectField(
181 "android/media/AudioManager",
182 "PROPERTY_OUTPUT_FRAMES_PER_BUFFER",
183 "Ljava/lang/String;");
184
185 auto sampleRateString = am.callObjectMethod("getProperty",
186 "(Ljava/lang/String;)Ljava/lang/String;",
187 sampleRateField.object());
188 auto framesPerBufferString = am.callObjectMethod("getProperty",
189 "(Ljava/lang/String;)Ljava/lang/String;",
190 framesPerBufferField.object());
191
192 if (!sampleRateString.isValid() || !framesPerBufferString.isValid())
193 return defaultValue;
194
195 framesPerBuffer = framesPerBufferString.toString().toInt();
196 sampleRate = sampleRateString.toString().toInt();
197
198 if (type == FramesPerBuffer)
199 return framesPerBuffer;
200
201 if (type == SampleRate)
202 return sampleRate;
203
204 return defaultValue;
205}
206
208{
209 if (!format.isValid())
210 return 0;
211
212 const int channelConfig = [&format]() -> int
213 {
214 if (format.channelCount() == 1)
215 return 4; /* MONO */
216 else if (format.channelCount() == 2)
217 return 12; /* STEREO */
218 else if (format.channelCount() > 2)
219 return 1052; /* SURROUND */
220 else
221 return 1; /* DEFAULT */
222 }();
223
224 const int audioFormat = [&format]() -> int
225 {
226 const int sdkVersion = QNativeInterface::QAndroidApplication::sdkVersion();
227 if (format.sampleFormat() == QAudioFormat::Float && sdkVersion >= 21)
228 return 4; /* PCM_FLOAT */
229 else if (format.sampleFormat() == QAudioFormat::UInt8)
230 return 3; /* PCM_8BIT */
231 else if (format.sampleFormat() == QAudioFormat::Int16)
232 return 2; /* PCM_16BIT*/
233 else
234 return 1; /* DEFAULT */
235 }();
236
237 const int sampleRate = format.sampleRate();
238 const int minBufferSize = QJniObject::callStaticMethod<jint>("android/media/AudioTrack",
239 "getMinBufferSize",
240 "(III)I",
241 sampleRate,
243 audioFormat);
244 return minBufferSize > 0 ? minBufferSize : format.bytesForDuration(DEFAULT_PERIOD_TIME_MS);
245}
246
248{
250 format.framesForDuration(MINIMUM_PERIOD_TIME_MS)));
251}
252
254{
255 static int isSupported = -1;
256
257 if (isSupported != -1)
258 return (isSupported == 1);
259
260 QJniObject ctx(QNativeInterface::QAndroidApplication::context());
261 if (!ctx.isValid())
262 return false;
263
264 QJniObject pm = ctx.callObjectMethod("getPackageManager", "()Landroid/content/pm/PackageManager;");
265 if (!pm.isValid())
266 return false;
267
268 QJniObject audioFeatureField = QJniObject::getStaticObjectField(
269 "android/content/pm/PackageManager",
270 "FEATURE_AUDIO_LOW_LATENCY",
271 "Ljava/lang/String;");
272 if (!audioFeatureField.isValid())
273 return false;
274
275 isSupported = pm.callMethod<jboolean>("hasSystemFeature",
276 "(Ljava/lang/String;)Z",
277 audioFeatureField.object());
278 return (isSupported == 1);
279}
280
282{
283 return qEnvironmentVariableIsSet("QT_OPENSL_INFO");
284}
285
286void QOpenSLESEngine::checkSupportedInputFormats()
287{
288 m_supportedInputChannelCounts = QList<int>() << 1;
289 m_supportedInputSampleRates.clear();
290
291 SLAndroidDataFormat_PCM_EX defaultFormat;
292 defaultFormat.formatType = SL_DATAFORMAT_PCM;
293 defaultFormat.numChannels = 1;
294 defaultFormat.sampleRate = SL_SAMPLINGRATE_44_1;
295 defaultFormat.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
296 defaultFormat.containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
297 defaultFormat.channelMask = SL_ANDROID_MAKE_INDEXED_CHANNEL_MASK(SL_SPEAKER_FRONT_CENTER);
298 defaultFormat.endianness = SL_BYTEORDER_LITTLEENDIAN;
299 defaultFormat.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
300
301 const SLuint32 rates[13] = { SL_SAMPLINGRATE_8,
302 SL_SAMPLINGRATE_11_025,
303 SL_SAMPLINGRATE_12,
304 SL_SAMPLINGRATE_16,
305 SL_SAMPLINGRATE_22_05,
306 SL_SAMPLINGRATE_24,
307 SL_SAMPLINGRATE_32,
308 SL_SAMPLINGRATE_44_1,
309 SL_SAMPLINGRATE_48,
310 SL_SAMPLINGRATE_64,
311 SL_SAMPLINGRATE_88_2,
312 SL_SAMPLINGRATE_96,
313 SL_SAMPLINGRATE_192 };
314
315
316 // Test sampling rates
317 for (size_t i = 0 ; i < std::size(rates); ++i) {
318 SLAndroidDataFormat_PCM_EX format = defaultFormat;
319 format.sampleRate = rates[i];
320
321 if (inputFormatIsSupported(format))
322 m_supportedInputSampleRates.append(rates[i] / 1000);
323
324 }
325
326 // Test if stereo is supported
327 {
328 SLAndroidDataFormat_PCM_EX format = defaultFormat;
329 format.numChannels = 2;
330 format.channelMask = SL_ANDROID_MAKE_INDEXED_CHANNEL_MASK(SL_SPEAKER_FRONT_LEFT
331 | SL_SPEAKER_FRONT_RIGHT);
332 if (inputFormatIsSupported(format))
333 m_supportedInputChannelCounts.append(2);
334 }
335
336 m_checkedInputFormats = true;
337}
338
339bool QOpenSLESEngine::inputFormatIsSupported(SLAndroidDataFormat_PCM_EX format)
340{
341 SLresult result;
342 SLObjectItf recorder = 0;
343 SLDataLocator_IODevice loc_dev = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,
344 SL_DEFAULTDEVICEID_AUDIOINPUT, NULL };
345 SLDataSource audioSrc = { &loc_dev, NULL };
346
347 SLDataLocator_AndroidSimpleBufferQueue loc_bq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };
348 SLDataSink audioSnk = { &loc_bq, &format };
349
350 // only ask permission when it is about to create the audiorecorder
351 if (!hasRecordPermission())
352 return false;
353
354 result = (*m_engine)->CreateAudioRecorder(m_engine, &recorder, &audioSrc, &audioSnk, 0, 0, 0);
355 if (result == SL_RESULT_SUCCESS)
356 result = (*recorder)->Realize(recorder, false);
357
358 if (result == SL_RESULT_SUCCESS) {
359 (*recorder)->Destroy(recorder);
360 return true;
361 }
362
363 return false;
364}
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
Definition qlist.h:74
void append(parameter_type t)
Definition qlist.h:441
void clear()
Definition qlist.h:417
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:127
@ 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()
#define CheckError(message)
#define SL_ANDROID_PCM_REPRESENTATION_INVALID
#define QStringLiteral(str)
Q_CORE_EXPORT bool qEnvironmentVariableIsSet(const char *varName) noexcept
view create()