/* //device/libs/android_runtime/android_media_MediaPlayer.cpp ** ** Copyright 2007, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "MediaPlayer-JNI" #include "utils/Log.h" #include <media/mediaplayer.h> #include <stdio.h> #include <assert.h> #include <limits.h> #include <unistd.h> #include <fcntl.h> #include <utils/threads.h> #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include "utils/Errors.h" // for status_t // ---------------------------------------------------------------------------- using namespace android; // ---------------------------------------------------------------------------- struct fields_t { jfieldID context; jfieldID surface; /* actually in android.view.Surface XXX */ jfieldID surface_native; jmethodID post_event; }; static fields_t fields; static Mutex sLock; // ---------------------------------------------------------------------------- // ref-counted object for callbacks class JNIMediaPlayerListener: public MediaPlayerListener { public: JNIMediaPlayerListener(JNIEnv* env, jobject thiz, jobject weak_thiz); ~JNIMediaPlayerListener(); void notify(int msg, int ext1, int ext2); private: JNIMediaPlayerListener(); jclass mClass; // Reference to MediaPlayer class jobject mObject; // Weak ref to MediaPlayer Java object to call on }; JNIMediaPlayerListener::JNIMediaPlayerListener(JNIEnv* env, jobject thiz, jobject weak_thiz) { // Hold onto the MediaPlayer class for use in calling the static method // that posts events to the application thread. jclass clazz = env->GetObjectClass(thiz); if (clazz == NULL) { LOGE("Can't find android/media/MediaPlayer"); jniThrowException(env, "java/lang/Exception", NULL); return; } mClass = (jclass)env->NewGlobalRef(clazz); // We use a weak reference so the MediaPlayer object can be garbage collected. // The reference is only used as a proxy for callbacks. mObject = env->NewGlobalRef(weak_thiz); } JNIMediaPlayerListener::~JNIMediaPlayerListener() { // remove global references JNIEnv *env = AndroidRuntime::getJNIEnv(); env->DeleteGlobalRef(mObject); env->DeleteGlobalRef(mClass); } void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2) { JNIEnv *env = AndroidRuntime::getJNIEnv(); env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, 0); } // ---------------------------------------------------------------------------- static sp<Surface> get_surface(JNIEnv* env, jobject clazz) { Surface* const p = (Surface*)env->GetIntField(clazz, fields.surface_native); return sp<Surface>(p); } static sp<MediaPlayer> getMediaPlayer(JNIEnv* env, jobject thiz) { Mutex::Autolock l(sLock); MediaPlayer* const p = (MediaPlayer*)env->GetIntField(thiz, fields.context); return sp<MediaPlayer>(p); } static sp<MediaPlayer> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer>& player) { Mutex::Autolock l(sLock); sp<MediaPlayer> old = (MediaPlayer*)env->GetIntField(thiz, fields.context); if (player.get()) { player->incStrong(thiz); } if (old != 0) { old->decStrong(thiz); } env->SetIntField(thiz, fields.context, (int)player.get()); return old; } // If exception is NULL and opStatus is not OK, this method sends an error // event to the client application; otherwise, if exception is not NULL and // opStatus is not OK, this method throws the given exception to the client // application. static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message) { if (exception == NULL) { // Don't throw exception. Instead, send an event. if (opStatus != (status_t) OK) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0); } } else { // Throw exception! if ( opStatus == (status_t) INVALID_OPERATION ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); } else if ( opStatus != (status_t) OK ) { if (strlen(message) > 230) { // if the message is too long, don't bother displaying the status code jniThrowException( env, exception, message); } else { char msg[256]; // append the status code to the message sprintf(msg, "%s: status=0x%X", message, opStatus); jniThrowException( env, exception, msg); } } } } static void android_media_MediaPlayer_setDataSource(JNIEnv *env, jobject thiz, jstring path) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } if (path == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return; } const char *pathStr = env->GetStringUTFChars(path, NULL); if (pathStr == NULL) { // Out of memory jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); return; } LOGV("setDataSource: path %s", pathStr); status_t opStatus = mp->setDataSource(pathStr); // Make sure that local ref is released before a potential exception env->ReleaseStringUTFChars(path, pathStr); process_media_player_call( env, thiz, opStatus, "java/io/IOException", "setDataSource failed." ); } static void android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } if (fileDescriptor == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return; } int fd = getParcelFileDescriptorFD(env, fileDescriptor); LOGV("setDataSourceFD: fd %d", fd); process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." ); } static void setVideoSurface(const sp<MediaPlayer>& mp, JNIEnv *env, jobject thiz) { jobject surface = env->GetObjectField(thiz, fields.surface); if (surface != NULL) { const sp<Surface>& native_surface = get_surface(env, surface); LOGV("prepare: surface=%p (id=%d)", native_surface.get(), native_surface->ID()); mp->setVideoSurface(native_surface); } } static void android_media_MediaPlayer_setVideoSurface(JNIEnv *env, jobject thiz) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } setVideoSurface(mp, env, thiz); } static void android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } setVideoSurface(mp, env, thiz); process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." ); } static void android_media_MediaPlayer_prepareAsync(JNIEnv *env, jobject thiz) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } jobject surface = env->GetObjectField(thiz, fields.surface); if (surface != NULL) { const sp<Surface>& native_surface = get_surface(env, surface); LOGV("prepareAsync: surface=%p (id=%d)", native_surface.get(), native_surface->ID()); mp->setVideoSurface(native_surface); } process_media_player_call( env, thiz, mp->prepareAsync(), "java/io/IOException", "Prepare Async failed." ); } static void android_media_MediaPlayer_start(JNIEnv *env, jobject thiz) { LOGV("start"); sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } process_media_player_call( env, thiz, mp->start(), NULL, NULL ); } static void android_media_MediaPlayer_stop(JNIEnv *env, jobject thiz) { LOGV("stop"); sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } process_media_player_call( env, thiz, mp->stop(), NULL, NULL ); } static void android_media_MediaPlayer_pause(JNIEnv *env, jobject thiz) { LOGV("pause"); sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } process_media_player_call( env, thiz, mp->pause(), NULL, NULL ); } static jboolean android_media_MediaPlayer_isPlaying(JNIEnv *env, jobject thiz) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return false; } const jboolean is_playing = mp->isPlaying(); LOGV("isPlaying: %d", is_playing); return is_playing; } static void android_media_MediaPlayer_seekTo(JNIEnv *env, jobject thiz, int msec) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } LOGV("seekTo: %d(msec)", msec); process_media_player_call( env, thiz, mp->seekTo(msec), NULL, NULL ); } static int android_media_MediaPlayer_getVideoWidth(JNIEnv *env, jobject thiz) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return 0; } int w; if (0 != mp->getVideoWidth(&w)) { LOGE("getVideoWidth failed"); w = 0; } LOGV("getVideoWidth: %d", w); return w; } static int android_media_MediaPlayer_getVideoHeight(JNIEnv *env, jobject thiz) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return 0; } int h; if (0 != mp->getVideoHeight(&h)) { LOGE("getVideoHeight failed"); h = 0; } LOGV("getVideoHeight: %d", h); return h; } static int android_media_MediaPlayer_getCurrentPosition(JNIEnv *env, jobject thiz) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return 0; } int msec; process_media_player_call( env, thiz, mp->getCurrentPosition(&msec), NULL, NULL ); LOGV("getCurrentPosition: %d (msec)", msec); return msec; } static int android_media_MediaPlayer_getDuration(JNIEnv *env, jobject thiz) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return 0; } int msec; process_media_player_call( env, thiz, mp->getDuration(&msec), NULL, NULL ); LOGV("getDuration: %d (msec)", msec); return msec; } static void android_media_MediaPlayer_reset(JNIEnv *env, jobject thiz) { LOGV("reset"); sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } process_media_player_call( env, thiz, mp->reset(), NULL, NULL ); } static void android_media_MediaPlayer_setAudioStreamType(JNIEnv *env, jobject thiz, int streamtype) { LOGV("setAudioStreamType: %d", streamtype); sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } process_media_player_call( env, thiz, mp->setAudioStreamType(streamtype) , NULL, NULL ); } static void android_media_MediaPlayer_setLooping(JNIEnv *env, jobject thiz, jboolean looping) { LOGV("setLooping: %d", looping); sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } process_media_player_call( env, thiz, mp->setLooping(looping), NULL, NULL ); } static jboolean android_media_MediaPlayer_isLooping(JNIEnv *env, jobject thiz) { LOGV("isLooping"); sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return false; } return mp->isLooping(); } static void android_media_MediaPlayer_setVolume(JNIEnv *env, jobject thiz, float leftVolume, float rightVolume) { LOGV("setVolume: left %f right %f", leftVolume, rightVolume); sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } process_media_player_call( env, thiz, mp->setVolume(leftVolume, rightVolume), NULL, NULL ); } // FIXME: deprecated static jobject android_media_MediaPlayer_getFrameAt(JNIEnv *env, jobject thiz, jint msec) { return NULL; } static void android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this) { LOGV("native_setup"); sp<MediaPlayer> mp = new MediaPlayer(); if (mp == NULL) { jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); return; } // create new listener and give it to MediaPlayer sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this); mp->setListener(listener); // Stow our new C++ MediaPlayer in an opaque field in the Java object. setMediaPlayer(env, thiz, mp); } static void android_media_MediaPlayer_release(JNIEnv *env, jobject thiz) { LOGV("release"); sp<MediaPlayer> mp = setMediaPlayer(env, thiz, 0); if (mp != NULL) { // this prevents native callbacks after the object is released mp->setListener(0); mp->disconnect(); } } static void android_media_MediaPlayer_native_finalize(JNIEnv *env, jobject thiz) { LOGV("native_finalize"); android_media_MediaPlayer_release(env, thiz); } // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { {"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaPlayer_setDataSource}, {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD}, {"_setVideoSurface", "()V", (void *)android_media_MediaPlayer_setVideoSurface}, {"prepare", "()V", (void *)android_media_MediaPlayer_prepare}, {"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync}, {"_start", "()V", (void *)android_media_MediaPlayer_start}, {"_stop", "()V", (void *)android_media_MediaPlayer_stop}, {"getVideoWidth", "()I", (void *)android_media_MediaPlayer_getVideoWidth}, {"getVideoHeight", "()I", (void *)android_media_MediaPlayer_getVideoHeight}, {"seekTo", "(I)V", (void *)android_media_MediaPlayer_seekTo}, {"_pause", "()V", (void *)android_media_MediaPlayer_pause}, {"isPlaying", "()Z", (void *)android_media_MediaPlayer_isPlaying}, {"getCurrentPosition", "()I", (void *)android_media_MediaPlayer_getCurrentPosition}, {"getDuration", "()I", (void *)android_media_MediaPlayer_getDuration}, {"_release", "()V", (void *)android_media_MediaPlayer_release}, {"_reset", "()V", (void *)android_media_MediaPlayer_reset}, {"setAudioStreamType", "(I)V", (void *)android_media_MediaPlayer_setAudioStreamType}, {"setLooping", "(Z)V", (void *)android_media_MediaPlayer_setLooping}, {"isLooping", "()Z", (void *)android_media_MediaPlayer_isLooping}, {"setVolume", "(FF)V", (void *)android_media_MediaPlayer_setVolume}, {"getFrameAt", "(I)Landroid/graphics/Bitmap;", (void *)android_media_MediaPlayer_getFrameAt}, {"native_setup", "(Ljava/lang/Object;)V", (void *)android_media_MediaPlayer_native_setup}, {"native_finalize", "()V", (void *)android_media_MediaPlayer_native_finalize}, }; static const char* const kClassPathName = "android/media/MediaPlayer"; static int register_android_media_MediaPlayer(JNIEnv *env) { jclass clazz; clazz = env->FindClass("android/media/MediaPlayer"); if (clazz == NULL) { LOGE("Can't find android/media/MediaPlayer"); return -1; } fields.context = env->GetFieldID(clazz, "mNativeContext", "I"); if (fields.context == NULL) { LOGE("Can't find MediaPlayer.mNativeContext"); return -1; } fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V"); if (fields.post_event == NULL) { LOGE("Can't find MediaPlayer.postEventFromNative"); return -1; } fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;"); if (fields.surface == NULL) { LOGE("Can't find MediaPlayer.mSurface"); return -1; } jclass surface = env->FindClass("android/view/Surface"); if (surface == NULL) { LOGE("Can't find android/view/Surface"); return -1; } fields.surface_native = env->GetFieldID(surface, "mSurface", "I"); if (fields.surface_native == NULL) { LOGE("Can't find Surface fields"); return -1; } return AndroidRuntime::registerNativeMethods(env, "android/media/MediaPlayer", gMethods, NELEM(gMethods)); } extern int register_android_media_MediaRecorder(JNIEnv *env); extern int register_android_media_MediaScanner(JNIEnv *env); extern int register_android_media_MediaMetadataRetriever(JNIEnv *env); extern int register_android_media_AmrInputStream(JNIEnv *env); extern int register_android_media_ResampleInputStream(JNIEnv *env); jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { LOGE("ERROR: GetEnv failed\n"); goto bail; } assert(env != NULL); if (register_android_media_MediaPlayer(env) < 0) { LOGE("ERROR: MediaPlayer native registration failed\n"); goto bail; } if (register_android_media_MediaRecorder(env) < 0) { LOGE("ERROR: MediaRecorder native registration failed\n"); goto bail; } if (register_android_media_MediaScanner(env) < 0) { LOGE("ERROR: MediaScanner native registration failed\n"); goto bail; } if (register_android_media_MediaMetadataRetriever(env) < 0) { LOGE("ERROR: MediaMetadataRetriever native registration failed\n"); goto bail; } if (register_android_media_AmrInputStream(env) < 0) { LOGE("ERROR: AmrInputStream native registration failed\n"); goto bail; } if (register_android_media_ResampleInputStream(env) < 0) { LOGE("ERROR: ResampleInputStream native registration failed\n"); goto bail; } /* success -- return valid version number */ result = JNI_VERSION_1_4; bail: return result; } // KTHXBYE