| 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");
|
| + }
|
| + }
|
| +}
|
|
|