IjkPlayer系列之播放器创建流程
躬行之
共 10263字,需浏览 21分钟
·
2022-04-01 21:17
今天介绍下 IjkPlayer 的播放器创建流程,本文开始将正式开始 IjkPlayer 的源码阅读之旅,阅读之前可以先看前面同系列文章:主要内容如下:
初始化so
Java层播放器创建
IjkMediaPlayer结构体
Native层播放器创建
调用流程图
初始化so
从 IjkPlayer 源码中VideoActivity
查看 IjkPlayer 的初始化,关键代码如下:1// 加载so库查看
2IjkMediaPlayer.loadLibrariesOnce(null);
3// android-ndk-profiler性能分析
4IjkMediaPlayer.native_profileBegin("libijkplayer.so");
loadLibrariesOnce
源码如下:1private static volatile boolean mIsLibLoaded = false;
2public static void loadLibrariesOnce(IjkLibLoader libLoader) {
3 synchronized (IjkMediaPlayer.class) {
4 // 保证只加载一次
5 if (!mIsLibLoaded) {
6 if (libLoader == null)
7 // sLocalLibLoader为默认的IjkLibLoader
8 libLoader = sLocalLibLoader;
9 libLoader.loadLibrary("ijkffmpeg");
10 libLoader.loadLibrary("ijksdl");
11 libLoader.loadLibrary("ijkplayer");
12 mIsLibLoaded = true;
13 }
14 }
15}
loadLibrariesOnce
加载相应的 so 库,native_profileBegin
则是 android-ndk-profiler 工具进行性能分析的函数,对应的还有native_profileEnd
,其定义如下:1public static native void native_profileBegin(String libName);上述方法分别调用了函数
2public static native void native_profileEnd();
monstartup
和 moncleanup
,分别在程序运行开始和结束调用,关于 android-ndk-profiler 的这里不做过多介绍。在 IjkPlayer系列之JNI基础及源码目录介绍 这篇文章中介绍了 JNI 的注册方式,其中 IjkPlayer 使用的就是其动态注册方式,即当调用 System.loadLibrary
加载库的时候会去查找 JNI_OnLoad
这个函数,然后在该函数回调中进行注册,下面看下 ijkplayer 中的 JNI_OnLoad
实现,如下:1JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)其中数组
2{
3 JNIEnv* env = NULL;
4 // 保存全局的JavaVM
5 g_jvm = vm;
6 if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
7 return -1;
8 }
9 assert(env != NULL);
10 // 互斥锁的初始化
11 pthread_mutex_init(&g_clazz.mutex, NULL );
12 // 将IjkMediaPlayer转换为一个全局引用
13 IJK_FIND_JAVA_CLASS(env, g_clazz.clazz, JNI_CLASS_IJKPLAYER);
14 // 注册Native方法与Java方法相对应
15 (*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) );
16 // 初始化:注册编解码器、解复用器、协议
17 ijkmp_global_init();
18 // 设置回调,对应Java层的onNativeInvoke回调函数
19 ijkmp_global_set_inject_callback(inject_callback);
20 // 注册av_base64_encode与FFmpegApi_av_base64_encode的对应关系
21 FFmpegApi_global_init(env);
22 return JNI_VERSION_1_4;
23}
g_methods
定义了 Native 函数与 Java 方法的一一对应关系,如下:1static JNINativeMethod g_methods[] = {上述代码主要工作如下:
2 {
3 "_setDataSource",
4 "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
5 (void *) IjkMediaPlayer_setDataSourceAndHeaders
6 },
7 { "_setDataSourceFd", "(I)V", (void *) IjkMediaPlayer_setDataSourceFd },
8 { "_setDataSource", "(Ltv/danmaku/ijk/media/player/misc/IMediaDataSource;)V", (void *)IjkMediaPlayer_setDataSourceCallback },
9 { "_setAndroidIOCallback", "(Ltv/danmaku/ijk/media/player/misc/IAndroidIO;)V", (void *)IjkMediaPlayer_setAndroidIOCallback },
10
11 { "_setVideoSurface", "(Landroid/view/Surface;)V", (void *) IjkMediaPlayer_setVideoSurface },
12 { "_prepareAsync", "()V", (void *) IjkMediaPlayer_prepareAsync },
13 { "_start", "()V", (void *) IjkMediaPlayer_start },
14 { "_stop", "()V", (void *) IjkMediaPlayer_stop },
15 { "seekTo", "(J)V", (void *) IjkMediaPlayer_seekTo },
16 { "_pause", "()V", (void *) IjkMediaPlayer_pause },
17 { "isPlaying", "()Z", (void *) IjkMediaPlayer_isPlaying },
18 { "getCurrentPosition", "()J", (void *) IjkMediaPlayer_getCurrentPosition },
19 { "getDuration", "()J", (void *) IjkMediaPlayer_getDuration },
20 { "_release", "()V", (void *) IjkMediaPlayer_release },
21 { "_reset", "()V", (void *) IjkMediaPlayer_reset },
22 { "setVolume", "(FF)V", (void *) IjkMediaPlayer_setVolume },
23 { "getAudioSessionId", "()I", (void *) IjkMediaPlayer_getAudioSessionId },
24 { "native_init", "()V", (void *) IjkMediaPlayer_native_init },
25 { "native_setup", "(Ljava/lang/Object;)V", (void *) IjkMediaPlayer_native_setup },
26 { "native_finalize", "()V", (void *) IjkMediaPlayer_native_finalize },
27
28 { "_setOption", "(ILjava/lang/String;Ljava/lang/String;)V", (void *) IjkMediaPlayer_setOption },
29 { "_setOption", "(ILjava/lang/String;J)V", (void *) IjkMediaPlayer_setOptionLong },
30
31 { "_getColorFormatName", "(I)Ljava/lang/String;", (void *) IjkMediaPlayer_getColorFormatName },
32 { "_getVideoCodecInfo", "()Ljava/lang/String;", (void *) IjkMediaPlayer_getVideoCodecInfo },
33 { "_getAudioCodecInfo", "()Ljava/lang/String;", (void *) IjkMediaPlayer_getAudioCodecInfo },
34 { "_getMediaMeta", "()Landroid/os/Bundle;", (void *) IjkMediaPlayer_getMediaMeta },
35 { "_setLoopCount", "(I)V", (void *) IjkMediaPlayer_setLoopCount },
36 { "_getLoopCount", "()I", (void *) IjkMediaPlayer_getLoopCount },
37 { "_getPropertyFloat", "(IF)F", (void *) ijkMediaPlayer_getPropertyFloat },
38 { "_setPropertyFloat", "(IF)V", (void *) ijkMediaPlayer_setPropertyFloat },
39 { "_getPropertyLong", "(IJ)J", (void *) ijkMediaPlayer_getPropertyLong },
40 { "_setPropertyLong", "(IJ)V", (void *) ijkMediaPlayer_setPropertyLong },
41 { "_setStreamSelected", "(IZ)V", (void *) ijkMediaPlayer_setStreamSelected },
42
43 { "native_profileBegin", "(Ljava/lang/String;)V", (void *) IjkMediaPlayer_native_profileBegin },
44 { "native_profileEnd", "()V", (void *) IjkMediaPlayer_native_profileEnd },
45
46 { "native_setLogLevel", "(I)V", (void *) IjkMediaPlayer_native_setLogLevel },
47 { "_setFrameAtTime", "(Ljava/lang/String;JJII)V", (void *) IjkMediaPlayer_setFrameAtTime },
48};
注册 Native 方法与 Java 方法之间的对应关系。
注册编解码器、解复用器、协议等。
设置
onNativeInvoke
回调供 Native 层调用,主要关心其返回值,比如断网重连等。
Java层播放器创建
看下 Java 层IjkMediaPlayer
的创建1IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();查看构造方法如下:
1public IjkMediaPlayer() {可见初始化的时候加载了 ijkffmpeg、ijksdl、ijkplayer 库,创建了
2 // sLocalLibLoader为默认的IjkLibLoader
3 this(sLocalLibLoader);
4}
5
6public IjkMediaPlayer(IjkLibLoader libLoader) {
7 initPlayer(libLoader);
8}
9
10private void initPlayer(IjkLibLoader libLoader) {
11 // 尝试加载库,避免之前从未加载过so
12 loadLibrariesOnce(libLoader);
13 // 对应c层IjkMediaPlayer_native_init方法,暂时为空实现
14 initNativeOnce();
15
16 // 用来处理Iik底层传递上来的消息
17 Looper looper;
18 if ((looper = Looper.myLooper()) != null) {
19 mEventHandler = new EventHandler(this, looper);
20 } else if ((looper = Looper.getMainLooper()) != null) {
21 mEventHandler = new EventHandler(this, looper);
22 } else {
23 mEventHandler = null;
24 }
25
26 // IjkMediaPlayer包装成弱引用传递到native层
27 native_setup(new WeakReference(this));
28}
mEventHandler
处理 Native 拋上来的播放事件,比如播放开始、播放完成、播放出错等事件,具体由 Native 层调用 Java 层的函数 postEventFromNative
来发送对应的事件,在阅读 C 层代码之前,先了解一下 IjkMediaPlayer
结构体。IjkMediaPlayer结构体
IjkPlayer 对应IjkMediaPlayer
结构体,其初始化主要是对该结构体进行初始化,其定义如下:1struct IjkMediaPlayer {整个播放流程中涉及到的参数基本都是
2 /* IjkMediaPlayer创建一次则ref_count计数加一次 */
3 volatile int ref_count;
4 /* 保护接口调用的锁*/
5 pthread_mutex_t mutex;
6 /* FFPlayer是原ffplayer里的结构体,有被ijk扩展 */
7 FFPlayer *ffplayer;
8 /* 用于ijkPlayer回调给应用层的一个消息循环函数 */
9 int (*msg_loop)(void*);
10 /* 消息线程 */
11 SDL_Thread *msg_thread;
12 SDL_Thread _msg_thread;
13 /* 播放器状态 */
14 int mp_state;
15 /* 播放地址 */
16 char *data_source;
17 /* Java层IjkMediaPlayer对应弱引用对象 */
18 void *weak_thiz;
19 /* 是否重新播放 */
20 int restart;
21 /* restart是否从头开始 */
22 int restart_from_beginning;
23 /* 标识用户是不是seek了进度条 */
24 int seek_req;
25 /* seek的毫秒值 */
26 long seek_msec;
27};
IjkMediaPlayer
结构体中的成员变量,后面 Native 层 IjkPlayer 的创建都是在初始化上述结构体中的成员变量。Native层播放器创建
Native 层播放器的创建主要是结构体IjkMediaPlayer
的创建及初始化,这里接着上文继续查看 native_setup
方法的具体实现如下:1static void上述代码主要是开始创建 C 层的
2IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
3{
4 MPTRACE("%s\n", __func__);
5 // 创建C层对应的IjkMediaPlayer
6 IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
7 JNI_CHECK_GOTO(mp, env, "java/lang/OutOfMemoryError", "mpjni: native_setup: ijkmp_create() failed", LABEL_RETURN);
8 // Java层mNativeMediaPlayer初始化
9 jni_set_media_player(env, thiz, mp);
10 // 初始化mp->weak_thiz
11 ijkmp_set_weak_thiz(mp, (*env)->NewGlobalRef(env, weak_this));
12 // 初始化ffp->inject_opaque等
13 ijkmp_set_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
14 // 初始化ffp->ijkio_inject_opaque等
15 ijkmp_set_ijkio_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
16 // 设置解码器选择回调
17 ijkmp_android_set_mediacodec_select_callback(mp, mediacodec_select_callback, ijkmp_get_weak_thiz(mp));
18
19LABEL_RETURN:
20 ijkmp_dec_ref_p(&mp);
21}
IjkMediaPlayer
、其对应结构体部分属性的初始化及解码器回调,mediacodec_select_callback
会通过调用 Java 层的函数 onSelectCodec
获取合适的解码器信息,可在 IjkMediaCodecInfo
中进行解码器的适配。继续查看 ijkmp_android_create
函数如下:1IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))其中
2{
3 // 填充IjkMediaPlayer结构体
4 IjkMediaPlayer *mp = ijkmp_create(msg_loop);
5 if (!mp)
6 goto fail;
7 // 初始化SDL_Vout,表示IJK中的显示上下文
8 mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();
9 if (!mp->ffplayer->vout)
10 goto fail;
11 // 初始化IJKFF_Pipeline,解码器、音频输出
12 mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);
13 if (!mp->ffplayer->pipeline)
14 goto fail;
15 // 绑定SDL_Vout到IJKFF_Pipeline_Opaque
16 ffpipeline_set_vout(mp->ffplayer->pipeline, mp->ffplayer->vout);
17 return mp;
18fail:
19 ijkmp_dec_ref_p(&mp);
20 return NULL;
21}
ijkmp_create
主要是创建了 FFPlayer
,并将 msg_loop
进行赋值,msg_loop
事件循环相关调用将在后续文章中介绍,继续看下后面几个函数:SDL_VoutAndroid_CreateForAndroidSurface
:初始化IjkPlayer
的 显示上下文SDL_Vout
。ffpipeline_create_from_android
:初始化IJKFF_Pipeline
,解码器、音频输出。ffpipeline_set_vout
:绑定SDL_Vout
到IJKFF_Pipeline_Opaque
。
调用流程图
IjkPlayer 创建主要函数调用流程如下:其他细节会在后续的文章中介绍,下一篇将介绍 IjkPlayer 中消息循环机制。推荐阅读:
评论