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