在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线程中进行时钟同步,同步完后,则会开始绘制第一帧。此时视频开始播放。
ijkplayer-android框架详解 ijkPlayer主流程分析 IjkPlayer播放器秒开优化以及常用Option设置 直播中的首屏加载优化