Reuse callback buffers in the Visualizer.

Don't re-allocate buffers used by Visualizer callbacks as this causes an
unacceptable amount of GC thrash.  Instead, lazily allocate the buffers and only
reallocate them when the required size changes.

See http://b/issue?id=5717519 for details.

Change-Id: Ibd157ed51f30687ce7c4ef0b4003258a484e0f5d
Signed-off-by: John Grossman <johngro@google.com>
diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java
index bcf7b89..1f1e60d 100755
--- a/media/java/android/media/audiofx/Visualizer.java
+++ b/media/java/android/media/audiofx/Visualizer.java
@@ -396,6 +396,9 @@
     public interface OnDataCaptureListener  {
         /**
          * Method called when a new waveform capture is available.
+         * <p>Data in the waveform buffer is valid only within the scope of the callback.
+         * Applications which needs access to the waveform data after returning from the callback
+         * should make a copy of the data instead of holding a reference.
          * @param visualizer Visualizer object on which the listener is registered.
          * @param waveform array of bytes containing the waveform representation.
          * @param samplingRate sampling rate of the audio visualized.
@@ -404,6 +407,9 @@
 
         /**
          * Method called when a new frequency capture is available.
+         * <p>Data in the fft buffer is valid only within the scope of the callback.
+         * Applications which needs access to the fft data after returning from the callback
+         * should make a copy of the data instead of holding a reference.
          * @param visualizer Visualizer object on which the listener is registered.
          * @param fft array of bytes containing the frequency representation.
          * @param samplingRate sampling rate of the audio visualized.
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index ecd4d07..433c459 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -23,6 +23,7 @@
 #include <nativehelper/jni.h>
 #include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
+#include <utils/threads.h>
 #include "media/Visualizer.h"
 
 using namespace android;
@@ -54,6 +55,43 @@
 struct visualizer_callback_cookie {
     jclass      visualizer_class;  // Visualizer class
     jobject     visualizer_ref;    // Visualizer object instance
+
+    // Lazily allocated arrays used to hold callback data provided to java
+    // applications.  These arrays are allocated during the first callback and
+    // reallocated when the size of the callback data changes.  Allocating on
+    // demand and saving the arrays means that applications cannot safely hold a
+    // reference to the provided data (they need to make a copy if they want to
+    // hold onto outside of the callback scope), but it avoids GC thrash caused
+    // by constantly allocating and releasing arrays to hold callback data.
+    Mutex       callback_data_lock;
+    jbyteArray  waveform_data;
+    jbyteArray  fft_data;
+
+    visualizer_callback_cookie() {
+        waveform_data = NULL;
+        fft_data = NULL;
+    }
+
+    ~visualizer_callback_cookie() {
+        cleanupBuffers();
+    }
+
+    void cleanupBuffers() {
+        AutoMutex lock(&callback_data_lock);
+        if (waveform_data || fft_data) {
+            JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+            if (waveform_data) {
+                env->DeleteGlobalRef(waveform_data);
+                waveform_data = NULL;
+            }
+
+            if (fft_data) {
+                env->DeleteGlobalRef(fft_data);
+                fft_data = NULL;
+            }
+        }
+    }
  };
 
 // ----------------------------------------------------------------------------
@@ -66,7 +104,6 @@
 
     ~visualizerJniStorage() {
     }
-
 };
 
 
@@ -93,6 +130,26 @@
 
 
 // ----------------------------------------------------------------------------
+static void ensureArraySize(JNIEnv *env, jbyteArray *array, uint32_t size) {
+    if (NULL != *array) {
+        uint32_t len = env->GetArrayLength(*array);
+        if (len == size)
+            return;
+
+        env->DeleteGlobalRef(*array);
+        *array = NULL;
+    }
+
+    jbyteArray localRef = env->NewByteArray(size);
+    if (NULL != localRef) {
+        // Promote to global ref.
+        *array = (jbyteArray)env->NewGlobalRef(localRef);
+
+        // Release our (now pointless) local ref.
+        env->DeleteLocalRef(localRef);
+    }
+}
+
 static void captureCallback(void* user,
         uint32_t waveformSize,
         uint8_t *waveform,
@@ -106,6 +163,7 @@
 
     visualizer_callback_cookie *callbackInfo = (visualizer_callback_cookie *)user;
     JNIEnv *env = AndroidRuntime::getJNIEnv();
+    AutoMutex lock(&callbackInfo->callback_data_lock);
 
     ALOGV("captureCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p",
             callbackInfo,
@@ -118,7 +176,11 @@
     }
 
     if (waveformSize != 0 && waveform != NULL) {
-        jbyteArray jArray = env->NewByteArray(waveformSize);
+        jbyteArray jArray;
+
+        ensureArraySize(env, &callbackInfo->waveform_data, waveformSize);
+        jArray = callbackInfo->waveform_data;
+
         if (jArray != NULL) {
             jbyte *nArray = env->GetByteArrayElements(jArray, NULL);
             memcpy(nArray, waveform, waveformSize);
@@ -131,12 +193,15 @@
                 samplingrate,
                 0,
                 jArray);
-            env->DeleteLocalRef(jArray);
         }
     }
 
     if (fftSize != 0 && fft != NULL) {
-        jbyteArray jArray = env->NewByteArray(fftSize);
+        jbyteArray jArray;
+
+        ensureArraySize(env, &callbackInfo->fft_data, fftSize);
+        jArray = callbackInfo->fft_data;
+
         if (jArray != NULL) {
             jbyte *nArray = env->GetByteArrayElements(jArray, NULL);
             memcpy(nArray, fft, fftSize);
@@ -149,7 +214,6 @@
                 samplingrate,
                 0,
                 jArray);
-            env->DeleteLocalRef(jArray);
         }
     }
 
@@ -345,7 +409,17 @@
         return VISUALIZER_ERROR_NO_INIT;
     }
 
-    return translateError(lpVisualizer->setEnabled(enabled));
+    jint retVal = translateError(lpVisualizer->setEnabled(enabled));
+
+    if (!enabled) {
+        visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetIntField(
+            thiz, fields.fidJniData);
+
+        if (NULL != lpJniStorage)
+            lpJniStorage->mCallbackData.cleanupBuffers();
+    }
+
+    return retVal;
 }
 
 static jboolean