diff options
Diffstat (limited to 'src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java')
-rw-r--r-- | src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java new file mode 100644 index 000000000..2b6fcc2dc --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java @@ -0,0 +1,361 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +package org.qtproject.qt.android.multimedia; + +import java.util.ArrayList; +import android.content.Context; +import android.media.AudioDeviceCallback; +import android.media.AudioDeviceInfo; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioRecord; +import android.media.AudioTrack; +import android.media.MediaRecorder; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +public class QtAudioDeviceManager +{ + private static final String TAG = "QtAudioDeviceManager"; + static private AudioManager m_audioManager = null; + static private final AudioDevicesReceiver m_audioDevicesReceiver = new AudioDevicesReceiver(); + static private Handler handler = new Handler(Looper.getMainLooper()); + static private AudioRecord m_recorder = null; + static private AudioTrack m_streamPlayer = null; + static private Thread m_streamingThread = null; + static private boolean m_isStreaming = false; + static private boolean m_useSpeaker = false; + static private final int m_sampleRate = 8000; + static private final int m_channels = AudioFormat.CHANNEL_CONFIGURATION_MONO; + static private final int m_audioFormat = AudioFormat.ENCODING_PCM_16BIT; + static private final int m_bufferSize = AudioRecord.getMinBufferSize(m_sampleRate, m_channels, m_audioFormat); + + public static native void onAudioInputDevicesUpdated(); + public static native void onAudioOutputDevicesUpdated(); + + static private void updateDeviceList() { + onAudioInputDevicesUpdated(); + onAudioOutputDevicesUpdated(); + if (m_useSpeaker) { + final AudioDeviceInfo[] audioDevices = + m_audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); + setAudioOutput(getModeForSpeaker(audioDevices), false, true); + } + } + + private static class AudioDevicesReceiver extends AudioDeviceCallback { + @Override + public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { + updateDeviceList(); + } + + @Override + public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { + updateDeviceList(); + } + } + + + public static void registerAudioHeadsetStateReceiver() + { + m_audioManager.registerAudioDeviceCallback(m_audioDevicesReceiver, handler); + } + + public static void unregisterAudioHeadsetStateReceiver() + { + m_audioManager.unregisterAudioDeviceCallback(m_audioDevicesReceiver); + } + + static public void setContext(Context context) + { + m_audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); + } + + private static String[] getAudioOutputDevices() + { + return getAudioDevices(AudioManager.GET_DEVICES_OUTPUTS); + } + + private static String[] getAudioInputDevices() + { + return getAudioDevices(AudioManager.GET_DEVICES_INPUTS); + } + + private static boolean isBluetoothDevice(AudioDeviceInfo deviceInfo) + { + switch (deviceInfo.getType()) { + case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: + case AudioDeviceInfo.TYPE_BLUETOOTH_SCO: + return true; + default: + return false; + } + } + + private static boolean setAudioInput(MediaRecorder recorder, int id) + { + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.P) + return false; + + final AudioDeviceInfo[] audioDevices = + m_audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS); + + for (AudioDeviceInfo deviceInfo : audioDevices) { + if (deviceInfo.getId() != id) + continue; + + boolean isPreferred = recorder.setPreferredDevice(deviceInfo); + if (isPreferred && isBluetoothDevice(deviceInfo)) { + m_audioManager.startBluetoothSco(); + m_audioManager.setBluetoothScoOn(true); + } + + return isPreferred; + } + + return false; + } + + private static void setInputMuted(boolean mute) + { + // This method mutes the microphone across the entire platform + m_audioManager.setMicrophoneMute(mute); + } + + private static boolean isMicrophoneMute() + { + return m_audioManager.isMicrophoneMute(); + } + + private static String audioDeviceTypeToString(int type) + { + // API <= 23 types + switch (type) + { + case AudioDeviceInfo.TYPE_AUX_LINE: + return "AUX Line"; + case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: + case AudioDeviceInfo.TYPE_BLUETOOTH_SCO: + return "Bluetooth"; + case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE: + return "Built in earpiece"; + case AudioDeviceInfo.TYPE_BUILTIN_MIC: + return "Built in microphone"; + case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: + return "Built in speaker"; + case AudioDeviceInfo.TYPE_DOCK: + return "Dock"; + case AudioDeviceInfo.TYPE_FM: + return "FM"; + case AudioDeviceInfo.TYPE_FM_TUNER: + return "FM TUNER"; + case AudioDeviceInfo.TYPE_HDMI: + return "HDMI"; + case AudioDeviceInfo.TYPE_HDMI_ARC: + return "HDMI ARC"; + case AudioDeviceInfo.TYPE_IP: + return "IP"; + case AudioDeviceInfo.TYPE_LINE_ANALOG: + return "Line analog"; + case AudioDeviceInfo.TYPE_LINE_DIGITAL: + return "Line digital"; + case AudioDeviceInfo.TYPE_TV_TUNER: + return "TV tuner"; + case AudioDeviceInfo.TYPE_USB_ACCESSORY: + return "USB accessory"; + case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: + return "Wired headphones"; + case AudioDeviceInfo.TYPE_WIRED_HEADSET: + return "Wired headset"; + } + + // API 24 + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + if (type == AudioDeviceInfo.TYPE_BUS) + return "Bus"; + } + + return "Unknown-Type"; + + } + + private static String[] getAudioDevices(int type) + { + ArrayList<String> devices = new ArrayList<>(); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + boolean builtInMicAdded = false; + boolean bluetoothDeviceAdded = false; + for (AudioDeviceInfo deviceInfo : m_audioManager.getDevices(type)) { + String deviceType = audioDeviceTypeToString(deviceInfo.getType()); + + if (deviceType.equals(audioDeviceTypeToString(AudioDeviceInfo.TYPE_UNKNOWN))) { + // Not supported device type + continue; + } else if (deviceType.equals(audioDeviceTypeToString(AudioDeviceInfo.TYPE_BUILTIN_MIC))) { + if (builtInMicAdded) { + // Built in mic already added. Second built in mic is CAMCORDER, but there + // is no reliable way of selecting it. AudioSource.MIC usually means the + // primary mic. AudioSource.CAMCORDER source might mean the secondary mic, + // but there's no guarantee. It depends e.g. on the physical placement + // of the mics. That's why we will not add built in microphone twice. + // Should we? + continue; + } + builtInMicAdded = true; + } else if (isBluetoothDevice(deviceInfo)) { + if (bluetoothDeviceAdded) { + // Bluetooth device already added. Second device is just a different + // technology profille (like A2DP or SCO). We should not add the same + // device twice. Should we? + continue; + } + bluetoothDeviceAdded = true; + } + + devices.add(deviceInfo.getId() + ":" + deviceType + " (" + + deviceInfo.getProductName().toString() +")"); + } + } + + String[] ret = new String[devices.size()]; + ret = devices.toArray(ret); + return ret; + } + + private static int getModeForSpeaker(AudioDeviceInfo[] audioDevices) + { + // If we want to force device to use speaker when Bluetooth or Wiread headset is connected, + // we need to use MODE_IN_COMMUNICATION. Otherwise the MODE_NORMAL can be used. + for (AudioDeviceInfo deviceInfo : audioDevices) { + switch (deviceInfo.getType()) { + case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: + case AudioDeviceInfo.TYPE_BLUETOOTH_SCO: + case AudioDeviceInfo.TYPE_WIRED_HEADSET: + case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: + return AudioManager.MODE_IN_COMMUNICATION; + default: break; + } + } + return AudioManager.MODE_NORMAL; + } + + + private static boolean setAudioOutput(int id) + { + m_useSpeaker = false; + final AudioDeviceInfo[] audioDevices = + m_audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); + for (AudioDeviceInfo deviceInfo : audioDevices) { + if (deviceInfo.getId() == id) { + switch (deviceInfo.getType()) + { + case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: + case AudioDeviceInfo.TYPE_BLUETOOTH_SCO: + setAudioOutput(AudioManager.MODE_IN_COMMUNICATION, true, false); + return true; + case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: + m_useSpeaker = true; + setAudioOutput(getModeForSpeaker(audioDevices), false, true); + return true; + case AudioDeviceInfo.TYPE_WIRED_HEADSET: + case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: + setAudioOutput(AudioManager.MODE_IN_COMMUNICATION, false, false); + return true; + case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE: + // It doesn't work when WIRED HEADPHONES are connected + // Earpiece has the lowest priority and setWiredHeadsetOn(boolean) + // method to force it is deprecated + Log.w(TAG, "Built in Earpiece may not work when " + + "Wired Headphones are connected"); + setAudioOutput(AudioManager.MODE_IN_CALL, false, false); + return true; + case AudioDeviceInfo.TYPE_HDMI: + case AudioDeviceInfo.TYPE_HDMI_ARC: + case AudioDeviceInfo.TYPE_HDMI_EARC: + setAudioOutput(AudioManager.MODE_NORMAL, false, false); + return true; + default: + return false; + } + } + } + return false; + } + + private static void setAudioOutput(int mode, boolean bluetoothOn, boolean speakerOn) + { + m_audioManager.setMode(mode); + if (bluetoothOn) { + m_audioManager.startBluetoothSco(); + } else { + m_audioManager.stopBluetoothSco(); + } + m_audioManager.setBluetoothScoOn(bluetoothOn); + m_audioManager.setSpeakerphoneOn(speakerOn); + + } + + private static void streamSound() + { + byte data[] = new byte[m_bufferSize]; + while (m_isStreaming) { + m_recorder.read(data, 0, m_bufferSize); + m_streamPlayer.play(); + m_streamPlayer.write(data, 0, m_bufferSize); + m_streamPlayer.stop(); + } + } + + private static void startSoundStreaming(int inputId, int outputId) + { + if (m_isStreaming) + stopSoundStreaming(); + + m_recorder = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, m_sampleRate, m_channels, + m_audioFormat, m_bufferSize); + m_streamPlayer = new AudioTrack(AudioManager.STREAM_MUSIC, m_sampleRate, m_channels, + m_audioFormat, m_bufferSize, AudioTrack.MODE_STREAM); + + final AudioDeviceInfo[] devices = m_audioManager.getDevices(AudioManager.GET_DEVICES_ALL); + for (AudioDeviceInfo deviceInfo : devices) { + if (deviceInfo.getId() == outputId) { + m_streamPlayer.setPreferredDevice(deviceInfo); + } else if (deviceInfo.getId() == inputId) { + m_recorder.setPreferredDevice(deviceInfo); + } + } + + m_recorder.startRecording(); + m_isStreaming = true; + + m_streamingThread = new Thread(new Runnable() { + public void run() { + streamSound(); + } + }); + + m_streamingThread.start(); + } + + private static void stopSoundStreaming() + { + if (!m_isStreaming) + return; + + m_isStreaming = false; + try { + m_streamingThread.join(); + m_streamingThread = null; + } catch (InterruptedException e) { + e.printStackTrace(); + } + m_recorder.stop(); + m_recorder.release(); + m_streamPlayer.release(); + m_streamPlayer = null; + m_recorder = null; + } +} |