android_media_Visualizer.cpp revision 449725f9aa67136a38c7554ba76ac4e27e5e3bd3
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include <stdio.h> 18 19//#define LOG_NDEBUG 0 20#define LOG_TAG "visualizers-JNI" 21 22#include <utils/Log.h> 23#include <nativehelper/jni.h> 24#include <nativehelper/JNIHelp.h> 25#include <android_runtime/AndroidRuntime.h> 26#include <utils/threads.h> 27#include "media/Visualizer.h" 28 29using namespace android; 30 31#define VISUALIZER_SUCCESS 0 32#define VISUALIZER_ERROR -1 33#define VISUALIZER_ERROR_ALREADY_EXISTS -2 34#define VISUALIZER_ERROR_NO_INIT -3 35#define VISUALIZER_ERROR_BAD_VALUE -4 36#define VISUALIZER_ERROR_INVALID_OPERATION -5 37#define VISUALIZER_ERROR_NO_MEMORY -6 38#define VISUALIZER_ERROR_DEAD_OBJECT -7 39 40#define NATIVE_EVENT_PCM_CAPTURE 0 41#define NATIVE_EVENT_FFT_CAPTURE 1 42 43// ---------------------------------------------------------------------------- 44static const char* const kClassPathName = "android/media/audiofx/Visualizer"; 45 46struct fields_t { 47 // these fields provide access from C++ to the... 48 jclass clazzEffect; // Visualizer class 49 jmethodID midPostNativeEvent; // event post callback method 50 jfieldID fidNativeVisualizer; // stores in Java the native Visualizer object 51 jfieldID fidJniData; // stores in Java additional resources used by the native Visualizer 52}; 53static fields_t fields; 54 55struct visualizer_callback_cookie { 56 jclass visualizer_class; // Visualizer class 57 jobject visualizer_ref; // Visualizer object instance 58 59 // Lazily allocated arrays used to hold callback data provided to java 60 // applications. These arrays are allocated during the first callback and 61 // reallocated when the size of the callback data changes. Allocating on 62 // demand and saving the arrays means that applications cannot safely hold a 63 // reference to the provided data (they need to make a copy if they want to 64 // hold onto outside of the callback scope), but it avoids GC thrash caused 65 // by constantly allocating and releasing arrays to hold callback data. 66 Mutex callback_data_lock; 67 jbyteArray waveform_data; 68 jbyteArray fft_data; 69 70 visualizer_callback_cookie() { 71 waveform_data = NULL; 72 fft_data = NULL; 73 } 74 75 ~visualizer_callback_cookie() { 76 cleanupBuffers(); 77 } 78 79 void cleanupBuffers() { 80 AutoMutex lock(&callback_data_lock); 81 if (waveform_data || fft_data) { 82 JNIEnv *env = AndroidRuntime::getJNIEnv(); 83 84 if (waveform_data) { 85 env->DeleteGlobalRef(waveform_data); 86 waveform_data = NULL; 87 } 88 89 if (fft_data) { 90 env->DeleteGlobalRef(fft_data); 91 fft_data = NULL; 92 } 93 } 94 } 95 }; 96 97// ---------------------------------------------------------------------------- 98class visualizerJniStorage { 99 public: 100 visualizer_callback_cookie mCallbackData; 101 102 visualizerJniStorage() { 103 } 104 105 ~visualizerJniStorage() { 106 } 107}; 108 109 110static jint translateError(int code) { 111 switch(code) { 112 case NO_ERROR: 113 return VISUALIZER_SUCCESS; 114 case ALREADY_EXISTS: 115 return VISUALIZER_ERROR_ALREADY_EXISTS; 116 case NO_INIT: 117 return VISUALIZER_ERROR_NO_INIT; 118 case BAD_VALUE: 119 return VISUALIZER_ERROR_BAD_VALUE; 120 case INVALID_OPERATION: 121 return VISUALIZER_ERROR_INVALID_OPERATION; 122 case NO_MEMORY: 123 return VISUALIZER_ERROR_NO_MEMORY; 124 case DEAD_OBJECT: 125 return VISUALIZER_ERROR_DEAD_OBJECT; 126 default: 127 return VISUALIZER_ERROR; 128 } 129} 130 131 132// ---------------------------------------------------------------------------- 133static void ensureArraySize(JNIEnv *env, jbyteArray *array, uint32_t size) { 134 if (NULL != *array) { 135 uint32_t len = env->GetArrayLength(*array); 136 if (len == size) 137 return; 138 139 env->DeleteGlobalRef(*array); 140 *array = NULL; 141 } 142 143 jbyteArray localRef = env->NewByteArray(size); 144 if (NULL != localRef) { 145 // Promote to global ref. 146 *array = (jbyteArray)env->NewGlobalRef(localRef); 147 148 // Release our (now pointless) local ref. 149 env->DeleteLocalRef(localRef); 150 } 151} 152 153static void captureCallback(void* user, 154 uint32_t waveformSize, 155 uint8_t *waveform, 156 uint32_t fftSize, 157 uint8_t *fft, 158 uint32_t samplingrate) { 159 160 int arg1 = 0; 161 int arg2 = 0; 162 size_t size; 163 164 visualizer_callback_cookie *callbackInfo = (visualizer_callback_cookie *)user; 165 JNIEnv *env = AndroidRuntime::getJNIEnv(); 166 AutoMutex lock(&callbackInfo->callback_data_lock); 167 168 ALOGV("captureCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p", 169 callbackInfo, 170 callbackInfo->visualizer_ref, 171 callbackInfo->visualizer_class); 172 173 if (!user || !env) { 174 ALOGW("captureCallback error user %p, env %p", user, env); 175 return; 176 } 177 178 if (waveformSize != 0 && waveform != NULL) { 179 jbyteArray jArray; 180 181 ensureArraySize(env, &callbackInfo->waveform_data, waveformSize); 182 jArray = callbackInfo->waveform_data; 183 184 if (jArray != NULL) { 185 jbyte *nArray = env->GetByteArrayElements(jArray, NULL); 186 memcpy(nArray, waveform, waveformSize); 187 env->ReleaseByteArrayElements(jArray, nArray, 0); 188 env->CallStaticVoidMethod( 189 callbackInfo->visualizer_class, 190 fields.midPostNativeEvent, 191 callbackInfo->visualizer_ref, 192 NATIVE_EVENT_PCM_CAPTURE, 193 samplingrate, 194 0, 195 jArray); 196 } 197 } 198 199 if (fftSize != 0 && fft != NULL) { 200 jbyteArray jArray; 201 202 ensureArraySize(env, &callbackInfo->fft_data, fftSize); 203 jArray = callbackInfo->fft_data; 204 205 if (jArray != NULL) { 206 jbyte *nArray = env->GetByteArrayElements(jArray, NULL); 207 memcpy(nArray, fft, fftSize); 208 env->ReleaseByteArrayElements(jArray, nArray, 0); 209 env->CallStaticVoidMethod( 210 callbackInfo->visualizer_class, 211 fields.midPostNativeEvent, 212 callbackInfo->visualizer_ref, 213 NATIVE_EVENT_FFT_CAPTURE, 214 samplingrate, 215 0, 216 jArray); 217 } 218 } 219 220 if (env->ExceptionCheck()) { 221 env->ExceptionDescribe(); 222 env->ExceptionClear(); 223 } 224} 225 226static Visualizer *getVisualizer(JNIEnv* env, jobject thiz) 227{ 228 Visualizer *v = (Visualizer *)env->GetIntField( 229 thiz, fields.fidNativeVisualizer); 230 if (v == NULL) { 231 jniThrowException(env, "java/lang/IllegalStateException", 232 "Unable to retrieve Visualizer pointer"); 233 } 234 return v; 235} 236 237// ---------------------------------------------------------------------------- 238// This function gets some field IDs, which in turn causes class initialization. 239// It is called from a static block in Visualizer, which won't run until the 240// first time an instance of this class is used. 241static void 242android_media_visualizer_native_init(JNIEnv *env) 243{ 244 245 ALOGV("android_media_visualizer_native_init"); 246 247 fields.clazzEffect = NULL; 248 249 // Get the Visualizer class 250 jclass clazz = env->FindClass(kClassPathName); 251 if (clazz == NULL) { 252 ALOGE("Can't find %s", kClassPathName); 253 return; 254 } 255 256 fields.clazzEffect = (jclass)env->NewGlobalRef(clazz); 257 258 // Get the postEvent method 259 fields.midPostNativeEvent = env->GetStaticMethodID( 260 fields.clazzEffect, 261 "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V"); 262 if (fields.midPostNativeEvent == NULL) { 263 ALOGE("Can't find Visualizer.%s", "postEventFromNative"); 264 return; 265 } 266 267 // Get the variables fields 268 // nativeTrackInJavaObj 269 fields.fidNativeVisualizer = env->GetFieldID( 270 fields.clazzEffect, 271 "mNativeVisualizer", "I"); 272 if (fields.fidNativeVisualizer == NULL) { 273 ALOGE("Can't find Visualizer.%s", "mNativeVisualizer"); 274 return; 275 } 276 // fidJniData; 277 fields.fidJniData = env->GetFieldID( 278 fields.clazzEffect, 279 "mJniData", "I"); 280 if (fields.fidJniData == NULL) { 281 ALOGE("Can't find Visualizer.%s", "mJniData"); 282 return; 283 } 284 285} 286 287 288static jint 289android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, 290 jint sessionId, jintArray jId) 291{ 292 ALOGV("android_media_visualizer_native_setup"); 293 visualizerJniStorage* lpJniStorage = NULL; 294 int lStatus = VISUALIZER_ERROR_NO_MEMORY; 295 Visualizer* lpVisualizer = NULL; 296 jint* nId = NULL; 297 298 lpJniStorage = new visualizerJniStorage(); 299 if (lpJniStorage == NULL) { 300 ALOGE("setup: Error creating JNI Storage"); 301 goto setup_failure; 302 } 303 304 lpJniStorage->mCallbackData.visualizer_class = (jclass)env->NewGlobalRef(fields.clazzEffect); 305 // we use a weak reference so the Visualizer object can be garbage collected. 306 lpJniStorage->mCallbackData.visualizer_ref = env->NewGlobalRef(weak_this); 307 308 ALOGV("setup: lpJniStorage: %p visualizer_ref %p visualizer_class %p, &mCallbackData %p", 309 lpJniStorage, 310 lpJniStorage->mCallbackData.visualizer_ref, 311 lpJniStorage->mCallbackData.visualizer_class, 312 &lpJniStorage->mCallbackData); 313 314 if (jId == NULL) { 315 ALOGE("setup: NULL java array for id pointer"); 316 lStatus = VISUALIZER_ERROR_BAD_VALUE; 317 goto setup_failure; 318 } 319 320 // create the native Visualizer object 321 lpVisualizer = new Visualizer(0, 322 NULL, 323 NULL, 324 sessionId); 325 if (lpVisualizer == NULL) { 326 ALOGE("Error creating Visualizer"); 327 goto setup_failure; 328 } 329 330 lStatus = translateError(lpVisualizer->initCheck()); 331 if (lStatus != VISUALIZER_SUCCESS && lStatus != VISUALIZER_ERROR_ALREADY_EXISTS) { 332 ALOGE("Visualizer initCheck failed %d", lStatus); 333 goto setup_failure; 334 } 335 336 nId = (jint *) env->GetPrimitiveArrayCritical(jId, NULL); 337 if (nId == NULL) { 338 ALOGE("setup: Error retrieving id pointer"); 339 lStatus = VISUALIZER_ERROR_BAD_VALUE; 340 goto setup_failure; 341 } 342 nId[0] = lpVisualizer->id(); 343 env->ReleasePrimitiveArrayCritical(jId, nId, 0); 344 nId = NULL; 345 346 env->SetIntField(thiz, fields.fidNativeVisualizer, (int)lpVisualizer); 347 348 env->SetIntField(thiz, fields.fidJniData, (int)lpJniStorage); 349 350 return VISUALIZER_SUCCESS; 351 352 // failures: 353setup_failure: 354 355 if (nId != NULL) { 356 env->ReleasePrimitiveArrayCritical(jId, nId, 0); 357 } 358 359 if (lpVisualizer) { 360 delete lpVisualizer; 361 } 362 env->SetIntField(thiz, fields.fidNativeVisualizer, 0); 363 364 if (lpJniStorage) { 365 delete lpJniStorage; 366 } 367 env->SetIntField(thiz, fields.fidJniData, 0); 368 369 return lStatus; 370} 371 372// ---------------------------------------------------------------------------- 373static void android_media_visualizer_native_finalize(JNIEnv *env, jobject thiz) { 374 ALOGV("android_media_visualizer_native_finalize jobject: %x\n", (int)thiz); 375 376 // delete the Visualizer object 377 Visualizer* lpVisualizer = (Visualizer *)env->GetIntField( 378 thiz, fields.fidNativeVisualizer); 379 if (lpVisualizer) { 380 ALOGV("deleting Visualizer: %x\n", (int)lpVisualizer); 381 delete lpVisualizer; 382 } 383 384 // delete the JNI data 385 visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetIntField( 386 thiz, fields.fidJniData); 387 if (lpJniStorage) { 388 ALOGV("deleting pJniStorage: %x\n", (int)lpJniStorage); 389 delete lpJniStorage; 390 } 391} 392 393// ---------------------------------------------------------------------------- 394static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) { 395 396 // do everything a call to finalize would 397 android_media_visualizer_native_finalize(env, thiz); 398 // + reset the native resources in the Java object so any attempt to access 399 // them after a call to release fails. 400 env->SetIntField(thiz, fields.fidNativeVisualizer, 0); 401 env->SetIntField(thiz, fields.fidJniData, 0); 402} 403 404static jint 405android_media_visualizer_native_setEnabled(JNIEnv *env, jobject thiz, jboolean enabled) 406{ 407 Visualizer* lpVisualizer = getVisualizer(env, thiz); 408 if (lpVisualizer == NULL) { 409 return VISUALIZER_ERROR_NO_INIT; 410 } 411 412 jint retVal = translateError(lpVisualizer->setEnabled(enabled)); 413 414 if (!enabled) { 415 visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetIntField( 416 thiz, fields.fidJniData); 417 418 if (NULL != lpJniStorage) 419 lpJniStorage->mCallbackData.cleanupBuffers(); 420 } 421 422 return retVal; 423} 424 425static jboolean 426android_media_visualizer_native_getEnabled(JNIEnv *env, jobject thiz) 427{ 428 Visualizer* lpVisualizer = getVisualizer(env, thiz); 429 if (lpVisualizer == NULL) { 430 return false; 431 } 432 433 return (jboolean)lpVisualizer->getEnabled(); 434} 435 436static jintArray 437android_media_visualizer_native_getCaptureSizeRange(JNIEnv *env, jobject thiz) 438{ 439 jintArray jRange = env->NewIntArray(2); 440 jint *nRange = env->GetIntArrayElements(jRange, NULL); 441 nRange[0] = Visualizer::getMinCaptureSize(); 442 nRange[1] = Visualizer::getMaxCaptureSize(); 443 ALOGV("getCaptureSizeRange() min %d max %d", nRange[0], nRange[1]); 444 env->ReleaseIntArrayElements(jRange, nRange, 0); 445 return jRange; 446} 447 448static jint 449android_media_visualizer_native_getMaxCaptureRate(JNIEnv *env, jobject thiz) 450{ 451 return Visualizer::getMaxCaptureRate(); 452} 453 454static jint 455android_media_visualizer_native_setCaptureSize(JNIEnv *env, jobject thiz, jint size) 456{ 457 Visualizer* lpVisualizer = getVisualizer(env, thiz); 458 if (lpVisualizer == NULL) { 459 return VISUALIZER_ERROR_NO_INIT; 460 } 461 462 return translateError(lpVisualizer->setCaptureSize(size)); 463} 464 465static jint 466android_media_visualizer_native_getCaptureSize(JNIEnv *env, jobject thiz) 467{ 468 Visualizer* lpVisualizer = getVisualizer(env, thiz); 469 if (lpVisualizer == NULL) { 470 return -1; 471 } 472 return lpVisualizer->getCaptureSize(); 473} 474 475static jint 476android_media_visualizer_native_getSamplingRate(JNIEnv *env, jobject thiz) 477{ 478 Visualizer* lpVisualizer = getVisualizer(env, thiz); 479 if (lpVisualizer == NULL) { 480 return -1; 481 } 482 return lpVisualizer->getSamplingRate(); 483} 484 485static jint 486android_media_visualizer_native_getWaveForm(JNIEnv *env, jobject thiz, jbyteArray jWaveform) 487{ 488 Visualizer* lpVisualizer = getVisualizer(env, thiz); 489 if (lpVisualizer == NULL) { 490 return VISUALIZER_ERROR_NO_INIT; 491 } 492 493 jbyte* nWaveform = (jbyte *) env->GetPrimitiveArrayCritical(jWaveform, NULL); 494 if (nWaveform == NULL) { 495 return VISUALIZER_ERROR_NO_MEMORY; 496 } 497 jint status = translateError(lpVisualizer->getWaveForm((uint8_t *)nWaveform)); 498 499 env->ReleasePrimitiveArrayCritical(jWaveform, nWaveform, 0); 500 return status; 501} 502 503static jint 504android_media_visualizer_native_getFft(JNIEnv *env, jobject thiz, jbyteArray jFft) 505{ 506 Visualizer* lpVisualizer = getVisualizer(env, thiz); 507 if (lpVisualizer == NULL) { 508 return VISUALIZER_ERROR_NO_INIT; 509 } 510 511 jbyte* nFft = (jbyte *) env->GetPrimitiveArrayCritical(jFft, NULL); 512 if (nFft == NULL) { 513 return VISUALIZER_ERROR_NO_MEMORY; 514 } 515 jint status = translateError(lpVisualizer->getFft((uint8_t *)nFft)); 516 517 env->ReleasePrimitiveArrayCritical(jFft, nFft, 0); 518 519 return status; 520} 521 522static jint 523android_media_setPeriodicCapture(JNIEnv *env, jobject thiz, jint rate, jboolean jWaveform, jboolean jFft) 524{ 525 Visualizer* lpVisualizer = getVisualizer(env, thiz); 526 if (lpVisualizer == NULL) { 527 return VISUALIZER_ERROR_NO_INIT; 528 } 529 visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetIntField(thiz, 530 fields.fidJniData); 531 if (lpJniStorage == NULL) { 532 return VISUALIZER_ERROR_NO_INIT; 533 } 534 535 ALOGV("setPeriodicCapture: rate %d, jWaveform %d jFft %d", 536 rate, 537 jWaveform, 538 jFft); 539 540 uint32_t flags = Visualizer::CAPTURE_CALL_JAVA; 541 if (jWaveform) flags |= Visualizer::CAPTURE_WAVEFORM; 542 if (jFft) flags |= Visualizer::CAPTURE_FFT; 543 Visualizer::capture_cbk_t cbk = captureCallback; 544 if (!jWaveform && !jFft) cbk = NULL; 545 546 return translateError(lpVisualizer->setCaptureCallBack(cbk, 547 &lpJniStorage->mCallbackData, 548 flags, 549 rate)); 550} 551 552// ---------------------------------------------------------------------------- 553 554// Dalvik VM type signatures 555static JNINativeMethod gMethods[] = { 556 {"native_init", "()V", (void *)android_media_visualizer_native_init}, 557 {"native_setup", "(Ljava/lang/Object;I[I)I", 558 (void *)android_media_visualizer_native_setup}, 559 {"native_finalize", "()V", (void *)android_media_visualizer_native_finalize}, 560 {"native_release", "()V", (void *)android_media_visualizer_native_release}, 561 {"native_setEnabled", "(Z)I", (void *)android_media_visualizer_native_setEnabled}, 562 {"native_getEnabled", "()Z", (void *)android_media_visualizer_native_getEnabled}, 563 {"getCaptureSizeRange", "()[I", (void *)android_media_visualizer_native_getCaptureSizeRange}, 564 {"getMaxCaptureRate", "()I", (void *)android_media_visualizer_native_getMaxCaptureRate}, 565 {"native_setCaptureSize", "(I)I", (void *)android_media_visualizer_native_setCaptureSize}, 566 {"native_getCaptureSize", "()I", (void *)android_media_visualizer_native_getCaptureSize}, 567 {"native_getSamplingRate", "()I", (void *)android_media_visualizer_native_getSamplingRate}, 568 {"native_getWaveForm", "([B)I", (void *)android_media_visualizer_native_getWaveForm}, 569 {"native_getFft", "([B)I", (void *)android_media_visualizer_native_getFft}, 570 {"native_setPeriodicCapture","(IZZ)I",(void *)android_media_setPeriodicCapture}, 571}; 572 573// ---------------------------------------------------------------------------- 574 575int register_android_media_visualizer(JNIEnv *env) 576{ 577 return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); 578} 579 580