Home | History | Annotate | Download | only in voiceengine
      1 /*
      2  *  Copyright (c) 2015 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.voiceengine;
     12 
     13 import android.annotation.TargetApi;
     14 import android.media.audiofx.AcousticEchoCanceler;
     15 import android.media.audiofx.AudioEffect;
     16 import android.media.audiofx.AudioEffect.Descriptor;
     17 import android.media.audiofx.AutomaticGainControl;
     18 import android.media.audiofx.NoiseSuppressor;
     19 import android.os.Build;
     20 
     21 import org.webrtc.Logging;
     22 
     23 import java.util.List;
     24 
     25 import java.util.UUID;
     26 
     27 // This class wraps control of three different platform effects. Supported
     28 // effects are: AcousticEchoCanceler (AEC), AutomaticGainControl (AGC) and
     29 // NoiseSuppressor (NS). Calling enable() will active all effects that are
     30 // supported by the device if the corresponding |shouldEnableXXX| member is set.
     31 class WebRtcAudioEffects {
     32   private static final boolean DEBUG = false;
     33 
     34   private static final String TAG = "WebRtcAudioEffects";
     35 
     36   // UUIDs for Software Audio Effects that we want to avoid using.
     37   // The implementor field will be set to "The Android Open Source Project".
     38   private static final UUID AOSP_ACOUSTIC_ECHO_CANCELER =
     39       UUID.fromString("bb392ec0-8d4d-11e0-a896-0002a5d5c51b");
     40   private static final UUID AOSP_AUTOMATIC_GAIN_CONTROL =
     41       UUID.fromString("aa8130e0-66fc-11e0-bad0-0002a5d5c51b");
     42   private static final UUID AOSP_NOISE_SUPPRESSOR =
     43       UUID.fromString("c06c8400-8e06-11e0-9cb6-0002a5d5c51b");
     44 
     45   // Static Boolean objects used to avoid expensive queries more than once.
     46   // The first result is cached in these members and then reused if needed.
     47   // Each member is null until it has been evaluated/set for the first time.
     48   private static Boolean canUseAcousticEchoCanceler = null;
     49   private static Boolean canUseAutomaticGainControl = null;
     50   private static Boolean canUseNoiseSuppressor = null;
     51 
     52   // Contains the audio effect objects. Created in enable() and destroyed
     53   // in release().
     54   private AcousticEchoCanceler aec = null;
     55   private AutomaticGainControl agc = null;
     56   private NoiseSuppressor ns = null;
     57 
     58   // Affects the final state given to the setEnabled() method on each effect.
     59   // The default state is set to "disabled" but each effect can also be enabled
     60   // by calling setAEC(), setAGC() and setNS().
     61   // To enable an effect, both the shouldEnableXXX member and the static
     62   // canUseXXX() must be true.
     63   private boolean shouldEnableAec = false;
     64   private boolean shouldEnableAgc = false;
     65   private boolean shouldEnableNs = false;
     66 
     67   // Checks if the device implements Acoustic Echo Cancellation (AEC).
     68   // Returns true if the device implements AEC, false otherwise.
     69   public static boolean isAcousticEchoCancelerSupported() {
     70     return WebRtcAudioUtils.runningOnJellyBeanOrHigher()
     71         && AcousticEchoCanceler.isAvailable();
     72   }
     73 
     74   // Checks if the device implements Automatic Gain Control (AGC).
     75   // Returns true if the device implements AGC, false otherwise.
     76   public static boolean isAutomaticGainControlSupported() {
     77     return WebRtcAudioUtils.runningOnJellyBeanOrHigher()
     78         && AutomaticGainControl.isAvailable();
     79   }
     80 
     81   // Checks if the device implements Noise Suppression (NS).
     82   // Returns true if the device implements NS, false otherwise.
     83   public static boolean isNoiseSuppressorSupported() {
     84     return WebRtcAudioUtils.runningOnJellyBeanOrHigher()
     85         && NoiseSuppressor.isAvailable();
     86   }
     87 
     88   // Returns true if the device is blacklisted for HW AEC usage.
     89   public static boolean isAcousticEchoCancelerBlacklisted() {
     90     List<String> blackListedModels =
     91         WebRtcAudioUtils.getBlackListedModelsForAecUsage();
     92     boolean isBlacklisted = blackListedModels.contains(Build.MODEL);
     93     if (isBlacklisted) {
     94       Logging.w(TAG, Build.MODEL + " is blacklisted for HW AEC usage!");
     95     }
     96     return isBlacklisted;
     97   }
     98 
     99   // Returns true if the device is blacklisted for HW AGC usage.
    100   public static boolean isAutomaticGainControlBlacklisted() {
    101    List<String> blackListedModels =
    102         WebRtcAudioUtils.getBlackListedModelsForAgcUsage();
    103     boolean isBlacklisted = blackListedModels.contains(Build.MODEL);
    104     if (isBlacklisted) {
    105       Logging.w(TAG, Build.MODEL + " is blacklisted for HW AGC usage!");
    106     }
    107     return isBlacklisted;
    108   }
    109 
    110   // Returns true if the device is blacklisted for HW NS usage.
    111   public static boolean isNoiseSuppressorBlacklisted() {
    112     List<String> blackListedModels =
    113         WebRtcAudioUtils.getBlackListedModelsForNsUsage();
    114     boolean isBlacklisted = blackListedModels.contains(Build.MODEL);
    115     if (isBlacklisted) {
    116       Logging.w(TAG, Build.MODEL + " is blacklisted for HW NS usage!");
    117     }
    118     return isBlacklisted;
    119   }
    120 
    121   // Returns true if the platform AEC should be excluded based on its UUID.
    122   // AudioEffect.queryEffects() can throw IllegalStateException.
    123   @TargetApi(18)
    124   private static boolean isAcousticEchoCancelerExcludedByUUID() {
    125     for (Descriptor d : AudioEffect.queryEffects()) {
    126       if (d.type.equals(AudioEffect.EFFECT_TYPE_AEC) &&
    127           d.uuid.equals(AOSP_ACOUSTIC_ECHO_CANCELER)) {
    128         return true;
    129       }
    130     }
    131     return false;
    132   }
    133 
    134   // Returns true if the platform AGC should be excluded based on its UUID.
    135   // AudioEffect.queryEffects() can throw IllegalStateException.
    136   @TargetApi(18)
    137   private static boolean isAutomaticGainControlExcludedByUUID() {
    138     for (Descriptor d : AudioEffect.queryEffects()) {
    139       if (d.type.equals(AudioEffect.EFFECT_TYPE_AGC) &&
    140           d.uuid.equals(AOSP_AUTOMATIC_GAIN_CONTROL)) {
    141         return true;
    142       }
    143     }
    144     return false;
    145   }
    146 
    147   // Returns true if the platform NS should be excluded based on its UUID.
    148   // AudioEffect.queryEffects() can throw IllegalStateException.
    149   @TargetApi(18)
    150   private static boolean isNoiseSuppressorExcludedByUUID() {
    151     for (Descriptor d : AudioEffect.queryEffects()) {
    152       if (d.type.equals(AudioEffect.EFFECT_TYPE_NS) &&
    153           d.uuid.equals(AOSP_NOISE_SUPPRESSOR)) {
    154         return true;
    155       }
    156     }
    157     return false;
    158   }
    159 
    160   // Returns true if all conditions for supporting the HW AEC are fulfilled.
    161   // It will not be possible to enable the HW AEC if this method returns false.
    162   public static boolean canUseAcousticEchoCanceler() {
    163     if (canUseAcousticEchoCanceler == null) {
    164       canUseAcousticEchoCanceler = new Boolean(
    165           isAcousticEchoCancelerSupported()
    166           && !WebRtcAudioUtils.useWebRtcBasedAcousticEchoCanceler()
    167           && !isAcousticEchoCancelerBlacklisted()
    168           && !isAcousticEchoCancelerExcludedByUUID());
    169       Logging.d(TAG, "canUseAcousticEchoCanceler: "
    170           + canUseAcousticEchoCanceler);
    171     }
    172     return canUseAcousticEchoCanceler;
    173   }
    174 
    175   // Returns true if all conditions for supporting the HW AGC are fulfilled.
    176   // It will not be possible to enable the HW AGC if this method returns false.
    177   public static boolean canUseAutomaticGainControl() {
    178     if (canUseAutomaticGainControl == null) {
    179       canUseAutomaticGainControl = new Boolean(
    180           isAutomaticGainControlSupported()
    181           && !WebRtcAudioUtils.useWebRtcBasedAutomaticGainControl()
    182           && !isAutomaticGainControlBlacklisted()
    183           && !isAutomaticGainControlExcludedByUUID());
    184       Logging.d(TAG, "canUseAutomaticGainControl: "
    185           + canUseAutomaticGainControl);
    186     }
    187     return canUseAutomaticGainControl;
    188   }
    189 
    190   // Returns true if all conditions for supporting the HW NS are fulfilled.
    191   // It will not be possible to enable the HW NS if this method returns false.
    192   public static boolean canUseNoiseSuppressor() {
    193     if (canUseNoiseSuppressor == null) {
    194       canUseNoiseSuppressor = new Boolean(
    195           isNoiseSuppressorSupported()
    196           && !WebRtcAudioUtils.useWebRtcBasedNoiseSuppressor()
    197           && !isNoiseSuppressorBlacklisted()
    198           && !isNoiseSuppressorExcludedByUUID());
    199       Logging.d(TAG, "canUseNoiseSuppressor: " + canUseNoiseSuppressor);
    200     }
    201     return canUseNoiseSuppressor;
    202   }
    203 
    204   static WebRtcAudioEffects create() {
    205     // Return null if VoIP effects (AEC, AGC and NS) are not supported.
    206     if (!WebRtcAudioUtils.runningOnJellyBeanOrHigher()) {
    207       Logging.w(TAG, "API level 16 or higher is required!");
    208       return null;
    209     }
    210     return new WebRtcAudioEffects();
    211   }
    212 
    213   private WebRtcAudioEffects() {
    214     Logging.d(TAG, "ctor" + WebRtcAudioUtils.getThreadInfo());
    215   }
    216 
    217   // Call this method to enable or disable the platform AEC. It modifies
    218   // |shouldEnableAec| which is used in enable() where the actual state
    219   // of the AEC effect is modified. Returns true if HW AEC is supported and
    220   // false otherwise.
    221   public boolean setAEC(boolean enable) {
    222     Logging.d(TAG, "setAEC(" + enable + ")");
    223     if (!canUseAcousticEchoCanceler()) {
    224       Logging.w(TAG, "Platform AEC is not supported");
    225       shouldEnableAec = false;
    226       return false;
    227     }
    228     if (aec != null && (enable != shouldEnableAec)) {
    229       Logging.e(TAG, "Platform AEC state can't be modified while recording");
    230       return false;
    231     }
    232     shouldEnableAec = enable;
    233     return true;
    234   }
    235 
    236   // Call this method to enable or disable the platform AGC. It modifies
    237   // |shouldEnableAgc| which is used in enable() where the actual state
    238   // of the AGC effect is modified. Returns true if HW AGC is supported and
    239   // false otherwise.
    240   public boolean setAGC(boolean enable) {
    241     Logging.d(TAG, "setAGC(" + enable + ")");
    242     if (!canUseAutomaticGainControl()) {
    243       Logging.w(TAG, "Platform AGC is not supported");
    244       shouldEnableAgc = false;
    245       return false;
    246     }
    247     if (agc != null && (enable != shouldEnableAgc)) {
    248       Logging.e(TAG, "Platform AGC state can't be modified while recording");
    249       return false;
    250     }
    251     shouldEnableAgc = enable;
    252     return true;
    253   }
    254 
    255   // Call this method to enable or disable the platform NS. It modifies
    256   // |shouldEnableNs| which is used in enable() where the actual state
    257   // of the NS effect is modified. Returns true if HW NS is supported and
    258   // false otherwise.
    259   public boolean setNS(boolean enable) {
    260     Logging.d(TAG, "setNS(" + enable + ")");
    261     if (!canUseNoiseSuppressor()) {
    262       Logging.w(TAG, "Platform NS is not supported");
    263       shouldEnableNs = false;
    264       return false;
    265     }
    266     if (ns != null && (enable != shouldEnableNs)) {
    267       Logging.e(TAG, "Platform NS state can't be modified while recording");
    268       return false;
    269     }
    270     shouldEnableNs = enable;
    271     return true;
    272   }
    273 
    274   public void enable(int audioSession) {
    275     Logging.d(TAG, "enable(audioSession=" + audioSession + ")");
    276     assertTrue(aec == null);
    277     assertTrue(agc == null);
    278     assertTrue(ns == null);
    279 
    280     // Add logging of supported effects but filter out "VoIP effects", i.e.,
    281     // AEC, AEC and NS.
    282     for (Descriptor d : AudioEffect.queryEffects()) {
    283       if (effectTypeIsVoIP(d.type) || DEBUG) {
    284         Logging.d(TAG, "name: " + d.name + ", "
    285             + "mode: " + d.connectMode + ", "
    286             + "implementor: " + d.implementor + ", "
    287             + "UUID: " + d.uuid);
    288       }
    289     }
    290 
    291     if (isAcousticEchoCancelerSupported()) {
    292       // Create an AcousticEchoCanceler and attach it to the AudioRecord on
    293       // the specified audio session.
    294       aec = AcousticEchoCanceler.create(audioSession);
    295       if (aec != null) {
    296         boolean enabled = aec.getEnabled();
    297         boolean enable = shouldEnableAec && canUseAcousticEchoCanceler();
    298         if (aec.setEnabled(enable) != AudioEffect.SUCCESS) {
    299           Logging.e(TAG, "Failed to set the AcousticEchoCanceler state");
    300         }
    301         Logging.d(TAG, "AcousticEchoCanceler: was "
    302             + (enabled ? "enabled" : "disabled")
    303             + ", enable: " + enable + ", is now: "
    304             + (aec.getEnabled() ? "enabled" : "disabled"));
    305       } else {
    306         Logging.e(TAG, "Failed to create the AcousticEchoCanceler instance");
    307       }
    308     }
    309 
    310     if (isAutomaticGainControlSupported()) {
    311       // Create an AutomaticGainControl and attach it to the AudioRecord on
    312       // the specified audio session.
    313       agc = AutomaticGainControl.create(audioSession);
    314       if (agc != null) {
    315         boolean enabled = agc.getEnabled();
    316         boolean enable = shouldEnableAgc && canUseAutomaticGainControl();
    317         if (agc.setEnabled(enable) != AudioEffect.SUCCESS) {
    318           Logging.e(TAG, "Failed to set the AutomaticGainControl state");
    319         }
    320         Logging.d(TAG, "AutomaticGainControl: was "
    321             + (enabled ? "enabled" : "disabled")
    322             + ", enable: " + enable + ", is now: "
    323             + (agc.getEnabled() ? "enabled" : "disabled"));
    324       } else {
    325         Logging.e(TAG, "Failed to create the AutomaticGainControl instance");
    326       }
    327     }
    328 
    329     if (isNoiseSuppressorSupported()) {
    330       // Create an NoiseSuppressor and attach it to the AudioRecord on the
    331       // specified audio session.
    332       ns = NoiseSuppressor.create(audioSession);
    333       if (ns != null) {
    334         boolean enabled = ns.getEnabled();
    335         boolean enable = shouldEnableNs && canUseNoiseSuppressor();
    336         if (ns.setEnabled(enable) != AudioEffect.SUCCESS) {
    337           Logging.e(TAG, "Failed to set the NoiseSuppressor state");
    338         }
    339         Logging.d(TAG, "NoiseSuppressor: was "
    340             + (enabled ? "enabled" : "disabled")
    341             + ", enable: " + enable + ", is now: "
    342             + (ns.getEnabled() ? "enabled" : "disabled"));
    343       } else {
    344         Logging.e(TAG, "Failed to create the NoiseSuppressor instance");
    345       }
    346     }
    347   }
    348 
    349   // Releases all native audio effect resources. It is a good practice to
    350   // release the effect engine when not in use as control can be returned
    351   // to other applications or the native resources released.
    352   public void release() {
    353     Logging.d(TAG, "release");
    354     if (aec != null) {
    355       aec.release();
    356       aec = null;
    357     }
    358     if (agc != null) {
    359       agc.release();
    360       agc = null;
    361     }
    362     if (ns != null) {
    363       ns.release();
    364       ns = null;
    365     }
    366   }
    367 
    368   // Returns true for effect types in |type| that are of "VoIP" types:
    369   // Acoustic Echo Canceler (AEC) or Automatic Gain Control (AGC) or
    370   // Noise Suppressor (NS). Note that, an extra check for support is needed
    371   // in each comparison since some devices includes effects in the
    372   // AudioEffect.Descriptor array that are actually not available on the device.
    373   // As an example: Samsung Galaxy S6 includes an AGC in the descriptor but
    374   // AutomaticGainControl.isAvailable() returns false.
    375   @TargetApi(18)
    376   private boolean effectTypeIsVoIP(UUID type) {
    377     if (!WebRtcAudioUtils.runningOnJellyBeanMR2OrHigher())
    378       return false;
    379 
    380     return (AudioEffect.EFFECT_TYPE_AEC.equals(type)
    381         && isAcousticEchoCancelerSupported())
    382         || (AudioEffect.EFFECT_TYPE_AGC.equals(type)
    383         && isAutomaticGainControlSupported())
    384         || (AudioEffect.EFFECT_TYPE_NS.equals(type)
    385         && isNoiseSuppressorSupported());
    386   }
    387 
    388   // Helper method which throws an exception when an assertion has failed.
    389   private static void assertTrue(boolean condition) {
    390     if (!condition) {
    391       throw new AssertionError("Expected condition to be true");
    392     }
    393   }
    394 }
    395