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.voiceengine; 12 13 import android.annotation.TargetApi; 14 import android.content.Context; 15 import android.content.pm.PackageManager; 16 import android.media.AudioFormat; 17 import android.media.AudioManager; 18 import android.media.AudioRecord; 19 import android.media.AudioTrack; 20 import android.os.Build; 21 22 import org.webrtc.Logging; 23 24 import java.lang.Math; 25 26 // WebRtcAudioManager handles tasks that uses android.media.AudioManager. 27 // At construction, storeAudioParameters() is called and it retrieves 28 // fundamental audio parameters like native sample rate and number of channels. 29 // The result is then provided to the caller by nativeCacheAudioParameters(). 30 // It is also possible to call init() to set up the audio environment for best 31 // possible "VoIP performance". All settings done in init() are reverted by 32 // dispose(). This class can also be used without calling init() if the user 33 // prefers to set up the audio environment separately. However, it is 34 // recommended to always use AudioManager.MODE_IN_COMMUNICATION. 35 // This class also adds support for output volume control of the 36 // STREAM_VOICE_CALL-type stream. 37 public class WebRtcAudioManager { 38 private static final boolean DEBUG = false; 39 40 private static final String TAG = "WebRtcAudioManager"; 41 42 private static boolean blacklistDeviceForOpenSLESUsage = false; 43 private static boolean blacklistDeviceForOpenSLESUsageIsOverridden = false; 44 45 // Call this method to override the deault list of blacklisted devices 46 // specified in WebRtcAudioUtils.BLACKLISTED_OPEN_SL_ES_MODELS. 47 // Allows an app to take control over which devices to exlude from using 48 // the OpenSL ES audio output path setBlacklistDeviceForOpenSLESUsage( boolean enable)49 public static synchronized void setBlacklistDeviceForOpenSLESUsage( 50 boolean enable) { 51 blacklistDeviceForOpenSLESUsageIsOverridden = true; 52 blacklistDeviceForOpenSLESUsage = enable; 53 } 54 55 // Default audio data format is PCM 16 bit per sample. 56 // Guaranteed to be supported by all devices. 57 private static final int BITS_PER_SAMPLE = 16; 58 59 private static final int DEFAULT_FRAME_PER_BUFFER = 256; 60 61 // TODO(henrika): add stereo support for playout. 62 private static final int CHANNELS = 1; 63 64 // List of possible audio modes. 65 private static final String[] AUDIO_MODES = new String[] { 66 "MODE_NORMAL", 67 "MODE_RINGTONE", 68 "MODE_IN_CALL", 69 "MODE_IN_COMMUNICATION", 70 }; 71 72 private final long nativeAudioManager; 73 private final Context context; 74 private final AudioManager audioManager; 75 76 private boolean initialized = false; 77 private int nativeSampleRate; 78 private int nativeChannels; 79 80 private boolean hardwareAEC; 81 private boolean hardwareAGC; 82 private boolean hardwareNS; 83 private boolean lowLatencyOutput; 84 private int sampleRate; 85 private int channels; 86 private int outputBufferSize; 87 private int inputBufferSize; 88 WebRtcAudioManager(Context context, long nativeAudioManager)89 WebRtcAudioManager(Context context, long nativeAudioManager) { 90 Logging.d(TAG, "ctor" + WebRtcAudioUtils.getThreadInfo()); 91 this.context = context; 92 this.nativeAudioManager = nativeAudioManager; 93 audioManager = (AudioManager) context.getSystemService( 94 Context.AUDIO_SERVICE); 95 if (DEBUG) { 96 WebRtcAudioUtils.logDeviceInfo(TAG); 97 } 98 storeAudioParameters(); 99 nativeCacheAudioParameters( 100 sampleRate, channels, hardwareAEC, hardwareAGC, hardwareNS, 101 lowLatencyOutput, outputBufferSize, inputBufferSize, 102 nativeAudioManager); 103 } 104 init()105 private boolean init() { 106 Logging.d(TAG, "init" + WebRtcAudioUtils.getThreadInfo()); 107 if (initialized) { 108 return true; 109 } 110 Logging.d(TAG, "audio mode is: " + AUDIO_MODES[audioManager.getMode()]); 111 initialized = true; 112 return true; 113 } 114 dispose()115 private void dispose() { 116 Logging.d(TAG, "dispose" + WebRtcAudioUtils.getThreadInfo()); 117 if (!initialized) { 118 return; 119 } 120 } 121 isCommunicationModeEnabled()122 private boolean isCommunicationModeEnabled() { 123 return (audioManager.getMode() == AudioManager.MODE_IN_COMMUNICATION); 124 } 125 isDeviceBlacklistedForOpenSLESUsage()126 private boolean isDeviceBlacklistedForOpenSLESUsage() { 127 boolean blacklisted = blacklistDeviceForOpenSLESUsageIsOverridden ? 128 blacklistDeviceForOpenSLESUsage : 129 WebRtcAudioUtils.deviceIsBlacklistedForOpenSLESUsage(); 130 if (blacklisted) { 131 Logging.e(TAG, Build.MODEL + " is blacklisted for OpenSL ES usage!"); 132 } 133 return blacklisted; 134 } 135 storeAudioParameters()136 private void storeAudioParameters() { 137 // Only mono is supported currently (in both directions). 138 // TODO(henrika): add support for stereo playout. 139 channels = CHANNELS; 140 sampleRate = getNativeOutputSampleRate(); 141 hardwareAEC = isAcousticEchoCancelerSupported(); 142 hardwareAGC = isAutomaticGainControlSupported(); 143 hardwareNS = isNoiseSuppressorSupported(); 144 lowLatencyOutput = isLowLatencyOutputSupported(); 145 outputBufferSize = lowLatencyOutput ? 146 getLowLatencyOutputFramesPerBuffer() : 147 getMinOutputFrameSize(sampleRate, channels); 148 // TODO(henrika): add support for low-latency input. 149 inputBufferSize = getMinInputFrameSize(sampleRate, channels); 150 } 151 152 // Gets the current earpiece state. hasEarpiece()153 private boolean hasEarpiece() { 154 return context.getPackageManager().hasSystemFeature( 155 PackageManager.FEATURE_TELEPHONY); 156 } 157 158 // Returns true if low-latency audio output is supported. isLowLatencyOutputSupported()159 private boolean isLowLatencyOutputSupported() { 160 return isOpenSLESSupported() && 161 context.getPackageManager().hasSystemFeature( 162 PackageManager.FEATURE_AUDIO_LOW_LATENCY); 163 } 164 165 // Returns true if low-latency audio input is supported. isLowLatencyInputSupported()166 public boolean isLowLatencyInputSupported() { 167 // TODO(henrika): investigate if some sort of device list is needed here 168 // as well. The NDK doc states that: "As of API level 21, lower latency 169 // audio input is supported on select devices. To take advantage of this 170 // feature, first confirm that lower latency output is available". 171 return WebRtcAudioUtils.runningOnLollipopOrHigher() && 172 isLowLatencyOutputSupported(); 173 } 174 175 // Returns the native output sample rate for this device's output stream. getNativeOutputSampleRate()176 private int getNativeOutputSampleRate() { 177 // Override this if we're running on an old emulator image which only 178 // supports 8 kHz and doesn't support PROPERTY_OUTPUT_SAMPLE_RATE. 179 if (WebRtcAudioUtils.runningOnEmulator()) { 180 Logging.d(TAG, "Running emulator, overriding sample rate to 8 kHz."); 181 return 8000; 182 } 183 // Default can be overriden by WebRtcAudioUtils.setDefaultSampleRateHz(). 184 // If so, use that value and return here. 185 if (WebRtcAudioUtils.isDefaultSampleRateOverridden()) { 186 Logging.d(TAG, "Default sample rate is overriden to " + 187 WebRtcAudioUtils.getDefaultSampleRateHz() + " Hz"); 188 return WebRtcAudioUtils.getDefaultSampleRateHz(); 189 } 190 // No overrides available. Deliver best possible estimate based on default 191 // Android AudioManager APIs. 192 final int sampleRateHz; 193 if (WebRtcAudioUtils.runningOnJellyBeanMR1OrHigher()) { 194 sampleRateHz = getSampleRateOnJellyBeanMR10OrHigher(); 195 } else { 196 sampleRateHz = WebRtcAudioUtils.getDefaultSampleRateHz(); 197 } 198 Logging.d(TAG, "Sample rate is set to " + sampleRateHz + " Hz"); 199 return sampleRateHz; 200 } 201 202 @TargetApi(17) getSampleRateOnJellyBeanMR10OrHigher()203 private int getSampleRateOnJellyBeanMR10OrHigher() { 204 String sampleRateString = audioManager.getProperty( 205 AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); 206 return (sampleRateString == null) 207 ? WebRtcAudioUtils.getDefaultSampleRateHz() 208 : Integer.parseInt(sampleRateString); 209 } 210 211 // Returns the native output buffer size for low-latency output streams. 212 @TargetApi(17) getLowLatencyOutputFramesPerBuffer()213 private int getLowLatencyOutputFramesPerBuffer() { 214 assertTrue(isLowLatencyOutputSupported()); 215 if (!WebRtcAudioUtils.runningOnJellyBeanMR1OrHigher()) { 216 return DEFAULT_FRAME_PER_BUFFER; 217 } 218 String framesPerBuffer = audioManager.getProperty( 219 AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); 220 return framesPerBuffer == null ? 221 DEFAULT_FRAME_PER_BUFFER : Integer.parseInt(framesPerBuffer); 222 } 223 224 // Returns true if the device supports an audio effect (AEC, AGC or NS). 225 // Four conditions must be fulfilled if functions are to return true: 226 // 1) the platform must support the built-in (HW) effect, 227 // 2) explicit use (override) of a WebRTC based version must not be set, 228 // 3) the device must not be blacklisted for use of the effect, and 229 // 4) the UUID of the effect must be approved (some UUIDs can be excluded). isAcousticEchoCancelerSupported()230 private static boolean isAcousticEchoCancelerSupported() { 231 return WebRtcAudioEffects.canUseAcousticEchoCanceler(); 232 } isAutomaticGainControlSupported()233 private static boolean isAutomaticGainControlSupported() { 234 return WebRtcAudioEffects.canUseAutomaticGainControl(); 235 } isNoiseSuppressorSupported()236 private static boolean isNoiseSuppressorSupported() { 237 return WebRtcAudioEffects.canUseNoiseSuppressor(); 238 } 239 240 // Returns the minimum output buffer size for Java based audio (AudioTrack). 241 // This size can also be used for OpenSL ES implementations on devices that 242 // lacks support of low-latency output. getMinOutputFrameSize(int sampleRateInHz, int numChannels)243 private static int getMinOutputFrameSize(int sampleRateInHz, int numChannels) { 244 final int bytesPerFrame = numChannels * (BITS_PER_SAMPLE / 8); 245 final int channelConfig; 246 if (numChannels == 1) { 247 channelConfig = AudioFormat.CHANNEL_OUT_MONO; 248 } else if (numChannels == 2) { 249 channelConfig = AudioFormat.CHANNEL_OUT_STEREO; 250 } else { 251 return -1; 252 } 253 return AudioTrack.getMinBufferSize( 254 sampleRateInHz, channelConfig, AudioFormat.ENCODING_PCM_16BIT) / 255 bytesPerFrame; 256 } 257 258 // Returns the native input buffer size for input streams. getLowLatencyInputFramesPerBuffer()259 private int getLowLatencyInputFramesPerBuffer() { 260 assertTrue(isLowLatencyInputSupported()); 261 return getLowLatencyOutputFramesPerBuffer(); 262 } 263 264 // Returns the minimum input buffer size for Java based audio (AudioRecord). 265 // This size can calso be used for OpenSL ES implementations on devices that 266 // lacks support of low-latency input. getMinInputFrameSize(int sampleRateInHz, int numChannels)267 private static int getMinInputFrameSize(int sampleRateInHz, int numChannels) { 268 final int bytesPerFrame = numChannels * (BITS_PER_SAMPLE / 8); 269 assertTrue(numChannels == CHANNELS); 270 return AudioRecord.getMinBufferSize(sampleRateInHz, 271 AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT) / 272 bytesPerFrame; 273 } 274 275 // Returns true if OpenSL ES audio is supported. isOpenSLESSupported()276 private static boolean isOpenSLESSupported() { 277 // Check for API level 9 or higher, to confirm use of OpenSL ES. 278 return WebRtcAudioUtils.runningOnGingerBreadOrHigher(); 279 } 280 281 // Helper method which throws an exception when an assertion has failed. assertTrue(boolean condition)282 private static void assertTrue(boolean condition) { 283 if (!condition) { 284 throw new AssertionError("Expected condition to be true"); 285 } 286 } 287 nativeCacheAudioParameters( int sampleRate, int channels, boolean hardwareAEC, boolean hardwareAGC, boolean hardwareNS, boolean lowLatencyOutput, int outputBufferSize, int inputBufferSize, long nativeAudioManager)288 private native void nativeCacheAudioParameters( 289 int sampleRate, int channels, boolean hardwareAEC, boolean hardwareAGC, 290 boolean hardwareNS, boolean lowLatencyOutput, int outputBufferSize, 291 int inputBufferSize, long nativeAudioManager); 292 } 293