1 /* 2 * Copyright 2018 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 android.media.AudioManager; 14 import android.content.Context; 15 import org.webrtc.JniCommon; 16 import org.webrtc.Logging; 17 18 /** 19 * AudioDeviceModule implemented using android.media.AudioRecord as input and 20 * android.media.AudioTrack as output. 21 */ 22 public class JavaAudioDeviceModule implements AudioDeviceModule { 23 private static final String TAG = "JavaAudioDeviceModule"; 24 builder(Context context)25 public static Builder builder(Context context) { 26 return new Builder(context); 27 } 28 29 public static class Builder { 30 private final Context context; 31 private final AudioManager audioManager; 32 private int inputSampleRate; 33 private int outputSampleRate; 34 private int audioSource = WebRtcAudioRecord.DEFAULT_AUDIO_SOURCE; 35 private int audioFormat = WebRtcAudioRecord.DEFAULT_AUDIO_FORMAT; 36 private AudioTrackErrorCallback audioTrackErrorCallback; 37 private AudioRecordErrorCallback audioRecordErrorCallback; 38 private SamplesReadyCallback samplesReadyCallback; 39 private AudioTrackStateCallback audioTrackStateCallback; 40 private AudioRecordStateCallback audioRecordStateCallback; 41 private boolean useHardwareAcousticEchoCanceler = isBuiltInAcousticEchoCancelerSupported(); 42 private boolean useHardwareNoiseSuppressor = isBuiltInNoiseSuppressorSupported(); 43 private boolean useStereoInput; 44 private boolean useStereoOutput; 45 Builder(Context context)46 private Builder(Context context) { 47 this.context = context; 48 this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 49 this.inputSampleRate = WebRtcAudioManager.getSampleRate(audioManager); 50 this.outputSampleRate = WebRtcAudioManager.getSampleRate(audioManager); 51 } 52 53 /** 54 * Call this method if the default handling of querying the native sample rate shall be 55 * overridden. Can be useful on some devices where the available Android APIs are known to 56 * return invalid results. 57 */ setSampleRate(int sampleRate)58 public Builder setSampleRate(int sampleRate) { 59 Logging.d(TAG, "Input/Output sample rate overridden to: " + sampleRate); 60 this.inputSampleRate = sampleRate; 61 this.outputSampleRate = sampleRate; 62 return this; 63 } 64 65 /** 66 * Call this method to specifically override input sample rate. 67 */ setInputSampleRate(int inputSampleRate)68 public Builder setInputSampleRate(int inputSampleRate) { 69 Logging.d(TAG, "Input sample rate overridden to: " + inputSampleRate); 70 this.inputSampleRate = inputSampleRate; 71 return this; 72 } 73 74 /** 75 * Call this method to specifically override output sample rate. 76 */ setOutputSampleRate(int outputSampleRate)77 public Builder setOutputSampleRate(int outputSampleRate) { 78 Logging.d(TAG, "Output sample rate overridden to: " + outputSampleRate); 79 this.outputSampleRate = outputSampleRate; 80 return this; 81 } 82 83 /** 84 * Call this to change the audio source. The argument should be one of the values from 85 * android.media.MediaRecorder.AudioSource. The default is AudioSource.VOICE_COMMUNICATION. 86 */ setAudioSource(int audioSource)87 public Builder setAudioSource(int audioSource) { 88 this.audioSource = audioSource; 89 return this; 90 } 91 92 /** 93 * Call this to change the audio format. The argument should be one of the values from 94 * android.media.AudioFormat ENCODING_PCM_8BIT, ENCODING_PCM_16BIT or ENCODING_PCM_FLOAT. 95 * Default audio data format is PCM 16 bit per sample. 96 * Guaranteed to be supported by all devices. 97 */ setAudioFormat(int audioFormat)98 public Builder setAudioFormat(int audioFormat) { 99 this.audioFormat = audioFormat; 100 return this; 101 } 102 103 /** 104 * Set a callback to retrieve errors from the AudioTrack. 105 */ setAudioTrackErrorCallback(AudioTrackErrorCallback audioTrackErrorCallback)106 public Builder setAudioTrackErrorCallback(AudioTrackErrorCallback audioTrackErrorCallback) { 107 this.audioTrackErrorCallback = audioTrackErrorCallback; 108 return this; 109 } 110 111 /** 112 * Set a callback to retrieve errors from the AudioRecord. 113 */ setAudioRecordErrorCallback(AudioRecordErrorCallback audioRecordErrorCallback)114 public Builder setAudioRecordErrorCallback(AudioRecordErrorCallback audioRecordErrorCallback) { 115 this.audioRecordErrorCallback = audioRecordErrorCallback; 116 return this; 117 } 118 119 /** 120 * Set a callback to listen to the raw audio input from the AudioRecord. 121 */ setSamplesReadyCallback(SamplesReadyCallback samplesReadyCallback)122 public Builder setSamplesReadyCallback(SamplesReadyCallback samplesReadyCallback) { 123 this.samplesReadyCallback = samplesReadyCallback; 124 return this; 125 } 126 127 /** 128 * Set a callback to retrieve information from the AudioTrack on when audio starts and stop. 129 */ setAudioTrackStateCallback(AudioTrackStateCallback audioTrackStateCallback)130 public Builder setAudioTrackStateCallback(AudioTrackStateCallback audioTrackStateCallback) { 131 this.audioTrackStateCallback = audioTrackStateCallback; 132 return this; 133 } 134 135 /** 136 * Set a callback to retrieve information from the AudioRecord on when audio starts and stops. 137 */ setAudioRecordStateCallback(AudioRecordStateCallback audioRecordStateCallback)138 public Builder setAudioRecordStateCallback(AudioRecordStateCallback audioRecordStateCallback) { 139 this.audioRecordStateCallback = audioRecordStateCallback; 140 return this; 141 } 142 143 /** 144 * Control if the built-in HW noise suppressor should be used or not. The default is on if it is 145 * supported. It is possible to query support by calling isBuiltInNoiseSuppressorSupported(). 146 */ setUseHardwareNoiseSuppressor(boolean useHardwareNoiseSuppressor)147 public Builder setUseHardwareNoiseSuppressor(boolean useHardwareNoiseSuppressor) { 148 if (useHardwareNoiseSuppressor && !isBuiltInNoiseSuppressorSupported()) { 149 Logging.e(TAG, "HW NS not supported"); 150 useHardwareNoiseSuppressor = false; 151 } 152 this.useHardwareNoiseSuppressor = useHardwareNoiseSuppressor; 153 return this; 154 } 155 156 /** 157 * Control if the built-in HW acoustic echo canceler should be used or not. The default is on if 158 * it is supported. It is possible to query support by calling 159 * isBuiltInAcousticEchoCancelerSupported(). 160 */ setUseHardwareAcousticEchoCanceler(boolean useHardwareAcousticEchoCanceler)161 public Builder setUseHardwareAcousticEchoCanceler(boolean useHardwareAcousticEchoCanceler) { 162 if (useHardwareAcousticEchoCanceler && !isBuiltInAcousticEchoCancelerSupported()) { 163 Logging.e(TAG, "HW AEC not supported"); 164 useHardwareAcousticEchoCanceler = false; 165 } 166 this.useHardwareAcousticEchoCanceler = useHardwareAcousticEchoCanceler; 167 return this; 168 } 169 170 /** 171 * Control if stereo input should be used or not. The default is mono. 172 */ setUseStereoInput(boolean useStereoInput)173 public Builder setUseStereoInput(boolean useStereoInput) { 174 this.useStereoInput = useStereoInput; 175 return this; 176 } 177 178 /** 179 * Control if stereo output should be used or not. The default is mono. 180 */ setUseStereoOutput(boolean useStereoOutput)181 public Builder setUseStereoOutput(boolean useStereoOutput) { 182 this.useStereoOutput = useStereoOutput; 183 return this; 184 } 185 186 /** 187 * Construct an AudioDeviceModule based on the supplied arguments. The caller takes ownership 188 * and is responsible for calling release(). 189 */ createAudioDeviceModule()190 public AudioDeviceModule createAudioDeviceModule() { 191 Logging.d(TAG, "createAudioDeviceModule"); 192 if (useHardwareNoiseSuppressor) { 193 Logging.d(TAG, "HW NS will be used."); 194 } else { 195 if (isBuiltInNoiseSuppressorSupported()) { 196 Logging.d(TAG, "Overriding default behavior; now using WebRTC NS!"); 197 } 198 Logging.d(TAG, "HW NS will not be used."); 199 } 200 if (useHardwareAcousticEchoCanceler) { 201 Logging.d(TAG, "HW AEC will be used."); 202 } else { 203 if (isBuiltInAcousticEchoCancelerSupported()) { 204 Logging.d(TAG, "Overriding default behavior; now using WebRTC AEC!"); 205 } 206 Logging.d(TAG, "HW AEC will not be used."); 207 } 208 final WebRtcAudioRecord audioInput = new WebRtcAudioRecord(context, audioManager, audioSource, 209 audioFormat, audioRecordErrorCallback, audioRecordStateCallback, samplesReadyCallback, 210 useHardwareAcousticEchoCanceler, useHardwareNoiseSuppressor); 211 final WebRtcAudioTrack audioOutput = new WebRtcAudioTrack( 212 context, audioManager, audioTrackErrorCallback, audioTrackStateCallback); 213 return new JavaAudioDeviceModule(context, audioManager, audioInput, audioOutput, 214 inputSampleRate, outputSampleRate, useStereoInput, useStereoOutput); 215 } 216 } 217 218 /* AudioRecord */ 219 // Audio recording error handler functions. 220 public enum AudioRecordStartErrorCode { 221 AUDIO_RECORD_START_EXCEPTION, 222 AUDIO_RECORD_START_STATE_MISMATCH, 223 } 224 225 public static interface AudioRecordErrorCallback { onWebRtcAudioRecordInitError(String errorMessage)226 void onWebRtcAudioRecordInitError(String errorMessage); onWebRtcAudioRecordStartError(AudioRecordStartErrorCode errorCode, String errorMessage)227 void onWebRtcAudioRecordStartError(AudioRecordStartErrorCode errorCode, String errorMessage); onWebRtcAudioRecordError(String errorMessage)228 void onWebRtcAudioRecordError(String errorMessage); 229 } 230 231 /** Called when audio recording starts and stops. */ 232 public static interface AudioRecordStateCallback { onWebRtcAudioRecordStart()233 void onWebRtcAudioRecordStart(); onWebRtcAudioRecordStop()234 void onWebRtcAudioRecordStop(); 235 } 236 237 /** 238 * Contains audio sample information. 239 */ 240 public static class AudioSamples { 241 /** See {@link AudioRecord#getAudioFormat()} */ 242 private final int audioFormat; 243 /** See {@link AudioRecord#getChannelCount()} */ 244 private final int channelCount; 245 /** See {@link AudioRecord#getSampleRate()} */ 246 private final int sampleRate; 247 248 private final byte[] data; 249 AudioSamples(int audioFormat, int channelCount, int sampleRate, byte[] data)250 public AudioSamples(int audioFormat, int channelCount, int sampleRate, byte[] data) { 251 this.audioFormat = audioFormat; 252 this.channelCount = channelCount; 253 this.sampleRate = sampleRate; 254 this.data = data; 255 } 256 getAudioFormat()257 public int getAudioFormat() { 258 return audioFormat; 259 } 260 getChannelCount()261 public int getChannelCount() { 262 return channelCount; 263 } 264 getSampleRate()265 public int getSampleRate() { 266 return sampleRate; 267 } 268 getData()269 public byte[] getData() { 270 return data; 271 } 272 } 273 274 /** Called when new audio samples are ready. This should only be set for debug purposes */ 275 public static interface SamplesReadyCallback { onWebRtcAudioRecordSamplesReady(AudioSamples samples)276 void onWebRtcAudioRecordSamplesReady(AudioSamples samples); 277 } 278 279 /* AudioTrack */ 280 // Audio playout/track error handler functions. 281 public enum AudioTrackStartErrorCode { 282 AUDIO_TRACK_START_EXCEPTION, 283 AUDIO_TRACK_START_STATE_MISMATCH, 284 } 285 286 public static interface AudioTrackErrorCallback { onWebRtcAudioTrackInitError(String errorMessage)287 void onWebRtcAudioTrackInitError(String errorMessage); onWebRtcAudioTrackStartError(AudioTrackStartErrorCode errorCode, String errorMessage)288 void onWebRtcAudioTrackStartError(AudioTrackStartErrorCode errorCode, String errorMessage); onWebRtcAudioTrackError(String errorMessage)289 void onWebRtcAudioTrackError(String errorMessage); 290 } 291 292 /** Called when audio playout starts and stops. */ 293 public static interface AudioTrackStateCallback { onWebRtcAudioTrackStart()294 void onWebRtcAudioTrackStart(); onWebRtcAudioTrackStop()295 void onWebRtcAudioTrackStop(); 296 } 297 298 /** 299 * Returns true if the device supports built-in HW AEC, and the UUID is approved (some UUIDs can 300 * be excluded). 301 */ isBuiltInAcousticEchoCancelerSupported()302 public static boolean isBuiltInAcousticEchoCancelerSupported() { 303 return WebRtcAudioEffects.isAcousticEchoCancelerSupported(); 304 } 305 306 /** 307 * Returns true if the device supports built-in HW NS, and the UUID is approved (some UUIDs can be 308 * excluded). 309 */ isBuiltInNoiseSuppressorSupported()310 public static boolean isBuiltInNoiseSuppressorSupported() { 311 return WebRtcAudioEffects.isNoiseSuppressorSupported(); 312 } 313 314 private final Context context; 315 private final AudioManager audioManager; 316 private final WebRtcAudioRecord audioInput; 317 private final WebRtcAudioTrack audioOutput; 318 private final int inputSampleRate; 319 private final int outputSampleRate; 320 private final boolean useStereoInput; 321 private final boolean useStereoOutput; 322 323 private final Object nativeLock = new Object(); 324 private long nativeAudioDeviceModule; 325 JavaAudioDeviceModule(Context context, AudioManager audioManager, WebRtcAudioRecord audioInput, WebRtcAudioTrack audioOutput, int inputSampleRate, int outputSampleRate, boolean useStereoInput, boolean useStereoOutput)326 private JavaAudioDeviceModule(Context context, AudioManager audioManager, 327 WebRtcAudioRecord audioInput, WebRtcAudioTrack audioOutput, int inputSampleRate, 328 int outputSampleRate, boolean useStereoInput, boolean useStereoOutput) { 329 this.context = context; 330 this.audioManager = audioManager; 331 this.audioInput = audioInput; 332 this.audioOutput = audioOutput; 333 this.inputSampleRate = inputSampleRate; 334 this.outputSampleRate = outputSampleRate; 335 this.useStereoInput = useStereoInput; 336 this.useStereoOutput = useStereoOutput; 337 } 338 339 @Override getNativeAudioDeviceModulePointer()340 public long getNativeAudioDeviceModulePointer() { 341 synchronized (nativeLock) { 342 if (nativeAudioDeviceModule == 0) { 343 nativeAudioDeviceModule = nativeCreateAudioDeviceModule(context, audioManager, audioInput, 344 audioOutput, inputSampleRate, outputSampleRate, useStereoInput, useStereoOutput); 345 } 346 return nativeAudioDeviceModule; 347 } 348 } 349 350 @Override release()351 public void release() { 352 synchronized (nativeLock) { 353 if (nativeAudioDeviceModule != 0) { 354 JniCommon.nativeReleaseRef(nativeAudioDeviceModule); 355 nativeAudioDeviceModule = 0; 356 } 357 } 358 } 359 360 @Override setSpeakerMute(boolean mute)361 public void setSpeakerMute(boolean mute) { 362 Logging.d(TAG, "setSpeakerMute: " + mute); 363 audioOutput.setSpeakerMute(mute); 364 } 365 366 @Override setMicrophoneMute(boolean mute)367 public void setMicrophoneMute(boolean mute) { 368 Logging.d(TAG, "setMicrophoneMute: " + mute); 369 audioInput.setMicrophoneMute(mute); 370 } 371 nativeCreateAudioDeviceModule(Context context, AudioManager audioManager, WebRtcAudioRecord audioInput, WebRtcAudioTrack audioOutput, int inputSampleRate, int outputSampleRate, boolean useStereoInput, boolean useStereoOutput)372 private static native long nativeCreateAudioDeviceModule(Context context, 373 AudioManager audioManager, WebRtcAudioRecord audioInput, WebRtcAudioTrack audioOutput, 374 int inputSampleRate, int outputSampleRate, boolean useStereoInput, boolean useStereoOutput); 375 } 376