看了很久的ijkplayer的视频播放,其实还是没有怎么看懂,只是个人浅浅的笔记
关键部分就是联网获取数据那部分,还没有搞定其实
从用户点击一个已有地址的网络视频开始,从源码分析播放流程。
1. // init player 加载native底层库
IjkMediaPlayer.loadLibrariesOnce(null);
IjkMediaPlayer.native_profileBegin("libijkplayer.so");
第一句话是加载三个重要的so文件
libLoader.loadLibrary("ijkffmpeg");
libLoader.loadLibrary("ijksdl");
libLoader.loadLibrary("ijkplayer");
第二句话调用了一个native方法public static native void native_profileBegin(String libName);
2. 设置uri,可以是rtmp,rtsp,http等,native ffplay代码中会根据该uri匹配不同的流媒体协议,具体参考ffplay。 支持本地和网络视频。
if (mVideoPath != null)
mVideoView.setVideoPath(mVideoPath);
else if (mVideoUri != null)
mVideoView.setVideoURI(mVideoUri);
3. 分析网络视频源:接着openVideo()
先创建播放器 mMediaPlayer = createPlayer(mSettings.getPlayer());接着是播放器的相关设置
诸如: mMediaPlayer.setOnPreparedListener(mPreparedListener);
mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
mMediaPlayer.setOnCompletionListener(mCompletionListener);
mMediaPlayer.setOnErrorListener(mErrorListener);
mMediaPlayer.setOnInfoListener(mInfoListener);
mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);等
当然,还需要设置播放源,会调用
tv.danmaku.ijk.media.player.IMediaPlayer#setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String,java.lang.String>)
开始播放mVideoView.start();调用tv.danmaku.ijk.media.player.IMediaPlayer#start,这里java层就结束了,进入c底层,其实从加载native底层库开始就进入c底层了
4. 分析c底层:/ijkmedia/ijkplayer/android/ijkplayer_jni.c这个文件里 static JNINativeMethod g_methods[] 一个数组,声明了全部的java层的native方法 ,而调用native的方法集中在类 tv.danmaku.ijk.media.player.IjkMediaPlayer
5. 没想到居然是c主动调用java,通过反射生成java端所需要的函数,这个地方不是我们正常JNI的调用思路
正常调用JNI的思路:是通过javah,获取一组带签名函数,然后实现这些函数。这种方法很常用,也是官方推荐的方法。
分析一下JNI运行时机制:
当在系统中调用System.loadLibrary函数时,该函数会找到对应的动态库,然后首先试图找到"JNI_OnLoad"函数,如果该函数存在,则调用它。
JNI_OnLoad可以和JNIEnv的registerNatives函数结合起来,实现动态的函数替换。很显然ijk的作者是使用的这种调用方式
在 ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/android/ijkplayer_jni.c
然后在JNI_OnLoad 初始化好多内容,比如上图三个 global 型的函数
这样就可以理解作者为什么一开始就先调用三个 so 文件了
ijkplayer-sample/src/main/jni/ijkmedia/ijksdl/android/ijksdl_android_jni.c里也有JNI_OnLoad。
Ffmpeg的个人感觉是不需要,因为作者直接使用的ffmpeg的代码,只需要正常引入就好。
6. J4a里生成的两个java文件也是很重要的类
目前还不清楚作用
7. ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ff_ffplay_def.h 这个定义了ijk播放器核心数据结构体的类,作者是直接使用的ffmpeg的ijkplayer-sample/src/main/jni/ffmpeg/ffplay.c在ffmpeg的文件的基础上的扩展,里面引用了很多ffmpeg的源文件。基本上就不需要关心ffmpeg的源代码了。
8. 这里只分析libijkplayer.so
8.1 ijkmp_global_init 在 ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ijkplayer.c
void ijkmp_global_init()
{
ffp_global_init();
}
ffp_global_init() 在
ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ijkplayer_internal.h的include的
ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ff_ffplay.h里,具体定义代码在
ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ff_ffplay.c中,主要是ffmpeg的初始化工作。
8.2 ijkmp_global_set_inject_callback(inject_callback)是在
ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ijkplayer.c的include的
ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ijkavformat/ijkavformat.h里声明,
在ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ijkavformat/utils.c里定义
这里使用最多的是ijkavformat.h里的
typedef int (*IjkAVInjectCallback)(void *opaque, int message, void *data, size_t data_size);
这个声明引入了 IjkAVInjectCallback 类型作为函数指针的同义字,该函数有四个不同 类型的参数以及一个 int 类型的返回值
IjkAVInjectCallback ijkav_register_inject_callback(IjkAVInjectCallback callback);定义一个函数ijkav_register_inject_callback,这个函数的参数是一个函数指针,返回值也是个函数指针,相当于int ( *ijkav_register_inject_callback( int (*callback)(void *opaque, int message, void *data, size_t data_size) ) )(void *opaque, int message, void *data, size_t data_size);
由于int (*callback)(void *opaque, int message, void *data, size_t data_size)
是IjkAVInjectCallback ,上面的简化一下就是
int ( *ijkav_register_inject_callback( IjkAVInjectCallback ) )(void *opaque, int message, void *data, size_t data_size);这里的
*ijkav_register_inject_callback( IjkAVInjectCallback ) 又是一个返回IjkAVInjectCallback的函数 ,再简化一下,即