diff --git a/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioEffects.java b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioEffects.java index e5408fd81b..255e92d9ec 100644 --- a/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioEffects.java +++ b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioEffects.java @@ -42,6 +42,11 @@ class WebRtcAudioEffects { private static final UUID AOSP_NOISE_SUPPRESSOR = UUID.fromString("c06c8400-8e06-11e0-9cb6-0002a5d5c51b"); + // Contains the available effect descriptors returned from the + // AudioEffect.getEffects() call. This result is cached to avoid doing the + // slow OS call multiple times. + private static Descriptor[] cachedEffects = null; + // Contains the audio effect objects. Created in enable() and destroyed // in release(). private AcousticEchoCanceler aec = null; @@ -60,22 +65,31 @@ class WebRtcAudioEffects { // Checks if the device implements Acoustic Echo Cancellation (AEC). // Returns true if the device implements AEC, false otherwise. public static boolean isAcousticEchoCancelerSupported() { + // Note: we're using isAcousticEchoCancelerEffectAvailable() instead of + // AcousticEchoCanceler.isAvailable() to avoid the expensive getEffects() + // OS API call. return WebRtcAudioUtils.runningOnJellyBeanOrHigher() - && AcousticEchoCanceler.isAvailable(); + && isAcousticEchoCancelerEffectAvailable(); } // Checks if the device implements Automatic Gain Control (AGC). // Returns true if the device implements AGC, false otherwise. public static boolean isAutomaticGainControlSupported() { + // Note: we're using isAutomaticGainControlEffectAvailable() instead of + // AutomaticGainControl.isAvailable() to avoid the expensive getEffects() + // OS API call. return WebRtcAudioUtils.runningOnJellyBeanOrHigher() - && AutomaticGainControl.isAvailable(); + && isAutomaticGainControlEffectAvailable(); } // Checks if the device implements Noise Suppression (NS). // Returns true if the device implements NS, false otherwise. public static boolean isNoiseSuppressorSupported() { + // Note: we're using isNoiseSuppressorEffectAvailable() instead of + // NoiseSuppressor.isAvailable() to avoid the expensive getEffects() + // OS API call. return WebRtcAudioUtils.runningOnJellyBeanOrHigher() - && NoiseSuppressor.isAvailable(); + && isNoiseSuppressorEffectAvailable(); } // Returns true if the device is blacklisted for HW AEC usage. @@ -115,7 +129,7 @@ class WebRtcAudioEffects { // AudioEffect.queryEffects() can throw IllegalStateException. @TargetApi(18) private static boolean isAcousticEchoCancelerExcludedByUUID() { - for (Descriptor d : AudioEffect.queryEffects()) { + for (Descriptor d : getAvailableEffects()) { if (d.type.equals(AudioEffect.EFFECT_TYPE_AEC) && d.uuid.equals(AOSP_ACOUSTIC_ECHO_CANCELER)) { return true; @@ -128,7 +142,7 @@ class WebRtcAudioEffects { // AudioEffect.queryEffects() can throw IllegalStateException. @TargetApi(18) private static boolean isAutomaticGainControlExcludedByUUID() { - for (Descriptor d : AudioEffect.queryEffects()) { + for (Descriptor d : getAvailableEffects()) { if (d.type.equals(AudioEffect.EFFECT_TYPE_AGC) && d.uuid.equals(AOSP_AUTOMATIC_GAIN_CONTROL)) { return true; @@ -141,7 +155,7 @@ class WebRtcAudioEffects { // AudioEffect.queryEffects() can throw IllegalStateException. @TargetApi(18) private static boolean isNoiseSuppressorExcludedByUUID() { - for (Descriptor d : AudioEffect.queryEffects()) { + for (Descriptor d : getAvailableEffects()) { if (d.type.equals(AudioEffect.EFFECT_TYPE_NS) && d.uuid.equals(AOSP_NOISE_SUPPRESSOR)) { return true; @@ -150,6 +164,24 @@ class WebRtcAudioEffects { return false; } + // Returns true if the device supports Acoustic Echo Cancellation (AEC). + @TargetApi(18) + private static boolean isAcousticEchoCancelerEffectAvailable() { + return isEffectTypeAvailable(AudioEffect.EFFECT_TYPE_AEC); + } + + // Returns true if the device supports Automatic Gain Control (AGC). + @TargetApi(18) + private static boolean isAutomaticGainControlEffectAvailable() { + return isEffectTypeAvailable(AudioEffect.EFFECT_TYPE_AGC); + } + + // Returns true if the device supports Noise Suppression (NS). + @TargetApi(18) + private static boolean isNoiseSuppressorEffectAvailable() { + return isEffectTypeAvailable(AudioEffect.EFFECT_TYPE_NS); + } + // Returns true if all conditions for supporting the HW AEC are fulfilled. // It will not be possible to enable the HW AEC if this method returns false. public static boolean canUseAcousticEchoCanceler() { @@ -378,4 +410,34 @@ class WebRtcAudioEffects { throw new AssertionError("Expected condition to be true"); } } + + // Returns the cached copy of the audio effects array, if available, or + // queries the operating system for the list of effects. + private static Descriptor[] getAvailableEffects() { + if (cachedEffects != null) { + return cachedEffects; + } + // The caching is best effort only - if this method is called from several + // threads in parallel, they may end up doing the underlying OS call + // multiple times. It's normally only called on one thread so there's no + // real need to optimize for the multiple threads case. + cachedEffects = AudioEffect.queryEffects(); + return cachedEffects; + } + + // Returns true if an effect of the specified type is available. Functionally + // equivalent to (NoiseSuppressor|AutomaticGainControl|...).isAvailable(), but + // faster as it avoids the expensive OS call to enumerate effects. + private static boolean isEffectTypeAvailable(UUID effectType) { + Descriptor[] effects = getAvailableEffects(); + if (effects == null) { + return false; + } + for (Descriptor d : effects) { + if (d.type.equals(effectType)) { + return true; + } + } + return false; + } }