AppRTCDemo (Android): built-in AEC should be enabled if device supports it and in combination with Java-based audio layer

BUG=4034
R=andrew@webrtc.org, perkj@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/32179004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7849 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/test/fakeaudiocapturemodule.h b/talk/app/webrtc/test/fakeaudiocapturemodule.h
index 79b72b6..72de840 100644
--- a/talk/app/webrtc/test/fakeaudiocapturemodule.h
+++ b/talk/app/webrtc/test/fakeaudiocapturemodule.h
@@ -198,6 +198,8 @@
   virtual int32_t ResetAudioDevice() OVERRIDE;
   virtual int32_t SetLoudspeakerStatus(bool enable) OVERRIDE;
   virtual int32_t GetLoudspeakerStatus(bool* enabled) const OVERRIDE;
+  virtual bool BuiltInAECIsAvailable() const { return false; }
+  virtual int32_t EnableBuiltInAEC(bool enable) { return -1; }
   // End of functions inherited from webrtc::AudioDeviceModule.
 
   // The following function is inherited from rtc::MessageHandler.
diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCAudioManager.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCAudioManager.java
index b2a1a44..5f641bc 100644
--- a/talk/examples/android/src/org/appspot/apprtc/AppRTCAudioManager.java
+++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCAudioManager.java
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2013, Google Inc.
+ * Copyright 2014, Google Inc.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
diff --git a/talk/media/webrtc/fakewebrtcvoiceengine.h b/talk/media/webrtc/fakewebrtcvoiceengine.h
index dc27e96..b8450be 100644
--- a/talk/media/webrtc/fakewebrtcvoiceengine.h
+++ b/talk/media/webrtc/fakewebrtcvoiceengine.h
@@ -836,6 +836,7 @@
   }
   WEBRTC_STUB(EnableBuiltInAEC, (bool enable));
   virtual bool BuiltInAECIsEnabled() const { return true; }
+  virtual bool BuiltInAECIsAvailable() const { return false; }
 
   // webrtc::VoENetEqStats
   WEBRTC_STUB(GetNetworkStatistics, (int, webrtc::NetworkStatistics&));
diff --git a/talk/media/webrtc/webrtcvoiceengine.cc b/talk/media/webrtc/webrtcvoiceengine.cc
index 9f5d306..66408f0 100644
--- a/talk/media/webrtc/webrtcvoiceengine.cc
+++ b/talk/media/webrtc/webrtcvoiceengine.cc
@@ -811,8 +811,23 @@
 
   webrtc::VoEAudioProcessing* voep = voe_wrapper_->processing();
 
-  bool echo_cancellation;
+  bool echo_cancellation = false;
   if (options.echo_cancellation.Get(&echo_cancellation)) {
+    // Check if platform supports built-in EC. Currently only supported on
+    // Android and in combination with Java based audio layer.
+    // TODO(henrika): investigate possibility to support built-in EC also
+    // in combination with Open SL ES audio.
+    const bool built_in_aec = voe_wrapper_->hw()->BuiltInAECIsAvailable();
+    if (built_in_aec) {
+      // Set mode of built-in EC according to the audio options.
+      voe_wrapper_->hw()->EnableBuiltInAEC(echo_cancellation);
+      if (echo_cancellation) {
+        // Disable internal software EC if device has its own built-in EC,
+        // i.e., replace the software EC with the built-in EC.
+        options.echo_cancellation.Set(false);
+        LOG(LS_INFO) << "Disabling EC since built-in EC will be used instead";
+      }
+    }
     if (voep->SetEcStatus(echo_cancellation, ec_mode) == -1) {
       LOG_RTCERR2(SetEcStatus, echo_cancellation, ec_mode);
       return false;
diff --git a/webrtc/modules/audio_device/android/audio_device_template.h b/webrtc/modules/audio_device/android/audio_device_template.h
index f851f47..10e3191 100644
--- a/webrtc/modules/audio_device/android/audio_device_template.h
+++ b/webrtc/modules/audio_device/android/audio_device_template.h
@@ -405,6 +405,14 @@
     return output_.GetLoudspeakerStatus(enable);
   }
 
+  bool BuiltInAECIsAvailable() const {
+    return input_.BuiltInAECIsAvailable();
+  }
+
+  int32_t EnableBuiltInAEC(bool enable) {
+    return input_.EnableBuiltInAEC(enable);
+  }
+
  private:
   OutputType output_;
   InputType input_;
diff --git a/webrtc/modules/audio_device/android/audio_record_jni.cc b/webrtc/modules/audio_device/android/audio_record_jni.cc
index 42d2fdf..6a51bc3 100644
--- a/webrtc/modules/audio_device/android/audio_record_jni.cc
+++ b/webrtc/modules/audio_device/android/audio_record_jni.cc
@@ -23,7 +23,6 @@
 #include "webrtc/modules/audio_device/android/audio_common.h"
 #include "webrtc/modules/audio_device/audio_device_config.h"
 #include "webrtc/modules/audio_device/audio_device_utility.h"
-
 #include "webrtc/system_wrappers/interface/event_wrapper.h"
 #include "webrtc/system_wrappers/interface/thread_wrapper.h"
 #include "webrtc/system_wrappers/interface/trace.h"
@@ -360,7 +359,6 @@
 
 int32_t AudioRecordJni::InitRecording() {
   CriticalSectionScoped lock(&_critSect);
-
   if (!_initialized)
   {
     WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
@@ -799,6 +797,84 @@
   return 0;
 }
 
+bool AudioRecordJni::BuiltInAECIsAvailable() const {
+  assert(_javaVM);
+
+  JNIEnv* env = NULL;
+  bool isAttached = false;
+
+  // Get the JNI env for this thread
+  if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+    jint res = _javaVM->AttachCurrentThread(&env, NULL);
+    if ((res < 0) || !env) {
+      return false;
+    }
+    isAttached = true;
+  }
+
+  // Get method ID for BuiltInAECIsAvailable
+  jmethodID builtInAECIsAvailable = env->GetStaticMethodID(
+      _javaScClass, "BuiltInAECIsAvailable", "()Z");
+  if (builtInAECIsAvailable == NULL) {
+    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
+                 "%s: Unable to get BuiltInAECIsAvailable ID", __FUNCTION__);
+    return false;
+  }
+
+  // Call the static BuiltInAECIsAvailable method
+  jboolean hw_aec = env->CallStaticBooleanMethod(_javaScClass,
+                                                 builtInAECIsAvailable);
+
+  // Detach this thread if it was attached
+  if (isAttached) {
+    if (_javaVM->DetachCurrentThread() < 0) {
+      WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
+                 "%s: Could not detach thread from JVM", __FUNCTION__);
+    }
+  }
+
+  return hw_aec;
+}
+
+int32_t AudioRecordJni::EnableBuiltInAEC(bool enable) {
+  assert(_javaVM);
+
+  jint res = 0;
+  JNIEnv* env = NULL;
+  bool isAttached = false;
+
+  // Get the JNI env for this thread
+  if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+    res = _javaVM->AttachCurrentThread(&env, NULL);
+    if ((res < 0) || !env) {
+      return false;
+    }
+    isAttached = true;
+  }
+
+  // Get method ID for EnableBuiltInAEC "(argument-types)return-type"
+  jmethodID enableBuiltInAEC = env->GetMethodID(_javaScClass,
+                                                "EnableBuiltInAEC",
+                                                "(Z)I");
+
+  // Call the EnableBuiltInAEC method
+  res = env->CallIntMethod(_javaScObj, enableBuiltInAEC, enable);
+  if (res < 0) {
+    WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
+                 "EnableBuiltInAEC failed (%d)", res);
+  }
+
+  // Detach this thread if it was attached
+  if (isAttached) {
+    if (_javaVM->DetachCurrentThread() < 0) {
+      WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
+                   "%s: Could not detach thread from JVM", __FUNCTION__);
+    }
+  }
+
+  return res;
+}
+
 int32_t AudioRecordJni::InitJavaResources() {
   // todo: Check if we already have created the java object
   _javaVM = globalJvm;
diff --git a/webrtc/modules/audio_device/android/audio_record_jni.h b/webrtc/modules/audio_device/android/audio_record_jni.h
index 1e58c99..363d544 100644
--- a/webrtc/modules/audio_device/android/audio_record_jni.h
+++ b/webrtc/modules/audio_device/android/audio_record_jni.h
@@ -109,6 +109,9 @@
 
   int32_t SetRecordingSampleRate(const uint32_t samplesPerSec);
 
+  bool BuiltInAECIsAvailable() const;
+  int32_t EnableBuiltInAEC(bool enable);
+
  private:
   void Lock() EXCLUSIVE_LOCK_FUNCTION(_critSect) {
     _critSect.Enter();
diff --git a/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java
index 9167af0..6014e71 100644
--- a/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java
+++ b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java
@@ -15,9 +15,13 @@
 
 import android.content.Context;
 import android.media.AudioFormat;
+import android.media.audiofx.AcousticEchoCanceler;
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.AudioEffect.Descriptor;
 import android.media.AudioManager;
 import android.media.AudioRecord;
 import android.media.MediaRecorder.AudioSource;
+import android.os.Build;
 import android.util.Log;
 
 class WebRtcAudioRecord {
@@ -35,6 +39,13 @@
 
     private int _bufferedRecSamples = 0;
 
+    private AcousticEchoCanceler _aec = null;
+    private boolean _useBuiltInAEC = false;
+
+    private static boolean runningOnJellyBeanOrHigher() {
+      return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+    }
+
     WebRtcAudioRecord() {
         try {
             _recBuffer = ByteBuffer.allocateDirect(2 * 480); // Max 10 ms @ 48
@@ -46,8 +57,42 @@
         _tempBufRec = new byte[2 * 480];
     }
 
+    public static boolean BuiltInAECIsAvailable() {
+      // AcousticEchoCanceler was added in API level 16 (Jelly Bean).
+      if (!runningOnJellyBeanOrHigher()) {
+        return false;
+      }
+      // TODO(henrika): add black-list based on device name. We could also
+      // use uuid to exclude devices but that would require a session ID from
+      // an existing AudioRecord object.
+      return AcousticEchoCanceler.isAvailable();
+    }
+
+    private int EnableBuiltInAEC(boolean enable) {
+      DoLog("EnableBuiltInAEC(" + enable + ')');
+      // AcousticEchoCanceler was added in API level 16 (Jelly Bean).
+      if (!runningOnJellyBeanOrHigher()) {
+        return -1;
+      }
+
+      _useBuiltInAEC = enable;
+
+      // Set AEC state if AEC has already been created.
+      if (_aec != null) {
+        int ret = _aec.setEnabled(enable);
+        if (ret != AudioEffect.SUCCESS) {
+          DoLogErr("AcousticEchoCanceler.setEnabled failed");
+          return -1;
+        }
+        DoLog("AcousticEchoCanceler.getEnabled: " + _aec.getEnabled());
+      }
+
+      return 0;
+    }
+
     @SuppressWarnings("unused")
     private int InitRecording(int audioSource, int sampleRate) {
+        DoLog("InitRecording");
         audioSource = AudioSource.VOICE_COMMUNICATION;
         // get the minimum buffer size that can be used
         int minRecBufSize = AudioRecord.getMinBufferSize(
@@ -64,6 +109,11 @@
         _bufferedRecSamples = sampleRate / 200;
         // DoLog("rough rec delay set to " + _bufferedRecSamples);
 
+        if (_aec != null) {
+            _aec.release();
+            _aec = null;
+        }
+
         // release the object
         if (_audioRecord != null) {
             _audioRecord.release();
@@ -91,11 +141,36 @@
 
         // DoLog("rec sample rate set to " + sampleRate);
 
+        DoLog("AcousticEchoCanceler.isAvailable: " + BuiltInAECIsAvailable());
+        if (!BuiltInAECIsAvailable()) {
+            return _bufferedRecSamples;
+        }
+
+        _aec = AcousticEchoCanceler.create(_audioRecord.getAudioSessionId());
+        if (_aec == null) {
+            DoLogErr("AcousticEchoCanceler.create failed");
+            return -1;
+        }
+
+        int ret = _aec.setEnabled(_useBuiltInAEC);
+        if (ret != AudioEffect.SUCCESS) {
+            DoLogErr("AcousticEchoCanceler.setEnabled failed");
+            return -1;
+        }
+
+        Descriptor descriptor = _aec.getDescriptor();
+        DoLog("AcousticEchoCanceler " +
+              "name: " + descriptor.name + ", " +
+              "implementor: " + descriptor.implementor + ", " +
+              "uuid: " + descriptor.uuid);
+        DoLog("AcousticEchoCanceler.getEnabled: " + _aec.getEnabled());
+
         return _bufferedRecSamples;
     }
 
     @SuppressWarnings("unused")
     private int StartRecording() {
+        DoLog("StartRecording");
         // start recording
         try {
             _audioRecord.startRecording();
@@ -111,6 +186,7 @@
 
     @SuppressWarnings("unused")
     private int StopRecording() {
+        DoLog("StopRecording");
         _recLock.lock();
         try {
             // only stop if we are recording
@@ -125,7 +201,13 @@
                 }
             }
 
-            // release the object
+            // Release the AEC object.
+            if (_aec != null) {
+                _aec.release();
+                _aec = null;
+            }
+
+            // Release the AudioRecord object.
             _audioRecord.release();
             _audioRecord = null;
 
@@ -185,7 +267,7 @@
         return _bufferedRecSamples;
     }
 
-    final String logTag = "WebRTC AD java";
+    final String logTag = "WebRtcAudioRecord-Java";
 
     private void DoLog(String msg) {
         Log.d(logTag, msg);
diff --git a/webrtc/modules/audio_device/android/opensles_input.h b/webrtc/modules/audio_device/android/opensles_input.h
index d27d824..2f819b3 100644
--- a/webrtc/modules/audio_device/android/opensles_input.h
+++ b/webrtc/modules/audio_device/android/opensles_input.h
@@ -118,6 +118,10 @@
   // Attach audio buffer
   void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer);
 
+  // Built-in AEC is only supported in combination with Java/AudioRecord.
+  bool BuiltInAECIsAvailable() const { return false; }
+  int32_t EnableBuiltInAEC(bool enable) { return -1; }
+
  private:
   enum {
     kNumInterfaces = 2,
diff --git a/webrtc/modules/audio_device/audio_device_generic.cc b/webrtc/modules/audio_device/audio_device_generic.cc
index 543c6ff..958abbf 100644
--- a/webrtc/modules/audio_device/audio_device_generic.cc
+++ b/webrtc/modules/audio_device/audio_device_generic.cc
@@ -58,10 +58,16 @@
     return -1;
 }
 
+bool AudioDeviceGeneric::BuiltInAECIsAvailable() const {
+  WEBRTC_TRACE(kTraceError, kTraceAudioDevice, -1,
+      "Built-in AEC not supported on this platform");
+  return false;
+}
+
 int32_t AudioDeviceGeneric::EnableBuiltInAEC(bool enable)
 {
     WEBRTC_TRACE(kTraceError, kTraceAudioDevice, -1,
-        "Windows AEC not supported on this platform");
+        "Built-in AEC not supported on this platform");
     return -1;
 }
 
diff --git a/webrtc/modules/audio_device/audio_device_generic.h b/webrtc/modules/audio_device/audio_device_generic.h
index a4c320e..800cc39 100644
--- a/webrtc/modules/audio_device/audio_device_generic.h
+++ b/webrtc/modules/audio_device/audio_device_generic.h
@@ -155,8 +155,13 @@
                                        unsigned int par3 = 0,
                                        unsigned int par4 = 0);
 
-    // Windows Core Audio only.
+    // Android only
+    virtual bool BuiltInAECIsAvailable() const;
+
+    // Windows Core Audio and Android only.
     virtual int32_t EnableBuiltInAEC(bool enable);
+
+    // Windows Core Audio only.
     virtual bool BuiltInAECIsEnabled() const;
 
 public:
diff --git a/webrtc/modules/audio_device/audio_device_impl.cc b/webrtc/modules/audio_device/audio_device_impl.cc
index 826a89d..a440381 100644
--- a/webrtc/modules/audio_device/audio_device_impl.cc
+++ b/webrtc/modules/audio_device/audio_device_impl.cc
@@ -1978,9 +1978,8 @@
 
 int32_t AudioDeviceModuleImpl::EnableBuiltInAEC(bool enable)
 {
-    CHECK_INITIALIZED();
-
-    return _ptrAudioDevice->EnableBuiltInAEC(enable);
+  CHECK_INITIALIZED();
+  return _ptrAudioDevice->EnableBuiltInAEC(enable);
 }
 
 bool AudioDeviceModuleImpl::BuiltInAECIsEnabled() const
@@ -1990,6 +1989,11 @@
     return _ptrAudioDevice->BuiltInAECIsEnabled();
 }
 
+bool AudioDeviceModuleImpl::BuiltInAECIsAvailable() const {
+  CHECK_INITIALIZED_BOOL();
+  return _ptrAudioDevice->BuiltInAECIsAvailable();
+}
+
 // ============================================================================
 //                                 Private Methods
 // ============================================================================
diff --git a/webrtc/modules/audio_device/audio_device_impl.h b/webrtc/modules/audio_device/audio_device_impl.h
index a545d58..dfc9b5d 100644
--- a/webrtc/modules/audio_device/audio_device_impl.h
+++ b/webrtc/modules/audio_device/audio_device_impl.h
@@ -199,6 +199,8 @@
     virtual int32_t SetLoudspeakerStatus(bool enable) OVERRIDE;
     virtual int32_t GetLoudspeakerStatus(bool* enabled) const OVERRIDE;
 
+    virtual bool BuiltInAECIsAvailable() const OVERRIDE;
+
     virtual int32_t EnableBuiltInAEC(bool enable) OVERRIDE;
     virtual bool BuiltInAECIsEnabled() const OVERRIDE;
 
diff --git a/webrtc/modules/audio_device/include/audio_device.h b/webrtc/modules/audio_device/include/audio_device.h
index ec40274..736ed35 100644
--- a/webrtc/modules/audio_device/include/audio_device.h
+++ b/webrtc/modules/audio_device/include/audio_device.h
@@ -182,15 +182,20 @@
   virtual int32_t SetLoudspeakerStatus(bool enable) = 0;
   virtual int32_t GetLoudspeakerStatus(bool* enabled) const = 0;
 
-  // *Experimental - not recommended for use.*
-  // Enables the Windows Core Audio built-in AEC. Fails on other platforms.
+  // Only supported on Android.
+  virtual bool BuiltInAECIsAvailable() const = 0;
+
+  // Enables the built-in AEC. Only supported on Windows and Android.
   //
+  // For usage on Windows (requires Core Audio):
   // Must be called before InitRecording(). When enabled:
   // 1. StartPlayout() must be called before StartRecording().
   // 2. StopRecording() should be called before StopPlayout().
   //    The reverse order may cause garbage audio to be rendered or the
   //    capture side to halt until StopRecording() is called.
-  virtual int32_t EnableBuiltInAEC(bool enable) { return -1; }
+  virtual int32_t EnableBuiltInAEC(bool enable) = 0;
+
+  // Don't use.
   virtual bool BuiltInAECIsEnabled() const { return false; }
 
  protected:
diff --git a/webrtc/modules/audio_device/include/fake_audio_device.h b/webrtc/modules/audio_device/include/fake_audio_device.h
index 5cdf54f..a627aec 100644
--- a/webrtc/modules/audio_device/include/fake_audio_device.h
+++ b/webrtc/modules/audio_device/include/fake_audio_device.h
@@ -144,6 +144,7 @@
   virtual int32_t ResetAudioDevice() { return 0; }
   virtual int32_t SetLoudspeakerStatus(bool enable) { return 0; }
   virtual int32_t GetLoudspeakerStatus(bool* enabled) const { return 0; }
+  virtual bool BuiltInAECIsAvailable() const { return false; }
   virtual int32_t EnableBuiltInAEC(bool enable) { return -1; }
   virtual bool BuiltInAECIsEnabled() const { return false; }
 };
diff --git a/webrtc/voice_engine/include/voe_hardware.h b/webrtc/voice_engine/include/voe_hardware.h
index 23255a8..db9bc56 100644
--- a/webrtc/voice_engine/include/voe_hardware.h
+++ b/webrtc/voice_engine/include/voe_hardware.h
@@ -89,8 +89,10 @@
     virtual int SetPlayoutSampleRate(unsigned int samples_per_sec) = 0;
     virtual int PlayoutSampleRate(unsigned int* samples_per_sec) const = 0;
 
+    virtual bool BuiltInAECIsAvailable() const = 0;
+    virtual int EnableBuiltInAEC(bool enable) = 0;
+
     // To be removed. Don't use.
-    virtual int EnableBuiltInAEC(bool enable) { return -1; }
     virtual bool BuiltInAECIsEnabled() const { return false; }
     virtual int GetRecordingDeviceStatus(bool& isAvailable) { return -1; }
     virtual int GetPlayoutDeviceStatus(bool& isAvailable) { return -1; }
diff --git a/webrtc/voice_engine/voe_hardware_impl.cc b/webrtc/voice_engine/voe_hardware_impl.cc
index eaf2a28..2099562 100644
--- a/webrtc/voice_engine/voe_hardware_impl.cc
+++ b/webrtc/voice_engine/voe_hardware_impl.cc
@@ -568,6 +568,22 @@
   return _shared->audio_device()->PlayoutSampleRate(samples_per_sec);
 }
 
+bool VoEHardwareImpl::BuiltInAECIsAvailable() const {
+if (!_shared->statistics().Initialized()) {
+    _shared->SetLastError(VE_NOT_INITED, kTraceError);
+    return false;
+  }
+  return _shared->audio_device()->BuiltInAECIsAvailable();
+}
+
+int VoEHardwareImpl::EnableBuiltInAEC(bool enable) {
+if (!_shared->statistics().Initialized()) {
+    _shared->SetLastError(VE_NOT_INITED, kTraceError);
+    return false;
+  }
+  return _shared->audio_device()->EnableBuiltInAEC(enable);
+}
+
 #endif  // WEBRTC_VOICE_ENGINE_HARDWARE_API
 
 }  // namespace webrtc
diff --git a/webrtc/voice_engine/voe_hardware_impl.h b/webrtc/voice_engine/voe_hardware_impl.h
index f3537ef..ca1cf56 100644
--- a/webrtc/voice_engine/voe_hardware_impl.h
+++ b/webrtc/voice_engine/voe_hardware_impl.h
@@ -48,6 +48,9 @@
     virtual int SetPlayoutSampleRate(unsigned int samples_per_sec);
     virtual int PlayoutSampleRate(unsigned int* samples_per_sec) const;
 
+    virtual bool BuiltInAECIsAvailable() const;
+    virtual int EnableBuiltInAEC(bool enable);
+
 protected:
     VoEHardwareImpl(voe::SharedData* shared);
     virtual ~VoEHardwareImpl();