• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.audio;
12 
13 import android.media.audiofx.AcousticEchoCanceler;
14 import android.media.audiofx.AudioEffect;
15 import android.media.audiofx.AudioEffect.Descriptor;
16 import android.media.audiofx.NoiseSuppressor;
17 import android.os.Build;
18 import androidx.annotation.Nullable;
19 import java.util.UUID;
20 import org.webrtc.Logging;
21 
22 // This class wraps control of three different platform effects. Supported
23 // effects are: AcousticEchoCanceler (AEC) and NoiseSuppressor (NS).
24 // Calling enable() will active all effects that are
25 // supported by the device if the corresponding `shouldEnableXXX` member is set.
26 class WebRtcAudioEffects {
27   private static final boolean DEBUG = false;
28 
29   private static final String TAG = "WebRtcAudioEffectsExternal";
30 
31   // UUIDs for Software Audio Effects that we want to avoid using.
32   // The implementor field will be set to "The Android Open Source Project".
33   private static final UUID AOSP_ACOUSTIC_ECHO_CANCELER =
34       UUID.fromString("bb392ec0-8d4d-11e0-a896-0002a5d5c51b");
35   private static final UUID AOSP_NOISE_SUPPRESSOR =
36       UUID.fromString("c06c8400-8e06-11e0-9cb6-0002a5d5c51b");
37 
38   // Contains the available effect descriptors returned from the
39   // AudioEffect.getEffects() call. This result is cached to avoid doing the
40   // slow OS call multiple times.
41   private static @Nullable Descriptor[] cachedEffects;
42 
43   // Contains the audio effect objects. Created in enable() and destroyed
44   // in release().
45   private @Nullable AcousticEchoCanceler aec;
46   private @Nullable NoiseSuppressor ns;
47 
48   // Affects the final state given to the setEnabled() method on each effect.
49   // The default state is set to "disabled" but each effect can also be enabled
50   // by calling setAEC() and setNS().
51   private boolean shouldEnableAec;
52   private boolean shouldEnableNs;
53 
54   // Returns true if all conditions for supporting HW Acoustic Echo Cancellation (AEC) are
55   // fulfilled.
isAcousticEchoCancelerSupported()56   public static boolean isAcousticEchoCancelerSupported() {
57     return isEffectTypeAvailable(AudioEffect.EFFECT_TYPE_AEC, AOSP_ACOUSTIC_ECHO_CANCELER);
58   }
59 
60   // Returns true if all conditions for supporting HW Noise Suppression (NS) are fulfilled.
isNoiseSuppressorSupported()61   public static boolean isNoiseSuppressorSupported() {
62     return isEffectTypeAvailable(AudioEffect.EFFECT_TYPE_NS, AOSP_NOISE_SUPPRESSOR);
63   }
64 
WebRtcAudioEffects()65   public WebRtcAudioEffects() {
66     Logging.d(TAG, "ctor" + WebRtcAudioUtils.getThreadInfo());
67   }
68 
69   // Call this method to enable or disable the platform AEC. It modifies
70   // `shouldEnableAec` which is used in enable() where the actual state
71   // of the AEC effect is modified. Returns true if HW AEC is supported and
72   // false otherwise.
setAEC(boolean enable)73   public boolean setAEC(boolean enable) {
74     Logging.d(TAG, "setAEC(" + enable + ")");
75     if (!isAcousticEchoCancelerSupported()) {
76       Logging.w(TAG, "Platform AEC is not supported");
77       shouldEnableAec = false;
78       return false;
79     }
80     if (aec != null && (enable != shouldEnableAec)) {
81       Logging.e(TAG, "Platform AEC state can't be modified while recording");
82       return false;
83     }
84     shouldEnableAec = enable;
85     return true;
86   }
87 
88   // Call this method to enable or disable the platform NS. It modifies
89   // `shouldEnableNs` which is used in enable() where the actual state
90   // of the NS effect is modified. Returns true if HW NS is supported and
91   // false otherwise.
setNS(boolean enable)92   public boolean setNS(boolean enable) {
93     Logging.d(TAG, "setNS(" + enable + ")");
94     if (!isNoiseSuppressorSupported()) {
95       Logging.w(TAG, "Platform NS is not supported");
96       shouldEnableNs = false;
97       return false;
98     }
99     if (ns != null && (enable != shouldEnableNs)) {
100       Logging.e(TAG, "Platform NS state can't be modified while recording");
101       return false;
102     }
103     shouldEnableNs = enable;
104     return true;
105   }
106 
enable(int audioSession)107   public void enable(int audioSession) {
108     Logging.d(TAG, "enable(audioSession=" + audioSession + ")");
109     assertTrue(aec == null);
110     assertTrue(ns == null);
111 
112     if (DEBUG) {
113       // Add logging of supported effects but filter out "VoIP effects", i.e.,
114       // AEC, AEC and NS. Avoid calling AudioEffect.queryEffects() unless the
115       // DEBUG flag is set since we have seen crashes in this API.
116       for (Descriptor d : AudioEffect.queryEffects()) {
117         if (effectTypeIsVoIP(d.type)) {
118           Logging.d(TAG,
119               "name: " + d.name + ", "
120                   + "mode: " + d.connectMode + ", "
121                   + "implementor: " + d.implementor + ", "
122                   + "UUID: " + d.uuid);
123         }
124       }
125     }
126 
127     if (isAcousticEchoCancelerSupported()) {
128       // Create an AcousticEchoCanceler and attach it to the AudioRecord on
129       // the specified audio session.
130       aec = AcousticEchoCanceler.create(audioSession);
131       if (aec != null) {
132         boolean enabled = aec.getEnabled();
133         boolean enable = shouldEnableAec && isAcousticEchoCancelerSupported();
134         if (aec.setEnabled(enable) != AudioEffect.SUCCESS) {
135           Logging.e(TAG, "Failed to set the AcousticEchoCanceler state");
136         }
137         Logging.d(TAG,
138             "AcousticEchoCanceler: was " + (enabled ? "enabled" : "disabled") + ", enable: "
139                 + enable + ", is now: " + (aec.getEnabled() ? "enabled" : "disabled"));
140       } else {
141         Logging.e(TAG, "Failed to create the AcousticEchoCanceler instance");
142       }
143     }
144 
145     if (isNoiseSuppressorSupported()) {
146       // Create an NoiseSuppressor and attach it to the AudioRecord on the
147       // specified audio session.
148       ns = NoiseSuppressor.create(audioSession);
149       if (ns != null) {
150         boolean enabled = ns.getEnabled();
151         boolean enable = shouldEnableNs && isNoiseSuppressorSupported();
152         if (ns.setEnabled(enable) != AudioEffect.SUCCESS) {
153           Logging.e(TAG, "Failed to set the NoiseSuppressor state");
154         }
155         Logging.d(TAG,
156             "NoiseSuppressor: was " + (enabled ? "enabled" : "disabled") + ", enable: " + enable
157                 + ", is now: " + (ns.getEnabled() ? "enabled" : "disabled"));
158       } else {
159         Logging.e(TAG, "Failed to create the NoiseSuppressor instance");
160       }
161     }
162   }
163 
164   // Releases all native audio effect resources. It is a good practice to
165   // release the effect engine when not in use as control can be returned
166   // to other applications or the native resources released.
release()167   public void release() {
168     Logging.d(TAG, "release");
169     if (aec != null) {
170       aec.release();
171       aec = null;
172     }
173     if (ns != null) {
174       ns.release();
175       ns = null;
176     }
177   }
178 
179   // Returns true for effect types in `type` that are of "VoIP" types:
180   // Acoustic Echo Canceler (AEC) or Automatic Gain Control (AGC) or
181   // Noise Suppressor (NS). Note that, an extra check for support is needed
182   // in each comparison since some devices includes effects in the
183   // AudioEffect.Descriptor array that are actually not available on the device.
184   // As an example: Samsung Galaxy S6 includes an AGC in the descriptor but
185   // AutomaticGainControl.isAvailable() returns false.
effectTypeIsVoIP(UUID type)186   private boolean effectTypeIsVoIP(UUID type) {
187     return (AudioEffect.EFFECT_TYPE_AEC.equals(type) && isAcousticEchoCancelerSupported())
188         || (AudioEffect.EFFECT_TYPE_NS.equals(type) && isNoiseSuppressorSupported());
189   }
190 
191   // Helper method which throws an exception when an assertion has failed.
assertTrue(boolean condition)192   private static void assertTrue(boolean condition) {
193     if (!condition) {
194       throw new AssertionError("Expected condition to be true");
195     }
196   }
197 
198   // Returns the cached copy of the audio effects array, if available, or
199   // queries the operating system for the list of effects.
getAvailableEffects()200   private static @Nullable Descriptor[] getAvailableEffects() {
201     if (cachedEffects != null) {
202       return cachedEffects;
203     }
204     // The caching is best effort only - if this method is called from several
205     // threads in parallel, they may end up doing the underlying OS call
206     // multiple times. It's normally only called on one thread so there's no
207     // real need to optimize for the multiple threads case.
208     cachedEffects = AudioEffect.queryEffects();
209     return cachedEffects;
210   }
211 
212   // Returns true if an effect of the specified type is available. Functionally
213   // equivalent to (NoiseSuppressor`AutomaticGainControl`...).isAvailable(), but
214   // faster as it avoids the expensive OS call to enumerate effects.
isEffectTypeAvailable(UUID effectType, UUID blockListedUuid)215   private static boolean isEffectTypeAvailable(UUID effectType, UUID blockListedUuid) {
216     Descriptor[] effects = getAvailableEffects();
217     if (effects == null) {
218       return false;
219     }
220     for (Descriptor d : effects) {
221       if (d.type.equals(effectType)) {
222         return !d.uuid.equals(blockListedUuid);
223       }
224     }
225     return false;
226   }
227 }
228