本文记录的是ijkplayer的初始化流程(重点在分析底层c代码的逻辑),为了更好的理解这部分内容,建议大家下载ijk的源码,最好结合ijkplayer android端调试配置好环境,有利于查看底层c代码
直接切入主题,因为要看初始化流程,直接看到VideoActivity.java/onCreate(),代码如下
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// init player
IjkMediaPlayer.loadLibrariesOnce(null);
...
// prefer mVideoPath
if (mVideoPath != null)
mVideoView.setVideoPath(mVideoPath);
else if (mVideoUri != null)
mVideoView.setVideoURI(mVideoUri);
else {
...
}
...
}
从上面代码看到,VideoActivity.java/onCreate()函数主要做了两件事:
1. IjkMediaPlayer.loadLibrariesOnce(null),加载库
2. setVideoPath或者setVideoURI设置播放路径
我们一个一个来仔细看这两步分别做了什么事
1. IjkMediaPlayer.loadLibrariesOnce()
代码如下
public static void loadLibrariesOnce(IjkLibLoader libLoader) {
synchronized (IjkMediaPlayer.class) {
if (!mIsLibLoaded) {
if (libLoader == null)
libLoader = sLocalLibLoader;
// libLoader.loadLibrary("ijkffmpeg");
libLoader.loadLibrary("ijksdl");
libLoader.loadLibrary("ijkplayer");
...
}
}
}
可以看到该函数里面调用了loadLibrary函数加载底层库(我这里为什么注销libLoader.loadLibrary("ijkffmpeg")这一行与调试有关,具体查看ijkplayer android端调试),加载好库后,java是怎么能够调用c代码的呢?
原来还有一个jni层的概念,在调用System.loadLibrary()函数时,会调用JNI_OnLoad,而IjkMediaPlayer.loadLibrariesOnce传入的是null,逻辑走libLoader=sLoadLibLoader,所以libLoader.loadLibrary最后就是调用的System.loadLibrary,此时就会调用ijkplayer_jni.c/JNI_OnLoad()函数
我们继续看看ijkplayer_jni.c/JNI_OnLoad()干了些什么事,代码如下
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
...
(*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) );
ijkmp_global_init();
...
}
ijkplayer_jni.c/JNI_OnLoad()主要做了两件事:
1) (*env)->RegisterNatives()该方法是为了注册g_methods中的native方法,其中有
static JNINativeMethod g_methods[] = {
{
"_setDataSource",
"(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
(void *) IjkMediaPlayer_setDataSourceAndHeaders
},
{ "_setDataSourceFd", "(I)V", (void *) IjkMediaPlayer_setDataSourceFd },
{ "_setDataSource", "(Ltv/danmaku/ijk/media/player/misc/IMediaDataSource;)V", (void *)IjkMediaPlayer_setDataSourceCallback },
{ "_setAndroidIOCallback", "(Ltv/danmaku/ijk/media/player/misc/IAndroidIO;)V", (void *)IjkMediaPlayer_setAndroidIOCallback },
{ "_setVideoSurface", "(Landroid/view/Surface;)V", (void *) IjkMediaPlayer_setVideoSurface },
{ "_prepareAsync", "()V", (void *) IjkMediaPlayer_prepareAsync },
{ "_start", "()V", (void *) IjkMediaPlayer_start },
{ "_stop", "()V", (void *) IjkMediaPlayer_stop },
{ "seekTo", "(J)V", (void *) IjkMediaPlayer_seekTo },
{ "_pause", "()V", (void *) IjkMediaPlayer_pause },
{ "isPlaying", "()Z", (void *) IjkMediaPlayer_isPlaying },
{ "getCurrentPosition", "()J", (void *) IjkMediaPlayer_getCurrentPosition },
{ "getDuration", "()J", (void *) IjkMediaPlayer_getDuration },
{ "_release", "()V", (void *) IjkMediaPlayer_release },
{ "_reset", "()V", (void *) IjkMediaPlayer_reset },
{ "setVolume", "(FF)V", (void *) IjkMediaPlayer_setVolume },
{ "getAudioSessionId", "()I", (void *) IjkMediaPlayer_getAudioSessionId },
{ "native_init", "()V", (void *) IjkMediaPlayer_native_init },
{ "native_setup", "(Ljava/lang/Object;)V", (void *) IjkMediaPlayer_native_setup },
{ "native_finalize", "()V", (void *) IjkMediaPlayer_native_finalize },
{ "_setOption", "(ILjava/lang/String;Ljava/lang/String;)V", (void *) IjkMediaPlayer_setOption },
{ "_setOption", "(ILjava/lang/String;J)V", (void *) IjkMediaPlayer_setOptionLong },
{ "_getColorFormatName", "(I)Ljava/lang/String;", (void *) IjkMediaPlayer_getColorFormatName },
{ "_getVideoCodecInfo", "()Ljava/lang/String;", (void *) IjkMediaPlayer_getVideoCodecInfo },
{ "_getAudioCodecInfo", "()Ljava/lang/String;", (void *) IjkMediaPlayer_getAudioCodecInfo },
{ "_getMediaMeta", "()Landroid/os/Bundle;", (void *) IjkMediaPlayer_getMediaMeta },
{ "_setLoopCount", "(I)V", (void *) IjkMediaPlayer_setLoopCount },
{ "_getLoopCount", "()I", (void *) IjkMediaPlayer_getLoopCount },
{ "_getPropertyFloat", "(IF)F", (void *) ijkMediaPlayer_getPropertyFloat },
{ "_setPropertyFloat", "(IF)V", (void *) ijkMediaPlayer_setPropertyFloat },
{ "_getPropertyLong", "(IJ)J", (void *) ijkMediaPlayer_getPropertyLong },
{ "_setPropertyLong", "(IJ)V", (void *) ijkMediaPlayer_setPropertyLong },
{ "_setStreamSelected", "(IZ)V", (void *) ijkMediaPlayer_setStreamSelected },
{ "native_profileBegin", "(Ljava/lang/String;)V", (void *) IjkMediaPlayer_native_profileBegin },
{ "native_profileEnd", "()V", (void *) IjkMediaPlayer_native_profileEnd },
{ "native_setLogLevel", "(I)V", (void *) IjkMediaPlayer_native_setLogLevel },
{ "_injectCacheNode", "(IJJJJ)V", (void *) IjkMediaPlayer_injectCacheNode },
};
这个类似数组的结构相当于是定义java层与c层之间的映射关系,比如在java层调用_prepareAsync函数,其实最终调用的是c层的IjkMediaPlayer_prepareAsync函数
2) ijkplayer_jni.c/ijkmp_global_init()->ijkplayer.c/ffp_global_init()->ff_ffplayer.c/ffp_global_init(),可见最终调用的是ff_ffplayer.c/ffp_global_init(),函数实现如下
void ffp_global_init()
{
...
/* register all codecs, demux and protocols */
avcodec_register_all(); //注册所有编译好的编解码器
...
av_register_all(); //注册所有编译好的封装器
...
avformat_network_init(); //对一些网络功能进行初始化
...
}
其中ff_ffplayer.c/ffp_global_init()又主要做了三件事
- avcodec_register_all(),注册所有编译好的编解码器
- av_register_all(),注册所有编译好的封装器
- avformat_network_init(),对一些网络功能进行初始化
2. setVideoPath或者setVideoURI
其实二者都是调用的IjkVideoView.java/setVideoURI()最后调用IjkVideoView.java/openVideo(),IjkVideoView.java/openVideo()代码如下
private void openVideo() {
if (mUri == null || mSurfaceHolder == null) {
// not ready for playback just yet, will try again later
return;
}
// we shouldn't clear the target state, because somebody might have
// called start() previously
release(false);
AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
try {
mMediaPlayer = createPlayer(mSettings.getPlayer());
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
mSettings.getUsingMediaDataSource() &&
(TextUtils.isEmpty(scheme) || scheme.equalsIgnoreCase("file"))) {
IMediaDataSource dataSource = new FileMediaDataSource(new File(mUri.toString()));
mMediaPlayer.setDataSource(dataSource);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mMediaPlayer.setDataSource(mAppContext, mUri, mHeaders);
} else {
mMediaPlayer.setDataSource(mUri.toString());
}
...
mMediaPlayer.prepareAsync();
...
} catch (IOException ex) {
...
} catch (IllegalArgumentException ex) {
...
} finally {
// REMOVED: mPendingSubtitleTracks.clear();
}
}
IjkVideoView.java/openVideo()又主要做了三件事:
1) createPlayer,这里默认是创建IjkMediaPlayer(当然这里你也可以选择创建其他的播放器),最终调用IjkMediaPlayer.java/initPlayer()函数初始化播放器,初始化代码如下
private void initPlayer(IjkLibLoader libLoader) {
loadLibrariesOnce(libLoader);
initNativeOnce();
Looper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
}
/*
* Native setup requires a weak reference to our object. It's easier to
* create it here than in C++.
*/
native_setup(new WeakReference(this));
}
a. loadLibrariesOnce之前解释过,也调用过
b. initNativeOnce,然后调用IjkMediaPlayer.java/native_init(),其实就是调用IjkMediaPlayer_native_init(),这里没做什么操作好像
c. 创建handler,用于后面c层代码调用java代码来发送message信息
d. native_setup,其实就是调用ijkplayer_jni.c/IjkMediaPlayer_native_setup()函数,该函数里面又调用了ijkplayer_jni.c/ijkmp_android_create()
IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))
{
IjkMediaPlayer *mp = ijkmp_create(msg_loop);
if (!mp)
goto fail;
mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();
if (!mp->ffplayer->vout)
goto fail;
mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);
if (!mp->ffplayer->pipeline)
goto fail;
ffpipeline_set_vout(mp->ffplayer->pipeline, mp->ffplayer->vout);
return mp;
fail:
ijkmp_dec_ref_p(&mp);
return NULL;
}
- ijkplayer_android.c/ijkmp_create()->ijkplayer.c/ffp_create()(这里可以看出ijk其实底层就是包装了一个ffplayer),创建播放器的时候还传入了一个msg_loop(ijkplayer_jni.c/message_loop_n),这个函数名是用于之后创建消息处理线程用的
- ijkplayer_android.c/SDL_VoutAndroid_CreateForAndroidSurface(),创建图像渲染对象
- ijkplayer_android.c/ffpipeline_create_from_android(),创建平台相关的IJKFF_Pipeline对象,包括视频解码以及音频输出部分
2) mMediaPlayer.setDataSource用于设置视频源,其实是调用了jni层的ijkplayer_jni.c/IjkMediaPlayer_setDataSourceAndHeaders()函数,最终调用了ijkplayer.c/ijkmp_set_data_source_l()
static int ijkmp_set_data_source_l(IjkMediaPlayer *mp, const char *url)
{
...
mp->data_source = strdup(url);
...
ijkmp_change_state_l(mp, MP_STATE_INITIALIZED);
return 0;
}
- mp->data_source = strdup(url)设置视频源
- ijkmp_change_state_l(),将MP_STATE_INITIALIZED消息推入ffplayer->msg_queue中,就是ffplayer的消息队列
3) mMediaPlayer.prepareAsync(),调用了ijkplayer_jni.c/ijkmp_prepare_async(),最终调用到ijkplayer.c/ijkmp_prepare_async_l()
static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)
{
...
ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING);
msg_queue_start(&mp->ffplayer->msg_queue);
// released in msg_loop
ijkmp_inc_ref(mp);
mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");
// msg_thread is detached inside msg_loop
// TODO: 9 release weak_thiz if pthread_create() failed;
int retval = ffp_prepare_async_l(mp->ffplayer, mp->data_source);
if (retval < 0) {
ijkmp_change_state_l(mp, MP_STATE_ERROR);
return retval;
}
return 0;
}
a ijkmp_change_state_l、msg_queue_start还是将消息推入ffplayer->msg_queue中
b mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop")用来创建处理处理消息的线程,这里的ijkmp_msg_loop就是在之前jkplayer_android.c/ijkmp_create()赋值的
c 在ffp_prepare_async_l()中
- 先调用ff_ffplayer.c/ ffpipeline_open_audio_output(),ffpipeline_open_audio_output内部调用func_open_audio_output()该函数指针在前面的ijkplayer_android.c/ffpipeline_create_from_android()中赋值其实就是调用ff_ffpipeline.c/ func_open_audio_output()最终调用ff_ffpipeline_android.c/ func_open_audio_output()用于选择相应音频输出方式(opensles or audiotrack)
- 调用ff_ffplayer.c/stream_open,其中stream_open()会创建读线程和视频渲染线程,这部分稍后的文章会详细讲解,只是这里稍微说一下在读线程read_thread()准备好后ffp->prepared = true将准备好的标志位置为true,ffp_notify_msg1(ffp, FFP_MSG_PREPARED)将准备好的消息推给消息队列,消息线程接到这个消息后调用java层的函数,向handler发送MEDIA_PREPARED,handler的handleMessage函数里面会处理这个消息
public void handleMessage(Message msg) {
...
switch (msg.what) {
case MEDIA_PREPARED:
player.notifyOnPrepared();
return;
...
default:
DebugLog.e(TAG, "Unknown message type " + msg.what);
}
}
}
最后调用下面函数,该函数又调用start(),即IjkMediaPlayer_start()函数开始视频的播放,到这里ijkplayer的初始化流程就结束了,内容很多,一定要下载代码多看几次
public void onPrepared(IMediaPlayer mp) {
mPrepareEndTime = System.currentTimeMillis();
mHudViewHolder.updateLoadCost(mPrepareEndTime - mPrepareStartTime);
mCurrentState = STATE_PREPARED;
// Get the capabilities of the player for this stream
// REMOVED: Metadata
if (mOnPreparedListener != null) {
mOnPreparedListener.onPrepared(mMediaPlayer);
}
if (mMediaController != null) {
mMediaController.setEnabled(true);
}
mVideoWidth = mp.getVideoWidth();
mVideoHeight = mp.getVideoHeight();
int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call
if (seekToPosition != 0) {
seekTo(seekToPosition);
}
if (mVideoWidth != 0 && mVideoHeight != 0) {
//Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);
// REMOVED: getHolder().setFixedSize(mVideoWidth, mVideoHeight);
if (mRenderView != null) {
mRenderView.setVideoSize(mVideoWidth, mVideoHeight);
mRenderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen);
if (!mRenderView.shouldWaitForResize() || mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
// We didn't actually change the size (it was already at the size
// we need), so we won't get a "surface changed" callback, so
// start the video here instead of in the callback.
if (mTargetState == STATE_PLAYING) {
start();
if (mMediaController != null) {
mMediaController.show();
}
} else if (!isPlaying() &&
(seekToPosition != 0 || getCurrentPosition() > 0)) {
if (mMediaController != null) {
// Show the media controls when we're paused into a video and make 'em stick.
mMediaController.show(0);
}
}
}
}
} else {
// We don't know the video size yet, but should start anyway.
// The video size might be reported to us later.
if (mTargetState == STATE_PLAYING) {
start();
}
}
}