Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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
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
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
68SLAndroidDataFormat_PCM_EX QOpenSLESEngine::audioFormatToSLFormatPCM(const QAudioFormat &format)
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()) {
82 case QAudioFormat::SampleFormat::UInt8:
83 format_pcm.representation = SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT;
84 break;
85 case QAudioFormat::SampleFormat::Int16:
86 case QAudioFormat::SampleFormat::Int32:
87 format_pcm.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
88 break;
89 case QAudioFormat::SampleFormat::Float:
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
101QList<QAudioDevice> QOpenSLESEngine::availableDevices(QAudioDevice::Mode mode)
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 auto devElement = env->GetObjectArrayElement(devsArray, i);
122 QString val = QJniObject(devElement).toString();
123 env->DeleteLocalRef(devElement);
124 int pos = val.indexOf(QStringLiteral(":"));
125 devices << (new QOpenSLESDeviceInfo(
126 val.left(pos).toUtf8(), val.mid(pos+1), mode, i == 0))->create();
127 }
128 }
129 return devices;
130}
131
132bool QOpenSLESEngine::setAudioOutput(const QByteArray &deviceId)
133{
134 return QJniObject::callStaticMethod<jboolean>(
135 "org/qtproject/qt/android/multimedia/QtAudioDeviceManager",
136 "setAudioOutput",
137 deviceId.toInt());
138}
139
141{
142 return qApp->checkPermission(QMicrophonePermission{}) == Qt::PermissionStatus::Granted;
143}
144
145QList<int> QOpenSLESEngine::supportedChannelCounts(QAudioDevice::Mode mode) const
146{
147 if (mode == QAudioDevice::Input) {
148 if (!m_checkedInputFormats)
149 const_cast<QOpenSLESEngine *>(this)->checkSupportedInputFormats();
150 return m_supportedInputChannelCounts;
151 } else {
152 return QList<int>() << 1 << 2;
153 }
154}
155
156QList<int> QOpenSLESEngine::supportedSampleRates(QAudioDevice::Mode mode) const
157{
158 if (mode == QAudioDevice::Input) {
159 if (!m_checkedInputFormats)
160 const_cast<QOpenSLESEngine *>(this)->checkSupportedInputFormats();
161 return m_supportedInputSampleRates;
162 } else {
163 return QList<int>() << 8000 << 11025 << 12000 << 16000 << 22050 << 24000
164 << 32000 << 44100 << 48000 << 64000 << 88200 << 96000 << 192000;
165 }
166}
167
169{
170 static int sampleRate = 0;
171 static int framesPerBuffer = 0;
172
173 if (type == FramesPerBuffer && framesPerBuffer != 0)
174 return framesPerBuffer;
175
176 if (type == SampleRate && sampleRate != 0)
177 return sampleRate;
178
179 QJniObject ctx(QNativeInterface::QAndroidApplication::context());
180 if (!ctx.isValid())
181 return defaultValue;
182
183
184 QJniObject audioServiceString = ctx.getStaticObjectField("android/content/Context",
185 "AUDIO_SERVICE",
186 "Ljava/lang/String;");
187 QJniObject am = ctx.callObjectMethod("getSystemService",
188 "(Ljava/lang/String;)Ljava/lang/Object;",
189 audioServiceString.object());
190 if (!am.isValid())
191 return defaultValue;
192
193 auto sampleRateField = QJniObject::getStaticObjectField("android/media/AudioManager",
194 "PROPERTY_OUTPUT_SAMPLE_RATE",
195 "Ljava/lang/String;");
196 auto framesPerBufferField = QJniObject::getStaticObjectField(
197 "android/media/AudioManager",
198 "PROPERTY_OUTPUT_FRAMES_PER_BUFFER",
199 "Ljava/lang/String;");
200
201 auto sampleRateString = am.callObjectMethod("getProperty",
202 "(Ljava/lang/String;)Ljava/lang/String;",
203 sampleRateField.object());
204 auto framesPerBufferString = am.callObjectMethod("getProperty",
205 "(Ljava/lang/String;)Ljava/lang/String;",
206 framesPerBufferField.object());
207
208 if (!sampleRateString.isValid() || !framesPerBufferString.isValid())
209 return defaultValue;
210
211 framesPerBuffer = framesPerBufferString.toString().toInt();
212 sampleRate = sampleRateString.toString().toInt();
213
214 if (type == FramesPerBuffer)
215 return framesPerBuffer;
216
217 if (type == SampleRate)
218 return sampleRate;
219
220 return defaultValue;
221}
222
224{
225 if (!format.isValid())
226 return 0;
227
228 const int channelConfig = [&format]() -> int
229 {
230 if (format.channelCount() == 1)
231 return 4; /* MONO */
232 else if (format.channelCount() == 2)
233 return 12; /* STEREO */
234 else if (format.channelCount() > 2)
235 return 1052; /* SURROUND */
236 else
237 return 1; /* DEFAULT */
238 }();
239
240 const int audioFormat = [&format]() -> int
241 {
242 const int sdkVersion = QNativeInterface::QAndroidApplication::sdkVersion();
243 if (format.sampleFormat() == QAudioFormat::Float && sdkVersion >= 21)
244 return 4; /* PCM_FLOAT */
245 else if (format.sampleFormat() == QAudioFormat::UInt8)
246 return 3; /* PCM_8BIT */
247 else if (format.sampleFormat() == QAudioFormat::Int16)
248 return 2; /* PCM_16BIT*/
249 else
250 return 1; /* DEFAULT */
251 }();
252
253 const int sampleRate = format.sampleRate();
254 const int minBufferSize = QJniObject::callStaticMethod<jint>("android/media/AudioTrack",
255 "getMinBufferSize",
256 "(III)I",
257 sampleRate,
258 channelConfig,
259 audioFormat);
260 return minBufferSize > 0 ? minBufferSize : format.bytesForDuration(DEFAULT_PERIOD_TIME_MS);
261}
262
264{
266 format.framesForDuration(MINIMUM_PERIOD_TIME_MS)));
267}
268
270{
271 static int isSupported = -1;
272
273 if (isSupported != -1)
274 return (isSupported == 1);
275
276 QJniObject ctx(QNativeInterface::QAndroidApplication::context());
277 if (!ctx.isValid())
278 return false;
279
280 QJniObject pm = ctx.callObjectMethod("getPackageManager", "()Landroid/content/pm/PackageManager;");
281 if (!pm.isValid())
282 return false;
283
284 QJniObject audioFeatureField = QJniObject::getStaticObjectField(
285 "android/content/pm/PackageManager",
286 "FEATURE_AUDIO_LOW_LATENCY",
287 "Ljava/lang/String;");
288 if (!audioFeatureField.isValid())
289 return false;
290
291 isSupported = pm.callMethod<jboolean>("hasSystemFeature",
292 "(Ljava/lang/String;)Z",
293 audioFeatureField.object());
294 return (isSupported == 1);
295}
296
298{
299 return qEnvironmentVariableIsSet("QT_OPENSL_INFO");
300}
301
302void QOpenSLESEngine::checkSupportedInputFormats()
303{
304 m_supportedInputChannelCounts = QList<int>() << 1;
305 m_supportedInputSampleRates.clear();
306
307 SLAndroidDataFormat_PCM_EX defaultFormat;
308 defaultFormat.formatType = SL_DATAFORMAT_PCM;
309 defaultFormat.numChannels = 1;
310 defaultFormat.sampleRate = SL_SAMPLINGRATE_44_1;
311 defaultFormat.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
312 defaultFormat.containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
313 defaultFormat.channelMask = SL_ANDROID_MAKE_INDEXED_CHANNEL_MASK(SL_SPEAKER_FRONT_CENTER);
314 defaultFormat.endianness = SL_BYTEORDER_LITTLEENDIAN;
315 defaultFormat.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
316
317 const SLuint32 rates[13] = { SL_SAMPLINGRATE_8,
318 SL_SAMPLINGRATE_11_025,
319 SL_SAMPLINGRATE_12,
320 SL_SAMPLINGRATE_16,
321 SL_SAMPLINGRATE_22_05,
322 SL_SAMPLINGRATE_24,
323 SL_SAMPLINGRATE_32,
324 SL_SAMPLINGRATE_44_1,
325 SL_SAMPLINGRATE_48,
326 SL_SAMPLINGRATE_64,
327 SL_SAMPLINGRATE_88_2,
328 SL_SAMPLINGRATE_96,
329 SL_SAMPLINGRATE_192 };
330
331
332 // Test sampling rates
333 for (size_t i = 0 ; i < std::size(rates); ++i) {
334 SLAndroidDataFormat_PCM_EX format = defaultFormat;
335 format.sampleRate = rates[i];
336
337 if (inputFormatIsSupported(format))
338 m_supportedInputSampleRates.append(rates[i] / 1000);
339
340 }
341
342 // Test if stereo is supported
343 {
344 SLAndroidDataFormat_PCM_EX format = defaultFormat;
345 format.numChannels = 2;
346 format.channelMask = SL_ANDROID_MAKE_INDEXED_CHANNEL_MASK(SL_SPEAKER_FRONT_LEFT
347 | SL_SPEAKER_FRONT_RIGHT);
348 if (inputFormatIsSupported(format))
349 m_supportedInputChannelCounts.append(2);
350 }
351
352 m_checkedInputFormats = true;
353}
354
355bool QOpenSLESEngine::inputFormatIsSupported(SLAndroidDataFormat_PCM_EX format)
356{
357 SLresult result;
358 SLObjectItf recorder = 0;
359 SLDataLocator_IODevice loc_dev = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,
360 SL_DEFAULTDEVICEID_AUDIOINPUT, NULL };
361 SLDataSource audioSrc = { &loc_dev, NULL };
362
363 SLDataLocator_AndroidSimpleBufferQueue loc_bq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };
364 SLDataSink audioSnk = { &loc_bq, &format };
365
366 // only ask permission when it is about to create the audiorecorder
368 return false;
369
370 result = (*m_engine)->CreateAudioRecorder(m_engine, &recorder, &audioSrc, &audioSnk, 0, 0, 0);
371 if (result == SL_RESULT_SUCCESS)
372 result = (*recorder)->Realize(recorder, false);
373
374 if (result == SL_RESULT_SUCCESS) {
375 (*recorder)->Destroy(recorder);
376 return true;
377 }
378
379 return false;
380}
The QAudioFormat class stores audio stream parameter information.
constexpr SampleFormat sampleFormat() const noexcept
Returns the current sample format.
constexpr bool isValid() const noexcept
Returns true if all of the parameters are valid.
constexpr int sampleRate() const noexcept
Returns the current sample rate in Hertz.
SampleFormat
Qt will always expect and use samples in the endianness of the host platform.
constexpr int channelCount() const noexcept
Returns the current channel count value.
constexpr int bytesPerSample() const noexcept
Returns the number of bytes required to represent one sample in this format.
Definition qlist.h:80
static int getLowLatencyBufferSize(const QAudioFormat &format)
static QOpenSLESEngine * instance()
static int getOutputValue(OutputValue type, int defaultValue=0)
static int getDefaultBufferSize(const QAudioFormat &format)
static bool supportsLowLatency()
static bool printDebugInfo()
QList< int > supportedSampleRates(QAudioDevice::Mode mode) const
QList< int > supportedChannelCounts(QAudioDevice::Mode mode) const
#define MINIMUM_PERIOD_TIME_MS
#define DEFAULT_PERIOD_TIME_MS
static bool hasRecordPermission()
static SLuint32 getChannelMask(unsigned channelCount)
Q_GLOBAL_STATIC(QOpenSLESEngine, openslesEngine)
#define CheckError(message)
#define SL_ANDROID_PCM_REPRESENTATION_INVALID