在stream_open
函数中,初始化完视频,音频,字幕的帧队列后,启动了两个线程
- video_refresh_thread:刷新视频帧线程
- read_thread:读取本地磁盘或者网络视频资源
read_thread流程
- 调用
avformat_alloc_context
- 创建
AVFormatContext
对象,主要为函数指针赋值,确定默认打开文件的函数,以及关闭文件的函数
- 创建
- 调用
avformat_open_input
- 调用
init_input
:打开文件,探测视频格式 - 调用
avio_skip
:跳过初始化的字节 - 调用
ff_id3v2_read_dict
:判断是否有id3v2格式的字段 - 调用
s->iformat->read_header
:从buffer里面读取视频头,确定解码器 - 调用
ff_id3v2_parse_chapters
:解析id3v2中的章节、描述等信息
- 调用
- 调用
avformat_find_stream_info
:解析视频中的各个流的信息,如video、audio流 - 调用
avformat_seek_file
:判断是否有seek操作,需要seek文件 - 调用
avformat_match_stream_specifier
:分离audio,video流信息,判断当前流的类型 - 调用
av_find_best_stream
:根据当前ffplayer找到video,audio,subtitle的流(一般的视频各个流都只有一个) - 调用
stream_component_open
:打开audio,video的流信息,根据流信息找到decoder,然后开启各自的线程进行解码 - 调用
ffp_notify_msg1
发送FFP_MSG_PREPARED
消息 - 进入无限循环,从解析流的线程中获取Packet,同步到video_refresh_thread线程中,进行时钟同步,开始播放
- 判断是否有seek操作
is->seek_req
,若有则调用avformat_seek_file
- 判断是否当前video、audio、subtitle的
PacketQueue
已满,如果已经满了,则直接进入下一次循环 - 判断当前视频是否暂停或者播放完成,判断是否为循环播放,以及循环播放次数,重新seek到start_time的位置,若未设置,默认为0
- 调用
av_read_frame
读取packet帧 - 将对应的packet放到对应的video、audio、subtitle的
PacketQueue
中 - 判断
ffp->packet_buffering
,如果是的话,则调用ffp_check_buffering
检查Buffer
- 判断是否有seek操作
read_thread主要函数分析
- avformat_alloc_context分析
- 创建
AVFormatContext
对象 - 为
s->io_open
赋值为io_open_default
,之后文件打开用该函数指针指向的函数打开文件
static void avformat_get_context_defaults(AVFormatContext *s) { ... s->io_open = io_open_default; s->io_close = io_close_default; ... } AVFormatContext *avformat_alloc_context(void) { AVFormatContext *ic; ic = av_malloc(sizeof(AVFormatContext)); if (!ic) return ic; avformat_get_context_defaults(ic); ... return ic; }
- avformat_open_input分析
在init_input
函数中会通过s->io_open
打开文件,而在avformat_alloc_context
初始化AVFormatContext
的时候,将io_open_default
函数指针赋值给了s->io_open
。所以如果没有修改的话,则使用该函数打开文件。
在该函数(io_open_default
)中:
- 根据文件名找到对应的protocol。如Http,Tcp,Rtsp等
- 通过对应protocol的url_open2打开链接
- 解析出protocol以及hostname
- 替换http协议为tcp协议,到tcp.c中的tcp_open
- 根据DNS寻找域名缓存
- 如果没找到,则判断如果支持PTHREAD,则开启线程通过getAddressInfo获取域名
int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options) { ... // 打开文件IO,并且探测文件格式 if ((ret = init_input(s, filename, &tmp)) < 0) goto fail; // 探测结果赋值 s->probe_score = ret; ... // 跳过字节数 avio_skip(s->pb, s->skip_initial_bytes); ... /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */ if (s->pb){ // 读取文件中的id3v2字段的值 ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta); } ... // 从buffer中读取文件头,确定文件头正确 if (!(s->flags&AVFMT_FLAG_PRIV_OPT)) { if (s->iformat->read_header2) { if (options) av_dict_copy(&tmp2, *options, 0); if ((ret = s->iformat->read_header2(s, &tmp2)) < 0) goto fail; } else if (s->iformat->read_header && (ret = s->iformat->read_header(s)) < 0) goto fail; } ... // 解析文件id3v2中的apic以及chapter if (id3v2_extra_meta) { if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") || !strcmp(s->iformat->name, "tta")) { if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0) goto fail; if ((ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0) goto fail; } else av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n"); } ff_id3v2_free_extra_meta(&id3v2_extra_meta); if ((ret = avformat_queue_attached_pictures(s)) < 0) goto fail; ... return ret; }
- stream_component_open分析
/* open a given stream. Return 0 if OK */ static int stream_component_open(FFPlayer *ffp, int stream_index) { ... // 根据stream_index找到对应的AVCodec codec = avcodec_find_decoder(avctx->codec_id); switch (avctx->codec_type) { case AVMEDIA_TYPE_AUDIO : is->last_audio_stream = stream_index; forced_codec_name = ffp->audio_codec_name; break; case AVMEDIA_TYPE_SUBTITLE: is->last_subtitle_stream = stream_index; forced_codec_name = ffp->subtitle_codec_name; break; case AVMEDIA_TYPE_VIDEO : is->last_video_stream = stream_index; forced_codec_name = ffp->video_codec_name; break; default: break; } // 根据forced_codec_name构造AVCodec if (forced_codec_name) codec = avcodec_find_decoder_by_name(forced_codec_name); ... avctx->codec_id = codec->id; ... ic->streams[stream_index]->discard = AVDISCARD_DEFAULT; switch (avctx->codec_type) { case AVMEDIA_TYPE_AUDIO: ... /* prepare audio output */ // 打开audio if ((ret = audio_open(ffp, channel_layout, nb_channels, sample_rate, &is->audio_tgt)) < 0) goto fail; ... // 初始化audio的解码器 decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread); ... // 开启线程执行audio_thread开始audio解码 if ((ret = decoder_start(&is->auddec, audio_thread, ffp, "ff_audio_dec")) < 0) goto out; SDL_AoutPauseAudio(ffp->aout, 0); break; case AVMEDIA_TYPE_VIDEO: ... // 初始化视频的解码器 decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread); // 打开Pipeline的视频解码器 ffp->node_vdec = ffpipeline_open_video_decoder(ffp->pipeline, ffp); // 启动video_thread线程开始视频解码 if ((ret = decoder_start(&is->viddec, video_thread, ffp, "ff_video_dec")) < 0) goto out; ... break; case AVMEDIA_TYPE_SUBTITLE: ... ffp_set_subtitle_codec_info(ffp, AVCODEC_MODULE_NAME, avcodec_get_name(avctx->codec_id)); decoder_init(&is->subdec, avctx, &is->subtitleq, is->continue_read_thread); if ((ret = decoder_start(&is->subdec, subtitle_thread, ffp, "ff_subtitle_dec")) < 0) goto out; ... return ret; }
结论
在read_thread的过程中,发现比较耗时的地方只有三个:
- s->io_open
- s->iformat->read_header
- SDL_Delay(20)
这三个地方总共耗时加起来大概已经180ms左右,所以需要针对这三个过程进行优化。至此,ijkplayer的prepared过程结束。在video_thread,audio_thread等解码完成后,会将解码完成的数据包同步到video_refresh_thread线程中进行时钟同步,同步完后,则会开始绘制第一帧。此时视频开始播放。