MediaPlayer JNI层介绍

Android  源码分析  2023年7月12日 am8:08发布1年前 (2023)更新 城堡大人
93 0 0

前言

之前的《MediaPlayer java层介绍》只是介绍了java中的常用方法,对于JNI层的调用没有进一步介绍,今天就介绍一下JNI层。

个人流水账哈,推荐看其他人的,我这只是自己的跟踪记录

涉及的代码:

frameworks\base\media\java\android\media\MediaPlayer.java
frameworks\base\media\jni\android_media_MediaPlayer.cpp
frameworks\base\core\jni\AndroidRuntime.cpp
frameworks\av\media\libmedia\mediaplayer.cpp

libnativehelper\include\nativehelper\JNIHelp.h

frameworks\av\media\libmedia\IMediaPlayer.cpp
frameworks\av\media\libmedia\IMediaDeathNotifier.cpp
frameworks\native\libs\binder\IServiceManager.cpp
frameworks\av\media\libmedia\IMediaPlayerService.cpp

正文

MediaPlayer.java

接上次demo调用的方法,主要涉及的本地方法,本文主要是按照下面的顺序去看代码的:

 1. System.loadLibrary("media_jni");
 2. native_init();
 3. native_setup();
 4. _setDataSource();
 5. _prepare();
 6. _start();

当然还有其他方法,都类似。

android_media_MediaPlayer.cpp

按照以前学习的知识,JNI对应的代码是以包名+类名命名,也就是android_media_MediaPlayer.cpp。

loadLibrary("media_jni")

动态注册,会调用JNI_OnLoad(),具体就不解释了。

我们关注点

static int register_android_media_MediaPlayer(JNIEnv *env){
    return AndroidRuntime::registerNativeMethods(env,
                "android/media/MediaPlayer", gMethods, NELEM(gMethods));
}
int AndroidRuntime::registerNativeMethods(JNIEnv* env,
 const char* className, const JNINativeMethod* gMethods, int numMethods){
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

jniRegisterNativeMethods是jniRegisterNativeMethods函数是Android平台提供的帮助函数,可看:

libnativehelper\include\nativehelper\JNIHelp.h

gMethods中可以看对应实现的方法

//部分
static const JNINativeMethod gMethods[] = {
    {"_setDataSource",      "(Ljava/io/FileDescriptor;JJ)V",    (void *)android_media_MediaPlayer_setDataSourceFD},
    {"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},
};
native_init

从上面gMethods可以知道

static void android_media_MediaPlayer_native_init(JNIEnv *env){
    jclass clazz;
    //加载android.media.MediaPlayer
    clazz = env->FindClass("android/media/MediaPlayer");
    //略
    // postEventFromNative java方法ID,用于回调
    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative","(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.post_event == NULL) {
        return;
    }
    //释放
    env->DeleteLocalRef(clazz);
    //略
}

有个重点,获取了Java方法的MethodID,后面可以调用postEventFromNative(),下面会介绍到。

native_setup()
static void android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this){
    //创建MediaPlayer对象
    sp<MediaPlayer> mp = new MediaPlayer();
    //回调监听,调用Java方法postEventFromNative回调
    sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
    mp->setListener(listener);
    //把MediaPlayer对象保存起来了,后面会通过get获取
    setMediaPlayer(env, thiz, mp);
}

MediaPlayer()后面看。

JNIMediaPlayerListener

JNIMediaPlayerListener类中有个notify()方法

void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2, const Parcel *obj){
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    if (obj && obj->dataSize() > 0) {
        jobject jParcel = createJavaParcelObject(env);
        //数据封装
        if (jParcel != NULL) {
            Parcel* nativeParcel = parcelForJavaObject(env, jParcel);
            nativeParcel->setData(obj->data(), obj->dataSize());
            //重点
            env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
                    msg, ext1, ext2, jParcel);
            env->DeleteLocalRef(jParcel);
        }
    } else {
        //重点
        env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
                msg, ext1, ext2, NULL);
    }
}

很明显,这里调用了java中的postEventFromNative方法,fields.post_event就是在native_init()获取的MedthoID。

CallStaticVoidMethod是jni.h中定义的,还有很多类似的方法,比如:

CallStaticDoubleMethod()
CallStaticIntMethod()
CallStaticBooleanMethodA()

根据Java方法回调的参数和属性进行调用不同的方法。

具体可看android-ndk-r21d-windows-x86_64[这个我下载的的NDK版本]中找jni.h文件,里面有很多JNI相关的定义。

setMediaPlayer

用于保存MediaPlayer对象

static sp<MediaPlayer> getMediaPlayer(JNIEnv* env, jobject thiz){
    Mutex::Autolock l(sLock);//锁
    //获取已经初始化过的MediaPlayer
    MediaPlayer* const p = (MediaPlayer*)env->GetLongField(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->GetLongField(thiz, fields.context);
    if (player.get()) {
        player->incStrong((void*)setMediaPlayer);
    }
    if (old != 0) {
        old->decStrong((void*)setMediaPlayer);
    }
    //保存MediaPlayer,后面会获取
    env->SetLongField(thiz, fields.context, (jlong)player.get());
    return old;
}
_setDataSource()

_setDataSource在MediaPlayer.java中有两个方法

private native void _setDataSource(FileDescriptor fd, long offset, long length);    
private native void _setDataSource(MediaDataSource dataSource);

因此在android_media_MediaPlayer.cpp也有两个,只不过两个传的参数不一样。看我们demo中调用的

static void android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length){
    //获取MediaPlayer对象
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    //也在上面nativehelper\JNIHelp.h定义,这里不关心
    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
    //重点mp->setDataSource(fd, offset, length)
    //process_media_player_call只是处理状态码,后面很多方法都用这个。
    process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." );
}

进入mediaplayer.cpp中的setDataSource()啦,暂时跳过,后面看。

prepare()
static void android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz){
    //获取MediaPlayer对象
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    //mp->prepare()
    process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." );
}

最终也是mp->prepare(),这里暂时跳过。

_start()
static void android_media_MediaPlayer_start(JNIEnv *env, jobject thiz){
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    //mp->start()
    process_media_player_call( env, thiz, mp->start(), NULL, NULL );
}

最终也是 mp->start()。

到此,其他方法都差不多,最终也是MediaPlayer类[mediaplayer.cpp]的调用。

mediaplayer.cpp

这里进入mediaplayer.cpp啦,然后根据上面进入的方法走一下。

接上面native_setup()中

sp<MediaPlayer> mp = new MediaPlayer();

初始化mp对象,构造函数中就是一些默认值的初始化,这里就不附上了。

native_setup()中传入了

sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
mp->setListener(listener);

传入JNIMediaPlayerListener,我们知道JNIMediaPlayerListener.notify()【上面有介绍】会调用postEventFromNative()方法,用于返回播放状态和状态等信息。

setDataSource()
status_t MediaPlayer::setDataSource(
        const sp<IMediaHTTPService> &httpService,
        const char *url, const KeyedVector<String8, String8> *headers){
    status_t err = BAD_VALUE;
    if (url != NULL) {
        //[重点]。BpMediaPlayerService的对象service
        const sp<IMediaPlayerService> service(getMediaPlayerService());
        if (service != 0) {
            //[重点]。创建BpMediaPlayer对象player
            sp<IMediaPlayer> player(service->create(this, mAudioSessionId));
            if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
                //BpMediaPlayer.setDataSource();
                (NO_ERROR != player->setDataSource(httpService, url, headers))) {
                player.clear();
            }
            err = attachNewPlayer(player);
        }
    }
    return err;
}
为啥说service是BpMediaPlayerService的对象?

getMediaPlayerService()

const sp<IMediaPlayerService>IMediaDeathNotifier::getMediaPlayerService()
{
    Mutex::Autolock _l(sServiceLock);
    //单例模式,如果是第一次,sMediaPlayerService==0
    if (sMediaPlayerService == 0) {
        //interface_cast获取BpMediaPlayerService的对象
        sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
    }
    return sMediaPlayerService;
}

至于interface_cast,推荐看《interface_cast简介

为啥说player是BpMediaPlayer
virtual sp<IMediaPlayer> create(
        const sp<IMediaPlayerClient>& client, audio_session_t audioSessionId) {
    Parcel data, reply;
    data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
    data.writeStrongBinder(IInterface::asBinder(client));
    data.writeInt32(audioSessionId);
    remote()->transact(CREATE, data, &reply);
    //interface_cast获取BpMediaPlayer
    return interface_cast<IMediaPlayer>(reply.readStrongBinder());
}

最后调用的是BpMediaPlayer的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);
    //有点晕,BpBinder(0)
    remote()->transact(SET_DATA_SOURCE_FD, data, &reply);
    return reply.readInt32();
}

PS: 至于remote()这个是谁,是BpBinder(0)?我目前有点晕了。。。后续补上

最终真正处理,也不是mediaplayer.cpp,,,好绕啊。今天不展开了。等下集。

prepare()
//prepare()
status_t MediaPlayer::prepare(){
    Mutex::Autolock _l(mLock);
    mLockThreadId = getThreadId();
    if (mPrepareSync) {
        mLockThreadId = 0;
        return -EALREADY;
    }
    mPrepareSync = true;
    //最终都是走了prepareAsync_l()
    status_t ret = prepareAsync_l();
    if (ret != NO_ERROR) {
        mLockThreadId = 0;
        return ret;
    }
    //[重点] 等待Prepare完成,也就是等mSignal释放锁,
    //这里跟prepareAsync()的区别
    if (mPrepareSync) {
        mSignal.wait(mLock);  
        mPrepareSync = false;
    }
    mLockThreadId = 0;
    return mPrepareStatus;
}
//prepareAsync()
status_t MediaPlayer::prepareAsync(){
    Mutex::Autolock _l(mLock);
    //最终都是走了prepareAsync_l()
    return prepareAsync_l();
}

或许你会问题 mSignal.wait(mLock)啥时候等到呢?具体看

void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj){
    bool send = true;
    bool locked = false;
    if (mLockThreadId != getThreadId()) {
        mLock.lock();
        locked = true;
    }
    switch (msg) {
    case MEDIA_PREPARED:
        mCurrentState = MEDIA_PLAYER_PREPARED;
        if (mPrepareSync) {
            mPrepareSync = false;
            mPrepareStatus = NO_ERROR;
            mSignal.signal();
        }
        break;
    case MEDIA_ERROR:
        mCurrentState = MEDIA_PLAYER_STATE_ERROR;
        if (mPrepareSync){
            mPrepareSync = false;
            mPrepareStatus = ext1;
            mSignal.signal();
            send = false;
        }
        break;
    }
    sp<MediaPlayerListener> listener = mListener;
    if (locked) mLock.unlock();
    if ((listener != 0) && send) {
        Mutex::Autolock _l(mNotifyLock);
        // 这个就是我们传入的JNIMediaPlayerListener
        listener->notify(msg, ext1, ext2, obj);
    }
}

prepareAsync()和prepare()都调用prepareAsync_l()

status_t MediaPlayer::prepareAsync_l(){
    if ( (mPlayer != 0) && ( mCurrentState & (MEDIA_PLAYER_INITIALIZED | MEDIA_PLAYER_STOPPED) ) ) {
        //执行BpMediaPlayer.prepareAsync()
        return mPlayer->prepareAsync();
    }
    //有个短暂的PREPARING状态
    mCurrentState = MEDIA_PLAYER_PREPARING;
    return INVALID_OPERATION;
}

BpMediaPlayer.prepareAsync()中也是调用远程的服务,这里暂不深入。

start()
status_t MediaPlayer::start(){
    status_t ret = NO_ERROR;
    Mutex::Autolock _l(mLock);
    mLockThreadId = getThreadId();
    if (mCurrentState & MEDIA_PLAYER_STARTED) {
        ret = NO_ERROR;
    } else if ( (mPlayer != 0) && ( mCurrentState & ( MEDIA_PLAYER_PREPARED |
                    MEDIA_PLAYER_PLAYBACK_COMPLETE | MEDIA_PLAYER_PAUSED ) ) ) {
        //设置是否循环mLoop
        mPlayer->setLooping(mLoop);
        //设置音量
        mPlayer->setVolume(mLeftVolume, mRightVolume);
        //这个不太懂。。。一般APP没设置过
        mPlayer->setAuxEffectSendLevel(mSendLevel);
        //改变播放状态
        mCurrentState = MEDIA_PLAYER_STARTED;
        //BpMediaPlayer.prepareAsync()
        ret = mPlayer->start();
        if (ret != NO_ERROR) {
            mCurrentState = MEDIA_PLAYER_STATE_ERROR;
        }
    } else {
        ret = INVALID_OPERATION;
    }
    mLockThreadId = 0;
    return ret;
}

提前设置配置部分配置,不过最终还是调用BpMediaPlayer中的方法。

对于其他的方法,其实也是调用BpMediaPlayer中的,这个后面继续分析。

参考文章

  1. MediaPlayer java层介绍

  2. Android 音频子系统,音频系统跟应用层直接相关的部分(八)

  3. interface_cast简介

  4. android 系统核心机制binder(04)binder C++层 TestServer分析

 历史上的今天

  1. 2024: MediaMetadataRetriever解析媒体文件元数据(0条评论)
  2. 2024: 记录Ubuntu更新命令(0条评论)
  3. 2021: SharedPreferences最后一次写入时断电偶尔存在不保存问题(0条评论)
  4. 2021: 付志勇:故乡(0条评论)
  5. 2019: 朱光潜:像个大人一样生存,像个孩子一样生活(0条评论)
版权声明 1、 本站名称: 91易搜
2、 本站网址: https://www.91es.com/
3、 本站部分文章来源于网络,仅供学习与参考,如有侵权,请留言

暂无评论

暂无评论...

随机推荐

BiuTextView替代TextView

前言Android 高版本后,TextView走马灯耗CPU。使用BiuTextView替代TextView来实现跑马灯。注意,下面测试还要其他应用使用surfaceflinger,所以很高,我们只是对比同一环境下,BiuTextView和TextView跑马灯的情况。正文说明:D...

中文汉字和英文数字的unicode编码范围

前言最近有做相关汉字转拼音处理,因此了解一下,摘抄于此,方便查阅。好记性不如烂笔头正文虽然不常用,了解一下。基本汉字有20902字。相关汉字编码介绍GB2312编码1981年5月1日发布的简体中文汉字编码国家标准。GB2312对汉字采用双字节编码,收录7445个图形字符,其中包括...

Ubuntu18编译FFmpeg笔记

前言系统版本 :Ubuntu 18FFmpeg版本 : ffmpeg version N-102948-g79ebdbb9b9(下载最新的,这是编译后的版本信息)最近有看就整理了一下自己编译ffmpeg的编译过程,方便自己查阅。正文下面就记录编译的过程用的编译脚本以及所遇到的问题。ND...

Notification使用

前言简单记录一下Notification的使用,这里设计简单Notification和自定义View的Notification的使用。记录于此,方便自己查阅和学习。正文简单Notification由于高版本需要添加NotificationChannel,下面就进行了一定的适配。pr...

Android通过adb启动辅助副屏

前言记录一下,方便自己查阅。adb 快速开启启动辅助副屏,方便自己调试。正文下面介绍一下自己用过的,均验证ok开启adb shell settings put global overlay_display_devices "720x480/160"就是显示720x480大小的副屏,至于...

陆文夫:脚步声

照理不应该被自己的脚步声吓住,因为在少年时我就在黑暗无人的旷野间听到过此种脚步。那时我住在江边的一个水陆码头上,那里没有学校,只有二里路外的村庄上有一位塾师在那里授馆,我只能去那里读书。那位塾师要求学生们苦读,即使不头悬梁、锥刺股,却也要“闻鸡起舞”,所谓闻鸡起舞就是在鸡鸣时分赶到学塾里去读早书。农...