/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>

//#define LOG_NDEBUG 0
#define LOG_TAG "visualizers-JNI"

#include <utils/Log.h>
#include <nativehelper/jni.h>
#include <nativehelper/JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
#include <utils/threads.h>
#include "media/Visualizer.h"

#include <ScopedUtfChars.h>

using namespace android;

#define VISUALIZER_SUCCESS                      0
#define VISUALIZER_ERROR                       -1
#define VISUALIZER_ERROR_ALREADY_EXISTS        -2
#define VISUALIZER_ERROR_NO_INIT               -3
#define VISUALIZER_ERROR_BAD_VALUE             -4
#define VISUALIZER_ERROR_INVALID_OPERATION     -5
#define VISUALIZER_ERROR_NO_MEMORY             -6
#define VISUALIZER_ERROR_DEAD_OBJECT           -7

#define NATIVE_EVENT_PCM_CAPTURE                0
#define NATIVE_EVENT_FFT_CAPTURE                1
#define NATIVE_EVENT_SERVER_DIED                2

// ----------------------------------------------------------------------------
static const char* const kClassPathName = "android/media/audiofx/Visualizer";
static const char* const kClassPeakRmsPathName =
        "android/media/audiofx/Visualizer$MeasurementPeakRms";

struct fields_t {
    // these fields provide access from C++ to the...
    jclass    clazzEffect;          // Visualizer class
    jmethodID midPostNativeEvent;   // event post callback method
    jfieldID  fidNativeVisualizer; // stores in Java the native Visualizer object
    jfieldID  fidJniData;           // stores in Java additional resources used by the native Visualizer
    jfieldID  fidPeak; // to access Visualizer.MeasurementPeakRms.mPeak
    jfieldID  fidRms;  // to access Visualizer.MeasurementPeakRms.mRms
};
static fields_t fields;

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;
            }
        }
    }
 };

// ----------------------------------------------------------------------------
class VisualizerJniStorage {
    public:
        visualizer_callback_cookie mCallbackData;

    VisualizerJniStorage() {
    }

    ~VisualizerJniStorage() {
    }
};


static jint translateError(int code) {
    switch(code) {
    case NO_ERROR:
        return VISUALIZER_SUCCESS;
    case ALREADY_EXISTS:
        return VISUALIZER_ERROR_ALREADY_EXISTS;
    case NO_INIT:
        return VISUALIZER_ERROR_NO_INIT;
    case BAD_VALUE:
        return VISUALIZER_ERROR_BAD_VALUE;
    case INVALID_OPERATION:
        return VISUALIZER_ERROR_INVALID_OPERATION;
    case NO_MEMORY:
        return VISUALIZER_ERROR_NO_MEMORY;
    case DEAD_OBJECT:
        return VISUALIZER_ERROR_DEAD_OBJECT;
    default:
        return VISUALIZER_ERROR;
    }
}

static Mutex sLock;

// ----------------------------------------------------------------------------
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,
        uint32_t fftSize,
        uint8_t *fft,
        uint32_t samplingrate) {

    visualizer_callback_cookie *callbackInfo = (visualizer_callback_cookie *)user;
    JNIEnv *env = AndroidRuntime::getJNIEnv();

    if (!user || !env) {
        ALOGW("captureCallback error user %p, env %p", user, env);
        return;
    }

    ALOGV("captureCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p",
            callbackInfo,
            callbackInfo->visualizer_ref,
            callbackInfo->visualizer_class);

    AutoMutex lock(&callbackInfo->callback_data_lock);

    if (waveformSize != 0 && waveform != NULL) {
        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);
            env->ReleaseByteArrayElements(jArray, nArray, 0);
            env->CallStaticVoidMethod(
                callbackInfo->visualizer_class,
                fields.midPostNativeEvent,
                callbackInfo->visualizer_ref,
                NATIVE_EVENT_PCM_CAPTURE,
                samplingrate,
                0,
                jArray);
        }
    }

    if (fftSize != 0 && fft != NULL) {
        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);
            env->ReleaseByteArrayElements(jArray, nArray, 0);
            env->CallStaticVoidMethod(
                callbackInfo->visualizer_class,
                fields.midPostNativeEvent,
                callbackInfo->visualizer_ref,
                NATIVE_EVENT_FFT_CAPTURE,
                samplingrate,
                0,
                jArray);
        }
    }

    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
}

// ----------------------------------------------------------------------------

static sp<Visualizer> getVisualizer(JNIEnv* env, jobject thiz)
{
    Mutex::Autolock l(sLock);
    Visualizer* const v =
            (Visualizer*)env->GetLongField(thiz, fields.fidNativeVisualizer);
    return sp<Visualizer>(v);
}

static sp<Visualizer> setVisualizer(JNIEnv* env, jobject thiz,
                                    const sp<Visualizer>& v)
{
    Mutex::Autolock l(sLock);
    sp<Visualizer> old =
            (Visualizer*)env->GetLongField(thiz, fields.fidNativeVisualizer);
    if (v.get()) {
        v->incStrong((void*)setVisualizer);
    }
    if (old != 0) {
        old->decStrong((void*)setVisualizer);
    }
    env->SetLongField(thiz, fields.fidNativeVisualizer, (jlong)v.get());
    return old;
}

// ----------------------------------------------------------------------------
// This function gets some field IDs, which in turn causes class initialization.
// It is called from a static block in Visualizer, which won't run until the
// first time an instance of this class is used.
static void
android_media_visualizer_native_init(JNIEnv *env)
{

    ALOGV("android_media_visualizer_native_init");

    fields.clazzEffect = NULL;

    // Get the Visualizer class
    jclass clazz = env->FindClass(kClassPathName);
    if (clazz == NULL) {
        ALOGE("Can't find %s", kClassPathName);
        return;
    }

    fields.clazzEffect = (jclass)env->NewGlobalRef(clazz);

    // Get the Visualizer.MeasurementPeakRms class
    clazz = env->FindClass(kClassPeakRmsPathName);
    if (clazz == NULL) {
        ALOGE("Can't find %s", kClassPeakRmsPathName);
        return;
    }
    jclass clazzMeasurementPeakRms = (jclass)env->NewGlobalRef(clazz);

    // Get the postEvent method
    fields.midPostNativeEvent = env->GetStaticMethodID(
            fields.clazzEffect,
            "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.midPostNativeEvent == NULL) {
        ALOGE("Can't find Visualizer.%s", "postEventFromNative");
        return;
    }

    // Get the variables fields
    //      nativeTrackInJavaObj
    fields.fidNativeVisualizer = env->GetFieldID(
            fields.clazzEffect,
            "mNativeVisualizer", "J");
    if (fields.fidNativeVisualizer == NULL) {
        ALOGE("Can't find Visualizer.%s", "mNativeVisualizer");
        return;
    }
    //      fidJniData;
    fields.fidJniData = env->GetFieldID(
            fields.clazzEffect,
            "mJniData", "J");
    if (fields.fidJniData == NULL) {
        ALOGE("Can't find Visualizer.%s", "mJniData");
        return;
    }
    //      fidPeak
    fields.fidPeak = env->GetFieldID(
            clazzMeasurementPeakRms,
            "mPeak", "I");
    if (fields.fidPeak == NULL) {
        ALOGE("Can't find Visualizer.MeasurementPeakRms.%s", "mPeak");
        return;
    }
    //      fidRms
    fields.fidRms = env->GetFieldID(
            clazzMeasurementPeakRms,
            "mRms", "I");
    if (fields.fidRms == NULL) {
        ALOGE("Can't find Visualizer.MeasurementPeakRms.%s", "mPeak");
        return;
    }

    env->DeleteGlobalRef(clazzMeasurementPeakRms);
}

static void android_media_visualizer_effect_callback(int32_t event,
                                                     void *user,
                                                     void *info) {
    if ((event == AudioEffect::EVENT_ERROR) &&
        (*((status_t*)info) == DEAD_OBJECT)) {
        VisualizerJniStorage* lpJniStorage = (VisualizerJniStorage*)user;
        visualizer_callback_cookie* callbackInfo = &lpJniStorage->mCallbackData;
        JNIEnv *env = AndroidRuntime::getJNIEnv();

        env->CallStaticVoidMethod(
            callbackInfo->visualizer_class,
            fields.midPostNativeEvent,
            callbackInfo->visualizer_ref,
            NATIVE_EVENT_SERVER_DIED,
            0, 0, NULL);
    }
}

static jint
android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
        jint sessionId, jintArray jId, jstring opPackageName)
{
    ALOGV("android_media_visualizer_native_setup");
    VisualizerJniStorage* lpJniStorage = NULL;
    int lStatus = VISUALIZER_ERROR_NO_MEMORY;
    sp<Visualizer> lpVisualizer;
    jint* nId = NULL;

    ScopedUtfChars opPackageNameStr(env, opPackageName);

    setVisualizer(env, thiz, 0);

    lpJniStorage = new VisualizerJniStorage();
    if (lpJniStorage == NULL) {
        ALOGE("setup: Error creating JNI Storage");
        goto setup_failure;
    }

    lpJniStorage->mCallbackData.visualizer_class = (jclass)env->NewGlobalRef(fields.clazzEffect);
    // we use a weak reference so the Visualizer object can be garbage collected.
    lpJniStorage->mCallbackData.visualizer_ref = env->NewGlobalRef(weak_this);

    ALOGV("setup: lpJniStorage: %p visualizer_ref %p visualizer_class %p, &mCallbackData %p",
            lpJniStorage,
            lpJniStorage->mCallbackData.visualizer_ref,
            lpJniStorage->mCallbackData.visualizer_class,
            &lpJniStorage->mCallbackData);

    if (jId == NULL) {
        ALOGE("setup: NULL java array for id pointer");
        lStatus = VISUALIZER_ERROR_BAD_VALUE;
        goto setup_failure;
    }

    // create the native Visualizer object
    lpVisualizer = new Visualizer(String16(opPackageNameStr.c_str()),
                                  0,
                                  android_media_visualizer_effect_callback,
                                  lpJniStorage,
                                  sessionId);
    if (lpVisualizer == 0) {
        ALOGE("Error creating Visualizer");
        goto setup_failure;
    }

    lStatus = translateError(lpVisualizer->initCheck());
    if (lStatus != VISUALIZER_SUCCESS && lStatus != VISUALIZER_ERROR_ALREADY_EXISTS) {
        ALOGE("Visualizer initCheck failed %d", lStatus);
        goto setup_failure;
    }

    nId = (jint *) env->GetPrimitiveArrayCritical(jId, NULL);
    if (nId == NULL) {
        ALOGE("setup: Error retrieving id pointer");
        lStatus = VISUALIZER_ERROR_BAD_VALUE;
        goto setup_failure;
    }
    nId[0] = lpVisualizer->id();
    env->ReleasePrimitiveArrayCritical(jId, nId, 0);
    nId = NULL;

    setVisualizer(env, thiz, lpVisualizer);

    env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage);

    return VISUALIZER_SUCCESS;

    // failures:
setup_failure:

    if (nId != NULL) {
        env->ReleasePrimitiveArrayCritical(jId, nId, 0);
    }

    if (lpJniStorage) {
        env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_class);
        env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_ref);
        delete lpJniStorage;
    }
    env->SetLongField(thiz, fields.fidJniData, 0);

    return (jint) lStatus;
}

// ----------------------------------------------------------------------------
static void android_media_visualizer_native_release(JNIEnv *env,  jobject thiz) {
    sp<Visualizer> lpVisualizer = setVisualizer(env, thiz, 0);
    if (lpVisualizer == 0) {
        return;
    }

    // delete the JNI data
    VisualizerJniStorage* lpJniStorage =
        (VisualizerJniStorage *)env->GetLongField(thiz, fields.fidJniData);

    // reset the native resources in the Java object so any attempt to access
    // them after a call to release fails.
    env->SetLongField(thiz, fields.fidJniData, 0);

    if (lpJniStorage) {
        ALOGV("deleting pJniStorage: %p\n", lpJniStorage);
        env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_class);
        env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_ref);
        delete lpJniStorage;
    }
}

static void android_media_visualizer_native_finalize(JNIEnv *env,  jobject thiz) {
    ALOGV("android_media_visualizer_native_finalize jobject: %p\n", thiz);
    android_media_visualizer_native_release(env, thiz);
}

// ----------------------------------------------------------------------------
static jint
android_media_visualizer_native_setEnabled(JNIEnv *env, jobject thiz, jboolean enabled)
{
    sp<Visualizer> lpVisualizer = getVisualizer(env, thiz);
    if (lpVisualizer == 0) {
        return VISUALIZER_ERROR_NO_INIT;
    }

    jint retVal = translateError(lpVisualizer->setEnabled(enabled));

    if (!enabled) {
        VisualizerJniStorage* lpJniStorage = (VisualizerJniStorage *)env->GetLongField(
            thiz, fields.fidJniData);

        if (NULL != lpJniStorage)
            lpJniStorage->mCallbackData.cleanupBuffers();
    }

    return retVal;
}

static jboolean
android_media_visualizer_native_getEnabled(JNIEnv *env, jobject thiz)
{
    sp<Visualizer> lpVisualizer = getVisualizer(env, thiz);
    if (lpVisualizer == 0) {
        return JNI_FALSE;
    }

    if (lpVisualizer->getEnabled()) {
        return JNI_TRUE;
    } else {
        return JNI_FALSE;
    }
}

static jintArray
android_media_visualizer_native_getCaptureSizeRange(JNIEnv *env, jobject /* thiz */)
{
    jintArray jRange = env->NewIntArray(2);
    jint *nRange = env->GetIntArrayElements(jRange, NULL);
    nRange[0] = Visualizer::getMinCaptureSize();
    nRange[1] = Visualizer::getMaxCaptureSize();
    ALOGV("getCaptureSizeRange() min %d max %d", nRange[0], nRange[1]);
    env->ReleaseIntArrayElements(jRange, nRange, 0);
    return jRange;
}

static jint
android_media_visualizer_native_getMaxCaptureRate(JNIEnv* /* env */, jobject /* thiz */)
{
    return (jint) Visualizer::getMaxCaptureRate();
}

static jint
android_media_visualizer_native_setCaptureSize(JNIEnv *env, jobject thiz, jint size)
{
    sp<Visualizer> lpVisualizer = getVisualizer(env, thiz);
    if (lpVisualizer == 0) {
        return VISUALIZER_ERROR_NO_INIT;
    }

    return translateError(lpVisualizer->setCaptureSize(size));
}

static jint
android_media_visualizer_native_getCaptureSize(JNIEnv *env, jobject thiz)
{
    sp<Visualizer> lpVisualizer = getVisualizer(env, thiz);
    if (lpVisualizer == 0) {
        return -1;
    }
    return (jint) lpVisualizer->getCaptureSize();
}

static jint
android_media_visualizer_native_setScalingMode(JNIEnv *env, jobject thiz, jint mode)
{
    sp<Visualizer> lpVisualizer = getVisualizer(env, thiz);
    if (lpVisualizer == 0) {
        return VISUALIZER_ERROR_NO_INIT;
    }

    return translateError(lpVisualizer->setScalingMode(mode));
}

static jint
android_media_visualizer_native_getScalingMode(JNIEnv *env, jobject thiz)
{
    sp<Visualizer> lpVisualizer = getVisualizer(env, thiz);
    if (lpVisualizer == 0) {
        return -1;
    }
    return (jint)lpVisualizer->getScalingMode();
}

static jint
android_media_visualizer_native_setMeasurementMode(JNIEnv *env, jobject thiz, jint mode)
{
    sp<Visualizer> lpVisualizer = getVisualizer(env, thiz);
    if (lpVisualizer == 0) {
        return VISUALIZER_ERROR_NO_INIT;
    }
    return translateError(lpVisualizer->setMeasurementMode(mode));
}

static jint
android_media_visualizer_native_getMeasurementMode(JNIEnv *env, jobject thiz)
{
    sp<Visualizer> lpVisualizer = getVisualizer(env, thiz);
    if (lpVisualizer == 0) {
        return MEASUREMENT_MODE_NONE;
    }
    return lpVisualizer->getMeasurementMode();
}

static jint
android_media_visualizer_native_getSamplingRate(JNIEnv *env, jobject thiz)
{
    sp<Visualizer> lpVisualizer = getVisualizer(env, thiz);
    if (lpVisualizer == 0) {
        return -1;
    }
    return (jint) lpVisualizer->getSamplingRate();
}

static jint
android_media_visualizer_native_getWaveForm(JNIEnv *env, jobject thiz, jbyteArray jWaveform)
{
    sp<Visualizer> lpVisualizer = getVisualizer(env, thiz);
    if (lpVisualizer == 0) {
        return VISUALIZER_ERROR_NO_INIT;
    }

    jbyte* nWaveform = (jbyte *) env->GetPrimitiveArrayCritical(jWaveform, NULL);
    if (nWaveform == NULL) {
        return VISUALIZER_ERROR_NO_MEMORY;
    }
    jint status = translateError(lpVisualizer->getWaveForm((uint8_t *)nWaveform));

    env->ReleasePrimitiveArrayCritical(jWaveform, nWaveform, 0);
    return status;
}

static jint
android_media_visualizer_native_getFft(JNIEnv *env, jobject thiz, jbyteArray jFft)
{
    sp<Visualizer> lpVisualizer = getVisualizer(env, thiz);
    if (lpVisualizer == 0) {
        return VISUALIZER_ERROR_NO_INIT;
    }

    jbyte* nFft = (jbyte *) env->GetPrimitiveArrayCritical(jFft, NULL);
    if (nFft == NULL) {
        return VISUALIZER_ERROR_NO_MEMORY;
    }
    jint status = translateError(lpVisualizer->getFft((uint8_t *)nFft));

    env->ReleasePrimitiveArrayCritical(jFft, nFft, 0);

    return status;
}

static jint
android_media_visualizer_native_getPeakRms(JNIEnv *env, jobject thiz, jobject jPeakRmsObj)
{
    sp<Visualizer> lpVisualizer = getVisualizer(env, thiz);
    if (lpVisualizer == 0) {
        return VISUALIZER_ERROR_NO_INIT;
    }
    int32_t measurements[2];
    jint status = translateError(
                lpVisualizer->getIntMeasurements(MEASUREMENT_MODE_PEAK_RMS,
                        2, measurements));
    if (status == VISUALIZER_SUCCESS) {
        // measurement worked, write the values to the java object
        env->SetIntField(jPeakRmsObj, fields.fidPeak, measurements[MEASUREMENT_IDX_PEAK]);
        env->SetIntField(jPeakRmsObj, fields.fidRms, measurements[MEASUREMENT_IDX_RMS]);
    }
    return status;
}

static jint
android_media_setPeriodicCapture(JNIEnv *env, jobject thiz, jint rate, jboolean jWaveform, jboolean jFft)
{
    sp<Visualizer> lpVisualizer = getVisualizer(env, thiz);
    if (lpVisualizer == 0) {
        return VISUALIZER_ERROR_NO_INIT;
    }
    VisualizerJniStorage* lpJniStorage = (VisualizerJniStorage *)env->GetLongField(thiz,
            fields.fidJniData);
    if (lpJniStorage == NULL) {
        return VISUALIZER_ERROR_NO_INIT;
    }

    ALOGV("setPeriodicCapture: rate %d, jWaveform %d jFft %d",
            rate,
            jWaveform,
            jFft);

    uint32_t flags = Visualizer::CAPTURE_CALL_JAVA;
    if (jWaveform) flags |= Visualizer::CAPTURE_WAVEFORM;
    if (jFft) flags |= Visualizer::CAPTURE_FFT;
    Visualizer::capture_cbk_t cbk = captureCallback;
    if (!jWaveform && !jFft) cbk = NULL;

    return translateError(lpVisualizer->setCaptureCallBack(cbk,
                                                &lpJniStorage->mCallbackData,
                                                flags,
                                                rate));
}

// ----------------------------------------------------------------------------

// Dalvik VM type signatures
static JNINativeMethod gMethods[] = {
    {"native_init",            "()V",     (void *)android_media_visualizer_native_init},
    {"native_setup",           "(Ljava/lang/Object;I[ILjava/lang/String;)I",
                                          (void *)android_media_visualizer_native_setup},
    {"native_finalize",          "()V",   (void *)android_media_visualizer_native_finalize},
    {"native_release",           "()V",   (void *)android_media_visualizer_native_release},
    {"native_setEnabled",        "(Z)I",  (void *)android_media_visualizer_native_setEnabled},
    {"native_getEnabled",        "()Z",   (void *)android_media_visualizer_native_getEnabled},
    {"getCaptureSizeRange",      "()[I",  (void *)android_media_visualizer_native_getCaptureSizeRange},
    {"getMaxCaptureRate",        "()I",   (void *)android_media_visualizer_native_getMaxCaptureRate},
    {"native_setCaptureSize",    "(I)I",  (void *)android_media_visualizer_native_setCaptureSize},
    {"native_getCaptureSize",    "()I",   (void *)android_media_visualizer_native_getCaptureSize},
    {"native_setScalingMode",    "(I)I",  (void *)android_media_visualizer_native_setScalingMode},
    {"native_getScalingMode",    "()I",   (void *)android_media_visualizer_native_getScalingMode},
    {"native_setMeasurementMode","(I)I",  (void *)android_media_visualizer_native_setMeasurementMode},
    {"native_getMeasurementMode","()I",   (void *)android_media_visualizer_native_getMeasurementMode},
    {"native_getSamplingRate",   "()I",   (void *)android_media_visualizer_native_getSamplingRate},
    {"native_getWaveForm",       "([B)I", (void *)android_media_visualizer_native_getWaveForm},
    {"native_getFft",            "([B)I", (void *)android_media_visualizer_native_getFft},
    {"native_getPeakRms",      "(Landroid/media/audiofx/Visualizer$MeasurementPeakRms;)I",
                                          (void *)android_media_visualizer_native_getPeakRms},
    {"native_setPeriodicCapture","(IZZ)I",(void *)android_media_setPeriodicCapture},
};

// ----------------------------------------------------------------------------

int register_android_media_visualizer(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
}