4package org.qtproject.qt.android.multimedia;
6import java.util.ArrayList;
7import java.util.Arrays;
8import java.util.Comparator;
9import java.util.HashMap;
11import java.util.concurrent.CountDownLatch;
12import java.util.concurrent.atomic.AtomicInteger;
13import android.content.Context;
14import android.media.AudioDeviceCallback;
15import android.media.AudioDeviceInfo;
16import android.media.AudioFormat;
17import android.media.AudioManager;
18import android.media.AudioRecord;
19import android.media.AudioTrack;
20import android.media.MediaRecorder;
21import android.os.Build;
22import android.os.Handler;
23import android.os.Looper;
24import android.util.Log;
25import android.util.Range;
27import org.qtproject.qt.android.UsedFromNativeCode;
29class QtAudioDeviceManager
31 private static final String TAG =
"QtAudioDeviceManager";
33 static private AudioManager m_audioManager =
null;
34 static private final AudioDevicesReceiver m_audioDevicesReceiver =
new AudioDevicesReceiver();
35 static private Handler handler =
new Handler(Looper.getMainLooper());
36 static private AudioRecord m_recorder =
null;
37 static private AudioTrack m_streamPlayer =
null;
38 static private Thread m_streamingThread =
null;
39 static private boolean m_isStreaming =
false;
40 static private boolean m_useSpeaker =
false;
41 static private final int m_sampleRate = 8000;
42 static private final int m_channels = AudioFormat.CHANNEL_CONFIGURATION_MONO;
43 static private final int m_audioFormat = AudioFormat.ENCODING_PCM_16BIT;
44 static private final int m_bufferSize = AudioRecord.getMinBufferSize(m_sampleRate, m_channels, m_audioFormat);
45 static private int m_currentOutputId = -1;
46 static private AtomicInteger m_scoCounter =
new AtomicInteger();
47 static private AtomicInteger m_communicationDeviceCounter =
new AtomicInteger();
48 static private AtomicInteger m_speakerphoneCounter =
new AtomicInteger();
49 static private int m_currentCommunicationDeviceId = -1;
57 static private long m_qAudioDevicesNativePtr = 0;
59 static native
void onAudioInputDevicesUpdated(
long qAudioDeviceNativePtr);
60 static native
void onAudioOutputDevicesUpdated(
long qAudioDeviceNativePtr);
62 static private void updateDeviceList() {
63 assert(handler.getLooper().isCurrentThread());
65 assert(m_qAudioDevicesNativePtr != 0);
67 if (m_currentOutputId != -1)
68 prepareAudioOutput(m_currentOutputId);
69 onAudioInputDevicesUpdated(m_qAudioDevicesNativePtr);
70 onAudioOutputDevicesUpdated(m_qAudioDevicesNativePtr);
73 private static class AudioDevicesReceiver
extends AudioDeviceCallback {
88 static void qAndroidAudioDevicesConstructed(
long nativePtr)
91 m_qAudioDevicesNativePtr = nativePtr;
92 m_audioManager.registerAudioDeviceCallback(m_audioDevicesReceiver, handler);
96 static void qAndroidAudioDevicesDestroyed()
98 assert(!handler.getLooper().isCurrentThread());
102 final CountDownLatch latch =
new CountDownLatch(1);
103 final boolean postSuccess = handler.post(() -> {
104 m_audioManager.unregisterAudioDeviceCallback(m_audioDevicesReceiver);
105 assert(m_qAudioDevicesNativePtr != 0);
106 m_qAudioDevicesNativePtr = 0;
113 "Unable to post cleanup job to corresponding thread during " +
114 "QAndroidAudioDevices cleanup.");
120 }
catch (Exception e) {
123 "Unable to wait for cleanup job to finish on corresponding thread during" +
124 "QAndroidAudioDevices cleanup.");
132 m_audioManager = (AudioManager)
context.getSystemService(
Context.AUDIO_SERVICE);
137 return getAudioDevices(AudioManager.GET_DEVICES_OUTPUTS);
142 return getAudioDevices(AudioManager.GET_DEVICES_INPUTS);
148 final AudioDeviceInfo[] audioDevices = m_audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
151 if (deviceInfo.getId() ==
id)
159 static int getDefaultSampleRate()
161 String sampleRate = m_audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
162 return Integer.parseInt(sampleRate);
168 switch (deviceInfo.getType()) {
178 static boolean prepareAudioInput(
int id)
181 m_audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
183 if (deviceInfo.getId() ==
id) {
184 switch (deviceInfo.getType())
191 return prepareAudioDevice(deviceInfo, AudioManager.MODE_NORMAL);
200 private static void setInputMuted(
boolean mute)
203 m_audioManager.setMicrophoneMute(mute);
206 private static boolean isMicrophoneMute()
208 return m_audioManager.isMicrophoneMute();
211 private static String audioDeviceTypeToString(
int type)
222 return "Built in earpiece";
224 return "Built in microphone";
226 return "Built in speaker";
240 return "Line analog";
242 return "Line digital";
246 return "USB accessory";
248 return "Wired headphones";
250 return "Wired headset";
254 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
259 return "Unknown-Type";
263 private static final HashMap<Integer, Integer> priorityMap =
new HashMap<Integer, Integer>() {{
270 private static final int DEFAULT_PRIORITY = 4;
273 Comparator<AudioDeviceInfo> deviceTypeComparator =
new Comparator<AudioDeviceInfo>() {
276 return getPriority(device1) - getPriority(device2);
280 return priorityMap.getOrDefault(
device.getType(), DEFAULT_PRIORITY);
284 Arrays.sort(
devices, deviceTypeComparator);
289 ArrayList<AudioDeviceInfo> filteredDevices =
new ArrayList<>();
291 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
292 boolean builtInMicAdded =
false;
293 boolean bluetoothDeviceAdded =
false;
295 sortAudioDevices(audioDevices);
304 if (builtInMicAdded) {
313 builtInMicAdded =
true;
314 }
else if (isBluetoothDevice(deviceInfo)) {
315 if (bluetoothDeviceAdded) {
321 bluetoothDeviceAdded =
true;
324 filteredDevices.add(deviceInfo);
328 return filteredDevices.toArray(
new AudioDeviceInfo[filteredDevices.size()]);
331 final private static int [] bluetoothTypes = {
333 AudioDeviceInfo.TYPE_BLUETOOTH_SCO
335 final private static int [] wiredTypes = {
337 AudioDeviceInfo.TYPE_WIRED_HEADPHONES
344 .flatMapToInt(Arrays::stream)
350 AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_NORMAL;
355 return getCorrectModeIfContainsAnyOfType(audioDevices, bluetoothTypes);
360 return getCorrectModeIfContainsAnyOfType(audioDevices, wiredTypes);
365 return getCorrectModeIfContainsAnyOfType(audioDevices, bluetoothTypes, wiredTypes);
368 private static boolean prepareAudioOutput(
int id)
371 m_audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
373 if (deviceInfo.getId() ==
id) {
374 switch (deviceInfo.getType())
378 return prepareAudioDevice(deviceInfo, getModeForBluetooth(audioDevices));
380 return prepareAudioDevice(deviceInfo, getModeForSpeaker(audioDevices));
383 return prepareAudioDevice(deviceInfo, getModeForWired(audioDevices));
388 Log.w(TAG,
"Built in Earpiece may not work when "
389 +
"Wired Headphones are connected");
390 return prepareAudioDevice(deviceInfo, AudioManager.MODE_IN_CALL);
394 return prepareAudioDevice(deviceInfo, AudioManager.MODE_NORMAL);
411 if (isBluetoothDevice(
device)) {
415 boolean isSameType = communicationDevice.getType() ==
device.getType();
416 boolean isSameAddress = communicationDevice.getAddress().equals(
device.getAddress());
417 if (isSameType && isSameAddress)
418 return communicationDevice;
421 Log.w(TAG,
"No matching bluetooth output device found for " +
device);
428 if (m_currentCommunicationDeviceId == -1 ||
device ==
null) {
432 return m_currentCommunicationDeviceId ==
device.getId();
436 static void releaseAudioDevice(
int id) {
440 releaseAudioDevice(
device);
448 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
450 AudioDeviceInfo communicationDevice = getValidCommunicationDevice(deviceInfo);
451 if (!deviceIsCurrentCommunicationDevice(communicationDevice))
454 if (m_communicationDeviceCounter.decrementAndGet() == 0) {
455 m_audioManager.clearCommunicationDevice();
456 m_currentCommunicationDeviceId = -1;
458 }
else if (isBluetoothDevice(deviceInfo) && m_audioManager.isBluetoothScoOn()
459 && m_scoCounter.decrementAndGet() == 0) {
460 m_audioManager.stopBluetoothSco();
461 m_audioManager.setBluetoothScoOn(
false);
462 }
else if (deviceInfo.getType() ==
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
463 && m_speakerphoneCounter.decrementAndGet() == 0) {
464 m_audioManager.setSpeakerphoneOn(
false);
470 if (deviceInfo ==
null)
473 m_audioManager.setMode(
mode);
475 boolean isSink = deviceInfo.isSink();
477 m_currentOutputId = deviceInfo.getId();
479 boolean isBluetoothDevice = isBluetoothDevice(deviceInfo);
480 boolean isBluetoothSource = isBluetoothDevice && !isSink;
481 boolean isCommunicationMode = (
mode == AudioManager.MODE_IN_CALL
482 ||
mode == AudioManager.MODE_IN_COMMUNICATION);
484 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
485 if (!isBluetoothSource && !isCommunicationMode)
489 AudioDeviceInfo communicationDevice = getValidCommunicationDevice(deviceInfo);
490 if (communicationDevice ==
null) {
491 Log.w(TAG,
"No suitable communication device to set to enable communication via "
492 + deviceInfo.getId());
496 if (deviceIsCurrentCommunicationDevice(communicationDevice)) {
497 m_communicationDeviceCounter.incrementAndGet();
499 }
else if (m_audioManager.setCommunicationDevice(communicationDevice)) {
505 m_currentCommunicationDeviceId = communicationDevice.getId();
506 m_communicationDeviceCounter.set(1);
513 boolean isSpeakerphoneDevice = deviceInfo.getType() ==
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
515 if (isBluetoothSource && m_scoCounter.getAndIncrement() == 0) {
516 m_audioManager.startBluetoothSco();
517 m_audioManager.setBluetoothScoOn(
true);
518 }
else if (isSpeakerphoneDevice
519 && m_speakerphoneCounter.getAndIncrement() == 0) {
522 m_audioManager.setSpeakerphoneOn(
true);
528 private static void streamSound()
530 byte data[] =
new byte[m_bufferSize];
531 while (m_isStreaming) {
532 m_recorder.read(
data, 0, m_bufferSize);
533 m_streamPlayer.play();
534 m_streamPlayer.write(
data, 0, m_bufferSize);
535 m_streamPlayer.stop();
539 private static void startSoundStreaming(
int inputId,
int outputId)
542 stopSoundStreaming();
544 m_recorder =
new AudioRecord(MediaRecorder.AudioSource.DEFAULT, m_sampleRate, m_channels,
545 m_audioFormat, m_bufferSize);
546 m_streamPlayer =
new AudioTrack(AudioManager.STREAM_MUSIC, m_sampleRate, m_channels,
547 m_audioFormat, m_bufferSize, AudioTrack.MODE_STREAM);
551 if (deviceInfo.getId() == outputId) {
552 m_streamPlayer.setPreferredDevice(deviceInfo);
553 }
else if (deviceInfo.getId() == inputId) {
554 m_recorder.setPreferredDevice(deviceInfo);
558 m_recorder.startRecording();
559 m_isStreaming =
true;
561 m_streamingThread =
new Thread(
new Runnable() {
568 m_streamingThread.start();
571 private static void stopSoundStreaming()
576 m_isStreaming =
false;
578 m_streamingThread.join();
579 m_streamingThread =
null;
580 }
catch (InterruptedException e) {
584 m_recorder.release();
585 m_streamPlayer.release();
586 m_streamPlayer =
null;
void AudioDeviceInfo()
[Audio callback capture setup peak meter]
IOBluetoothDevice * device
static const QString context()
Catch::Generators::GeneratorWrapper< T > handler(Catch::Generators::GeneratorWrapper< T > &&generator)
Returns a generator wrapping generator that ensures that changes its semantics so that the first call...
QTCONCURRENT_RUN_NODISCARD auto run(QThreadPool *pool, Function &&f, Args &&...args)
GLsizei GLenum GLenum * types
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
[0]
static int compare(quint64 a, quint64 b)
static QInputDevice::DeviceType deviceType(const UINT cursorType)
manager put(request, myData, this, [this](QRestReply &reply) { if(reply.isSuccess()) })
[5]