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