android_media_MediaPlayer.cpp revision 8b0b174198793cabb2b3fcc015f9bfdc9d5082b5
1/* //device/libs/android_runtime/android_media_MediaPlayer.cpp
2**
3** Copyright 2007, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9**     http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
18//#define LOG_NDEBUG 0
19#define LOG_TAG "MediaPlayer-JNI"
20#include "utils/Log.h"
21
22#include <media/mediaplayer.h>
23#include <stdio.h>
24#include <assert.h>
25#include <limits.h>
26#include <unistd.h>
27#include <fcntl.h>
28#include <utils/threads.h>
29#include "jni.h"
30#include "JNIHelp.h"
31#include "android_runtime/AndroidRuntime.h"
32#include "utils/Errors.h"  // for status_t
33
34
35// ----------------------------------------------------------------------------
36
37using namespace android;
38
39// ----------------------------------------------------------------------------
40
41struct fields_t {
42    jfieldID    context;
43    jfieldID    surface;
44    /* actually in android.view.Surface XXX */
45    jfieldID    surface_native;
46
47    jmethodID   post_event;
48};
49static fields_t fields;
50
51static Mutex sLock;
52
53// ----------------------------------------------------------------------------
54// ref-counted object for callbacks
55class JNIMediaPlayerListener: public MediaPlayerListener
56{
57public:
58    JNIMediaPlayerListener(JNIEnv* env, jobject thiz, jobject weak_thiz);
59    ~JNIMediaPlayerListener();
60    void notify(int msg, int ext1, int ext2);
61private:
62    JNIMediaPlayerListener();
63    jclass      mClass;     // Reference to MediaPlayer class
64    jobject     mObject;    // Weak ref to MediaPlayer Java object to call on
65};
66
67JNIMediaPlayerListener::JNIMediaPlayerListener(JNIEnv* env, jobject thiz, jobject weak_thiz)
68{
69
70    // Hold onto the MediaPlayer class for use in calling the static method
71    // that posts events to the application thread.
72    jclass clazz = env->GetObjectClass(thiz);
73    if (clazz == NULL) {
74        LOGE("Can't find android/media/MediaPlayer");
75        jniThrowException(env, "java/lang/Exception", NULL);
76        return;
77    }
78    mClass = (jclass)env->NewGlobalRef(clazz);
79
80    // We use a weak reference so the MediaPlayer object can be garbage collected.
81    // The reference is only used as a proxy for callbacks.
82    mObject  = env->NewGlobalRef(weak_thiz);
83}
84
85JNIMediaPlayerListener::~JNIMediaPlayerListener()
86{
87    // remove global references
88    JNIEnv *env = AndroidRuntime::getJNIEnv();
89    env->DeleteGlobalRef(mObject);
90    env->DeleteGlobalRef(mClass);
91}
92
93void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2)
94{
95    JNIEnv *env = AndroidRuntime::getJNIEnv();
96    env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, 0);
97}
98
99// ----------------------------------------------------------------------------
100
101static sp<Surface> get_surface(JNIEnv* env, jobject clazz)
102{
103    Surface* const p = (Surface*)env->GetIntField(clazz, fields.surface_native);
104    return sp<Surface>(p);
105}
106
107static sp<MediaPlayer> getMediaPlayer(JNIEnv* env, jobject thiz)
108{
109    Mutex::Autolock l(sLock);
110    MediaPlayer* const p = (MediaPlayer*)env->GetIntField(thiz, fields.context);
111    return sp<MediaPlayer>(p);
112}
113
114static sp<MediaPlayer> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer>& player)
115{
116    Mutex::Autolock l(sLock);
117    sp<MediaPlayer> old = (MediaPlayer*)env->GetIntField(thiz, fields.context);
118    if (player.get()) {
119        player->incStrong(thiz);
120    }
121    if (old != 0) {
122        old->decStrong(thiz);
123    }
124    env->SetIntField(thiz, fields.context, (int)player.get());
125    return old;
126}
127
128// If exception is NULL and opStatus is not OK, this method sends an error
129// event to the client application; otherwise, if exception is not NULL and
130// opStatus is not OK, this method throws the given exception to the client
131// application.
132static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message)
133{
134    if (exception == NULL) {  // Don't throw exception. Instead, send an event.
135        if (opStatus != (status_t) OK) {
136            sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
137            if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0);
138        }
139    } else {  // Throw exception!
140        if ( opStatus == (status_t) INVALID_OPERATION ) {
141            jniThrowException(env, "java/lang/IllegalStateException", NULL);
142        } else if ( opStatus != (status_t) OK ) {
143            if (strlen(message) > 230) {
144               // if the message is too long, don't bother displaying the status code
145               jniThrowException( env, exception, message);
146            } else {
147               char msg[256];
148                // append the status code to the message
149               sprintf(msg, "%s: status=0x%X", message, opStatus);
150               jniThrowException( env, exception, msg);
151            }
152        }
153    }
154}
155
156static void
157android_media_MediaPlayer_setDataSource(JNIEnv *env, jobject thiz, jstring path)
158{
159    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
160    if (mp == NULL ) {
161        jniThrowException(env, "java/lang/IllegalStateException", NULL);
162        return;
163    }
164
165    if (path == NULL) {
166        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
167        return;
168    }
169
170    const char *pathStr = env->GetStringUTFChars(path, NULL);
171    if (pathStr == NULL) {  // Out of memory
172        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
173        return;
174    }
175    LOGV("setDataSource: path %s", pathStr);
176    status_t opStatus = mp->setDataSource(pathStr);
177
178    // Make sure that local ref is released before a potential exception
179    env->ReleaseStringUTFChars(path, pathStr);
180    process_media_player_call( env, thiz, opStatus, "java/io/IOException", "setDataSource failed." );
181}
182
183static void
184android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
185{
186    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
187    if (mp == NULL ) {
188        jniThrowException(env, "java/lang/IllegalStateException", NULL);
189        return;
190    }
191
192    if (fileDescriptor == NULL) {
193        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
194        return;
195    }
196    int fd = getParcelFileDescriptorFD(env, fileDescriptor);
197    LOGV("setDataSourceFD: fd %d", fd);
198    process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." );
199}
200
201static void setVideoSurface(const sp<MediaPlayer>& mp, JNIEnv *env, jobject thiz)
202{
203    jobject surface = env->GetObjectField(thiz, fields.surface);
204    if (surface != NULL) {
205        const sp<Surface>& native_surface = get_surface(env, surface);
206        LOGV("prepare: surface=%p (id=%d)",
207             native_surface.get(), native_surface->ID());
208        mp->setVideoSurface(native_surface);
209    }
210}
211
212static void
213android_media_MediaPlayer_setVideoSurface(JNIEnv *env, jobject thiz)
214{
215    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
216    if (mp == NULL ) {
217        jniThrowException(env, "java/lang/IllegalStateException", NULL);
218        return;
219    }
220    setVideoSurface(mp, env, thiz);
221}
222
223static void
224android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz)
225{
226    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
227    if (mp == NULL ) {
228        jniThrowException(env, "java/lang/IllegalStateException", NULL);
229        return;
230    }
231    setVideoSurface(mp, env, thiz);
232    process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." );
233}
234
235static void
236android_media_MediaPlayer_prepareAsync(JNIEnv *env, jobject thiz)
237{
238    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
239    if (mp == NULL ) {
240        jniThrowException(env, "java/lang/IllegalStateException", NULL);
241        return;
242    }
243    jobject surface = env->GetObjectField(thiz, fields.surface);
244    if (surface != NULL) {
245        const sp<Surface>& native_surface = get_surface(env, surface);
246        LOGV("prepareAsync: surface=%p (id=%d)",
247             native_surface.get(), native_surface->ID());
248        mp->setVideoSurface(native_surface);
249    }
250    process_media_player_call( env, thiz, mp->prepareAsync(), "java/io/IOException", "Prepare Async failed." );
251}
252
253static void
254android_media_MediaPlayer_start(JNIEnv *env, jobject thiz)
255{
256    LOGV("start");
257    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
258    if (mp == NULL ) {
259        jniThrowException(env, "java/lang/IllegalStateException", NULL);
260        return;
261    }
262    process_media_player_call( env, thiz, mp->start(), NULL, NULL );
263}
264
265static void
266android_media_MediaPlayer_stop(JNIEnv *env, jobject thiz)
267{
268    LOGV("stop");
269    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
270    if (mp == NULL ) {
271        jniThrowException(env, "java/lang/IllegalStateException", NULL);
272        return;
273    }
274    process_media_player_call( env, thiz, mp->stop(), NULL, NULL );
275}
276
277static void
278android_media_MediaPlayer_pause(JNIEnv *env, jobject thiz)
279{
280    LOGV("pause");
281    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
282    if (mp == NULL ) {
283        jniThrowException(env, "java/lang/IllegalStateException", NULL);
284        return;
285    }
286    process_media_player_call( env, thiz, mp->pause(), NULL, NULL );
287}
288
289static jboolean
290android_media_MediaPlayer_isPlaying(JNIEnv *env, jobject thiz)
291{
292    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
293    if (mp == NULL ) {
294        jniThrowException(env, "java/lang/IllegalStateException", NULL);
295        return false;
296    }
297    const jboolean is_playing = mp->isPlaying();
298
299    LOGV("isPlaying: %d", is_playing);
300    return is_playing;
301}
302
303static void
304android_media_MediaPlayer_seekTo(JNIEnv *env, jobject thiz, int msec)
305{
306    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
307    if (mp == NULL ) {
308        jniThrowException(env, "java/lang/IllegalStateException", NULL);
309        return;
310    }
311    LOGV("seekTo: %d(msec)", msec);
312    process_media_player_call( env, thiz, mp->seekTo(msec), NULL, NULL );
313}
314
315static int
316android_media_MediaPlayer_getVideoWidth(JNIEnv *env, jobject thiz)
317{
318    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
319    if (mp == NULL ) {
320        jniThrowException(env, "java/lang/IllegalStateException", NULL);
321        return 0;
322    }
323    int w;
324    if (0 != mp->getVideoWidth(&w)) {
325        LOGE("getVideoWidth failed");
326        w = 0;
327    }
328    LOGV("getVideoWidth: %d", w);
329    return w;
330}
331
332static int
333android_media_MediaPlayer_getVideoHeight(JNIEnv *env, jobject thiz)
334{
335    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
336    if (mp == NULL ) {
337        jniThrowException(env, "java/lang/IllegalStateException", NULL);
338        return 0;
339    }
340    int h;
341    if (0 != mp->getVideoHeight(&h)) {
342        LOGE("getVideoHeight failed");
343        h = 0;
344    }
345    LOGV("getVideoHeight: %d", h);
346    return h;
347}
348
349
350static int
351android_media_MediaPlayer_getCurrentPosition(JNIEnv *env, jobject thiz)
352{
353    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
354    if (mp == NULL ) {
355        jniThrowException(env, "java/lang/IllegalStateException", NULL);
356        return 0;
357    }
358    int msec;
359    process_media_player_call( env, thiz, mp->getCurrentPosition(&msec), NULL, NULL );
360    LOGV("getCurrentPosition: %d (msec)", msec);
361    return msec;
362}
363
364static int
365android_media_MediaPlayer_getDuration(JNIEnv *env, jobject thiz)
366{
367    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
368    if (mp == NULL ) {
369        jniThrowException(env, "java/lang/IllegalStateException", NULL);
370        return 0;
371    }
372    int msec;
373    process_media_player_call( env, thiz, mp->getDuration(&msec), NULL, NULL );
374    LOGV("getDuration: %d (msec)", msec);
375    return msec;
376}
377
378static void
379android_media_MediaPlayer_reset(JNIEnv *env, jobject thiz)
380{
381    LOGV("reset");
382    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
383    if (mp == NULL ) {
384        jniThrowException(env, "java/lang/IllegalStateException", NULL);
385        return;
386    }
387    process_media_player_call( env, thiz, mp->reset(), NULL, NULL );
388}
389
390static void
391android_media_MediaPlayer_setAudioStreamType(JNIEnv *env, jobject thiz, int streamtype)
392{
393    LOGV("setAudioStreamType: %d", streamtype);
394    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
395    if (mp == NULL ) {
396        jniThrowException(env, "java/lang/IllegalStateException", NULL);
397        return;
398    }
399    process_media_player_call( env, thiz, mp->setAudioStreamType(streamtype) , NULL, NULL );
400}
401
402static void
403android_media_MediaPlayer_setLooping(JNIEnv *env, jobject thiz, jboolean looping)
404{
405    LOGV("setLooping: %d", looping);
406    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
407    if (mp == NULL ) {
408        jniThrowException(env, "java/lang/IllegalStateException", NULL);
409        return;
410    }
411    process_media_player_call( env, thiz, mp->setLooping(looping), NULL, NULL );
412}
413
414static jboolean
415android_media_MediaPlayer_isLooping(JNIEnv *env, jobject thiz)
416{
417    LOGV("isLooping");
418    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
419    if (mp == NULL ) {
420        jniThrowException(env, "java/lang/IllegalStateException", NULL);
421        return false;
422    }
423    return mp->isLooping();
424}
425
426static void
427android_media_MediaPlayer_setVolume(JNIEnv *env, jobject thiz, float leftVolume, float rightVolume)
428{
429    LOGV("setVolume: left %f  right %f", leftVolume, rightVolume);
430    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
431    if (mp == NULL ) {
432        jniThrowException(env, "java/lang/IllegalStateException", NULL);
433        return;
434    }
435    process_media_player_call( env, thiz, mp->setVolume(leftVolume, rightVolume), NULL, NULL );
436}
437
438// FIXME: deprecated
439static jobject
440android_media_MediaPlayer_getFrameAt(JNIEnv *env, jobject thiz, jint msec)
441{
442    return NULL;
443}
444
445static void
446android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
447{
448    LOGV("native_setup");
449    sp<MediaPlayer> mp = new MediaPlayer();
450    if (mp == NULL) {
451        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
452        return;
453    }
454
455    // create new listener and give it to MediaPlayer
456    sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
457    mp->setListener(listener);
458
459    // Stow our new C++ MediaPlayer in an opaque field in the Java object.
460    setMediaPlayer(env, thiz, mp);
461}
462
463static void
464android_media_MediaPlayer_release(JNIEnv *env, jobject thiz)
465{
466    LOGV("release");
467    sp<MediaPlayer> mp = setMediaPlayer(env, thiz, 0);
468    if (mp != NULL) {
469        // this prevents native callbacks after the object is released
470        mp->setListener(0);
471        mp->disconnect();
472    }
473}
474
475static void
476android_media_MediaPlayer_native_finalize(JNIEnv *env, jobject thiz)
477{
478    LOGV("native_finalize");
479    android_media_MediaPlayer_release(env, thiz);
480}
481
482// ----------------------------------------------------------------------------
483
484static JNINativeMethod gMethods[] = {
485    {"setDataSource",       "(Ljava/lang/String;)V",            (void *)android_media_MediaPlayer_setDataSource},
486    {"setDataSource",       "(Ljava/io/FileDescriptor;JJ)V",    (void *)android_media_MediaPlayer_setDataSourceFD},
487    {"_setVideoSurface",    "()V",                              (void *)android_media_MediaPlayer_setVideoSurface},
488    {"prepare",             "()V",                              (void *)android_media_MediaPlayer_prepare},
489    {"prepareAsync",        "()V",                              (void *)android_media_MediaPlayer_prepareAsync},
490    {"_start",              "()V",                              (void *)android_media_MediaPlayer_start},
491    {"_stop",               "()V",                              (void *)android_media_MediaPlayer_stop},
492    {"getVideoWidth",       "()I",                              (void *)android_media_MediaPlayer_getVideoWidth},
493    {"getVideoHeight",      "()I",                              (void *)android_media_MediaPlayer_getVideoHeight},
494    {"seekTo",              "(I)V",                             (void *)android_media_MediaPlayer_seekTo},
495    {"_pause",              "()V",                              (void *)android_media_MediaPlayer_pause},
496    {"isPlaying",           "()Z",                              (void *)android_media_MediaPlayer_isPlaying},
497    {"getCurrentPosition",  "()I",                              (void *)android_media_MediaPlayer_getCurrentPosition},
498    {"getDuration",         "()I",                              (void *)android_media_MediaPlayer_getDuration},
499    {"_release",            "()V",                              (void *)android_media_MediaPlayer_release},
500    {"_reset",              "()V",                              (void *)android_media_MediaPlayer_reset},
501    {"setAudioStreamType",  "(I)V",                             (void *)android_media_MediaPlayer_setAudioStreamType},
502    {"setLooping",          "(Z)V",                             (void *)android_media_MediaPlayer_setLooping},
503    {"isLooping",           "()Z",                              (void *)android_media_MediaPlayer_isLooping},
504    {"setVolume",           "(FF)V",                            (void *)android_media_MediaPlayer_setVolume},
505    {"getFrameAt",          "(I)Landroid/graphics/Bitmap;",     (void *)android_media_MediaPlayer_getFrameAt},
506    {"native_setup",        "(Ljava/lang/Object;)V",            (void *)android_media_MediaPlayer_native_setup},
507    {"native_finalize",     "()V",                              (void *)android_media_MediaPlayer_native_finalize},
508};
509
510static const char* const kClassPathName = "android/media/MediaPlayer";
511
512static int register_android_media_MediaPlayer(JNIEnv *env)
513{
514    jclass clazz;
515
516    clazz = env->FindClass("android/media/MediaPlayer");
517    if (clazz == NULL) {
518        LOGE("Can't find android/media/MediaPlayer");
519        return -1;
520    }
521
522    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
523    if (fields.context == NULL) {
524        LOGE("Can't find MediaPlayer.mNativeContext");
525        return -1;
526    }
527
528    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
529                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
530    if (fields.post_event == NULL) {
531        LOGE("Can't find MediaPlayer.postEventFromNative");
532        return -1;
533    }
534
535    fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;");
536    if (fields.surface == NULL) {
537        LOGE("Can't find MediaPlayer.mSurface");
538        return -1;
539    }
540
541    jclass surface = env->FindClass("android/view/Surface");
542    if (surface == NULL) {
543        LOGE("Can't find android/view/Surface");
544        return -1;
545    }
546
547    fields.surface_native = env->GetFieldID(surface, "mSurface", "I");
548    if (fields.surface_native == NULL) {
549        LOGE("Can't find Surface fields");
550        return -1;
551    }
552
553    return AndroidRuntime::registerNativeMethods(env,
554                "android/media/MediaPlayer", gMethods, NELEM(gMethods));
555}
556
557extern int register_android_media_MediaRecorder(JNIEnv *env);
558extern int register_android_media_MediaScanner(JNIEnv *env);
559extern int register_android_media_MediaMetadataRetriever(JNIEnv *env);
560extern int register_android_media_AmrInputStream(JNIEnv *env);
561extern int register_android_media_ResampleInputStream(JNIEnv *env);
562
563jint JNI_OnLoad(JavaVM* vm, void* reserved)
564{
565    JNIEnv* env = NULL;
566    jint result = -1;
567
568    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
569        LOGE("ERROR: GetEnv failed\n");
570        goto bail;
571    }
572    assert(env != NULL);
573
574    if (register_android_media_MediaPlayer(env) < 0) {
575        LOGE("ERROR: MediaPlayer native registration failed\n");
576        goto bail;
577    }
578
579    if (register_android_media_MediaRecorder(env) < 0) {
580        LOGE("ERROR: MediaRecorder native registration failed\n");
581        goto bail;
582    }
583
584    if (register_android_media_MediaScanner(env) < 0) {
585        LOGE("ERROR: MediaScanner native registration failed\n");
586        goto bail;
587    }
588
589    if (register_android_media_MediaMetadataRetriever(env) < 0) {
590        LOGE("ERROR: MediaMetadataRetriever native registration failed\n");
591        goto bail;
592    }
593
594    if (register_android_media_AmrInputStream(env) < 0) {
595        LOGE("ERROR: AmrInputStream native registration failed\n");
596        goto bail;
597    }
598
599    if (register_android_media_ResampleInputStream(env) < 0) {
600        LOGE("ERROR: ResampleInputStream native registration failed\n");
601        goto bail;
602    }
603
604    /* success -- return valid version number */
605    result = JNI_VERSION_1_4;
606
607bail:
608    return result;
609}
610
611// KTHXBYE
612