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