Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
QtAudioDeviceManager.java
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
4package org.qtproject.qt.android.multimedia;
5
6import java.util.ArrayList;
7import android.bluetooth.BluetoothA2dp;
8import android.bluetooth.BluetoothAdapter;
9import android.bluetooth.BluetoothDevice;
10import android.bluetooth.BluetoothHeadset;
11import android.content.BroadcastReceiver;
12import android.content.Context;
13import android.content.Intent;
14import android.content.IntentFilter;
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.util.Log;
22
24{
25 private static final String TAG = "QtAudioDeviceManager";
26 static private AudioManager m_audioManager = null;
27 static private final AudioDevicesReceiver m_audioDevicesReceiver = new AudioDevicesReceiver();
28 static private AudioRecord m_recorder = null;
29 static private AudioTrack m_streamPlayer = null;
30 static private Thread m_streamingThread = null;
31 static private boolean m_isStreaming = false;
32 static private final int m_sampleRate = 8000;
33 static private final int m_channels = AudioFormat.CHANNEL_CONFIGURATION_MONO;
34 static private final int m_audioFormat = AudioFormat.ENCODING_PCM_16BIT;
35 static private final int m_bufferSize = AudioRecord.getMinBufferSize(m_sampleRate, m_channels, m_audioFormat);
36
37 public static native void onAudioInputDevicesUpdated();
38 public static native void onAudioOutputDevicesUpdated();
39
40 static private class AudioDevicesReceiver extends BroadcastReceiver
41 {
42 @Override
43 public void onReceive(Context context, Intent intent) {
46 }
47 }
48
49 public static void registerAudioHeadsetStateReceiver(Context context)
50 {
51 IntentFilter audioDevicesFilter = new IntentFilter();
52 audioDevicesFilter.addAction(AudioManager.ACTION_HEADSET_PLUG);
53 audioDevicesFilter.addAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
54 audioDevicesFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
55 audioDevicesFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
56 audioDevicesFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
57 audioDevicesFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
58 audioDevicesFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
59 audioDevicesFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
60 audioDevicesFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
61 audioDevicesFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
62 audioDevicesFilter.addAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
63
64 context.registerReceiver(m_audioDevicesReceiver, audioDevicesFilter);
65 }
66
67 static public void setContext(Context context)
68 {
69 m_audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
70 }
71
72 private static String[] getAudioOutputDevices()
73 {
74 return getAudioDevices(AudioManager.GET_DEVICES_OUTPUTS);
75 }
76
77 private static String[] getAudioInputDevices()
78 {
79 return getAudioDevices(AudioManager.GET_DEVICES_INPUTS);
80 }
81
82 private static boolean isBluetoothDevice(AudioDeviceInfo deviceInfo)
83 {
84 switch (deviceInfo.getType()) {
85 case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
86 case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
87 return true;
88 default:
89 return false;
90 }
91 }
92
93 private static boolean setAudioInput(MediaRecorder recorder, int id)
94 {
95 if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.P)
96 return false;
97
98 final AudioDeviceInfo[] audioDevices =
99 m_audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
100
101 for (AudioDeviceInfo deviceInfo : audioDevices) {
102 if (deviceInfo.getId() != id)
103 continue;
104
105 boolean isPreferred = recorder.setPreferredDevice(deviceInfo);
106 if (isPreferred && isBluetoothDevice(deviceInfo)) {
107 m_audioManager.startBluetoothSco();
108 m_audioManager.setBluetoothScoOn(true);
109 }
110
111 return isPreferred;
112 }
113
114 return false;
115 }
116
117 private static void setInputMuted(boolean mute)
118 {
119 // This method mutes the microphone across the entire platform
120 m_audioManager.setMicrophoneMute(mute);
121 }
122
123 private static boolean isMicrophoneMute()
124 {
125 return m_audioManager.isMicrophoneMute();
126 }
127
128 private static String audioDeviceTypeToString(int type)
129 {
130 switch (type)
131 {
132 case AudioDeviceInfo.TYPE_AUX_LINE:
133 return "AUX Line";
134 case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
135 case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
136 return "Bluetooth";
137 case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
138 return "Built in earpiece";
139 case AudioDeviceInfo.TYPE_BUILTIN_MIC:
140 return "Built in microphone";
141 case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
142 return "Built in speaker";
143 case AudioDeviceInfo.TYPE_DOCK:
144 return "Dock";
145 case AudioDeviceInfo.TYPE_FM:
146 return "FM";
147 case AudioDeviceInfo.TYPE_FM_TUNER:
148 return "FM TUNER";
149 case AudioDeviceInfo.TYPE_HDMI:
150 return "HDMI";
151 case AudioDeviceInfo.TYPE_HDMI_ARC:
152 return "HDMI ARC";
153 case AudioDeviceInfo.TYPE_IP:
154 return "IP";
155 case AudioDeviceInfo.TYPE_LINE_ANALOG:
156 return "Line analog";
157 case AudioDeviceInfo.TYPE_LINE_DIGITAL:
158 return "Line digital";
159 case AudioDeviceInfo.TYPE_TV_TUNER:
160 return "TV tuner";
161 case AudioDeviceInfo.TYPE_USB_ACCESSORY:
162 return "USB accessory";
163 case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
164 return "Wired headphones";
165 case AudioDeviceInfo.TYPE_WIRED_HEADSET:
166 return "Wired headset";
167 case AudioDeviceInfo.TYPE_TELEPHONY:
168 case AudioDeviceInfo.TYPE_UNKNOWN:
169 default:
170 return "Unknown-Type";
171 }
172 }
173
174 private static String[] getAudioDevices(int type)
175 {
176 ArrayList<String> devices = new ArrayList<>();
177
178 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
179 boolean builtInMicAdded = false;
180 boolean bluetoothDeviceAdded = false;
181 for (AudioDeviceInfo deviceInfo : m_audioManager.getDevices(type)) {
182 String deviceType = audioDeviceTypeToString(deviceInfo.getType());
183
184 if (deviceType.equals(audioDeviceTypeToString(AudioDeviceInfo.TYPE_UNKNOWN))) {
185 // Not supported device type
186 continue;
187 } else if (deviceType.equals(audioDeviceTypeToString(AudioDeviceInfo.TYPE_BUILTIN_MIC))) {
188 if (builtInMicAdded) {
189 // Built in mic already added. Second built in mic is CAMCORDER, but there
190 // is no reliable way of selecting it. AudioSource.MIC usually means the
191 // primary mic. AudioSource.CAMCORDER source might mean the secondary mic,
192 // but there's no guarantee. It depends e.g. on the physical placement
193 // of the mics. That's why we will not add built in microphone twice.
194 // Should we?
195 continue;
196 }
197 builtInMicAdded = true;
198 } else if (isBluetoothDevice(deviceInfo)) {
199 if (bluetoothDeviceAdded) {
200 // Bluetooth device already added. Second device is just a different
201 // technology profille (like A2DP or SCO). We should not add the same
202 // device twice. Should we?
203 continue;
204 }
205 bluetoothDeviceAdded = true;
206 }
207
208 devices.add(deviceInfo.getId() + ":" + deviceType + " ("
209 + deviceInfo.getProductName().toString() +")");
210 }
211 }
212
213 String[] ret = new String[devices.size()];
214 ret = devices.toArray(ret);
215 return ret;
216 }
217
218 private static boolean setAudioOutput(int id)
219 {
220 final AudioDeviceInfo[] audioDevices =
221 m_audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
222 for (AudioDeviceInfo deviceInfo : audioDevices) {
223 if (deviceInfo.getId() == id) {
224 switch (deviceInfo.getType())
225 {
226 case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
227 case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
228 setAudioOutput(AudioManager.MODE_IN_COMMUNICATION, true, false);
229 return true;
230 case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
231 setAudioOutput(AudioManager.STREAM_MUSIC, false, true);
232 return true;
233 case AudioDeviceInfo.TYPE_WIRED_HEADSET:
234 case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
235 setAudioOutput(AudioManager.MODE_IN_COMMUNICATION, false, false);
236 return true;
237 case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
238 // It doesn't work when WIRED HEADPHONES are connected
239 // Earpiece has the lowest priority and setWiredHeadsetOn(boolean)
240 // method to force it is deprecated
241 Log.w(TAG, "Built in Earpiece may not work when "
242 + "Wired Headphones are connected");
243 setAudioOutput(AudioManager.MODE_IN_CALL, false, false);
244 return true;
245 default:
246 return false;
247 }
248 }
249 }
250 return false;
251 }
252
253 private static void setAudioOutput(int mode, boolean bluetoothOn, boolean speakerOn)
254 {
255 m_audioManager.setMode(mode);
256 if (bluetoothOn) {
257 m_audioManager.startBluetoothSco();
258 } else {
259 m_audioManager.stopBluetoothSco();
260 }
261 m_audioManager.setBluetoothScoOn(bluetoothOn);
262 m_audioManager.setSpeakerphoneOn(speakerOn);
263
264 }
265
266 private static void streamSound()
267 {
268 byte data[] = new byte[m_bufferSize];
269 while (m_isStreaming) {
270 m_recorder.read(data, 0, m_bufferSize);
271 m_streamPlayer.play();
272 m_streamPlayer.write(data, 0, m_bufferSize);
273 m_streamPlayer.stop();
274 }
275 }
276
277 private static void startSoundStreaming(int inputId, int outputId)
278 {
279 if (m_isStreaming)
280 stopSoundStreaming();
281
282 m_recorder = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, m_sampleRate, m_channels,
283 m_audioFormat, m_bufferSize);
284 m_streamPlayer = new AudioTrack(AudioManager.STREAM_MUSIC, m_sampleRate, m_channels,
285 m_audioFormat, m_bufferSize, AudioTrack.MODE_STREAM);
286
287 final AudioDeviceInfo[] devices = m_audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
288 for (AudioDeviceInfo deviceInfo : devices) {
289 if (deviceInfo.getId() == outputId) {
290 m_streamPlayer.setPreferredDevice(deviceInfo);
291 } else if (deviceInfo.getId() == inputId) {
292 m_recorder.setPreferredDevice(deviceInfo);
293 }
294 }
295
296 m_recorder.startRecording();
297 m_isStreaming = true;
298
299 m_streamingThread = new Thread(new Runnable() {
300 public void run() {
301 streamSound();
302 }
303 });
304
305 m_streamingThread.start();
306 }
307
308 private static void stopSoundStreaming()
309 {
310 if (!m_isStreaming)
311 return;
312
313 m_isStreaming = false;
314 try {
315 m_streamingThread.join();
316 m_streamingThread = null;
317 } catch (InterruptedException e) {
318 e.printStackTrace();
319 }
320 m_recorder.stop();
321 m_recorder.release();
322 m_streamPlayer.release();
323 m_streamPlayer = null;
324 m_recorder = null;
325 }
326}
void AudioDeviceInfo()
[Audio output state changed]
Definition audio.cpp:160
QMediaRecorder * recorder
Definition camera.cpp:20
double e
@ BluetoothAdapter
@ BluetoothDevice
auto run(QThreadPool *pool, Function &&f, Args &&...args)
static void * context
EGLDeviceEXT * devices
return ret
GLenum mode
GLenum type
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
static QInputDevice::DeviceType deviceType(const UINT cursorType)