blob: 94071223264121b020f600f83917970962623008 [file] [log] [blame]
/*
* 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>
#include <unordered_set>
//#define LOG_NDEBUG 0
#define LOG_TAG "visualizers-JNI"
#include <utils/Log.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
#include <utils/threads.h>
#include "Visualizer.h"
#include <nativehelper/ScopedUtfChars.h>
#include <android/content/AttributionSourceState.h>
#include <android_os_Parcel.h>
using namespace android;
using content::AttributionSourceState;
#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
// 'busy_count' and 'cond' together with 'sLock' are used to serialize
// concurrent access to the callback cookie from 'setup'/'release'
// and the callback.
int busy_count;
Condition cond;
// 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.
// 'callback_data_lock' must never be held at the same time with 'sLock'.
Mutex callback_data_lock;
jbyteArray waveform_data;
jbyteArray fft_data;
// Assumes use of default initialization by the client.
~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;
}
}
}
};
// ----------------------------------------------------------------------------
struct VisualizerJniStorage {
visualizer_callback_cookie mCallbackData{};
};
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 std::unordered_set<visualizer_callback_cookie*> sVisualizerCallBackCookies;
// ----------------------------------------------------------------------------
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;
}
{
Mutex::Autolock l(sLock);
if (sVisualizerCallBackCookies.count(callbackInfo) == 0) {
return;
}
callbackInfo->busy_count++;
}
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,
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,
jArray);
}
}
} // callback_data_lock scope
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
{
Mutex::Autolock l(sLock);
callbackInfo->busy_count--;
callbackInfo->cond.broadcast();
}
}
// ----------------------------------------------------------------------------
static sp<Visualizer> getVisualizer(JNIEnv* env, jobject thiz)
{
Mutex::Autolock l(sLock);
Visualizer* const v =
(Visualizer*)env->GetLongField(thiz, fields.fidNativeVisualizer);
return sp<Visualizer>::fromExisting(v);
}
static sp<Visualizer> setVisualizer(JNIEnv* env, jobject thiz,
const sp<Visualizer>& v)
{
Mutex::Autolock l(sLock);
sp<Visualizer> old = sp<Visualizer>::fromExisting(
(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;II[B)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)) {
visualizer_callback_cookie* callbackInfo =
(visualizer_callback_cookie *)user;
JNIEnv *env = AndroidRuntime::getJNIEnv();
if (!user || !env) {
ALOGW("effectCallback error user %p, env %p", user, env);
return;
}
{
Mutex::Autolock l(sLock);
if (sVisualizerCallBackCookies.count(callbackInfo) == 0) {
return;
}
callbackInfo->busy_count++;
}
ALOGV("effectCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p",
callbackInfo,
callbackInfo->visualizer_ref,
callbackInfo->visualizer_class);
env->CallStaticVoidMethod(
callbackInfo->visualizer_class,
fields.midPostNativeEvent,
callbackInfo->visualizer_ref,
NATIVE_EVENT_SERVER_DIED,
0, NULL);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
{
Mutex::Autolock l(sLock);
callbackInfo->busy_count--;
callbackInfo->cond.broadcast();
}
}
}
static jint
android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jint sessionId, jintArray jId, jobject jAttributionSource)
{
ALOGV("android_media_visualizer_native_setup");
VisualizerJniStorage* lpJniStorage = NULL;
int lStatus = VISUALIZER_ERROR_NO_MEMORY;
sp<Visualizer> lpVisualizer;
jint* nId = NULL;
AttributionSourceState attributionSource;
Parcel* parcel = nullptr;
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
parcel = parcelForJavaObject(env, jAttributionSource);
attributionSource.readFromParcel(parcel);
lpVisualizer = sp<Visualizer>::make(attributionSource);
if (lpVisualizer == 0) {
ALOGE("Error creating Visualizer");
goto setup_failure;
}
lpVisualizer->set(0,
android_media_visualizer_effect_callback,
&lpJniStorage->mCallbackData,
(audio_session_t) sessionId);
lStatus = translateError(lpVisualizer->initCheck());
if (lStatus != VISUALIZER_SUCCESS && lStatus != VISUALIZER_ERROR_ALREADY_EXISTS) {
ALOGE("Visualizer initCheck failed %d", lStatus);
goto setup_failure;
}
nId = env->GetIntArrayElements(jId, nullptr /* isCopy */);
if (nId == NULL) {
ALOGE("setup: Error retrieving id pointer");
lStatus = VISUALIZER_ERROR_BAD_VALUE;
goto setup_failure;
}
nId[0] = lpVisualizer->id();
env->ReleaseIntArrayElements(jId, nId, 0 /* mode */);
nId = NULL;
setVisualizer(env, thiz, lpVisualizer);
{
Mutex::Autolock l(sLock);
sVisualizerCallBackCookies.insert(&lpJniStorage->mCallbackData);
}
env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage);
return VISUALIZER_SUCCESS;
// failures:
setup_failure:
if (nId != NULL) {
env->ReleaseIntArrayElements(jId, nId, 0 /* mode */);
}
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;
}
// ----------------------------------------------------------------------------
#define CALLBACK_COND_WAIT_TIMEOUT_MS 1000
static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) {
{
sp<Visualizer> lpVisualizer = setVisualizer(env, thiz, 0);
if (lpVisualizer == 0) {
return;
}
lpVisualizer->release();
// Visualizer can still can be held by AudioEffect::EffectClient
}
// 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) {
{
Mutex::Autolock l(sLock);
visualizer_callback_cookie *lpCookie = &lpJniStorage->mCallbackData;
ALOGV("deleting pJniStorage: %p\n", lpJniStorage);
sVisualizerCallBackCookies.erase(lpCookie);
while (lpCookie->busy_count > 0) {
if (lpCookie->cond.waitRelative(sLock,
milliseconds(CALLBACK_COND_WAIT_TIMEOUT_MS)) !=
NO_ERROR) {
break;
}
}
ALOG_ASSERT(lpCookie->busy_count == 0, "Unbalanced busy_count inc/dec");
env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_class);
env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_ref);
} // sLock scope
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 = env->GetByteArrayElements(jWaveform, nullptr /* isCopy */);
if (nWaveform == NULL) {
return VISUALIZER_ERROR_NO_MEMORY;
}
jint status = translateError(lpVisualizer->getWaveForm((uint8_t *)nWaveform));
env->ReleaseByteArrayElements(jWaveform, nWaveform, 0 /* mode */);
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 = env->GetByteArrayElements(jFft, nullptr /* isCopy */);
if (nFft == NULL) {
return VISUALIZER_ERROR_NO_MEMORY;
}
jint status = translateError(lpVisualizer->getFft((uint8_t *)nFft));
env->ReleaseByteArrayElements(jFft, nFft, 0 /* mode */);
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 const JNINativeMethod gMethods[] = {
{"native_init", "()V", (void *)android_media_visualizer_native_init},
{"native_setup", "(Ljava/lang/Object;I[ILandroid/os/Parcel;)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));
}