8b93d78c01
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1826714 Change-Id: I5b6c9dc465cd733569ec33afacee530e5b27eb11
774 lines
26 KiB
C++
774 lines
26 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>
|
|
#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>(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;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 = (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);
|
|
|
|
{
|
|
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->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;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
#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 = (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[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));
|
|
}
|