xref: /aosp_15_r20/external/webrtc/sdk/android/src/java/org/webrtc/audio/WebRtcAudioUtils.java (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 package org.webrtc.audio;
12 
13 import static android.media.AudioManager.MODE_IN_CALL;
14 import static android.media.AudioManager.MODE_IN_COMMUNICATION;
15 import static android.media.AudioManager.MODE_NORMAL;
16 import static android.media.AudioManager.MODE_RINGTONE;
17 
18 import android.annotation.TargetApi;
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.media.AudioDeviceInfo;
22 import android.media.AudioFormat;
23 import android.media.AudioManager;
24 import android.media.MediaRecorder.AudioSource;
25 import android.os.Build;
26 import java.lang.Thread;
27 import java.util.Arrays;
28 import org.webrtc.Logging;
29 
30 final class WebRtcAudioUtils {
31   private static final String TAG = "WebRtcAudioUtilsExternal";
32 
33   // Helper method for building a string of thread information.
getThreadInfo()34   public static String getThreadInfo() {
35     return "@[name=" + Thread.currentThread().getName() + ", id=" + Thread.currentThread().getId()
36         + "]";
37   }
38 
39   // Returns true if we're running on emulator.
runningOnEmulator()40   public static boolean runningOnEmulator() {
41     return Build.HARDWARE.equals("goldfish") && Build.BRAND.startsWith("generic_");
42   }
43 
44   // Information about the current build, taken from system properties.
logDeviceInfo(String tag)45   static void logDeviceInfo(String tag) {
46     Logging.d(tag,
47         "Android SDK: " + Build.VERSION.SDK_INT + ", "
48             + "Release: " + Build.VERSION.RELEASE + ", "
49             + "Brand: " + Build.BRAND + ", "
50             + "Device: " + Build.DEVICE + ", "
51             + "Id: " + Build.ID + ", "
52             + "Hardware: " + Build.HARDWARE + ", "
53             + "Manufacturer: " + Build.MANUFACTURER + ", "
54             + "Model: " + Build.MODEL + ", "
55             + "Product: " + Build.PRODUCT);
56   }
57 
58   // Logs information about the current audio state. The idea is to call this
59   // method when errors are detected to log under what conditions the error
60   // occurred. Hopefully it will provide clues to what might be the root cause.
logAudioState(String tag, Context context, AudioManager audioManager)61   static void logAudioState(String tag, Context context, AudioManager audioManager) {
62     logDeviceInfo(tag);
63     logAudioStateBasic(tag, context, audioManager);
64     logAudioStateVolume(tag, audioManager);
65     logAudioDeviceInfo(tag, audioManager);
66   }
67 
68   // Converts AudioDeviceInfo types to local string representation.
deviceTypeToString(int type)69   static String deviceTypeToString(int type) {
70     switch (type) {
71       case AudioDeviceInfo.TYPE_UNKNOWN:
72         return "TYPE_UNKNOWN";
73       case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
74         return "TYPE_BUILTIN_EARPIECE";
75       case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
76         return "TYPE_BUILTIN_SPEAKER";
77       case AudioDeviceInfo.TYPE_WIRED_HEADSET:
78         return "TYPE_WIRED_HEADSET";
79       case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
80         return "TYPE_WIRED_HEADPHONES";
81       case AudioDeviceInfo.TYPE_LINE_ANALOG:
82         return "TYPE_LINE_ANALOG";
83       case AudioDeviceInfo.TYPE_LINE_DIGITAL:
84         return "TYPE_LINE_DIGITAL";
85       case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
86         return "TYPE_BLUETOOTH_SCO";
87       case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
88         return "TYPE_BLUETOOTH_A2DP";
89       case AudioDeviceInfo.TYPE_HDMI:
90         return "TYPE_HDMI";
91       case AudioDeviceInfo.TYPE_HDMI_ARC:
92         return "TYPE_HDMI_ARC";
93       case AudioDeviceInfo.TYPE_USB_DEVICE:
94         return "TYPE_USB_DEVICE";
95       case AudioDeviceInfo.TYPE_USB_ACCESSORY:
96         return "TYPE_USB_ACCESSORY";
97       case AudioDeviceInfo.TYPE_DOCK:
98         return "TYPE_DOCK";
99       case AudioDeviceInfo.TYPE_FM:
100         return "TYPE_FM";
101       case AudioDeviceInfo.TYPE_BUILTIN_MIC:
102         return "TYPE_BUILTIN_MIC";
103       case AudioDeviceInfo.TYPE_FM_TUNER:
104         return "TYPE_FM_TUNER";
105       case AudioDeviceInfo.TYPE_TV_TUNER:
106         return "TYPE_TV_TUNER";
107       case AudioDeviceInfo.TYPE_TELEPHONY:
108         return "TYPE_TELEPHONY";
109       case AudioDeviceInfo.TYPE_AUX_LINE:
110         return "TYPE_AUX_LINE";
111       case AudioDeviceInfo.TYPE_IP:
112         return "TYPE_IP";
113       case AudioDeviceInfo.TYPE_BUS:
114         return "TYPE_BUS";
115       case AudioDeviceInfo.TYPE_USB_HEADSET:
116         return "TYPE_USB_HEADSET";
117       default:
118         return "TYPE_UNKNOWN";
119     }
120   }
121 
122   @TargetApi(Build.VERSION_CODES.N)
audioSourceToString(int source)123   public static String audioSourceToString(int source) {
124     // AudioSource.UNPROCESSED requires API level 29. Use local define instead.
125     final int VOICE_PERFORMANCE = 10;
126     switch (source) {
127       case AudioSource.DEFAULT:
128         return "DEFAULT";
129       case AudioSource.MIC:
130         return "MIC";
131       case AudioSource.VOICE_UPLINK:
132         return "VOICE_UPLINK";
133       case AudioSource.VOICE_DOWNLINK:
134         return "VOICE_DOWNLINK";
135       case AudioSource.VOICE_CALL:
136         return "VOICE_CALL";
137       case AudioSource.CAMCORDER:
138         return "CAMCORDER";
139       case AudioSource.VOICE_RECOGNITION:
140         return "VOICE_RECOGNITION";
141       case AudioSource.VOICE_COMMUNICATION:
142         return "VOICE_COMMUNICATION";
143       case AudioSource.UNPROCESSED:
144         return "UNPROCESSED";
145       case VOICE_PERFORMANCE:
146         return "VOICE_PERFORMANCE";
147       default:
148         return "INVALID";
149     }
150   }
151 
channelMaskToString(int mask)152   public static String channelMaskToString(int mask) {
153     // For input or AudioRecord, the mask should be AudioFormat#CHANNEL_IN_MONO or
154     // AudioFormat#CHANNEL_IN_STEREO. AudioFormat#CHANNEL_IN_MONO is guaranteed to work on all
155     // devices.
156     switch (mask) {
157       case AudioFormat.CHANNEL_IN_STEREO:
158         return "IN_STEREO";
159       case AudioFormat.CHANNEL_IN_MONO:
160         return "IN_MONO";
161       default:
162         return "INVALID";
163     }
164   }
165 
166   @TargetApi(Build.VERSION_CODES.N)
audioEncodingToString(int enc)167   public static String audioEncodingToString(int enc) {
168     switch (enc) {
169       case AudioFormat.ENCODING_INVALID:
170         return "INVALID";
171       case AudioFormat.ENCODING_PCM_16BIT:
172         return "PCM_16BIT";
173       case AudioFormat.ENCODING_PCM_8BIT:
174         return "PCM_8BIT";
175       case AudioFormat.ENCODING_PCM_FLOAT:
176         return "PCM_FLOAT";
177       case AudioFormat.ENCODING_AC3:
178         return "AC3";
179       case AudioFormat.ENCODING_E_AC3:
180         return "AC3";
181       case AudioFormat.ENCODING_DTS:
182         return "DTS";
183       case AudioFormat.ENCODING_DTS_HD:
184         return "DTS_HD";
185       case AudioFormat.ENCODING_MP3:
186         return "MP3";
187       default:
188         return "Invalid encoding: " + enc;
189     }
190   }
191 
192   // Reports basic audio statistics.
logAudioStateBasic(String tag, Context context, AudioManager audioManager)193   private static void logAudioStateBasic(String tag, Context context, AudioManager audioManager) {
194     Logging.d(tag,
195         "Audio State: "
196             + "audio mode: " + modeToString(audioManager.getMode()) + ", "
197             + "has mic: " + hasMicrophone(context) + ", "
198             + "mic muted: " + audioManager.isMicrophoneMute() + ", "
199             + "music active: " + audioManager.isMusicActive() + ", "
200             + "speakerphone: " + audioManager.isSpeakerphoneOn() + ", "
201             + "BT SCO: " + audioManager.isBluetoothScoOn());
202   }
203 
204   // Adds volume information for all possible stream types.
logAudioStateVolume(String tag, AudioManager audioManager)205   private static void logAudioStateVolume(String tag, AudioManager audioManager) {
206     final int[] streams = {AudioManager.STREAM_VOICE_CALL, AudioManager.STREAM_MUSIC,
207         AudioManager.STREAM_RING, AudioManager.STREAM_ALARM, AudioManager.STREAM_NOTIFICATION,
208         AudioManager.STREAM_SYSTEM};
209     Logging.d(tag, "Audio State: ");
210     // Some devices may not have volume controls and might use a fixed volume.
211     boolean fixedVolume = audioManager.isVolumeFixed();
212     Logging.d(tag, "  fixed volume=" + fixedVolume);
213     if (!fixedVolume) {
214       for (int stream : streams) {
215         StringBuilder info = new StringBuilder();
216         info.append("  " + streamTypeToString(stream) + ": ");
217         info.append("volume=").append(audioManager.getStreamVolume(stream));
218         info.append(", max=").append(audioManager.getStreamMaxVolume(stream));
219         logIsStreamMute(tag, audioManager, stream, info);
220         Logging.d(tag, info.toString());
221       }
222     }
223   }
224 
logIsStreamMute( String tag, AudioManager audioManager, int stream, StringBuilder info)225   private static void logIsStreamMute(
226       String tag, AudioManager audioManager, int stream, StringBuilder info) {
227     if (Build.VERSION.SDK_INT >= 23) {
228       info.append(", muted=").append(audioManager.isStreamMute(stream));
229     }
230   }
231 
logAudioDeviceInfo(String tag, AudioManager audioManager)232   private static void logAudioDeviceInfo(String tag, AudioManager audioManager) {
233     if (Build.VERSION.SDK_INT < 23) {
234       return;
235     }
236     final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
237     if (devices.length == 0) {
238       return;
239     }
240     Logging.d(tag, "Audio Devices: ");
241     for (AudioDeviceInfo device : devices) {
242       StringBuilder info = new StringBuilder();
243       info.append("  ").append(deviceTypeToString(device.getType()));
244       info.append(device.isSource() ? "(in): " : "(out): ");
245       // An empty array indicates that the device supports arbitrary channel counts.
246       if (device.getChannelCounts().length > 0) {
247         info.append("channels=").append(Arrays.toString(device.getChannelCounts()));
248         info.append(", ");
249       }
250       if (device.getEncodings().length > 0) {
251         // Examples: ENCODING_PCM_16BIT = 2, ENCODING_PCM_FLOAT = 4.
252         info.append("encodings=").append(Arrays.toString(device.getEncodings()));
253         info.append(", ");
254       }
255       if (device.getSampleRates().length > 0) {
256         info.append("sample rates=").append(Arrays.toString(device.getSampleRates()));
257         info.append(", ");
258       }
259       info.append("id=").append(device.getId());
260       Logging.d(tag, info.toString());
261     }
262   }
263 
264   // Converts media.AudioManager modes into local string representation.
modeToString(int mode)265   static String modeToString(int mode) {
266     switch (mode) {
267       case MODE_IN_CALL:
268         return "MODE_IN_CALL";
269       case MODE_IN_COMMUNICATION:
270         return "MODE_IN_COMMUNICATION";
271       case MODE_NORMAL:
272         return "MODE_NORMAL";
273       case MODE_RINGTONE:
274         return "MODE_RINGTONE";
275       default:
276         return "MODE_INVALID";
277     }
278   }
279 
streamTypeToString(int stream)280   private static String streamTypeToString(int stream) {
281     switch (stream) {
282       case AudioManager.STREAM_VOICE_CALL:
283         return "STREAM_VOICE_CALL";
284       case AudioManager.STREAM_MUSIC:
285         return "STREAM_MUSIC";
286       case AudioManager.STREAM_RING:
287         return "STREAM_RING";
288       case AudioManager.STREAM_ALARM:
289         return "STREAM_ALARM";
290       case AudioManager.STREAM_NOTIFICATION:
291         return "STREAM_NOTIFICATION";
292       case AudioManager.STREAM_SYSTEM:
293         return "STREAM_SYSTEM";
294       default:
295         return "STREAM_INVALID";
296     }
297   }
298 
299   // Returns true if the device can record audio via a microphone.
hasMicrophone(Context context)300   private static boolean hasMicrophone(Context context) {
301     return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE);
302   }
303 }
304