/* * 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 <jni.h> #include <nativehelper/JNIHelp.h> #include <android_runtime/AndroidRuntime.h> #include <utils/threads.h> #include "media/Visualizer.h" #include <nativehelper/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, (audio_session_t) 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) { { //limit scope so that lpVisualizer is deleted before JNI storage data. sp<Visualizer> lpVisualizer = setVisualizer(env, thiz, 0); if (lpVisualizer == 0) { return; } lpVisualizer->release(); } // 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 const 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)); }