摘要:本文主要描述了FFmpeg中用于打开编解码器接口av_read_frame
的具体调用流程,详细描述了该接口被调用时所作的具体工作。
关键字:ffmpeg
、av_read_frame
读者须知:读者需要了解FFmpeg的基本使用流程,以及一些FFmpeg的基本常识,了解FFmpegIO相关的内容,以及大致的解码流程。
使用FFmpeg解码视频时需要主动打开解码器获得解码器相关的Context,然后直接调用av_read_frame
读取AVPacket
码流数据送到FFmpeg中进行解码即可。本文主要通过源码了解FFmpeg搜索和打开解码器的基本实现原理和FFmpeg具体的解码流程。
2 av_read_frame
av_read_frame
用于从已经打开的文件中读取未经过解码的码流AVPacket
,对于视频帧就是一帧的压缩帧,对于音频帧如果音频是固定大小的话则可以是多帧,否则也是一帧。av_read_frame
内部读取码流时调用avpriv_packet_list_get
和av_read_frame_internal
。
avpriv_packet_list_get
比较简单就是从当前媒体的PackList中取出一帧。av_read_frame
的函数实现比较长,其大致流程为:
- 调用
ff_read_packet
读取一帧码流; - 如果1步骤失败则调用
parse_packet
刷新解析器,否则继续到步骤3; - 如果当前context需要更新解码器context,则将internal的解码器context更新到stream的解码器context;
- 如果成功拿到预期的帧则下一步,否则跳转到步骤1;
- 后续的工作就是解析元数据,计算需要丢弃的数据大小等。
ff_read_packet
会先检查缓冲区是否有帧没有的话就会调用s->iformat->read_packet
即对应个是的解析码流的函数进行解码。每个FFmpeg支持的格式都有一个FormatContext描述对应格式的信息以及解析对应格式的函数指针,比如下面是mov的格式描述:
static const AVClass mov_class = {.class_name = "mov,mp4,m4a,3gp,3g2,mj2",.item_name = av_default_item_name,.option = mov_options,.version = LIBAVUTIL_VERSION_INT,
};const AVInputFormat ff_mov_demuxer = {.name = "mov,mp4,m4a,3gp,3g2,mj2",.long_name = NULL_IF_CONFIG_SMALL("QuickTime / MOV"),.priv_class = &mov_class,.priv_data_size = sizeof(MOVContext),.extensions = "mov,mp4,m4a,3gp,3g2,mj2,psp,m4b,ism,ismv,isma,f4v",.flags_internal = FF_FMT_INIT_CLEANUP,.read_probe = mov_probe,.read_header = mov_read_header,.read_packet = mov_read_packet,.read_close = mov_read_close,.read_seek = mov_read_seek,.flags = AVFMT_NO_BYTE_SEEK | AVFMT_SEEK_TO_PTS,
};
从上面的过程能够看出av_read_frame
完全是同步的操作,可能是比较耗时的,因为如果一直拿不到帧就会一直遍历当前媒体文件的buffer,因此一般建议开一个线程读取Packet。