Index: webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioEffects.java |
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 |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0b0cbe4711dfd04ebe45f52b2d39f76960763334 |
--- /dev/null |
+++ b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioEffects.java |
@@ -0,0 +1,377 @@ |
+/* |
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. |
+ * |
+ * Use of this source code is governed by a BSD-style license |
+ * that can be found in the LICENSE file in the root of the source |
+ * tree. An additional intellectual property rights grant can be found |
+ * in the file PATENTS. All contributing project authors may |
+ * be found in the AUTHORS file in the root of the source tree. |
+ */ |
+ |
+package org.webrtc.voiceengine; |
+ |
+import android.media.audiofx.AcousticEchoCanceler; |
+import android.media.audiofx.AudioEffect; |
+import android.media.audiofx.AudioEffect.Descriptor; |
+import android.media.audiofx.AutomaticGainControl; |
+import android.media.audiofx.NoiseSuppressor; |
+import android.os.Build; |
+ |
+import org.webrtc.Logging; |
+ |
+import java.util.List; |
+ |
+import java.util.UUID; |
+ |
+// This class wraps control of three different platform effects. Supported |
+// effects are: AcousticEchoCanceler (AEC), AutomaticGainControl (AGC) and |
+// NoiseSuppressor (NS). Calling enable() will active all effects that are |
+// supported by the device if the corresponding |shouldEnableXXX| member is set. |
+class WebRtcAudioEffects { |
+ private static final boolean DEBUG = false; |
+ |
+ private static final String TAG = "WebRtcAudioEffects"; |
+ |
+ // UUIDs for Software Audio Effects that we want to avoid using. |
+ // The implementor field will be set to "The Android Open Source Project". |
+ private static final UUID AOSP_ACOUSTIC_ECHO_CANCELER = |
+ UUID.fromString("bb392ec0-8d4d-11e0-a896-0002a5d5c51b"); |
+ private static final UUID AOSP_AUTOMATIC_GAIN_CONTROL = |
+ UUID.fromString("aa8130e0-66fc-11e0-bad0-0002a5d5c51b"); |
+ private static final UUID AOSP_NOISE_SUPPRESSOR = |
+ UUID.fromString("c06c8400-8e06-11e0-9cb6-0002a5d5c51b"); |
+ |
+ // Static Boolean objects used to avoid expensive queries more than once. |
+ // The first result is cached in these members and then reused if needed. |
+ // Each member is null until it has been evaluated/set for the first time. |
+ private static Boolean canUseAcousticEchoCanceler = null; |
+ private static Boolean canUseAutomaticGainControl = null; |
+ private static Boolean canUseNoiseSuppressor = null; |
+ |
+ // Contains the audio effect objects. Created in enable() and destroyed |
+ // in release(). |
+ private AcousticEchoCanceler aec = null; |
+ private AutomaticGainControl agc = null; |
+ private NoiseSuppressor ns = null; |
+ |
+ // Affects the final state given to the setEnabled() method on each effect. |
+ // The default state is set to "disabled" but each effect can also be enabled |
+ // by calling setAEC(), setAGC() and setNS(). |
+ // To enable an effect, both the shouldEnableXXX member and the static |
+ // canUseXXX() must be true. |
+ private boolean shouldEnableAec = false; |
+ private boolean shouldEnableAgc = false; |
+ private boolean shouldEnableNs = false; |
+ |
+ // Checks if the device implements Acoustic Echo Cancellation (AEC). |
+ // Returns true if the device implements AEC, false otherwise. |
+ public static boolean isAcousticEchoCancelerSupported() { |
+ return WebRtcAudioUtils.runningOnJellyBeanOrHigher() |
+ && AcousticEchoCanceler.isAvailable(); |
+ } |
+ |
+ // Checks if the device implements Automatic Gain Control (AGC). |
+ // Returns true if the device implements AGC, false otherwise. |
+ public static boolean isAutomaticGainControlSupported() { |
+ return WebRtcAudioUtils.runningOnJellyBeanOrHigher() |
+ && AutomaticGainControl.isAvailable(); |
+ } |
+ |
+ // Checks if the device implements Noise Suppression (NS). |
+ // Returns true if the device implements NS, false otherwise. |
+ public static boolean isNoiseSuppressorSupported() { |
+ return WebRtcAudioUtils.runningOnJellyBeanOrHigher() |
+ && NoiseSuppressor.isAvailable(); |
+ } |
+ |
+ // Returns true if the device is blacklisted for HW AEC usage. |
+ public static boolean isAcousticEchoCancelerBlacklisted() { |
+ List<String> blackListedModels = |
+ WebRtcAudioUtils.getBlackListedModelsForAecUsage(); |
+ boolean isBlacklisted = blackListedModels.contains(Build.MODEL); |
+ if (isBlacklisted) { |
+ Logging.w(TAG, Build.MODEL + " is blacklisted for HW AEC usage!"); |
+ } |
+ return isBlacklisted; |
+ } |
+ |
+ // Returns true if the device is blacklisted for HW AGC usage. |
+ public static boolean isAutomaticGainControlBlacklisted() { |
+ List<String> blackListedModels = |
+ WebRtcAudioUtils.getBlackListedModelsForAgcUsage(); |
+ boolean isBlacklisted = blackListedModels.contains(Build.MODEL); |
+ if (isBlacklisted) { |
+ Logging.w(TAG, Build.MODEL + " is blacklisted for HW AGC usage!"); |
+ } |
+ return isBlacklisted; |
+ } |
+ |
+ // Returns true if the device is blacklisted for HW NS usage. |
+ public static boolean isNoiseSuppressorBlacklisted() { |
+ List<String> blackListedModels = |
+ WebRtcAudioUtils.getBlackListedModelsForNsUsage(); |
+ boolean isBlacklisted = blackListedModels.contains(Build.MODEL); |
+ if (isBlacklisted) { |
+ Logging.w(TAG, Build.MODEL + " is blacklisted for HW NS usage!"); |
+ } |
+ return isBlacklisted; |
+ } |
+ |
+ // Returns true if the platform AEC should be excluded based on its UUID. |
+ // AudioEffect.queryEffects() can throw IllegalStateException. |
+ private static boolean isAcousticEchoCancelerExcludedByUUID() { |
+ for (Descriptor d : AudioEffect.queryEffects()) { |
+ if (d.type.equals(AudioEffect.EFFECT_TYPE_AEC) && |
+ d.uuid.equals(AOSP_ACOUSTIC_ECHO_CANCELER)) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ // Returns true if the platform AGC should be excluded based on its UUID. |
+ // AudioEffect.queryEffects() can throw IllegalStateException. |
+ private static boolean isAutomaticGainControlExcludedByUUID() { |
+ for (Descriptor d : AudioEffect.queryEffects()) { |
+ if (d.type.equals(AudioEffect.EFFECT_TYPE_AGC) && |
+ d.uuid.equals(AOSP_AUTOMATIC_GAIN_CONTROL)) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ // Returns true if the platform NS should be excluded based on its UUID. |
+ // AudioEffect.queryEffects() can throw IllegalStateException. |
+ private static boolean isNoiseSuppressorExcludedByUUID() { |
+ for (Descriptor d : AudioEffect.queryEffects()) { |
+ if (d.type.equals(AudioEffect.EFFECT_TYPE_NS) && |
+ d.uuid.equals(AOSP_NOISE_SUPPRESSOR)) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ // 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() { |
+ if (canUseAcousticEchoCanceler == null) { |
+ canUseAcousticEchoCanceler = new Boolean( |
+ isAcousticEchoCancelerSupported() |
+ && !WebRtcAudioUtils.useWebRtcBasedAcousticEchoCanceler() |
+ && !isAcousticEchoCancelerBlacklisted() |
+ && !isAcousticEchoCancelerExcludedByUUID()); |
+ Logging.d(TAG, "canUseAcousticEchoCanceler: " |
+ + canUseAcousticEchoCanceler); |
+ } |
+ return canUseAcousticEchoCanceler; |
+ } |
+ |
+ // Returns true if all conditions for supporting the HW AGC are fulfilled. |
+ // It will not be possible to enable the HW AGC if this method returns false. |
+ public static boolean canUseAutomaticGainControl() { |
+ if (canUseAutomaticGainControl == null) { |
+ canUseAutomaticGainControl = new Boolean( |
+ isAutomaticGainControlSupported() |
+ && !WebRtcAudioUtils.useWebRtcBasedAutomaticGainControl() |
+ && !isAutomaticGainControlBlacklisted() |
+ && !isAutomaticGainControlExcludedByUUID()); |
+ Logging.d(TAG, "canUseAutomaticGainControl: " |
+ + canUseAutomaticGainControl); |
+ } |
+ return canUseAutomaticGainControl; |
+ } |
+ |
+ // Returns true if all conditions for supporting the HW NS are fulfilled. |
+ // It will not be possible to enable the HW NS if this method returns false. |
+ public static boolean canUseNoiseSuppressor() { |
+ if (canUseNoiseSuppressor == null) { |
+ canUseNoiseSuppressor = new Boolean( |
+ isNoiseSuppressorSupported() |
+ && !WebRtcAudioUtils.useWebRtcBasedNoiseSuppressor() |
+ && !isNoiseSuppressorBlacklisted() |
+ && !isNoiseSuppressorExcludedByUUID()); |
+ Logging.d(TAG, "canUseNoiseSuppressor: " + canUseNoiseSuppressor); |
+ } |
+ return canUseNoiseSuppressor; |
+ } |
+ |
+ static WebRtcAudioEffects create() { |
+ // Return null if VoIP effects (AEC, AGC and NS) are not supported. |
+ if (!WebRtcAudioUtils.runningOnJellyBeanOrHigher()) { |
+ Logging.w(TAG, "API level 16 or higher is required!"); |
+ return null; |
+ } |
+ return new WebRtcAudioEffects(); |
+ } |
+ |
+ private WebRtcAudioEffects() { |
+ Logging.d(TAG, "ctor" + WebRtcAudioUtils.getThreadInfo()); |
+ for (Descriptor d : AudioEffect.queryEffects()) { |
+ if (effectTypeIsVoIP(d.type)) { |
+ // Only log information for VoIP effects (AEC, AEC and NS). |
+ Logging.d(TAG, "name: " + d.name + ", " + |
+ "mode: " + d.connectMode + ", " + |
+ "implementor: " + d.implementor + ", " + |
+ "UUID: " + d.uuid); |
+ } |
+ } |
+ } |
+ |
+ // Call this method to enable or disable the platform AEC. It modifies |
+ // |shouldEnableAec| which is used in enable() where the actual state |
+ // of the AEC effect is modified. Returns true if HW AEC is supported and |
+ // false otherwise. |
+ public boolean setAEC(boolean enable) { |
+ Logging.d(TAG, "setAEC(" + enable + ")"); |
+ if (!canUseAcousticEchoCanceler()) { |
+ Logging.w(TAG, "Platform AEC is not supported"); |
+ shouldEnableAec = false; |
+ return false; |
+ } |
+ if (aec != null && (enable != shouldEnableAec)) { |
+ Logging.e(TAG, "Platform AEC state can't be modified while recording"); |
+ return false; |
+ } |
+ shouldEnableAec = enable; |
+ return true; |
+ } |
+ |
+ // Call this method to enable or disable the platform AGC. It modifies |
+ // |shouldEnableAgc| which is used in enable() where the actual state |
+ // of the AGC effect is modified. Returns true if HW AGC is supported and |
+ // false otherwise. |
+ public boolean setAGC(boolean enable) { |
+ Logging.d(TAG, "setAGC(" + enable + ")"); |
+ if (!canUseAutomaticGainControl()) { |
+ Logging.w(TAG, "Platform AGC is not supported"); |
+ shouldEnableAgc = false; |
+ return false; |
+ } |
+ if (agc != null && (enable != shouldEnableAgc)) { |
+ Logging.e(TAG, "Platform AGC state can't be modified while recording"); |
+ return false; |
+ } |
+ shouldEnableAgc = enable; |
+ return true; |
+ } |
+ |
+ // Call this method to enable or disable the platform NS. It modifies |
+ // |shouldEnableNs| which is used in enable() where the actual state |
+ // of the NS effect is modified. Returns true if HW NS is supported and |
+ // false otherwise. |
+ public boolean setNS(boolean enable) { |
+ Logging.d(TAG, "setNS(" + enable + ")"); |
+ if (!canUseNoiseSuppressor()) { |
+ Logging.w(TAG, "Platform NS is not supported"); |
+ shouldEnableNs = false; |
+ return false; |
+ } |
+ if (ns != null && (enable != shouldEnableNs)) { |
+ Logging.e(TAG, "Platform NS state can't be modified while recording"); |
+ return false; |
+ } |
+ shouldEnableNs = enable; |
+ return true; |
+ } |
+ |
+ public void enable(int audioSession) { |
+ Logging.d(TAG, "enable(audioSession=" + audioSession + ")"); |
+ assertTrue(aec == null); |
+ assertTrue(agc == null); |
+ assertTrue(ns == null); |
+ |
+ if (isAcousticEchoCancelerSupported()) { |
+ // Create an AcousticEchoCanceler and attach it to the AudioRecord on |
+ // the specified audio session. |
+ aec = AcousticEchoCanceler.create(audioSession); |
+ if (aec != null) { |
+ boolean enabled = aec.getEnabled(); |
+ boolean enable = shouldEnableAec && canUseAcousticEchoCanceler(); |
+ if (aec.setEnabled(enable) != AudioEffect.SUCCESS) { |
+ Logging.e(TAG, "Failed to set the AcousticEchoCanceler state"); |
+ } |
+ Logging.d(TAG, "AcousticEchoCanceler: was " + |
+ (enabled ? "enabled" : "disabled") + |
+ ", enable: " + enable + ", is now: " + |
+ (aec.getEnabled() ? "enabled" : "disabled")); |
+ } else { |
+ Logging.e(TAG, "Failed to create the AcousticEchoCanceler instance"); |
+ } |
+ } |
+ |
+ if (isAutomaticGainControlSupported()) { |
+ // Create an AutomaticGainControl and attach it to the AudioRecord on |
+ // the specified audio session. |
+ agc = AutomaticGainControl.create(audioSession); |
+ if (agc != null) { |
+ boolean enabled = agc.getEnabled(); |
+ boolean enable = shouldEnableAgc && canUseAutomaticGainControl(); |
+ if (agc.setEnabled(enable) != AudioEffect.SUCCESS) { |
+ Logging.e(TAG, "Failed to set the AutomaticGainControl state"); |
+ } |
+ Logging.d(TAG, "AutomaticGainControl: was " + |
+ (enabled ? "enabled" : "disabled") + |
+ ", enable: " + enable + ", is now: " + |
+ (agc.getEnabled() ? "enabled" : "disabled")); |
+ } else { |
+ Logging.e(TAG, "Failed to create the AutomaticGainControl instance"); |
+ } |
+ } |
+ |
+ if (isNoiseSuppressorSupported()) { |
+ // Create an NoiseSuppressor and attach it to the AudioRecord on the |
+ // specified audio session. |
+ ns = NoiseSuppressor.create(audioSession); |
+ if (ns != null) { |
+ boolean enabled = ns.getEnabled(); |
+ boolean enable = shouldEnableNs && canUseNoiseSuppressor(); |
+ if (ns.setEnabled(enable) != AudioEffect.SUCCESS) { |
+ Logging.e(TAG, "Failed to set the NoiseSuppressor state"); |
+ } |
+ Logging.d(TAG, "NoiseSuppressor: was " + |
+ (enabled ? "enabled" : "disabled") + |
+ ", enable: " + enable + ", is now: " + |
+ (ns.getEnabled() ? "enabled" : "disabled")); |
+ } else { |
+ Logging.e(TAG, "Failed to create the NoiseSuppressor instance"); |
+ } |
+ } |
+ } |
+ |
+ // Releases all native audio effect resources. It is a good practice to |
+ // release the effect engine when not in use as control can be returned |
+ // to other applications or the native resources released. |
+ public void release() { |
+ Logging.d(TAG, "release"); |
+ if (aec != null) { |
+ aec.release(); |
+ aec = null; |
+ } |
+ if (agc != null) { |
+ agc.release(); |
+ agc = null; |
+ } |
+ if (ns != null) { |
+ ns.release(); |
+ ns = null; |
+ } |
+ } |
+ |
+ // Returns true for effect types in |type| that are of "VoIP" types: |
+ // Acoustic Echo Canceler (AEC) or Automatic Gain Control (AGC) or |
+ // Noise Suppressor (NS). |
+ private boolean effectTypeIsVoIP(UUID type) { |
+ return AudioEffect.EFFECT_TYPE_AEC.equals(type) |
+ || AudioEffect.EFFECT_TYPE_AGC.equals(type) |
+ || AudioEffect.EFFECT_TYPE_NS.equals(type); |
+ } |
+ |
+ // Helper method which throws an exception when an assertion has failed. |
+ private static void assertTrue(boolean condition) { |
+ if (!condition) { |
+ throw new AssertionError("Expected condition to be true"); |
+ } |
+ } |
+} |