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.atomic.AtomicInteger;
12import android.content.Context;
13import android.media.AudioDeviceCallback;
14import android.media.AudioDeviceInfo;
15import android.media.AudioFormat;
16import android.media.AudioManager;
17import android.media.AudioRecord;
18import android.media.AudioTrack;
19import android.media.MediaRecorder;
20import android.os.Build;
21import android.os.Handler;
22import android.os.Looper;
23import android.util.Log;
24import android.util.Range;
26import org.qtproject.qt.android.UsedFromNativeCode;
28class QtAudioDeviceManager
30 private static final String TAG =
"QtAudioDeviceManager";
32 static private AudioManager m_audioManager =
null;
33 static private final AudioDevicesReceiver m_audioDevicesReceiver =
new AudioDevicesReceiver();
34 static private Handler handler =
new Handler(Looper.getMainLooper());
35 static private AudioRecord m_recorder =
null;
36 static private AudioTrack m_streamPlayer =
null;
37 static private Thread m_streamingThread =
null;
38 static private boolean m_isStreaming =
false;
39 static private boolean m_useSpeaker =
false;
40 static private final int m_sampleRate = 8000;
41 static private final int m_channels = AudioFormat.CHANNEL_CONFIGURATION_MONO;
42 static private final int m_audioFormat = AudioFormat.ENCODING_PCM_16BIT;
43 static private final int m_bufferSize = AudioRecord.getMinBufferSize(m_sampleRate, m_channels, m_audioFormat);
44 static private int m_currentOutputId = -1;
45 static private AtomicInteger m_scoCounter =
new AtomicInteger();
46 static private AtomicInteger m_communicationDeviceCounter =
new AtomicInteger();
47 static private AtomicInteger m_speakerphoneCounter =
new AtomicInteger();
48 static private int m_currentCommunicationDeviceId = -1;
50 static native
void onAudioInputDevicesUpdated();
51 static native
void onAudioOutputDevicesUpdated();
53 static private void updateDeviceList() {
54 if (m_currentOutputId != -1)
55 prepareAudioOutput(m_currentOutputId);
56 onAudioInputDevicesUpdated();
57 onAudioOutputDevicesUpdated();
60 private static class AudioDevicesReceiver
extends AudioDeviceCallback {
73 static void registerAudioHeadsetStateReceiver()
75 m_audioManager.registerAudioDeviceCallback(m_audioDevicesReceiver, handler);
78 static void unregisterAudioHeadsetStateReceiver()
80 m_audioManager.unregisterAudioDeviceCallback(m_audioDevicesReceiver);
85 m_audioManager = (AudioManager)
context.getSystemService(
Context.AUDIO_SERVICE);
90 return getAudioDevices(AudioManager.GET_DEVICES_OUTPUTS);
95 return getAudioDevices(AudioManager.GET_DEVICES_INPUTS);
99 static int getDefaultSampleRate()
101 String sampleRate = m_audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
102 return Integer.parseInt(sampleRate);
108 switch (deviceInfo.getType()) {
121 static boolean prepareAudioInput(
int id)
124 m_audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
126 if (deviceInfo.getId() ==
id) {
127 switch (deviceInfo.getType())
134 return prepareAudioDevice(deviceInfo, AudioManager.MODE_NORMAL);
143 private static void setInputMuted(
boolean mute)
146 m_audioManager.setMicrophoneMute(mute);
149 private static boolean isMicrophoneMute()
151 return m_audioManager.isMicrophoneMute();
154 private static String audioDeviceTypeToString(
int type)
165 return "Built in earpiece";
167 return "Built in microphone";
169 return "Built in speaker";
183 return "Line analog";
185 return "Line digital";
189 return "USB accessory";
191 return "Wired headphones";
193 return "Wired headset";
197 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
202 return "Unknown-Type";
206 private static final HashMap<Integer, Integer> priorityMap =
new HashMap<Integer, Integer>() {{
213 private static final int DEFAULT_PRIORITY = 4;
216 Comparator<AudioDeviceInfo> deviceTypeComparator =
new Comparator<AudioDeviceInfo>() {
219 return getPriority(device1) - getPriority(device2);
223 return priorityMap.getOrDefault(
device.getType(), DEFAULT_PRIORITY);
227 Arrays.sort(
devices, deviceTypeComparator);
232 ArrayList<AudioDeviceInfo> filteredDevices =
new ArrayList<>();
234 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
235 boolean builtInMicAdded =
false;
236 boolean bluetoothDeviceAdded =
false;
238 sortAudioDevices(audioDevices);
247 if (builtInMicAdded) {
256 builtInMicAdded =
true;
257 }
else if (isBluetoothDevice(deviceInfo)) {
258 if (bluetoothDeviceAdded) {
264 bluetoothDeviceAdded =
true;
267 filteredDevices.add(deviceInfo);
271 return filteredDevices.toArray(
new AudioDeviceInfo[filteredDevices.size()]);
274 final private static int [] bluetoothTypes = {
276 AudioDeviceInfo.TYPE_BLUETOOTH_SCO
278 final private static int [] wiredTypes = {
280 AudioDeviceInfo.TYPE_WIRED_HEADPHONES
287 .flatMapToInt(Arrays::stream)
293 AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_NORMAL;
298 return getCorrectModeIfContainsAnyOfType(audioDevices, bluetoothTypes);
303 return getCorrectModeIfContainsAnyOfType(audioDevices, wiredTypes);
308 return getCorrectModeIfContainsAnyOfType(audioDevices, bluetoothTypes, wiredTypes);
311 private static boolean prepareAudioOutput(
int id)
314 m_audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
316 if (deviceInfo.getId() ==
id) {
317 switch (deviceInfo.getType())
321 return prepareAudioDevice(deviceInfo, getModeForBluetooth(audioDevices));
323 return prepareAudioDevice(deviceInfo, getModeForSpeaker(audioDevices));
326 return prepareAudioDevice(deviceInfo, getModeForWired(audioDevices));
331 Log.w(TAG,
"Built in Earpiece may not work when "
332 +
"Wired Headphones are connected");
333 return prepareAudioDevice(deviceInfo, AudioManager.MODE_IN_CALL);
337 return prepareAudioDevice(deviceInfo, AudioManager.MODE_NORMAL);
354 if (isBluetoothDevice(
device)) {
358 boolean isSameType = communicationDevice.getType() ==
device.getType();
359 boolean isSameAddress = communicationDevice.getAddress().equals(
device.getAddress());
360 if (isSameType && isSameAddress)
361 return communicationDevice;
364 Log.w(TAG,
"No matching bluetooth output device found for " +
device);
371 if (m_currentCommunicationDeviceId == -1 ||
device ==
null) {
375 return m_currentCommunicationDeviceId ==
device.getId();
379 static void releaseAudioDevice(
int id) {
383 releaseAudioDevice(
device);
391 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
393 AudioDeviceInfo communicationDevice = getValidCommunicationDevice(deviceInfo);
394 if (!deviceIsCurrentCommunicationDevice(communicationDevice))
397 if (m_communicationDeviceCounter.decrementAndGet() == 0) {
398 m_audioManager.clearCommunicationDevice();
399 m_currentCommunicationDeviceId = -1;
401 }
else if (isBluetoothDevice(deviceInfo) && m_audioManager.isBluetoothScoOn()
402 && m_scoCounter.decrementAndGet() == 0) {
403 m_audioManager.stopBluetoothSco();
404 m_audioManager.setBluetoothScoOn(
false);
405 }
else if (deviceInfo.getType() ==
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
406 && m_speakerphoneCounter.decrementAndGet() == 0) {
407 m_audioManager.setSpeakerphoneOn(
false);
413 if (deviceInfo ==
null)
416 m_audioManager.setMode(
mode);
418 boolean isSink = deviceInfo.isSink();
420 m_currentOutputId = deviceInfo.getId();
422 boolean isBluetoothDevice = isBluetoothDevice(deviceInfo);
423 boolean isBluetoothSource = isBluetoothDevice && !isSink;
424 boolean isCommunicationMode = (
mode == AudioManager.MODE_IN_CALL
425 ||
mode == AudioManager.MODE_IN_COMMUNICATION);
427 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
428 if (!isBluetoothSource && !isCommunicationMode)
432 AudioDeviceInfo communicationDevice = getValidCommunicationDevice(deviceInfo);
433 if (communicationDevice ==
null) {
434 Log.w(TAG,
"No suitable communication device to set to enable communication via "
435 + deviceInfo.getId());
439 if (deviceIsCurrentCommunicationDevice(communicationDevice)) {
440 m_communicationDeviceCounter.incrementAndGet();
442 }
else if (m_audioManager.setCommunicationDevice(communicationDevice)) {
448 m_currentCommunicationDeviceId = communicationDevice.getId();
449 m_communicationDeviceCounter.set(1);
456 boolean isSpeakerphoneDevice = deviceInfo.getType() ==
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
458 if (isBluetoothSource && m_scoCounter.getAndIncrement() == 0) {
459 m_audioManager.startBluetoothSco();
460 m_audioManager.setBluetoothScoOn(
true);
461 }
else if (isSpeakerphoneDevice
462 && m_speakerphoneCounter.getAndIncrement() == 0) {
465 m_audioManager.setSpeakerphoneOn(
true);
471 private static void streamSound()
473 byte data[] =
new byte[m_bufferSize];
474 while (m_isStreaming) {
475 m_recorder.read(
data, 0, m_bufferSize);
476 m_streamPlayer.play();
477 m_streamPlayer.write(
data, 0, m_bufferSize);
478 m_streamPlayer.stop();
482 private static void startSoundStreaming(
int inputId,
int outputId)
485 stopSoundStreaming();
487 m_recorder =
new AudioRecord(MediaRecorder.AudioSource.DEFAULT, m_sampleRate, m_channels,
488 m_audioFormat, m_bufferSize);
489 m_streamPlayer =
new AudioTrack(AudioManager.STREAM_MUSIC, m_sampleRate, m_channels,
490 m_audioFormat, m_bufferSize, AudioTrack.MODE_STREAM);
494 if (deviceInfo.getId() == outputId) {
495 m_streamPlayer.setPreferredDevice(deviceInfo);
496 }
else if (deviceInfo.getId() == inputId) {
497 m_recorder.setPreferredDevice(deviceInfo);
501 m_recorder.startRecording();
502 m_isStreaming =
true;
504 m_streamingThread =
new Thread(
new Runnable() {
511 m_streamingThread.start();
514 private static void stopSoundStreaming()
519 m_isStreaming =
false;
521 m_streamingThread.join();
522 m_streamingThread =
null;
523 }
catch (InterruptedException e) {
527 m_recorder.release();
528 m_streamPlayer.release();
529 m_streamPlayer =
null;
void AudioDeviceInfo()
[Audio output state changed]
IOBluetoothDevice * device
static const QString context()
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]