本篇涉及核心代码
ijkmp_prepare_async
在iOS层,调用了prepareToPlay方法
- (void)prepareToPlay
{
......
ijkmp_prepare_async(_mediaPlayer);
}
深入跟进,ijkplayer内部最终会调用到ffplay.c中的
int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
启动播放器的入口函数,在此会设置player选项,打开audio output,最重要的是调用stream_open方法。,还有ijkmp_prepare_async_l中的
mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");
也是相当重要的部分,是上一篇中media_player_msg_loop的创建入口,启动了消息循环(msg_loop)线程。
ffp_prepare_async_l
int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
{
......
/* 判断直播视频流协议rtmp或rtsp */
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);
}
/* AVM格式中有长度限制*/
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:";
}
}
// 开启音频输出
if (!ffp->aout) {
ffp->aout = ffpipeline_open_audio_output(ffp->pipeline, ffp);
if (!ffp->aout)
return -1;
}
......
/*核心部分*/
VideoState *is = stream_open(ffp, file_name, NULL);
.......
return 0;
}
stream_open
根据地址打开视频流,详见代码注释。
static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)
{
......
/* 创建3个队列 */
/* 视频队列初始化: 解码前(videoq)解码后(pictq)*/
if (frame_queue_init(&is->pictq, &is->videoq, ffp->pictq_size, 1) < 0)
goto fail;
/* 字幕队列初始化:解码前(subtitleq)解码后(subpq)*/
if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
goto fail;
/* 音频队列初始化:解码前(audioq)解码后(sampq)*/
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;
......
/* 创建视频渲染线程video_refresh_thread*/
is->video_refresh_tid = SDL_CreateThreadEx(&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout");
......
/* 创建数据读取线程*/
is->read_tid = SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read");
......
}
read_thread(数据读取)
1、创建上下文结构体(表示输入上下文),用于数据填充
ic = avformat_alloc_context();
2、读取网络数据包的信息,探测流的协议,如http还是rtmp等。
err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);
3、探测媒体类型,可得到当前文件的封装格式,音视频编码参数等信息
err = avformat_find_stream_info(ic, opts);
4、核心函数stream_component_open,开启视频、音频解码器,创建相应的解码线程。后面第五篇将深入学习
/* open the streams */
if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {
stream_component_open(ffp, st_index[AVMEDIA_TYPE_AUDIO]);
} else {
ffp->av_sync_type = AV_SYNC_VIDEO_MASTER;
is->av_sync_type = ffp->av_sync_type;
}
ret = -1;
if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
ret = stream_component_open(ffp, st_index[AVMEDIA_TYPE_VIDEO]);
}
if (is->show_mode == SHOW_MODE_NONE)
is->show_mode = ret >= 0 ? SHOW_MODE_VIDEO : SHOW_MODE_RDFT;
if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0) {
stream_component_open(ffp, st_index[AVMEDIA_TYPE_SUBTITLE]);
}
5、开始无限循环,不断获取待播放的数据,调用ffmpeg的av_read_frame()读取AVPacket,然后加入队列。
for (;;) {
//AVPacket pkt;
//进入循环,读取packet
ret = av_read_frame(ic, pkt);
//读取packet之后,根据packet类型分到不同的packetQueue里面去
if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {
packet_queue_put(&is->audioq, pkt);
} else if (pkt->stream_index == is->video_stream && pkt_in_play_range
&& !(is->video_st && (is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC))) {
packet_queue_put(&is->videoq, pkt); stream_component_open
} else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {
packet_queue_put(&is->subtitleq, pkt);
} else {
av_packet_unref(pkt);
}
}
video_refresh_thread视频渲染线程
iOS平台上采用OpenGL渲染解码后的YUV图像,渲染线程为video_refresh_thread
static int video_refresh_thread(void *arg)
{
......
// 不断的显示下一帧
while (!is->abort_request) {
if (remaining_time > 0.0)
// 如果距离下一帧还有时间,就用av_usleep先暂停会
av_usleep((int)(int64_t)(remaining_time * 1000000.0));
remaining_time = REFRESH_RATE;
if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
video_refresh(ffp, &remaining_time);
}
......
}
参考:https://blog.csdn.net/xipiaoyouzi/article/details/74280170