1 背景
公司的底层播放器实际上是ffplayer作为基础修改的,当然需要好好学习研究。 记录下来,作为以后备忘。(发觉人老了,就容易忘事)。ps 入坑需谨慎,很容易入门到放弃。
2 分析入口
分析的连接https://github.com/Bilibili/ijkplayer
版本号: 0.8.1.2 android版
基本上IjkPlayer的接口和MediaPlayer接口一致。http://www.jianshu.com/p/55fbfd5b564a
每次我们发觉分析源码总找不到下口的地方,实际上就是没找到分析的入口。那在哪里呢?当然看源码demo了。https://github.com/Bilibili/ijkplayer/blob/5b73bf7eae66a3942e2689ecbe6e51b679abe161/android/ijkplayer/ijkplayer-example/src/main/java/tv/danmaku/ijk/media/example/activities/VideoActivity.java
这个类里面的onCreate 方法里面:
实际上初始化,作者已注释了 init player。我们来看看初始化里做了什么?
3 IjkMediaPlayer.loadLibrariesOnce(null);
这个很简单load了几个需要的lib(ijkffmpeg,ijksdl,ijkplayer)
事实上这里还有隐藏逻辑,看其中源码,你会发现,卧槽,我都找不到定义jni的方法名,熟悉jni的同学知道,jni方法名是Java+ 全类名 命名,可是你会发觉比如你想找到_start方法,你是找不到的。 这里使用的一个trick , 事实上我觉得这对写代码的人,
提高了写作效率,但可读性没那么高。在jni加载lib的时候,会首先查找JNI_OnLoad() 方法,同理卸载的时候JNI_OnUnload()。我们看下他都干了些啥?
a. 这个是ijkplayer_jni做的
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 },
};
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env = NULL;
g_jvm = vm;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
pthread_mutex_init(&g_clazz.mutex, NULL );
// FindClass returns LocalReference
IJK_FIND_JAVA_CLASS(env, g_clazz.clazz, JNI_CLASS_IJKPLAYER);
(*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) );
ijkmp_global_init();
ijkmp_global_set_inject_callback(inject_callback);
FFmpegApi_global_init(env);
return JNI_VERSION_1_4;
}
来依次看
#define IJK_FIND_JAVA_CLASS(env__, var__, classsign__) \
do { \
jclass clazz = (*env__)->FindClass(env__, classsign__); \
if (J4A_ExceptionCheck__catchAll(env) || !(clazz)) { \
ALOGE("FindClass failed: %s", classsign__); \
return -1; \
} \
var__ = (*env__)->NewGlobalRef(env__, clazz); \
if (J4A_ExceptionCheck__catchAll(env) || !(var__)) { \
ALOGE("FindClass::NewGlobalRef failed: %s", classsign__); \
(*env__)->DeleteLocalRef(env__, clazz); \
return -1; \
} \
(*env__)->DeleteLocalRef(env__, clazz); \
} while(0);
这里宏定义的代码块,在ijkplayer非常常见,忍不住吐槽下, do while(0)貌似没其他作用,仅仅为了装逼? 去掉的话不好么?整个源码里面非常多的 do while(0) 定义的块。这个函数的作用,就是把java class 传递给c。
(*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) ); 这句就是动态注册各个方法了。当然c语言和c++注册的代码稍微有点区别,c++没有env参数。
ijkmp_global_init();
ijkmedia/ijkplayer/ijkplayer.c
void ijkmp_global_init()
{
ffp_global_init();
}
哈哈,作为java程序猿,源码缩进格式也看着别扭。继续看ijkmedia/ijkplayer/ff_ffplay.c
void ffp_global_init()
{
if (g_ffmpeg_global_inited)
return;
/* register all codecs, demux and protocols */
avcodec_register_all();
#if CONFIG_AVDEVICE
avdevice_register_all();
#endif
#if CONFIG_AVFILTER
avfilter_register_all();
#endif
av_register_all();
ijkav_register_all();
avformat_network_init();
av_lockmgr_register(lockmgr);
av_log_set_callback(ffp_log_callback_brief);
av_init_packet(&flush_pkt);
flush_pkt.data = (uint8_t *)&flush_pkt;
g_ffmpeg_global_inited = true;
}
看到这里你会说,啊,原来ffmpeg的api 初始化都在这里呀,哇咔咔~~ 当然如果想了解细节可以继续跟源码读下去,不想的话,我继续出发了。
ijkmp_global_set_inject_callback(inject_callback)
ijkmedia/ijkplayer/ijkplayer.c
void ijkmp_global_set_inject_callback(ijk_inject_callback cb)
{
ffp_global_set_inject_callback(cb);
}
别紧张就是一个回调接口。
FFmpegApi_global_init
ijkmedia/ijkplayer/android/ffmpeg_api_jni.c
int FFmpegApi_global_init(JNIEnv *env)
{
int ret = 0;
IJK_FIND_JAVA_CLASS(env, g_clazz.clazz, JNI_CLASS_FFMPEG_API);
(*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods));
return ret;
}
native_profileBegin
这个跟进源码看里面没什么有效的代码,略。
代码位于 https://github.com/Bilibili/ijkplayer/blob/eb265a564f471e91778158156ab49d46bb00197d/android/ijkplayer/ijkplayer-example/src/main/java/tv/danmaku/ijk/media/example/widget/media/IjkVideoView.java
## setVideoURI(Uri.parse(path));
public void setVideoPath(String path) {
setVideoURI(Uri.parse(path));
}
本质上也是调用setVideoURI.
private void setVideoURI(Uri uri, Map headers) {
mUri = uri;
mHeaders = headers;
mSeekWhenPrepared = 0;
openVideo();
requestLayout();
invalidate();
}
其他的初始化参数略, headers是视频的一些信息,这里可以用来添加防盗链参数,主要看openVideo()做啥的。
@TargetApi(Build.VERSION_CODES.M)
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());
....
String scheme = mUri.getScheme();
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());
}
bindSurfaceHolder(mMediaPlayer, mSurfaceHolder);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setScreenOnWhilePlaying(true);
mPrepareStartTime = System.currentTimeMillis();
mMediaPlayer.prepareAsync();
if (mHudViewHolder != null)
mHudViewHolder.setMediaPlayer(mMediaPlayer);
// REMOVED: mPendingSubtitleTracks
// we don't set the target state here either, but preserve the
// target state that was there before.
mCurrentState = STATE_PREPARING;
attachMediaController();
} catch (IOException ex) {
...
} catch (IllegalArgumentException ex) {
...
} finally {
...
}
}
可以看到一系列参数的初始化。
createPlayer(mSettings.getPlayer()) 这里根据设置,选取播放器。 兼容了系统,ExoPlayer,及IjkPlayer(默认)。
public IMediaPlayer createPlayer(int playerType) {
IMediaPlayer mediaPlayer = null;
switch (playerType) {
case Settings.PV_PLAYER__IjkExoMediaPlayer: {
IjkExoMediaPlayer IjkExoMediaPlayer = new IjkExoMediaPlayer(mAppContext);
mediaPlayer = IjkExoMediaPlayer;
}
break;
case Settings.PV_PLAYER__AndroidMediaPlayer: {
AndroidMediaPlayer androidMediaPlayer = new AndroidMediaPlayer();
mediaPlayer = androidMediaPlayer;
}
break;
case Settings.PV_PLAYER__IjkMediaPlayer:
default: {
IjkMediaPlayer ijkMediaPlayer = null;
if (mUri != null) {
ijkMediaPlayer = new IjkMediaPlayer();
....
...
}
break;
}
if (mSettings.getEnableDetachedSurfaceTextureView()) {
mediaPlayer = new TextureMediaPlayer(mediaPlayer);
}
return mediaPlayer;
}
https://github.com/Bilibili/ijkplayer/blob/master/android/ijkplayer/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java
/**
* Default constructor. Consider using one of the create() methods for
* synchronously instantiating a IjkMediaPlayer from a Uri or resource.
*
* When done with the IjkMediaPlayer, you should call {@link #release()}, to
* free the resources. If not released, too many IjkMediaPlayer instances
* may result in an exception.
*
*/
public IjkMediaPlayer() {
this(sLocalLibLoader);
}
/**
* do not loadLibaray
* @param libLoader
* custom library loader, can be null.
*/
public IjkMediaPlayer(IjkLibLoader libLoader) {
initPlayer(libLoader);
}
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));
}
loadLibrariesOnce 这里再次load下,确保lib被加载。initNativeOnce只是打印。 初始化了EventHandler,及native_setup()。
static void
IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
MPTRACE("%s\n", __func__);
IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
JNI_CHECK_GOTO(mp, env, "java/lang/OutOfMemoryError", "mpjni: native_setup: ijkmp_create() failed", LABEL_RETURN);
jni_set_media_player(env, thiz, mp);
ijkmp_set_weak_thiz(mp, (*env)->NewGlobalRef(env, weak_this));
ijkmp_set_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
ijkmp_set_ijkio_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
ijkmp_android_set_mediacodec_select_callback(mp, mediacodec_select_callback, ijkmp_get_weak_thiz(mp));
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
}
代码的层级每次调用都比较深,由于jni的缘故,很容易就走神了。先看ijkmp_android_create().参数是函数指针,函数 message_loop
static int message_loop(void *arg)
{
MPTRACE("%s\n", __func__);
JNIEnv *env = NULL;
if (JNI_OK != SDL_JNI_SetupThreadEnv(&env)) {
ALOGE("%s: SetupThreadEnv failed\n", __func__);
return -1;
}
IjkMediaPlayer *mp = (IjkMediaPlayer*) arg;
JNI_CHECK_GOTO(mp, env, NULL, "mpjni: native_message_loop: null mp", LABEL_RETURN);
message_loop_n(env, mp);
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
MPTRACE("message_loop exit");
return 0;
}
message_loop_n 都是做什么的呢?
static void message_loop_n(JNIEnv *env, IjkMediaPlayer *mp)
{
jobject weak_thiz = (jobject) ijkmp_get_weak_thiz(mp);
JNI_CHECK_GOTO(weak_thiz, env, NULL, "mpjni: message_loop_n: null weak_thiz", LABEL_RETURN);
while (1) {
AVMessage msg;
int retval = ijkmp_get_msg(mp, &msg, 1);
if (retval < 0)
break;
// block-get should never return 0
assert(retval > 0);
switch (msg.what) {
case FFP_MSG_FLUSH:
MPTRACE("FFP_MSG_FLUSH:\n");
post_event(env, weak_thiz, MEDIA_NOP, 0, 0);
break;
case FFP_MSG_ERROR:
MPTRACE("FFP_MSG_ERROR: %d\n", msg.arg1);
post_event(env, weak_thiz, MEDIA_ERROR, MEDIA_ERROR_IJK_PLAYER, msg.arg1);
break;
case FFP_MSG_PREPARED:
MPTRACE("FFP_MSG_PREPARED:\n");
post_event(env, weak_thiz, MEDIA_PREPARED, 0, 0);
break;
case FFP_MSG_COMPLETED:
...
}
我们发觉这个其实是消息队列形式,看下post_event函数。直接调用了J4AC_IjkMediaPlayer__postEventFromNative(env, weak_this, what, arg1, arg2, NULL) 函数。
void J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer__postEventFromNative(JNIEnv *env, jobject weakThiz, jint what, jint arg1, jint arg2, jobject obj)
{
(*env)->CallStaticVoidMethod(env, class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.id, class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.method_postEventFromNative, weakThiz, what, arg1, arg2, obj);
}
其中,class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.method_postEventFromNative
class_id = class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.id;
name = "postEventFromNative";
sign = "(Ljava/lang/Object;IIILjava/lang/Object;)V"; class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.method_postEventFromNative = J4A_GetStaticMethodID__catchAll(env, class_id, name, sign);
所以class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.method_postEventFromNative 本质是调用postEventFromNative android/ijkplayer/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java
@CalledByNative
private static void postEventFromNative(Object weakThiz, int what,
int arg1, int arg2, Object obj) {
if (weakThiz == null)
return;
@SuppressWarnings("rawtypes")
IjkMediaPlayer mp = (IjkMediaPlayer) ((WeakReference) weakThiz).get();
if (mp == null) {
return;
}
if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
// this acquires the wakelock if needed, and sets the client side
// state
mp.start();
}
if (mp.mEventHandler != null) {
Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
mp.mEventHandler.sendMessage(m);
}
}
具体是初始化了IjkMediaPlayer 这个结构体,留个坑,单独写个章节介绍其c层数据结构。
位于https://github.com/Bilibili/ijkplayer/blob/eb265a564f471e91778158156ab49d46bb00197d/ijkmedia/ijkplayer/android/ijkplayer_android.c
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;
}
ijkmp_create 初始化了FFPlayer结构的参数初始化。初始化msg_queue_init 消息队列。af_mutex, vf_mutex 音视频互斥锁。这里涉及的FFPlayer初始化参数很多。
FFPlayer *ffp_create()
{
...
FFPlayer* ffp = (FFPlayer*) av_mallocz(sizeof(FFPlayer));
if (!ffp)
return NULL;
msg_queue_init(&ffp->msg_queue);
ffp->af_mutex = SDL_CreateMutex();
ffp->vf_mutex = SDL_CreateMutex();
ffp_reset_internal(ffp);
ffp->av_class = &ffp_context_class;
ffp->meta = ijkmeta_create();
av_opt_set_defaults(ffp);
return ffp;
}
回到mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface(); 实际是调用 SDL_VoutAndroid_CreateForANativeWindow();
mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer); 初始化了IJKFF_Pipeline 结构体。回到IjkMediaPlayer_native_setup 方法。 jni_set_media_player(env, thiz, mp) 其实是调用java的方法set
void J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer__mNativeMediaPlayer__set(JNIEnv *env, jobject thiz, jlong value)
{
(*env)->SetLongField(env, thiz, class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.field_mNativeMediaPlayer, value);
}
与postEvent类似,找到mNativeMediaPlayer变量(android/ijkplayer/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java)。
回到openVedio函数内mMediaPlayer.setDataSource(), 到c层的ijkmedia/ijkplayer/android/ijkplayer_jni.c IjkMediaPlayer_setDataSourceAndHeaders函数。
static void
IjkMediaPlayer_setDataSourceAndHeaders(
JNIEnv *env, jobject thiz, jstring path,
jobjectArray keys, jobjectArray values)
{
MPTRACE("%s\n", __func__);
int retval = 0;
const char *c_path = NULL;
IjkMediaPlayer *mp = jni_get_media_player(env, thiz);
JNI_CHECK_GOTO(path, env, "java/lang/IllegalArgumentException", "mpjni: setDataSource: null path", LABEL_RETURN);
JNI_CHECK_GOTO(mp, env, "java/lang/IllegalStateException", "mpjni: setDataSource: null mp", LABEL_RETURN);
c_path = (*env)->GetStringUTFChars(env, path, NULL );
JNI_CHECK_GOTO(c_path, env, "java/lang/OutOfMemoryError", "mpjni: setDataSource: path.string oom", LABEL_RETURN);
ALOGV("setDataSource: path %s", c_path);
retval = ijkmp_set_data_source(mp, c_path);
(*env)->ReleaseStringUTFChars(env, path, c_path);
IJK_CHECK_MPRET_GOTO(retval, env, LABEL_RETURN);
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
ijkmp_set_data_source(mp, c_path) 函数主要代码ijkmp_change_state_l(mp, MP_STATE_INITIALIZED);
void ijkmp_change_state_l(IjkMediaPlayer *mp, int new_state)
{
mp->mp_state = new_state;
ffp_notify_msg1(mp->ffplayer, FFP_MSG_PLAYBACK_STATE_CHANGED);
}
发了个FFP_MSG_PLAYBACK_STATE_CHANGED消息,但是我们看message_loop_n里面是没有回调java的。
先分析到这里,有空的话把时序图和其他的类图的坑填上。ps:纠正下java 函数称 method, c,c++叫函数,写作中没转过来。