| /* |
| * 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) || DEBUG) { |
| // 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). Note that, an extra check for support is needed |
| // in each comparison since some devices includes effects in the |
| // AudioEffect.Descriptor array that are actually not available on the device. |
| // As an example: Samsung Galaxy S6 includes an AGC in the descriptor but |
| // AutomaticGainControl.isAvailable() returns false. |
| private boolean effectTypeIsVoIP(UUID type) { |
| return (AudioEffect.EFFECT_TYPE_AEC.equals(type) |
| && isAcousticEchoCancelerSupported()) |
| || (AudioEffect.EFFECT_TYPE_AGC.equals(type) |
| && isAutomaticGainControlSupported()) |
| || (AudioEffect.EFFECT_TYPE_NS.equals(type) |
| && isNoiseSuppressorSupported()); |
| } |
| |
| // 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"); |
| } |
| } |
| } |