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 <jni.h>
24 #include <nativehelper/JNIPlatformHelp.h>
25 #include <nativehelper/ScopedUtfChars.h>
26 #include <android_runtime/AndroidRuntime.h>
27 #include "SoundPool.h"
28
29 using namespace android;
30
31 static struct fields_t {
32 jfieldID mNativeContext;
33 jmethodID mPostEvent;
34 jclass mSoundPoolClass;
35 } fields;
MusterSoundPool(JNIEnv * env,jobject thiz)36 static inline SoundPool* MusterSoundPool(JNIEnv *env, jobject thiz) {
37 // NOLINTNEXTLINE(performance-no-int-to-ptr)
38 return reinterpret_cast<SoundPool*>(env->GetLongField(thiz, fields.mNativeContext));
39 }
40 static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes";
41 struct audio_attributes_fields_t {
42 jfieldID fieldUsage; // AudioAttributes.mUsage
43 jfieldID fieldContentType; // AudioAttributes.mContentType
44 jfieldID fieldFlags; // AudioAttributes.mFlags
45 jfieldID fieldFormattedTags;// AudioAttributes.mFormattedTags
46 };
47 static audio_attributes_fields_t javaAudioAttrFields;
48
49 // ----------------------------------------------------------------------------
50
51 static jint
android_media_SoundPool_load_FD(JNIEnv * env,jobject thiz,jobject fileDescriptor,jlong offset,jlong length,jint priority)52 android_media_SoundPool_load_FD(JNIEnv *env, jobject thiz, jobject fileDescriptor,
53 jlong offset, jlong length, jint priority)
54 {
55 ALOGV("android_media_SoundPool_load_FD");
56 SoundPool *ap = MusterSoundPool(env, thiz);
57 if (ap == nullptr) return 0;
58 return (jint) ap->load(jniGetFDFromFileDescriptor(env, fileDescriptor),
59 int64_t(offset), int64_t(length), int(priority));
60 }
61
62 static jboolean
android_media_SoundPool_unload(JNIEnv * env,jobject thiz,jint sampleID)63 android_media_SoundPool_unload(JNIEnv *env, jobject thiz, jint sampleID) {
64 ALOGV("android_media_SoundPool_unload\n");
65 SoundPool *ap = MusterSoundPool(env, thiz);
66 if (ap == nullptr) return JNI_FALSE;
67 return ap->unload(sampleID) ? JNI_TRUE : JNI_FALSE;
68 }
69
70 static jint
android_media_SoundPool_play(JNIEnv * env,jobject thiz,jint sampleID,jfloat leftVolume,jfloat rightVolume,jint priority,jint loop,jfloat rate)71 android_media_SoundPool_play(JNIEnv *env, jobject thiz, jint sampleID,
72 jfloat leftVolume, jfloat rightVolume, jint priority, jint loop,
73 jfloat rate)
74 {
75 ALOGV("android_media_SoundPool_play\n");
76 SoundPool *ap = MusterSoundPool(env, thiz);
77 if (ap == nullptr) return 0;
78 return (jint) ap->play(sampleID, leftVolume, rightVolume, priority, loop, rate);
79 }
80
81 static void
android_media_SoundPool_pause(JNIEnv * env,jobject thiz,jint channelID)82 android_media_SoundPool_pause(JNIEnv *env, jobject thiz, jint channelID)
83 {
84 ALOGV("android_media_SoundPool_pause");
85 SoundPool *ap = MusterSoundPool(env, thiz);
86 if (ap == nullptr) return;
87 ap->pause(channelID);
88 }
89
90 static void
android_media_SoundPool_resume(JNIEnv * env,jobject thiz,jint channelID)91 android_media_SoundPool_resume(JNIEnv *env, jobject thiz, jint channelID)
92 {
93 ALOGV("android_media_SoundPool_resume");
94 SoundPool *ap = MusterSoundPool(env, thiz);
95 if (ap == nullptr) return;
96 ap->resume(channelID);
97 }
98
99 static void
android_media_SoundPool_autoPause(JNIEnv * env,jobject thiz)100 android_media_SoundPool_autoPause(JNIEnv *env, jobject thiz)
101 {
102 ALOGV("android_media_SoundPool_autoPause");
103 SoundPool *ap = MusterSoundPool(env, thiz);
104 if (ap == nullptr) return;
105 ap->autoPause();
106 }
107
108 static void
android_media_SoundPool_autoResume(JNIEnv * env,jobject thiz)109 android_media_SoundPool_autoResume(JNIEnv *env, jobject thiz)
110 {
111 ALOGV("android_media_SoundPool_autoResume");
112 SoundPool *ap = MusterSoundPool(env, thiz);
113 if (ap == nullptr) return;
114 ap->autoResume();
115 }
116
117 static void
android_media_SoundPool_stop(JNIEnv * env,jobject thiz,jint channelID)118 android_media_SoundPool_stop(JNIEnv *env, jobject thiz, jint channelID)
119 {
120 ALOGV("android_media_SoundPool_stop");
121 SoundPool *ap = MusterSoundPool(env, thiz);
122 if (ap == nullptr) return;
123 ap->stop(channelID);
124 }
125
126 static void
android_media_SoundPool_setVolume(JNIEnv * env,jobject thiz,jint channelID,jfloat leftVolume,jfloat rightVolume)127 android_media_SoundPool_setVolume(JNIEnv *env, jobject thiz, jint channelID,
128 jfloat leftVolume, jfloat rightVolume)
129 {
130 ALOGV("android_media_SoundPool_setVolume");
131 SoundPool *ap = MusterSoundPool(env, thiz);
132 if (ap == nullptr) return;
133 ap->setVolume(channelID, (float) leftVolume, (float) rightVolume);
134 }
135
136 static void
android_media_SoundPool_mute(JNIEnv * env,jobject thiz,jboolean muting)137 android_media_SoundPool_mute(JNIEnv *env, jobject thiz, jboolean muting)
138 {
139 ALOGV("android_media_SoundPool_mute(%d)", muting);
140 SoundPool *ap = MusterSoundPool(env, thiz);
141 if (ap == nullptr) return;
142 ap->mute(muting == JNI_TRUE);
143 }
144
145 static void
android_media_SoundPool_setPriority(JNIEnv * env,jobject thiz,jint channelID,jint priority)146 android_media_SoundPool_setPriority(JNIEnv *env, jobject thiz, jint channelID,
147 jint priority)
148 {
149 ALOGV("android_media_SoundPool_setPriority");
150 SoundPool *ap = MusterSoundPool(env, thiz);
151 if (ap == nullptr) return;
152 ap->setPriority(channelID, (int) priority);
153 }
154
155 static void
android_media_SoundPool_setLoop(JNIEnv * env,jobject thiz,jint channelID,int loop)156 android_media_SoundPool_setLoop(JNIEnv *env, jobject thiz, jint channelID,
157 int loop)
158 {
159 ALOGV("android_media_SoundPool_setLoop");
160 SoundPool *ap = MusterSoundPool(env, thiz);
161 if (ap == nullptr) return;
162 ap->setLoop(channelID, loop);
163 }
164
165 static void
android_media_SoundPool_setRate(JNIEnv * env,jobject thiz,jint channelID,jfloat rate)166 android_media_SoundPool_setRate(JNIEnv *env, jobject thiz, jint channelID,
167 jfloat rate)
168 {
169 ALOGV("android_media_SoundPool_setRate");
170 SoundPool *ap = MusterSoundPool(env, thiz);
171 if (ap == nullptr) return;
172 ap->setRate(channelID, (float) rate);
173 }
174
android_media_callback(SoundPoolEvent event,SoundPool * soundPool,void * user)175 static void android_media_callback(SoundPoolEvent event, SoundPool* soundPool, void* user)
176 {
177 ALOGV("callback: (%d, %d, %d, %p, %p)", event.mMsg, event.mArg1, event.mArg2, soundPool, user);
178 JNIEnv *env = AndroidRuntime::getJNIEnv();
179 env->CallStaticVoidMethod(
180 fields.mSoundPoolClass, fields.mPostEvent, user, event.mMsg, event.mArg1, event.mArg2,
181 nullptr /* object */);
182 }
183
184 static jint
android_media_SoundPool_native_setup(JNIEnv * env,jobject thiz,jobject weakRef,jint maxChannels,jobject jaa,jstring opPackageName)185 android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef,
186 jint maxChannels, jobject jaa, jstring opPackageName)
187 {
188 if (jaa == nullptr) {
189 ALOGE("Error creating SoundPool: invalid audio attributes");
190 return -1;
191 }
192
193 audio_attributes_t *paa = nullptr;
194 // read the AudioAttributes values
195 paa = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t));
196 const auto jtags =
197 (jstring) env->GetObjectField(jaa, javaAudioAttrFields.fieldFormattedTags);
198 const char* tags = env->GetStringUTFChars(jtags, nullptr);
199 // copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it
200 strncpy(paa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1);
201 env->ReleaseStringUTFChars(jtags, tags);
202 paa->usage = (audio_usage_t) env->GetIntField(jaa, javaAudioAttrFields.fieldUsage);
203 paa->content_type =
204 (audio_content_type_t) env->GetIntField(jaa, javaAudioAttrFields.fieldContentType);
205 paa->flags = (audio_flags_mask_t) env->GetIntField(jaa, javaAudioAttrFields.fieldFlags);
206
207 ALOGV("android_media_SoundPool_native_setup");
208 ScopedUtfChars opPackageNameStr(env, opPackageName);
209 auto *ap = new SoundPool(maxChannels, paa, opPackageNameStr.c_str());
210 if (ap == nullptr) {
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
227 static void
android_media_SoundPool_release(JNIEnv * env,jobject thiz)228 android_media_SoundPool_release(JNIEnv *env, jobject thiz)
229 {
230 ALOGV("android_media_SoundPool_release");
231 SoundPool *ap = MusterSoundPool(env, thiz);
232 if (ap != nullptr) {
233
234 // release weak reference and clear callback
235 auto weakRef = (jobject) ap->getUserData();
236 ap->setCallback(nullptr /* callback */, nullptr /* user */);
237 if (weakRef != nullptr) {
238 env->DeleteGlobalRef(weakRef);
239 }
240
241 // clear native context
242 env->SetLongField(thiz, fields.mNativeContext, 0);
243 delete ap;
244 }
245 }
246
247 // ----------------------------------------------------------------------------
248
249 // Dalvik VM type signatures
250 static JNINativeMethod gMethods[] = {
251 { "_load",
252 "(Ljava/io/FileDescriptor;JJI)I",
253 (void *)android_media_SoundPool_load_FD
254 },
255 { "unload",
256 "(I)Z",
257 (void *)android_media_SoundPool_unload
258 },
259 { "_play",
260 "(IFFIIF)I",
261 (void *)android_media_SoundPool_play
262 },
263 { "pause",
264 "(I)V",
265 (void *)android_media_SoundPool_pause
266 },
267 { "resume",
268 "(I)V",
269 (void *)android_media_SoundPool_resume
270 },
271 { "autoPause",
272 "()V",
273 (void *)android_media_SoundPool_autoPause
274 },
275 { "autoResume",
276 "()V",
277 (void *)android_media_SoundPool_autoResume
278 },
279 { "stop",
280 "(I)V",
281 (void *)android_media_SoundPool_stop
282 },
283 { "_setVolume",
284 "(IFF)V",
285 (void *)android_media_SoundPool_setVolume
286 },
287 { "_mute",
288 "(Z)V",
289 (void *)android_media_SoundPool_mute
290 },
291 { "setPriority",
292 "(II)V",
293 (void *)android_media_SoundPool_setPriority
294 },
295 { "setLoop",
296 "(II)V",
297 (void *)android_media_SoundPool_setLoop
298 },
299 { "setRate",
300 "(IF)V",
301 (void *)android_media_SoundPool_setRate
302 },
303 { "native_setup",
304 "(Ljava/lang/Object;ILjava/lang/Object;Ljava/lang/String;)I",
305 (void*)android_media_SoundPool_native_setup
306 },
307 { "native_release",
308 "()V",
309 (void*)android_media_SoundPool_release
310 }
311 };
312
313 static const char* const kClassPathName = "android/media/SoundPool";
314
JNI_OnLoad(JavaVM * vm,void *)315 jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
316 {
317 JNIEnv* env = nullptr;
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 != nullptr);
326
327 clazz = env->FindClass(kClassPathName);
328 if (clazz == nullptr) {
329 ALOGE("Can't find %s", kClassPathName);
330 return result;
331 }
332
333 fields.mNativeContext = env->GetFieldID(clazz, "mNativeContext", "J");
334 if (fields.mNativeContext == nullptr) {
335 ALOGE("Can't find SoundPool.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 == nullptr) {
342 ALOGE("Can't find android/media/SoundPool.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(
351 env, kClassPathName, gMethods, NELEM(gMethods)) < 0) {
352 return result;
353 }
354
355 // Get the AudioAttributes class and fields
356 jclass audioAttrClass = env->FindClass(kAudioAttributesClassPathName);
357 if (audioAttrClass == nullptr) {
358 ALOGE("Can't find %s", kAudioAttributesClassPathName);
359 return result;
360 }
361 auto audioAttributesClassRef = (jclass)env->NewGlobalRef(audioAttrClass);
362 javaAudioAttrFields.fieldUsage = env->GetFieldID(audioAttributesClassRef, "mUsage", "I");
363 javaAudioAttrFields.fieldContentType
364 = env->GetFieldID(audioAttributesClassRef, "mContentType", "I");
365 javaAudioAttrFields.fieldFlags = env->GetFieldID(audioAttributesClassRef, "mFlags", "I");
366 javaAudioAttrFields.fieldFormattedTags =
367 env->GetFieldID(audioAttributesClassRef, "mFormattedTags", "Ljava/lang/String;");
368 env->DeleteGlobalRef(audioAttributesClassRef);
369 if (javaAudioAttrFields.fieldUsage == nullptr
370 || javaAudioAttrFields.fieldContentType == nullptr
371 || javaAudioAttrFields.fieldFlags == nullptr
372 || javaAudioAttrFields.fieldFormattedTags == nullptr) {
373 ALOGE("Can't initialize AudioAttributes fields");
374 return result;
375 }
376
377 /* success -- return valid version number */
378 return JNI_VERSION_1_4;
379 }
380