以打开一个网络视频的流程为例,我们需要依次调用IJKMediaPlayer
的setDisplay/setSurface
、setDataSource
、prepareAsync
,在收到视频加载成功的OnPreparedListener
监听中调用start
播放视频。其对应的native
方法为
_setVideoSurface
、_setDataSource
、_prepareAsync
,在c
调用java
的postEventFromNative
方法中收到what == MEDIA_PREPARED
的消息,表示视频加载成功,回调消息到OnPreparedListener
中,再调用_start
方法播放视频。
接下来依次分析
_setVideoSurface
、_setDataSource
、_prepareAsync
、postEventFromNative
、_start
是如何调用的?
一、_setVideoSurface
_setVideoSurface
在ijkplayer_jni.c
中对应的实现方法为IjkMediaPlayer_setVideoSurface
{ "_setVideoSurface", "(Landroid/view/Surface;)V", (void *) IjkMediaPlayer_setVideoSurface },
IjkMediaPlayer_setVideoSurface
static void
IjkMediaPlayer_setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface)
{
MPTRACE("%s\n", __func__);
IjkMediaPlayer *mp = jni_get_media_player(env, thiz);
JNI_CHECK_GOTO(mp, env, NULL, "mpjni: setVideoSurface: null mp", LABEL_RETURN);
ijkmp_android_set_surface(env, mp, jsurface);
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
return;
}
IjkMediaPlayer_setVideoSurface
调用ijkmp_android_set_surface
方法保存surface
ijkplayer_android.c
void ijkmp_android_set_surface(JNIEnv *env, IjkMediaPlayer *mp, jobject android_surface)
{
if (!mp)
return;
MPTRACE("ijkmp_set_android_surface(surface=%p)", (void*)android_surface);
pthread_mutex_lock(&mp->mutex);
ijkmp_android_set_surface_l(env, mp, android_surface);
pthread_mutex_unlock(&mp->mutex);
MPTRACE("ijkmp_set_android_surface(surface=%p)=void", (void*)android_surface);
}
void ijkmp_android_set_surface_l(JNIEnv *env, IjkMediaPlayer *mp, jobject android_surface)
{
if (!mp || !mp->ffplayer || !mp->ffplayer->vout)
return;
SDL_VoutAndroid_SetAndroidSurface(env, mp->ffplayer->vout, android_surface);
ffpipeline_set_surface(env, mp->ffplayer->pipeline, android_surface);
}
void SDL_VoutAndroid_SetAndroidSurface(JNIEnv *env, SDL_Vout *vout, jobject android_surface)
{
ANativeWindow *native_window = NULL;
if (android_surface) {
native_window = ANativeWindow_fromSurface(env, android_surface);
if (!native_window) {
ALOGE("%s: ANativeWindow_fromSurface: failed\n", __func__);
// do not return fail here;
}
}
SDL_VoutAndroid_SetNativeWindow(vout, native_window);
if (native_window)
ANativeWindow_release(native_window);
}
ANativeWindow
代表的是本地窗口,可以看成NDK
提供Native
版本的Surface
。通过ANativeWindow_fromSurface
获得ANativeWindow
指针,ANativeWindow_release
进行释放。类似Java
,可以对它进行lock
、unlockAndPost
以及通过ANativeWindow_Buffer
进行图像数据的修改。
二、_setDataSource
_setDataSource
的jni
实现方法为IjkMediaPlayer_setDataSourceAndHeaders
{
"_setDataSource",
"(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
(void *) IjkMediaPlayer_setDataSourceAndHeaders
}
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);
...
c_path = (*env)->GetStringUTFChars(env, path, NULL );
...
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
int ijkmp_set_data_source(IjkMediaPlayer *mp, const char *url)
{
assert(mp);
assert(url);
MPTRACE("ijkmp_set_data_source(url=\"%s\")\n", url);
pthread_mutex_lock(&mp->mutex);
int retval = ijkmp_set_data_source_l(mp, url);
pthread_mutex_unlock(&mp->mutex);
MPTRACE("ijkmp_set_data_source(url=\"%s\")=%d\n", url, retval);
return retval;
}
保存播放地址,重置播放器状态
static int ijkmp_set_data_source_l(IjkMediaPlayer *mp, const char *url)
{
assert(mp);
assert(url);
// MPST_RET_IF_EQ(mp->mp_state, MP_STATE_IDLE);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_INITIALIZED);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_ASYNC_PREPARING);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_PREPARED);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_STARTED);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_PAUSED);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_COMPLETED);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_STOPPED);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_ERROR);
MPST_RET_IF_EQ(mp->mp_state, MP_STATE_END);
freep((void**)&mp->data_source);
mp->data_source = strdup(url);
if (!mp->data_source)
return EIJK_OUT_OF_MEMORY;
ijkmp_change_state_l(mp, MP_STATE_INITIALIZED);
return 0;
}
三、_prepareAsync
_prepareAsync
的jni
实现为IjkMediaPlayer_prepareAsync
{ "_prepareAsync", "()V", (void *) IjkMediaPlayer_prepareAsync },
IjkMediaPlayer_prepareAsync
static void
IjkMediaPlayer_prepareAsync(JNIEnv *env, jobject thiz)
{
MPTRACE("%s\n", __func__);
int retval = 0;
IjkMediaPlayer *mp = jni_get_media_player(env, thiz);
JNI_CHECK_GOTO(mp, env, "java/lang/IllegalStateException", "mpjni: prepareAsync: null mp", LABEL_RETURN);
retval = ijkmp_prepare_async(mp);
IJK_CHECK_MPRET_GOTO(retval, env, LABEL_RETURN);
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
}
ijkmp_prepare_async
int ijkmp_prepare_async(IjkMediaPlayer *mp)
{
assert(mp);
MPTRACE("ijkmp_prepare_async()\n");
pthread_mutex_lock(&mp->mutex);
int retval = ijkmp_prepare_async_l(mp);
pthread_mutex_unlock(&mp->mutex);
MPTRACE("ijkmp_prepare_async()=%d\n", retval);
return retval;
}
修改播放器状态为PREPARING(MP_STATE_ASYNC_PREPARING)
,启动播放器消息队列&mp->ffplayer->msg_queue
,创建消息处理线程mp->msg_thread
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;
}
ffp_prepare_async_l 打印ijk信息,开始加载数据
int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
{
...
av_log(NULL, AV_LOG_INFO, "===== versions =====\n");
ffp_show_version_str(ffp, "ijkplayer", ijk_version_info());
ffp_show_version_str(ffp, "FFmpeg", av_version_info());
ffp_show_version_int(ffp, "libavutil", avutil_version());
ffp_show_version_int(ffp, "libavcodec", avcodec_version());
ffp_show_version_int(ffp, "libavformat", avformat_version());
ffp_show_version_int(ffp, "libswscale", swscale_version());
ffp_show_version_int(ffp, "libswresample", swresample_version());
av_log(NULL, AV_LOG_INFO, "===== options =====\n");
ffp_show_dict(ffp, "player-opts", ffp->player_opts);
ffp_show_dict(ffp, "format-opts", ffp->format_opts);
ffp_show_dict(ffp, "codec-opts ", ffp->codec_opts);
ffp_show_dict(ffp, "sws-opts ", ffp->sws_dict);
ffp_show_dict(ffp, "swr-opts ", ffp->swr_opts);
av_log(NULL, AV_LOG_INFO, "===================\n");
av_opt_set_dict(ffp, &ffp->player_opts);
if (!ffp->aout) {
ffp->aout = ffpipeline_open_audio_output(ffp->pipeline, ffp);
if (!ffp->aout)
return -1;
}
#if CONFIG_AVFILTER
if (ffp->vfilter0) {
GROW_ARRAY(ffp->vfilters_list, ffp->nb_vfilters);
ffp->vfilters_list[ffp->nb_vfilters - 1] = ffp->vfilter0;
}
#endif
VideoState *is = stream_open(ffp, file_name, NULL);
if (!is) {
av_log(NULL, AV_LOG_WARNING, "ffp_prepare_async_l: stream_open failed OOM");
return EIJK_OUT_OF_MEMORY;
}
ffp->is = is;
ffp->input_filename = av_strdup(file_name);
return 0;
}
stream_open 创建数据读取线程
is->read_tid = SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read");
static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)
{
assert(!ffp->is);
VideoState *is;
is = av_mallocz(sizeof(VideoState));
if (!is)
return NULL;
is->filename = av_strdup(filename);
if (!is->filename)
goto fail;
is->iformat = iformat;
is->ytop = 0;
is->xleft = 0;
#if defined(__ANDROID__)
if (ffp->soundtouch_enable) {
is->handle = ijk_soundtouch_create();
}
#endif
/* start video display */
if (frame_queue_init(&is->pictq, &is->videoq, ffp->pictq_size, 1) < 0)
goto fail;
if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
goto fail;
if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
goto fail;
if (packet_queue_init(&is->videoq) < 0 ||
packet_queue_init(&is->audioq) < 0 ||
packet_queue_init(&is->subtitleq) < 0)
goto fail;
if (!(is->continue_read_thread = SDL_CreateCond())) {
av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
goto fail;
}
if (!(is->video_accurate_seek_cond = SDL_CreateCond())) {
av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
ffp->enable_accurate_seek = 0;
}
if (!(is->audio_accurate_seek_cond = SDL_CreateCond())) {
av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
ffp->enable_accurate_seek = 0;
}
init_clock(&is->vidclk, &is->videoq.serial);
init_clock(&is->audclk, &is->audioq.serial);
init_clock(&is->extclk, &is->extclk.serial);
is->audio_clock_serial = -1;
if (ffp->startup_volume < 0)
av_log(NULL, AV_LOG_WARNING, "-volume=%d < 0, setting to 0\n", ffp->startup_volume);
if (ffp->startup_volume > 100)
av_log(NULL, AV_LOG_WARNING, "-volume=%d > 100, setting to 100\n", ffp->startup_volume);
ffp->startup_volume = av_clip(ffp->startup_volume, 0, 100);
ffp->startup_volume = av_clip(SDL_MIX_MAXVOLUME * ffp->startup_volume / 100, 0, SDL_MIX_MAXVOLUME);
is->audio_volume = ffp->startup_volume;
is->muted = 0;
is->av_sync_type = ffp->av_sync_type;
is->play_mutex = SDL_CreateMutex();
is->accurate_seek_mutex = SDL_CreateMutex();
ffp->is = is;
is->pause_req = !ffp->start_on_prepared;
is->video_refresh_tid = SDL_CreateThreadEx(&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout");
if (!is->video_refresh_tid) {
av_freep(&ffp->is);
return NULL;
}
is->initialized_decoder = 0;
is->read_tid = SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read");
if (!is->read_tid) {
av_log(NULL, AV_LOG_FATAL, "SDL_CreateThread(): %s\n", SDL_GetError());
goto fail;
}
if (ffp->async_init_decoder && !ffp->video_disable && ffp->video_mime_type && strlen(ffp->video_mime_type) > 0
&& ffp->mediacodec_default_name && strlen(ffp->mediacodec_default_name) > 0) {
if (ffp->mediacodec_all_videos || ffp->mediacodec_avc || ffp->mediacodec_hevc || ffp->mediacodec_mpeg2) {
decoder_init(&is->viddec, NULL, &is->videoq, is->continue_read_thread);
ffp->node_vdec = ffpipeline_init_video_decoder(ffp->pipeline, ffp);
}
}
is->initialized_decoder = 1;
return is;
fail:
is->initialized_decoder = 1;
is->abort_request = true;
if (is->video_refresh_tid)
SDL_WaitThread(is->video_refresh_tid, NULL);
stream_close(ffp);
return NULL;
}
static int read_thread(void *arg) {
...
if (is->video_st && is->video_st->codecpar) {
AVCodecParameters *codecpar = is->video_st->codecpar;
ffp_notify_msg3(ffp, FFP_MSG_VIDEO_SIZE_CHANGED, codecpar->width, codecpar->height);
ffp_notify_msg3(ffp, FFP_MSG_SAR_CHANGED, codecpar->sample_aspect_ratio.num, codecpar->sample_aspect_ratio.den);
}
ffp->prepared = true;
ffp_notify_msg1(ffp, FFP_MSG_PREPARED);
...
}
创建解码器
static IJKFF_Pipenode *func_init_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
{
IJKFF_Pipeline_Opaque *opaque = pipeline->opaque;
IJKFF_Pipenode *node = NULL;
if (ffp->mediacodec_all_videos || ffp->mediacodec_avc || ffp->mediacodec_hevc || ffp->mediacodec_mpeg2)
node = ffpipenode_init_decoder_from_android_mediacodec(ffp, pipeline, opaque->weak_vout);
return node;
}
ffp->node_vdec = ffpipeline_init_video_decoder(ffp->pipeline, ffp);
四、postEventFromNative
以上为例,数据读取成功之后,改变播放器状态
ffp_notify_msg1(ffp, FFP_MSG_PREPARED);
inline static void ffp_notify_msg1(FFPlayer *ffp, int what) {
msg_queue_put_simple3(&ffp->msg_queue, what, 0, 0);
}
inline static void msg_queue_put_simple3(MessageQueue *q, int what, int arg1, int arg2)
{
AVMessage msg;
msg_init_msg(&msg);
msg.what = what;
msg.arg1 = arg1;
msg.arg2 = arg2;
msg_queue_put(q, &msg);
}
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;
...
}
}
通过jni
回调消息到java
inline static void post_event(JNIEnv *env, jobject weak_this, int what, int arg1, int arg2)
{
// MPTRACE("post_event(%p, %p, %d, %d, %d)", (void*)env, (void*) weak_this, what, arg1, arg2);
J4AC_IjkMediaPlayer__postEventFromNative(env, weak_this, what, arg1, arg2, NULL);
// MPTRACE("post_event()=void");
}
#define J4AC_IjkMediaPlayer__postEventFromNative J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer__postEventFromNative
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_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);
if (class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.method_postEventFromNative == NULL)
goto fail;
这样在IjkMediaPlayer.java的postEventFromNative方法就收到了视频加载成功的消息
五、_start
_start
在jni
中的实现方法为IjkMediaPlayer_start
{ "_start", "()V", (void *) IjkMediaPlayer_start },
static void
IjkMediaPlayer_start(JNIEnv *env, jobject thiz)
{
MPTRACE("%s\n", __func__);
IjkMediaPlayer *mp = jni_get_media_player(env, thiz);
JNI_CHECK_GOTO(mp, env, "java/lang/IllegalStateException", "mpjni: start: null mp", LABEL_RETURN);
ijkmp_start(mp);
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
}
ijkmp_start_l
执行之后,回调start
状态
int ijkmp_start(IjkMediaPlayer *mp)
{
assert(mp);
MPTRACE("ijkmp_start()\n");
pthread_mutex_lock(&mp->mutex);
int retval = ijkmp_start_l(mp);
pthread_mutex_unlock(&mp->mutex);
MPTRACE("ijkmp_start()=%d\n", retval);
return retval;
}
static int ijkmp_start_l(IjkMediaPlayer *mp)
{
assert(mp);
MP_RET_IF_FAILED(ikjmp_chkst_start_l(mp->mp_state));
ffp_remove_msg(mp->ffplayer, FFP_REQ_START);
ffp_remove_msg(mp->ffplayer, FFP_REQ_PAUSE);
ffp_notify_msg1(mp->ffplayer, FFP_REQ_START);
return 0;
}
case FFP_REQ_START:
MPTRACE("ijkmp_get_msg: FFP_REQ_START\n");
continue_wait_next_msg = 1;
pthread_mutex_lock(&mp->mutex);
if (0 == ikjmp_chkst_start_l(mp->mp_state)) {
// FIXME: 8 check seekable
if (mp->restart) {
if (mp->restart_from_beginning) {
av_log(mp->ffplayer, AV_LOG_DEBUG, "ijkmp_get_msg: FFP_REQ_START: restart from beginning\n");
retval = ffp_start_from_l(mp->ffplayer, 0);
if (retval == 0)
ijkmp_change_state_l(mp, MP_STATE_STARTED);
} else {
av_log(mp->ffplayer, AV_LOG_DEBUG, "ijkmp_get_msg: FFP_REQ_START: restart from seek pos\n");
retval = ffp_start_l(mp->ffplayer);
if (retval == 0)
ijkmp_change_state_l(mp, MP_STATE_STARTED);
}
mp->restart = 0;
mp->restart_from_beginning = 0;
} else {
av_log(mp->ffplayer, AV_LOG_DEBUG, "ijkmp_get_msg: FFP_REQ_START: start on fly\n");
retval = ffp_start_l(mp->ffplayer);
if (retval == 0)
ijkmp_change_state_l(mp, MP_STATE_STARTED);
}
}
pthread_mutex_unlock(&mp->mutex);
break;
切换播放状态到播放
int ffp_start_l(FFPlayer *ffp)
{
assert(ffp);
VideoState *is = ffp->is;
if (!is)
return EIJK_NULL_IS_PTR;
toggle_pause(ffp, 0);
return 0;
}