android_media_Visualizer.cpp revision 67a12184776605b6e6ddc01ca91b533a2e37a40b
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#define NATIVE_EVENT_SERVER_DIED                2
43
44// ----------------------------------------------------------------------------
45static const char* const kClassPathName = "android/media/audiofx/Visualizer";
46
47struct 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};
54static fields_t fields;
55
56struct 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// ----------------------------------------------------------------------------
99class visualizerJniStorage {
100    public:
101        visualizer_callback_cookie mCallbackData;
102
103    visualizerJniStorage() {
104    }
105
106    ~visualizerJniStorage() {
107    }
108};
109
110
111static 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// ----------------------------------------------------------------------------
134static 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
154static 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
228static 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.
243static void
244android_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
289static 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
307static jint
308android_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:
372setup_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// ----------------------------------------------------------------------------
392static 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// ----------------------------------------------------------------------------
413static 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
423static jint
424android_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
444static jboolean
445android_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
455static jintArray
456android_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
467static jint
468android_media_visualizer_native_getMaxCaptureRate(JNIEnv *env, jobject thiz)
469{
470    return Visualizer::getMaxCaptureRate();
471}
472
473static jint
474android_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
484static jint
485android_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
494static jint
495android_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
505static jint
506android_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
515static jint
516android_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
525static jint
526android_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
543static jint
544android_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
562static jint
563android_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
595static 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
617int register_android_media_visualizer(JNIEnv *env)
618{
619    return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
620}
621
622