1/*
2 * Copyright (C) 2008 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 "SoundPool-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 <media/SoundPool.h>
27
28using namespace android;
29
30static struct fields_t {
31    jfieldID    mNativeContext;
32    jmethodID   mPostEvent;
33    jclass      mSoundPoolClass;
34} fields;
35static inline SoundPool* MusterSoundPool(JNIEnv *env, jobject thiz) {
36    return (SoundPool*)env->GetLongField(thiz, fields.mNativeContext);
37}
38static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes";
39struct audio_attributes_fields_t {
40    jfieldID  fieldUsage;        // AudioAttributes.mUsage
41    jfieldID  fieldContentType;  // AudioAttributes.mContentType
42    jfieldID  fieldFlags;        // AudioAttributes.mFlags
43    jfieldID  fieldFormattedTags;// AudioAttributes.mFormattedTags
44};
45static audio_attributes_fields_t javaAudioAttrFields;
46
47// ----------------------------------------------------------------------------
48static jint
49android_media_SoundPool_SoundPoolImpl_load_URL(JNIEnv *env, jobject thiz, jstring path, jint priority)
50{
51    ALOGV("android_media_SoundPool_SoundPoolImpl_load_URL");
52    SoundPool *ap = MusterSoundPool(env, thiz);
53    if (path == NULL) {
54        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
55        return 0;
56    }
57    const char* s = env->GetStringUTFChars(path, NULL);
58    int id = ap->load(s, priority);
59    env->ReleaseStringUTFChars(path, s);
60    return (jint) id;
61}
62
63static jint
64android_media_SoundPool_SoundPoolImpl_load_FD(JNIEnv *env, jobject thiz, jobject fileDescriptor,
65        jlong offset, jlong length, jint priority)
66{
67    ALOGV("android_media_SoundPool_SoundPoolImpl_load_FD");
68    SoundPool *ap = MusterSoundPool(env, thiz);
69    if (ap == NULL) return 0;
70    return (jint) ap->load(jniGetFDFromFileDescriptor(env, fileDescriptor),
71            int64_t(offset), int64_t(length), int(priority));
72}
73
74static jboolean
75android_media_SoundPool_SoundPoolImpl_unload(JNIEnv *env, jobject thiz, jint sampleID) {
76    ALOGV("android_media_SoundPool_SoundPoolImpl_unload\n");
77    SoundPool *ap = MusterSoundPool(env, thiz);
78    if (ap == NULL) return JNI_FALSE;
79    return ap->unload(sampleID) ? JNI_TRUE : JNI_FALSE;
80}
81
82static jint
83android_media_SoundPool_SoundPoolImpl_play(JNIEnv *env, jobject thiz, jint sampleID,
84        jfloat leftVolume, jfloat rightVolume, jint priority, jint loop,
85        jfloat rate)
86{
87    ALOGV("android_media_SoundPool_SoundPoolImpl_play\n");
88    SoundPool *ap = MusterSoundPool(env, thiz);
89    if (ap == NULL) return 0;
90    return (jint) ap->play(sampleID, leftVolume, rightVolume, priority, loop, rate);
91}
92
93static void
94android_media_SoundPool_SoundPoolImpl_pause(JNIEnv *env, jobject thiz, jint channelID)
95{
96    ALOGV("android_media_SoundPool_SoundPoolImpl_pause");
97    SoundPool *ap = MusterSoundPool(env, thiz);
98    if (ap == NULL) return;
99    ap->pause(channelID);
100}
101
102static void
103android_media_SoundPool_SoundPoolImpl_resume(JNIEnv *env, jobject thiz, jint channelID)
104{
105    ALOGV("android_media_SoundPool_SoundPoolImpl_resume");
106    SoundPool *ap = MusterSoundPool(env, thiz);
107    if (ap == NULL) return;
108    ap->resume(channelID);
109}
110
111static void
112android_media_SoundPool_SoundPoolImpl_autoPause(JNIEnv *env, jobject thiz)
113{
114    ALOGV("android_media_SoundPool_SoundPoolImpl_autoPause");
115    SoundPool *ap = MusterSoundPool(env, thiz);
116    if (ap == NULL) return;
117    ap->autoPause();
118}
119
120static void
121android_media_SoundPool_SoundPoolImpl_autoResume(JNIEnv *env, jobject thiz)
122{
123    ALOGV("android_media_SoundPool_SoundPoolImpl_autoResume");
124    SoundPool *ap = MusterSoundPool(env, thiz);
125    if (ap == NULL) return;
126    ap->autoResume();
127}
128
129static void
130android_media_SoundPool_SoundPoolImpl_stop(JNIEnv *env, jobject thiz, jint channelID)
131{
132    ALOGV("android_media_SoundPool_SoundPoolImpl_stop");
133    SoundPool *ap = MusterSoundPool(env, thiz);
134    if (ap == NULL) return;
135    ap->stop(channelID);
136}
137
138static void
139android_media_SoundPool_SoundPoolImpl_setVolume(JNIEnv *env, jobject thiz, jint channelID,
140        jfloat leftVolume, jfloat rightVolume)
141{
142    ALOGV("android_media_SoundPool_SoundPoolImpl_setVolume");
143    SoundPool *ap = MusterSoundPool(env, thiz);
144    if (ap == NULL) return;
145    ap->setVolume(channelID, (float) leftVolume, (float) rightVolume);
146}
147
148static void
149android_media_SoundPool_SoundPoolImpl_setPriority(JNIEnv *env, jobject thiz, jint channelID,
150        jint priority)
151{
152    ALOGV("android_media_SoundPool_SoundPoolImpl_setPriority");
153    SoundPool *ap = MusterSoundPool(env, thiz);
154    if (ap == NULL) return;
155    ap->setPriority(channelID, (int) priority);
156}
157
158static void
159android_media_SoundPool_SoundPoolImpl_setLoop(JNIEnv *env, jobject thiz, jint channelID,
160        int loop)
161{
162    ALOGV("android_media_SoundPool_SoundPoolImpl_setLoop");
163    SoundPool *ap = MusterSoundPool(env, thiz);
164    if (ap == NULL) return;
165    ap->setLoop(channelID, loop);
166}
167
168static void
169android_media_SoundPool_SoundPoolImpl_setRate(JNIEnv *env, jobject thiz, jint channelID,
170       jfloat rate)
171{
172    ALOGV("android_media_SoundPool_SoundPoolImpl_setRate");
173    SoundPool *ap = MusterSoundPool(env, thiz);
174    if (ap == NULL) return;
175    ap->setRate(channelID, (float) rate);
176}
177
178static void android_media_callback(SoundPoolEvent event, SoundPool* soundPool, void* user)
179{
180    ALOGV("callback: (%d, %d, %d, %p, %p)", event.mMsg, event.mArg1, event.mArg2, soundPool, user);
181    JNIEnv *env = AndroidRuntime::getJNIEnv();
182    env->CallStaticVoidMethod(fields.mSoundPoolClass, fields.mPostEvent, user, event.mMsg, event.mArg1, event.mArg2, NULL);
183}
184
185static jint
186android_media_SoundPool_SoundPoolImpl_native_setup(JNIEnv *env, jobject thiz, jobject weakRef,
187        jint maxChannels, jobject jaa)
188{
189    if (jaa == 0) {
190        ALOGE("Error creating SoundPool: invalid audio attributes");
191        return -1;
192    }
193
194    audio_attributes_t *paa = NULL;
195    // read the AudioAttributes values
196    paa = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t));
197    const jstring jtags =
198            (jstring) env->GetObjectField(jaa, javaAudioAttrFields.fieldFormattedTags);
199    const char* tags = env->GetStringUTFChars(jtags, NULL);
200    // copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it
201    strncpy(paa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1);
202    env->ReleaseStringUTFChars(jtags, tags);
203    paa->usage = (audio_usage_t) env->GetIntField(jaa, javaAudioAttrFields.fieldUsage);
204    paa->content_type =
205            (audio_content_type_t) env->GetIntField(jaa, javaAudioAttrFields.fieldContentType);
206    paa->flags = env->GetIntField(jaa, javaAudioAttrFields.fieldFlags);
207
208    ALOGV("android_media_SoundPool_SoundPoolImpl_native_setup");
209    SoundPool *ap = new SoundPool(maxChannels, paa);
210    if (ap == NULL) {
211        return -1;
212    }
213
214    // save pointer to SoundPool C++ object in opaque field in Java object
215    env->SetLongField(thiz, fields.mNativeContext, (jlong) ap);
216
217    // set callback with weak reference
218    jobject globalWeakRef = env->NewGlobalRef(weakRef);
219    ap->setCallback(android_media_callback, globalWeakRef);
220
221    // audio attributes were copied in SoundPool creation
222    free(paa);
223
224    return 0;
225}
226
227static void
228android_media_SoundPool_SoundPoolImpl_release(JNIEnv *env, jobject thiz)
229{
230    ALOGV("android_media_SoundPool_SoundPoolImpl_release");
231    SoundPool *ap = MusterSoundPool(env, thiz);
232    if (ap != NULL) {
233
234        // release weak reference
235        jobject weakRef = (jobject) ap->getUserData();
236        if (weakRef != NULL) {
237            env->DeleteGlobalRef(weakRef);
238        }
239
240        // clear callback and native context
241        ap->setCallback(NULL, NULL);
242        env->SetLongField(thiz, fields.mNativeContext, 0);
243        delete ap;
244    }
245}
246
247// ----------------------------------------------------------------------------
248
249// Dalvik VM type signatures
250static JNINativeMethod gMethods[] = {
251    {   "_load",
252        "(Ljava/lang/String;I)I",
253        (void *)android_media_SoundPool_SoundPoolImpl_load_URL
254    },
255    {   "_load",
256        "(Ljava/io/FileDescriptor;JJI)I",
257        (void *)android_media_SoundPool_SoundPoolImpl_load_FD
258    },
259    {   "unload",
260        "(I)Z",
261        (void *)android_media_SoundPool_SoundPoolImpl_unload
262    },
263    {   "_play",
264        "(IFFIIF)I",
265        (void *)android_media_SoundPool_SoundPoolImpl_play
266    },
267    {   "pause",
268        "(I)V",
269        (void *)android_media_SoundPool_SoundPoolImpl_pause
270    },
271    {   "resume",
272        "(I)V",
273        (void *)android_media_SoundPool_SoundPoolImpl_resume
274    },
275    {   "autoPause",
276        "()V",
277        (void *)android_media_SoundPool_SoundPoolImpl_autoPause
278    },
279    {   "autoResume",
280        "()V",
281        (void *)android_media_SoundPool_SoundPoolImpl_autoResume
282    },
283    {   "stop",
284        "(I)V",
285        (void *)android_media_SoundPool_SoundPoolImpl_stop
286    },
287    {   "_setVolume",
288        "(IFF)V",
289        (void *)android_media_SoundPool_SoundPoolImpl_setVolume
290    },
291    {   "setPriority",
292        "(II)V",
293        (void *)android_media_SoundPool_SoundPoolImpl_setPriority
294    },
295    {   "setLoop",
296        "(II)V",
297        (void *)android_media_SoundPool_SoundPoolImpl_setLoop
298    },
299    {   "setRate",
300        "(IF)V",
301        (void *)android_media_SoundPool_SoundPoolImpl_setRate
302    },
303    {   "native_setup",
304        "(Ljava/lang/Object;ILjava/lang/Object;)I",
305        (void*)android_media_SoundPool_SoundPoolImpl_native_setup
306    },
307    {   "release",
308        "()V",
309        (void*)android_media_SoundPool_SoundPoolImpl_release
310    }
311};
312
313static const char* const kClassPathName = "android/media/SoundPool$SoundPoolImpl";
314
315jint JNI_OnLoad(JavaVM* vm, void* reserved)
316{
317    JNIEnv* env = NULL;
318    jint result = -1;
319    jclass clazz;
320
321    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
322        ALOGE("ERROR: GetEnv failed\n");
323        return result;
324    }
325    assert(env != NULL);
326
327    clazz = env->FindClass(kClassPathName);
328    if (clazz == NULL) {
329        ALOGE("Can't find %s", kClassPathName);
330        return result;
331    }
332
333    fields.mNativeContext = env->GetFieldID(clazz, "mNativeContext", "J");
334    if (fields.mNativeContext == NULL) {
335        ALOGE("Can't find SoundPoolImpl.mNativeContext");
336        return result;
337    }
338
339    fields.mPostEvent = env->GetStaticMethodID(clazz, "postEventFromNative",
340                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
341    if (fields.mPostEvent == NULL) {
342        ALOGE("Can't find android/media/SoundPoolImpl.postEventFromNative");
343        return result;
344    }
345
346    // create a reference to class. Technically, we're leaking this reference
347    // since it's a static object.
348    fields.mSoundPoolClass = (jclass) env->NewGlobalRef(clazz);
349
350    if (AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)) < 0)
351        return result;
352
353    // Get the AudioAttributes class and fields
354    jclass audioAttrClass = env->FindClass(kAudioAttributesClassPathName);
355    if (audioAttrClass == NULL) {
356        ALOGE("Can't find %s", kAudioAttributesClassPathName);
357        return result;
358    }
359    jclass audioAttributesClassRef = (jclass)env->NewGlobalRef(audioAttrClass);
360    javaAudioAttrFields.fieldUsage = env->GetFieldID(audioAttributesClassRef, "mUsage", "I");
361    javaAudioAttrFields.fieldContentType
362                                   = env->GetFieldID(audioAttributesClassRef, "mContentType", "I");
363    javaAudioAttrFields.fieldFlags = env->GetFieldID(audioAttributesClassRef, "mFlags", "I");
364    javaAudioAttrFields.fieldFormattedTags =
365            env->GetFieldID(audioAttributesClassRef, "mFormattedTags", "Ljava/lang/String;");
366    env->DeleteGlobalRef(audioAttributesClassRef);
367    if (javaAudioAttrFields.fieldUsage == NULL || javaAudioAttrFields.fieldContentType == NULL
368            || javaAudioAttrFields.fieldFlags == NULL
369            || javaAudioAttrFields.fieldFormattedTags == NULL) {
370        ALOGE("Can't initialize AudioAttributes fields");
371        return result;
372    }
373
374    /* success -- return valid version number */
375    return JNI_VERSION_1_4;
376}
377