本系列是对ijkPlayer进行源码分析,本篇是对ijkPlayer的整体概述,分析了主要流程、主要结构体和初始化流程,为后面的文章打下基础。
- 本系列如下:
整体概述
视频渲染流程
音频播放流程
read线程流程
音频解码流程
视频解码流程
视频向音频同步
read_thread -> PacketQueue(AVPacket) -> FrameQueue(AVFrame) -> 渲染
对应关系 | PacketQueue | FrameQueue | Clock | 解码线程 | 渲染线程 |
---|---|---|---|---|---|
视频 | videoq | pictq | vidclk | video_thread | video_refresh_thread |
音频 | audioq | sampq | audclk | audio_thread | aout_thread |
字幕 | subtitleq | subpq | extclk | subtitle_thread | video_refresh_thread |
在native_setup方法中创建了IjkMediaPlayer、FFPlayer、IJKFF_Pipeline
在prepare阶段通过stream_open创建了VideoState。
表示native层的Player,与Java层一对一绑定。作为Java到c的入口封装。
struct IjkMediaPlayer {
volatile int ref_count;
pthread_mutex_t mutex;
FFPlayer *ffplayer;
int (*msg_loop)(void*);
SDL_Thread *msg_thread;
SDL_Thread _msg_thread;
int mp_state;
char *data_source;
void *weak_thiz;
int restart;
int restart_from_beginning;
int seek_req;
};
具体的播放器,internal player,包含编码、输出等;代码进入到ff_ffplay.c后,就都使用的是FFPlayer。持有VideoState。
typedef struct FFPlayer {
VideoState *is;
/* extra fields */
SDL_Aout *aout;
SDL_Vout *vout;
struct IJKFF_Pipeline *pipeline;
struct IJKFF_Pipenode *node_vdec;
MessageQueue msg_queue;
}
VideoState,在stream_open中被创建,表示播放过程中的所有状态。
typedef struct VideoState {
Clock audclk;
Clock vidclk;
Clock extclk;
FrameQueue pictq;
FrameQueue subpq;
FrameQueue sampq;
Decoder auddec;
Decoder viddec;
Decoder subdec;
PacketQueue audioq;
PacketQueue subtitleq;
PacketQueue videoq;
int seek_req;
double frame_timer;
int abort_request;
int force_refresh;
int paused;
}
ffpipeline封装了视频解码器和音频解码器/输出,但实现都是通过函数指针调用别人的,有点像门面模式。
定义在ff_ffpipeline.h 和 ff_ffpipeline.c中
// ffpipeline_android.c中
typedef struct IJKFF_Pipeline_Opaque {
FFPlayer *ffp;
SDL_mutex *surface_mutex;
jobject jsurface;
volatile bool is_surface_need_reconfigure;
bool (*mediacodec_select_callback)(void *opaque, ijkmp_mediacodecinfo_context *mcc);
void *mediacodec_select_callback_opaque;
SDL_Vout *weak_vout;
float left_volume;
float right_volume;
} IJKFF_Pipeline_Opaque;
// 在ff_ffpipeline.h
typedef struct IJKFF_Pipeline_Opaque IJKFF_Pipeline_Opaque;
typedef struct IJKFF_Pipeline IJKFF_Pipeline;
struct IJKFF_Pipeline {
SDL_Class *opaque_class;
IJKFF_Pipeline_Opaque *opaque;
void (*func_destroy) (IJKFF_Pipeline *pipeline);
IJKFF_Pipenode *(*func_open_video_decoder) (IJKFF_Pipeline *pipeline, FFPlayer *ffp);
SDL_Aout *(*func_open_audio_output) (IJKFF_Pipeline *pipeline, FFPlayer *ffp);
IJKFF_Pipenode *(*func_init_video_decoder) (IJKFF_Pipeline *pipeline, FFPlayer *ffp);
int (*func_config_video_decoder) (IJKFF_Pipeline *pipeline, FFPlayer *ffp);
};
IJKFF_Pipeline *ffpipeline_alloc(SDL_Class *opaque_class, size_t opaque_size);
void ffpipeline_free(IJKFF_Pipeline *pipeline);
void ffpipeline_free_p(IJKFF_Pipeline **pipeline);
// 打开视频解码器
IJKFF_Pipenode *ffpipeline_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp);
// 打开音频解码器
SDL_Aout *ffpipeline_open_audio_output(IJKFF_Pipeline *pipeline, FFPlayer *ffp);
// 异步初始化IJKFF_Pipenode
IJKFF_Pipenode* ffpipeline_init_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp);
// 异步初始化视频解码器
int ffpipeline_config_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp);
serial字段主要用于标记当前节点的序列号,ffplay中多处用到serial的概念,一般用于区分是否连续数据。当seek时会触发serial字段变化。
一般情况下新增节点与上一个节点的serial是一样的,但当队列中加入一个flush_pkt后,后续节点的serial会比之前大1。
serial是Packet、PacketQueue、Frame、Clock中有该值,会在各个地方都会判断。
Decoder里有pkt_serial,每次解码Packet时从pkt去取。
// ijkplayer_android.c
IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))
{
// 创建IjkMediaPlayer、FFPlayer
IjkMediaPlayer *mp = ijkmp_create(msg_loop);
// 创建SDL_Vout
mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();
// 创建IJKFF_Pipeline
mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);
// 互相绑定
ffpipeline_set_vout(mp->ffplayer->pipeline, mp->ffplayer->vout);
return mp;
}
IjkMediaPlayer *ijkmp_create(int (*msg_loop)(void*))
{
IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));
mp->ffplayer = ffp_create();
mp->msg_loop = msg_loop; // 赋值,后续prepare会启动线程调用该方法
ijkmp_inc_ref(mp);
pthread_mutex_init(&mp->mutex, NULL);
return mp;
}
FFPlayer *ffp_create() {
FFPlayer *ffp = (FFPlayer *) av_mallocz(sizeof(FFPlayer));
msg_queue_init(&ffp->msg_queue);
ffp->af_mutex = SDL_CreateMutex();
ffp->vf_mutex = SDL_CreateMutex();
ffp->stat_mutex = SDL_CreateMutex();
ffp_reset_internal(ffp);
ffp->av_class = &ffp_context_class;
ffp->meta = ijkmeta_create();
av_opt_set_defaults(ffp);
return ffp;
}
VideoState
在stream_open中被创建,封装了所有流程中需要的参数,大杂烩。
Java层prepareAsync
-> ijkplayer_jni.c#IjkMediaPlayer_prepareAsync
-> ijkplayer.c#ijkmp_prepare_async
-> ijkplayer.c#ijkmp_prepare_async_l,创建ijkmp_msg_loop
线程,启动msg_queue
-> ff_ffplay.c#ffp_prepare_async_l,创建SDL_Aout,调用stream_open开始进入播放流程
static int ijkmp_prepare_async_l(IjkMediaPlayer *mp){
ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING); // 改变状态
msg_queue_start(&mp->ffplayer->msg_queue); // 启动message_queue
SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");
ffp_prepare_async_l(mp->ffplayer, mp->data_source)
}
int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name){
VideoState *is = stream_open(ffp, file_name, NULL);
ffp->is = is;
}
so加载,调用JNI_OnLoad方法,执行相关初始化操作
libLoader.loadLibrary(“ijkffmpeg”); // ffmpeg
libLoader.loadLibrary(“ijksdl”); // sdl
libLoader.loadLibrary(“ijkplayer”); // player,文件依赖了ijkj4a,只打了这三个so
Java层调用:
new ijkMediaPlayer,构造函数调用native_setup
mMediaPlayer.setDataSource
mMediaPlayer.setDisplay
mMediaPlayer.prepareAsync
mMediaPlayer.start
消息队列,会在后面单独分析
ijkplayer_jni.c#static int message_loop(void *arg)
ijkplayer.c#prepare中创建线程,启动loop;
消息队列是使用锁机制实现的looper那一套,等待唤醒实现的looper。
参考
ijkPlayer主流程分析
ijkplayer框架深入剖析
解析 IJKPlayer
带问题重读ijkPlayer
知乎专栏:音视频技术