android_frameworks_base/media/jni/audioeffect/android_media_Visualizer.cpp
Jean-Michel Trivi badca26cb2 Add audio level monitoring capabilities in Visualizer effect
Extend the visualizer audio effect with the capability to query
 peak and RMS values for the currently playing audio.
Values are expressed in mB and are retrieved as an array of
 int values in the native layer, and written directly as
 object fields for the JNI.

Bug 8413913

Change-Id: I808075a18e61f85c566544a2bdaae10e5c4a644b
2013-09-23 14:03:39 -07:00

694 lines
23 KiB
C++

/*
* 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"
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 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) {
int arg1 = 0;
int arg2 = 0;
size_t size;
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 Visualizer *getVisualizer(JNIEnv* env, jobject thiz)
{
Visualizer *v = (Visualizer *)env->GetIntField(
thiz, fields.fidNativeVisualizer);
if (v == NULL) {
jniThrowException(env, "java/lang/IllegalStateException",
"Unable to retrieve Visualizer pointer");
}
return v;
}
// ----------------------------------------------------------------------------
// 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", "I");
if (fields.fidNativeVisualizer == NULL) {
ALOGE("Can't find Visualizer.%s", "mNativeVisualizer");
return;
}
// fidJniData;
fields.fidJniData = env->GetFieldID(
fields.clazzEffect,
"mJniData", "I");
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, 0);
}
}
static jint
android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jint sessionId, jintArray jId)
{
ALOGV("android_media_visualizer_native_setup");
visualizerJniStorage* lpJniStorage = NULL;
int lStatus = VISUALIZER_ERROR_NO_MEMORY;
Visualizer* lpVisualizer = NULL;
jint* nId = NULL;
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(0,
android_media_visualizer_effect_callback,
lpJniStorage,
sessionId);
if (lpVisualizer == NULL) {
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;
env->SetIntField(thiz, fields.fidNativeVisualizer, (int)lpVisualizer);
env->SetIntField(thiz, fields.fidJniData, (int)lpJniStorage);
return VISUALIZER_SUCCESS;
// failures:
setup_failure:
if (nId != NULL) {
env->ReleasePrimitiveArrayCritical(jId, nId, 0);
}
if (lpVisualizer) {
delete lpVisualizer;
}
env->SetIntField(thiz, fields.fidNativeVisualizer, 0);
if (lpJniStorage) {
delete lpJniStorage;
}
env->SetIntField(thiz, fields.fidJniData, 0);
return lStatus;
}
// ----------------------------------------------------------------------------
static void android_media_visualizer_native_finalize(JNIEnv *env, jobject thiz) {
ALOGV("android_media_visualizer_native_finalize jobject: %x\n", (int)thiz);
// delete the Visualizer object
Visualizer* lpVisualizer = (Visualizer *)env->GetIntField(
thiz, fields.fidNativeVisualizer);
if (lpVisualizer) {
ALOGV("deleting Visualizer: %x\n", (int)lpVisualizer);
delete lpVisualizer;
}
// delete the JNI data
visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetIntField(
thiz, fields.fidJniData);
if (lpJniStorage) {
ALOGV("deleting pJniStorage: %x\n", (int)lpJniStorage);
delete lpJniStorage;
}
}
// ----------------------------------------------------------------------------
static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) {
// do everything a call to finalize would
android_media_visualizer_native_finalize(env, thiz);
// + reset the native resources in the Java object so any attempt to access
// them after a call to release fails.
env->SetIntField(thiz, fields.fidNativeVisualizer, 0);
env->SetIntField(thiz, fields.fidJniData, 0);
}
static jint
android_media_visualizer_native_setEnabled(JNIEnv *env, jobject thiz, jboolean enabled)
{
Visualizer* lpVisualizer = getVisualizer(env, thiz);
if (lpVisualizer == NULL) {
return VISUALIZER_ERROR_NO_INIT;
}
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
android_media_visualizer_native_getEnabled(JNIEnv *env, jobject thiz)
{
Visualizer* lpVisualizer = getVisualizer(env, thiz);
if (lpVisualizer == NULL) {
return false;
}
return (jboolean)lpVisualizer->getEnabled();
}
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 Visualizer::getMaxCaptureRate();
}
static jint
android_media_visualizer_native_setCaptureSize(JNIEnv *env, jobject thiz, jint size)
{
Visualizer* lpVisualizer = getVisualizer(env, thiz);
if (lpVisualizer == NULL) {
return VISUALIZER_ERROR_NO_INIT;
}
return translateError(lpVisualizer->setCaptureSize(size));
}
static jint
android_media_visualizer_native_getCaptureSize(JNIEnv *env, jobject thiz)
{
Visualizer* lpVisualizer = getVisualizer(env, thiz);
if (lpVisualizer == NULL) {
return -1;
}
return lpVisualizer->getCaptureSize();
}
static jint
android_media_visualizer_native_setScalingMode(JNIEnv *env, jobject thiz, jint mode)
{
Visualizer* lpVisualizer = getVisualizer(env, thiz);
if (lpVisualizer == NULL) {
return VISUALIZER_ERROR_NO_INIT;
}
return translateError(lpVisualizer->setScalingMode(mode));
}
static jint
android_media_visualizer_native_getScalingMode(JNIEnv *env, jobject thiz)
{
Visualizer* lpVisualizer = getVisualizer(env, thiz);
if (lpVisualizer == NULL) {
return -1;
}
return lpVisualizer->getScalingMode();
}
static jint
android_media_visualizer_native_setMeasurementMode(JNIEnv *env, jobject thiz, jint mode)
{
Visualizer* lpVisualizer = getVisualizer(env, thiz);
if (lpVisualizer == NULL) {
return VISUALIZER_ERROR_NO_INIT;
}
return translateError(lpVisualizer->setMeasurementMode(mode));
}
static jint
android_media_visualizer_native_getMeasurementMode(JNIEnv *env, jobject thiz)
{
Visualizer* lpVisualizer = getVisualizer(env, thiz);
if (lpVisualizer == NULL) {
return MEASUREMENT_MODE_NONE;
}
return lpVisualizer->getMeasurementMode();
}
static jint
android_media_visualizer_native_getSamplingRate(JNIEnv *env, jobject thiz)
{
Visualizer* lpVisualizer = getVisualizer(env, thiz);
if (lpVisualizer == NULL) {
return -1;
}
return lpVisualizer->getSamplingRate();
}
static jint
android_media_visualizer_native_getWaveForm(JNIEnv *env, jobject thiz, jbyteArray jWaveform)
{
Visualizer* lpVisualizer = getVisualizer(env, thiz);
if (lpVisualizer == NULL) {
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)
{
Visualizer* lpVisualizer = getVisualizer(env, thiz);
if (lpVisualizer == NULL) {
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)
{
Visualizer* lpVisualizer = getVisualizer(env, thiz);
if (lpVisualizer == NULL) {
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)
{
Visualizer* lpVisualizer = getVisualizer(env, thiz);
if (lpVisualizer == NULL) {
return VISUALIZER_ERROR_NO_INIT;
}
visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetIntField(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[I)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));
}