Android源码分析之Framework的MediaPlayer

在Android中MediaPlayer用来播放音频和视频文件,在这里分析下在Framework层中MediaPlayer是如何调用的,MediaPlayer的代码位于:./frameworks/base/media/java/android/media/MediaPlayer.java   下面用到的代码是基于Android 4.4

打开后有一个静态代码块是加载库文件的,只要这个类被创建就会加载库。

    static {
        System.loadLibrary("media_jni");
        native_init();
    }

libmedia_jni.so的源代码位于:./frameworks/base/media/jni

在jni这个文件夹中有个makefile文件 Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES:=     android_media_ImageReader.cpp     android_media_MediaCrypto.cpp     android_media_MediaCodec.cpp     android_media_MediaCodecList.cpp     android_media_MediaDrm.cpp     android_media_MediaExtractor.cpp     android_media_MediaMuxer.cpp     android_media_MediaPlayer.cpp     android_media_MediaRecorder.cpp     android_media_MediaScanner.cpp     android_media_MediaMetadataRetriever.cpp     android_media_ResampleInputStream.cpp     android_media_MediaProfiles.cpp     android_media_AmrInputStream.cpp     android_media_Utils.cpp     android_mtp_MtpDatabase.cpp     android_mtp_MtpDevice.cpp     android_mtp_MtpServer.cpp
LOCAL_SHARED_LIBRARIES :=     libandroid_runtime     libnativehelper     libutils     libbinder     libmedia     libskia     libui     liblog     libcutils     libgui     libstagefright     libstagefright_foundation     libcamera_client     libmtp     libusbhost     libexif     libstagefright_amrnb_common
LOCAL_REQUIRED_MODULES :=     libjhead_jni

LOCAL_STATIC_LIBRARIES :=     libstagefright_amrnbenc

LOCAL_C_INCLUDES +=     external/libexif/     external/tremor/Tremor     frameworks/base/core/jni     frameworks/av/media/libmedia     frameworks/av/media/libstagefright     frameworks/av/media/libstagefright/codecs/amrnb/enc/src     frameworks/av/media/libstagefright/codecs/amrnb/common     frameworks/av/media/libstagefright/codecs/amrnb/common/include     $(TOP)/mediatek/external/amr     frameworks/av/media/mtp     frameworks/native/include/media/openmax     $(call include-path-for, libhardware)/hardware     system/media/camera/include     $(PV_INCLUDES)     $(JNI_H_INCLUDE)     $(call include-path-for, corecg graphics)

ifeq ($(strip $(MTK_TB_DEBUG_SUPPORT)),yes)
LOCAL_C_INCLUDES +=     $(MTK_PATH_SOURCE)/frameworks/base/include
endif

ifeq ($(strip $(MTK_HIGH_QUALITY_THUMBNAIL)),yes)
LOCAL_CFLAGS += -DMTK_HIGH_QUALITY_THUMBNAIL
endif

ifeq ($(strip $(MTK_USE_ANDROID_MM_DEFAULT_CODE)),yes)
LOCAL_CFLAGS += -DANDROID_DEFAULT_CODE
endif

LOCAL_CFLAGS +=

LOCAL_LDLIBS := -lpthread

LOCAL_MODULE:= libmedia_jni

include $(BUILD_SHARED_LIBRARY)

# build libsoundpool.so
# build libaudioeffect_jni.so
include $(call all-makefiles-under,$(LOCAL_PATH))

LOCAL_MODULE:= libmedia_jni 编译后会生成libmedia_jni库

打开文件有四个重载函数

setDataSource (String path)

setDataSource (FileDescriptor fd)

setDataSource (Context context, Uri uri)

setDataSource (FileDescriptor fd, long offset, long length)

在APP中,如原生的Music

 /**
         * M: add async prepare to aviod ANR, add information and duration update listener
         *
         * @param player The mediaplayer
         * @param path The data source path
         * @param async M: use async prepare if it's set true .
         * @return If set data source success, return true, otherwise false.
         */
        private boolean setDataSourceImpl(MediaPlayer player, String path, boolean async) {
            MusicLogUtils.d(TAG, "setDataSourceImpl(" + path + ");async = " + async);
            try {
                player.reset();
                if (async) {
                    player.setOnPreparedListener(preparedlistener);
                } else {
                    player.setOnPreparedListener(null);
                }
                if (path.startsWith("content://")) {
                    player.setDataSource(MediaPlaybackService.this, Uri.parse(path));
                } else {
					// / M: add add for DRM secure flag @{
					MediaPlayerEx.setContextForSecureFlag(player,
							MediaPlaybackService.this.getApplicationContext());
					// / @}
					player.setDataSource(path);
                }
                /// M:  Attach auxiliary audio effect only with valid effect id
                if (mAuxEffectId > 0) {
                    player.attachAuxEffect(mAuxEffectId);
                    player.setAuxEffectSendLevel(1.0f);
                    mWhetherAttachWhenPause = false;
                    MusicLogUtils.d(TAG, "setDataSourceImpl: attachAuxEffect mAuxEffectId = " + mAuxEffectId);
                }
                player.setAudioStreamType(AudioManager.STREAM_MUSIC);
                if (async) {
                    player.prepareAsync();
                } else {
                    player.prepare();
                }
            } catch (IOException ex) {
                // TODO: notify the user why the file couldn't be opened
                MusicLogUtils.e(TAG, "setDataSourceImpl: " + ex);
                return false;
            } catch (IllegalArgumentException ex) {
                // TODO: notify the user why the file couldn't be opened
                MusicLogUtils.e(TAG, "setDataSourceImpl: " + ex);
                return false;
            } catch (IllegalStateException ex) {
                MusicLogUtils.e(TAG, "setDataSourceImpl: " + ex);
                return false;
            }
            player.setOnCompletionListener(listener);
            player.setOnErrorListener(errorListener);
            player.setOnInfoListener(infoListener);
            player.setOnDurationUpdateListener(durationListener);
            sendSessionIdToAudioEffect(false);
            return true;
        }

不管在App中调用的是哪个函数,最后在MediaPlayer.java中都是调用

 /**
     * Sets the data source (FileDescriptor) to use.  The FileDescriptor must be
     * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
     * to close the file descriptor. It is safe to do so as soon as this call returns.
     *
     * @param fd the FileDescriptor for the file you want to play
     * @param offset the offset into the file where the data to be played starts, in bytes
     * @param length the length in bytes of the data to be played
     * @throws IllegalStateException if it is called in an invalid state
     */
    public void setDataSource(FileDescriptor fd, long offset, long length)
            throws IOException, IllegalArgumentException, IllegalStateException {
        disableProxyListener();
        _setDataSource(fd, offset, length);
    }

    private native void _setDataSource(FileDescriptor fd, long offset, long length)
            throws IOException, IllegalArgumentException, IllegalStateException;

_setDataSource有native修饰,是libmedia_jni.so中的方法,找到 ./frameworks/base/media/jni/android_media_MediaPlayer.cpp

// ----------------------------------------------------------------------------

static JNINativeMethod gMethods[] = {
    {
        "_setDataSource",
        "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
        (void *)android_media_MediaPlayer_setDataSourceAndHeaders
    },

    {"_setDataSource",       "(Ljava/io/FileDescriptor;JJ)V",    (void *)android_media_MediaPlayer_setDataSourceFD},
    {"_setVideoSurface",    "(Landroid/view/Surface;)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},
    {"native_invoke",       "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer_invoke},
    {"native_setMetadataFilter", "(Landroid/os/Parcel;)I",      (void *)android_media_MediaPlayer_setMetadataFilter},
    {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z",          (void *)android_media_MediaPlayer_getMetadata},
    {"native_init",         "()V",                              (void *)android_media_MediaPlayer_native_init},
    {"native_setup",        "(Ljava/lang/Object;)V",            (void *)android_media_MediaPlayer_native_setup},
    {"native_finalize",     "()V",                              (void *)android_media_MediaPlayer_native_finalize},
    {"getAudioSessionId",   "()I",                              (void *)android_media_MediaPlayer_get_audio_session_id},
    {"setAudioSessionId",   "(I)V",                             (void *)android_media_MediaPlayer_set_audio_session_id},
    {"setAuxEffectSendLevel", "(F)V",                           (void *)android_media_MediaPlayer_setAuxEffectSendLevel},
    {"attachAuxEffect",     "(I)V",                             (void *)android_media_MediaPlayer_attachAuxEffect},
    {"native_pullBatteryData", "(Landroid/os/Parcel;)I",        (void *)android_media_MediaPlayer_pullBatteryData},
    {"setParameter",        "(ILandroid/os/Parcel;)Z",          (void *)android_media_MediaPlayer_setParameter},
    {"getParameter",        "(ILandroid/os/Parcel;)V",          (void *)android_media_MediaPlayer_getParameter},
    {"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I",  (void *)android_media_MediaPlayer_setRetransmitEndpoint},
    {"setNextMediaPlayer",  "(Landroid/media/MediaPlayer;)V",   (void *)android_media_MediaPlayer_setNextMediaPlayer},
    {"updateProxyConfig", "(Landroid/net/ProxyProperties;)V", (void *)android_media_MediaPlayer_updateProxyConfig},
};

会进入如下函数

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 = jniGetFDFromFileDescriptor(env, fileDescriptor);
    ALOGV("setDataSourceFD: fd %d", fd);
    process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." );
}

mp->setDataSource(fd, offset, length) //调用打开文件方法

打开 ./frameworks/av/media/libmedia/mediaplayer.cpp   setDataSource有三个重载函数,根据参数

status_t MediaPlayer::setDataSource(int fd, int64_t offset, int64_t length)
{
    ALOGV("setDataSource(%d, %lld, %lld)", fd, offset, length);
    status_t err = UNKNOWN_ERROR;
    const sp<IMediaPlayerService>& service(getMediaPlayerService());
    if (service != 0) {
        sp<IMediaPlayer> player(service->create(this, mAudioSessionId));
        if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
            (NO_ERROR != player->setDataSource(fd, offset, length))) {
            player.clear();
        }
        err = attachNewPlayer(player);
    }
    return err;
}

打开 ./frameworks/av/media/libmedia/IMediaPlayer.cpp setDataSource也有三个重载函数,根据参数

    status_t setDataSource(int fd, int64_t offset, int64_t length) {
        Parcel data, reply;
        data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
        data.writeFileDescriptor(fd);
        data.writeInt64(offset);
        data.writeInt64(length);
        remote()->transact(SET_DATA_SOURCE_FD, data, &reply);
        return reply.readInt32();
    }

这个打开文件流程只要是针对本地媒体文件。

process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." ) //根据返回的结果处理

// 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) PERMISSION_DENIED ) {
            jniThrowException(env, "java/lang/SecurityException", 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);
            }
        }
    }
}

其它功能(start‘stop‘play‘seek_to)的流程也差不多,难点就是JNI技术和APP中的AIDL,当然以上只是一个简单的调用过程,要更深入了解还是要花些时间的。

时间: 2024-08-09 21:58:57

Android源码分析之Framework的MediaPlayer的相关文章

Android 源码分析工具

标 题: [原创]Android源码分析工具及方法作 者: MindMac时 间: 2014-01-02,09:32:35链 接: http://bbs.pediy.com/showthread.php?t=183278 在对 Android 源码进行分析时,如果有得力的工具辅助,会达到事半功倍的效果.本文介绍了一些在分析 Android 源码时使用的一些工具和方法,希望能够帮助到有需要的同学. Eclipse 在 Android 应用程序开发过程中,一般会使用 Eclipse,当然 Googl

Android源码分析(十七)----init.rc文件添加脚本代码

一:init.rc文件修改 开机后运行一次: chmod 777 /system/bin/bt_config.sh service bt_config /system/bin/bt_config.sh class main user root group root oneshot 开机后等待android启动完成后再运行: service bt_config /system/bin/bt_config.sh class main user root group root disabled one

Cordova Android源码分析系列一(项目总览和CordovaActivity分析)

PhoneGap/Cordova是一个专业的移动应用开发框架,是一个全面的WEB APP开发的框架,提供了以WEB形式来访问终端设备的API的功能.这对于采用WEB APP进行开发者来说是个福音,这可以避免了原生开发的某些功能.Cordova 只是个原生外壳,app的内核是一个完整的webapp,需要调用的原生功能将以原生插件的形式实现,以暴露js接口的方式调用. Cordova Android项目是Cordova Android原生部分的Java代码实现,提供了Android原生代码和上层We

Android源码分析:Telephony部分–GSMPhone

Android源码分析:Telephony部分–GSMPhone红狼博客 PhoneProxy/GSMPhone/CDMAPhone 如果说RILJ提供了工具或管道,那么Phone接口的子类及PhoneFactory则为packages/app/Phone这个应用程序进程使用RILJ这个工具或管道提供了极大的方便,它们一个管理整个整个手机的Telephony功能. GSMPhone和CDMAPhone实现了Phone中定义的接口.接口类Phone定义了一套API,这套API用于使用RILJ(见后

Android源码分析:Telephony部分–phone进程

Android源码分析:Telephony部分–phone进程红狼博客 com.android.phone进程 它就象个后台进程一样,开机即运行并一直存在.它的代码位于:packages/apps/Phone/src/com/android/phone 当有来电时,它会作出反应,如显示UI和铃声提示:当在通话过程中,它显示InCallScreen: 当要拨号时ITeleohony的接口调用最终到Phone进程,然后由它去与PhoneFactory创建的GSMPhone或CDMAPhone进行交互

Android源码分析之SparseArray

本来接下来应该分析MessageQueue了,可是我这几天正好在实际开发中又再次用到了SparseArray(之前有用到过一次,那次只是 大概浏览了下源码,没做深入研究),于是在兴趣的推动下,花了些时间深入研究了下,趁着记忆还是新鲜的,就先在这里分析了. MessageQueue的分析应该会在本周末给出. 和以往一样,首先我们来看看关键字段和ctor: private static final Object DELETED = new Object(); private boolean mGar

Android源码分析之SharedPreferences

在Android的日常开发中,相信大家都用过SharedPreferences来保存用户的某些settings值.Shared Preferences 以键值对的形式存储私有的原生类型数据,这里的私有的是指只对你自己的app可见的,也就是说别的app是无法访问到的. 客户端代码为了使用它有2种方式,一种是通过Context#getSharedPreferences(String prefName, int mode)方法, 另一种是Activity自己的getPreferences(int mo

Android源码分析之Builder模式

http://www.w3c.com.cn/android%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8Bbuilder%E6%A8%A1%E5%BC%8F Android源码分析之Builder模式

Android源码分析之模板方法模式

模式的定义 定义一个操作中的算法的框架,而将一些步骤延迟到子类中.使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤. 使用场景 1.多个子类有公有的方法,并且逻辑基本相同时. 2.重要.复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现. 3.重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为. UML类图 角色介绍 AbstractClass : 抽象类,定义了一套算法框架. ConcreteClass1 :