在stream_open
函数中,初始化完视频,音频,字幕的帧队列后,启动了两个线程
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
消息is->seek_req
,若有则调用avformat_seek_file
PacketQueue
已满,如果已经满了,则直接进入下一次循环av_read_frame
读取packet帧PacketQueue
中ffp->packet_buffering
,如果是的话,则调用ffp_check_buffering
检查BufferAVFormatContext
对象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; }
在init_input
函数中会通过s->io_open
打开文件,而在avformat_alloc_context
初始化AVFormatContext
的时候,将io_open_default
函数指针赋值给了s->io_open
。所以如果没有修改的话,则使用该函数打开文件。
在该函数(io_open_default
)中:
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; }
/* 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的过程中,发现比较耗时的地方只有三个:
这三个地方总共耗时加起来大概已经180ms左右,所以需要针对这三个过程进行优化。至此,ijkplayer的prepared过程结束。在video_thread,audio_thread等解码完成后,会将解码完成的数据包同步到video_refresh_thread线程中进行时钟同步,同步完后,则会开始绘制第一帧。此时视频开始播放。