网站首页 > 精选教程 正文
一、前言
本文重点分析native 层的流程,对于业务层的流程可直接参考ijkplayer-example 中VideoActivity.java文件
二、初始化
2.1 so 加载及jni方法动态绑定
上层业务加载完ijkplayer.so / ijksdl.so/ijkffmpeg.so 之后会执行一次jni接口的绑定,jni方法列表如下(ijkplayer_jni.c):
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 },
{ "_setFrameAtTime", "(Ljava/lang/String;JJII)V", (void *) IjkMediaPlayer_setFrameAtTime },
{ "setParameter", "(IJJJ)V", (void *) NioMediaPlayer_setParameter },
{ "getParameter", "(I)J", (void *) NioMediaPlayer_getParameter },
};
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env = NULL;
//jni 版本判断
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);
//进行jni 接口的动态绑定
(*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;
}
2.2、初始化ffmpeg 相关
ijkplayer 是在ffplay 上做的二次封装它的播放,动态方法绑定之后 会进行ffmpeg 相关的初始化工作ijkmp_global_init() -> ijkplayer.c (ijkmp_global_init()) -> (ff_ffplay.c)ffp_global_init()
ff_ffplay.c (ffp_global_init() ) 函数如下:
void ffp_global_init()
{
if (g_ffmpeg_global_inited)
return;
ALOGD("ijkmediaplayer version : %s", ijkmp_version());
/* 1.register all codecs, demux and protocols */
avcodec_register_all();
#if CONFIG_AVDEVICE
//2、用于注册输入/输出设备,属于libavdevice模块,是libavformat模块的补充,
//包含muxers与demuxers两部分。设备与平台相关,比如Android、iOS、Windows、Mac、Linux等不同平台提供不同设备
//avdevice_register_all();
#endif
#if CONFIG_AVFILTER
//3. 注册所有filter器 滤镜
avfilter_register_all();
#endif
//4. 注册所有文件格式和编解码库
//即注册所有文件格式打包解包器
av_register_all();
// ijk 层的初始化 主要是 初始化一些支持的协议与 解复用
ijkav_register_all();
//4. 初始化网络相关参数
//默认状态下 , FFMPEG 是不允许联网的 , 必须调用该函数 ,
//初始化网络后 FFMPEG 才能进行联网
avformat_network_init();
//注册一个运行时锁
//使用多个线程同时播放多个视频源的时候,在调用avcodec_open/close的时候,
//可能导致失败,这个可以查阅ffmpeg的源码分析其中的原因,
//失败的主要原因是在调用此2函数时,ffmpeg为了确保该2函数为原子操作,
//在avcodec_open/close两函数的开头和结尾处使用了一个变量entangled_thread_counter
//来记录当前函数是否已经有其他线程进入,如果有其他线程正在此2函数内运行,则会调用失败
av_lockmgr_register(lockmgr);
//查看ffmpeg的日志信息,可使用如下函数
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;
}
3.以上2步 在加载so 时即会完成之后 业务层会执行native_setup() 方法:
IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
MPTRACE("%s\n", __func__);
//创建IjkMediaPlayer 播放实例,
//需要注意的是ijkmp_android_create的入参是一个函数指针,
//这里的传入的是message_loop
// 创建Native层的IjkMediaPlayer对象,并且将message_loop对象赋与mp->msg_loop
IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
JNI_CHECK_GOTO(mp, env, "java/lang/OutOfMemoryError", "mpjni: native_setup: ijkmp_create() failed", LABEL_RETURN);
//将播放器实例 在native 层做一次绑定,方便其他接口调用,将mp 赋值给了 结构体
// 通过JNI将上步创建的IjkMediaPlayer对象的指针地址保存到Java层的mNativeMediaPlayer属性中
// 并且释放旧的IjkMediaPlayer
// J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer 成员变量
// jfieldID field_mNativeMediaPlayer
// 文件在 :IjkMediaPlayer.c
// 此处需要特别注意的是 J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer
// 实例的构造是通过宏进行类加载的
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);
}
ijkmp_android_create函数在文件ijkplayer_android.c 中,源码如下:
这里传入了函数message_loop,函数里面的内容后面分析,可以看到,ijkmp_create的入参也是一个函数指针,文件在 :ijkplayer.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;
}
这里面的流程我们也比较熟悉了,首先是去底层创建FFmpeg,之后会将上面传下来的msg_loop赋值给IjkMediaPlayer结构体维护的函数指针变量msg_loop。进入ffp_create函数看一下跟消息队列相关的内容:
FFPlayer *ffp_create()
{
av_log(NULL, AV_LOG_INFO, "av_version_info: %s\n", av_version_info());
av_log(NULL, AV_LOG_INFO, "ijk_version_info: %s\n", ijk_version_info());
/* 1.申请FFPlayer结构体内存并初始化为0 */
FFPlayer* ffp = (FFPlayer*) av_mallocz(sizeof(FFPlayer));
if (!ffp)
return NULL;
/* 2.初始化ffp的消息队列msg_queue */
msg_queue_init(&ffp->msg_queue);
ffp->af_mutex = SDL_CreateMutex();
ffp->vf_mutex = SDL_CreateMutex();
/* 3.对FFPlayer结构体成员进行reset操作 */
ffp_reset_internal(ffp);
ffp->av_class = &ffp_context_class;
ffp->meta = ijkmeta_create();
av_opt_set_defaults(ffp);
las_stat_init(&ffp->las_player_statistic);
return ffp;
}
初始化各个结构体的关联图:
三、播放阶段
1.native_setup执行完,业务层通过setDataSource 设置文件地址,这个地址最终会传给native 层。
setDataSource 最终会调用到ijkmp_set_data_source_l
- 将原来的mp->data_source指针释放
- 重新将url生成char *赋值给mp->data_source
- 将ijkmediaplayer对象的状态修改成MP_STATE_INITIALIZED
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;
}
2.接着进入prepareAsync阶段最终也会调用到ijkmp_prepare_async_l中
- 将ijkmediaplayer的状态修改为MP_STATE_ASYNC_PREPARING
- 初始化消息队列&mp->ffplayer->msg_queue
- 初始化消息处理线程,线程处理function为ijk_msg_loop
- 调用ffp_prepare_async_l调用ffmpeg中的方法开始prepare
static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)
{
...
ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING);
// 向&mp->ffplayer->msg_queue中丢入一个FFP_MSG_FLUSH消息
// 最终目的只是初始化Native的MessageQueue
msg_queue_start(&mp->ffplayer->msg_queue);
// released in msg_loop
ijkmp_inc_ref(mp);
// 创建处理消息队列的线程,
// 向Java层IjkMediaPlayer的postEventFromNative回调
// 该回调会回调到EventHandler中,而队列中只有FFP_MSG_FLUSH这个消息,这只是一个NOP的测试消息
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中真正调用ffmpeg开始准备播放
- 判断url协议是否为rtmp或者rtsp,如果是则取消timeout参数
- 如果url长度大于1024,则加入ijklongurl参数
- 调用stream_open打开视频流,使用FFPlayer播放
int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
{
...
if (av_stristart(file_name, "rtmp", NULL) ||
av_stristart(file_name, "rtsp", NULL)) {
// There is total different meaning for 'timeout' option in rtmp
av_log(ffp, AV_LOG_WARNING, "remove 'timeout' option for rtmp.\n");
av_dict_set(&ffp->format_opts, "timeout", NULL, 0);
}
/* there is a length limit in avformat */
if (strlen(file_name) + 1 > 1024) {
av_log(ffp, AV_LOG_ERROR, "%s too long url\n", __func__);
if (avio_find_protocol_name("ijklongurl:")) {
av_dict_set(&ffp->format_opts, "ijklongurl-url", file_name, 0);
file_name = "ijklongurl:";
}
}
...
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开始打开视频流,其中VideoState也就代表着视频当前的状态,包括帧,数据,解码器等等 初始化帧队列:&is->pictq,&is->subpq,&is->sampq,Queue大小为16
- 初始化数据包队列:&is->videoq,&is->audioq,&is->subtitleq
- 初始化时针:&is->vidclk,&is->audclk,&is->extclk,代表当前视频video,audio的时刻
- 创建视频刷新的线程is->video_refresh_tid
- 创建视频读取线程is->read_tid
- 调用decoder_init初始化视频解码器
static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)
{
VideoState *is; // 视频内容以及状态
...
/* 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;
...
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;
}
整体的播放结构流程图:
附:状态图
状态涵义:
四、参考文档
https://www.jianshu.com/p/42a583c5e968
https://blog.csdn.net/fanyun_01/article/details/121630725
猜你喜欢
- 2024-11-01 关于启动电容器的匹配 启动电容器怎么接线
- 2024-11-01 冷水机的基本运行参数包括哪些? 冷水机参数含义
- 2024-11-01 Java线程池解读:从入门到精通,核心参数全掌握!
- 2024-11-01 使用Java编写求和的代码 使用java编写求和的代码是什么
- 2024-11-01 稳压器启动电流多大? 稳压器输出多少正常
- 2024-11-01 Java容器化参数配置最佳实践 java容器有哪几种
- 2024-11-01 Java变量 java变量的数据类型分为两种
- 2024-11-01 深入理解JVM运行原理:从内存布局到执行流程的全面解析
- 2024-11-01 丹佛斯VLT2900变频器维修后试运行参数设置方法及教...
- 2024-11-01 Java 21:有什么新变化? java+21:有什么新变化?举例说明
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- nginx反向代理 (57)
- nginx日志 (56)
- nginx限制ip访问 (62)
- mac安装nginx (55)
- java和mysql (59)
- java中final (62)
- win10安装java (72)
- java启动参数 (64)
- java链表反转 (64)
- 字符串反转java (72)
- java逻辑运算符 (59)
- java 请求url (65)
- java信号量 (57)
- java定义枚举 (59)
- java字符串压缩 (56)
- java中的反射 (59)
- java 三维数组 (55)
- java插入排序 (68)
- java线程的状态 (62)
- java异步调用 (55)
- java中的异常处理 (62)
- java锁机制 (54)
- java静态内部类 (55)
- java怎么添加图片 (60)
- java 权限框架 (55)
本文暂时没有评论,来添加一个吧(●'◡'●)