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