----------------------------------------
avformat_open_input() 代码分析
author:hjjdebug
date: 2022年 10月 05日 星期三
----------------------------------------
我不想把代码抄下来一行行分析,有人已经这么做了.
而是想就关键问题进行分析.
文件被成功打开后,会返回一个 AVFormatContext 结构.
这是一个复杂的结构,构建也需要分步进行.
说明: 我调试的时候是用ffmpeg 库打开了一个ts 流, 测试代码见后.
就从ts流格式入手吧.
下面从4个方面来分析
----------------------------------------
甲. 输入文件格式是如何确定的? avf->iformat
----------------------------------------
init_input->av_probe_input_buffer2->av_probe_input_format2 来决定的.
其输入参数是pd, 输出参数是score得分和AVInputFormat *
score 满分100, 得分越高可信度越高.
1. 先解释一下输入参数 pd 是什么吧,
它是探测数据结构.
typedef struct AVProbeData {
const char *filename;
unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */
int buf_size; /**< Size of buf except extra allocated bytes */
const char *mime_type; /**< mime_type, when known. */
} AVProbeData;
意义很明确,能看得懂,就是检测缓冲区中的一段数据,下面给个pd 实例
(gdb) p pd
$3 = {
filename = 0x7fffffffe22f "/opt/test/test1.ts",
buf = 0x555555562360 "G@\021\020",
buf_size = 2048,
mime_type = 0x0
}
2. av_probe_input_format2 是如何探测的? 它返回score及s->iformat.
while ((fmt1 = av_demuxer_iterate(&i))) { //这下会枚举几百种格式
score = fmt1->read_probe(&pd);
}
原来它是枚举每一种输入格式,调用输入格式的探测函数,返回得分,得分高者即为探测的格式.
----------------------------------------
乙: avf->nb_streams 是如何确定的? 答:在read_header中确定
----------------------------------------
ts 流中,找到pmt 表时,就能确定streams 的个数.
此时的调用栈为:
#0 0x00007ffff6654284 in avformat_new_stream (s=0x555555559040, c=0x0) at libavformat/utils.c:4596
#1 0x00007ffff657d285 in pmt_cb (filter=0x55555555ee80, section=0x55555555de00 "\002\260\035", section_len=32) at libavformat/mpegts.c:2418
#2 0x00007ffff6576fcc in write_section_data (ts=0x55555556a3c0, tss1=0x55555555ee80, buf=0x5555555624dd "\002\260\035", buf_size=183, is_start=1) at libavformat/mpegts.c:466
#3 0x00007ffff657e816 in handle_packet (ts=0x55555556a3c0, packet=0x5555555624d8 "GP", pos=564) at libavformat/mpegts.c:2810
#4 0x00007ffff657f020 in handle_packets (ts=0x55555556a3c0, nb_packets=26595) at libavformat/mpegts.c:2975
#5 0x00007ffff657f694 in mpegts_read_header (s=0x555555559040) at libavformat/mpegts.c:3093
#6 0x00007ffff6644e51 in avformat_open_input (ps=0x555555558018 <avf>, filename=0x7fffffffe22f "/opt/test/test1.ts", fmt=0x0, options=0x0) at libavformat/utils.c:609
大致意思为,
1. 调用iformat->read_header(s) 此例就调到了mpegts_read_header(s), 因为ts流的read_header 指针就指向这里
此处曾把AVFormatContext s 放到 ts->stream
ts->stream = s;
此处 ts 是私有类 MpegTSContext, 后面avformat_new_stream 时会用到它
2. 调用 handle_packets(ts, probesize / ts->raw_packet_size);
此处 probesize=5000,000 tw->raw_packet_size 是188字节,传进的是包的个数26595, ts 是个什么变量呢?
MpegTSContext *ts = s->priv_data; // ts 是AVFormatContext 的私有数据,在init_input()时,获得了inputformat.,然后分配私有数据.并依据options初始化了这个私有类.
/* Allocate private data. */
if (s->iformat->priv_data_size > 0) {
if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {
ret = AVERROR(ENOMEM);
goto fail;
}
if (s->iformat->priv_class) {
//请正确理解变量,指针,指针的指针这3层关系,一个变量,可以存数据(data),也可以存数据地址(指针),也可以存指针的地址(指针的指针)
//例如数据时int,则后面时int*, int **这样的声明关系
*(const AVClass **) s->priv_data = s->iformat->priv_class;
av_opt_set_defaults(s->priv_data);
if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)
goto fail;
}
}
此处:
s 是AVFormatContext,
ts 是私有类 MpegTSContext
ts 结构有一个8K 个 mpeg ts 过滤器 MpegTSFilter *pids[8192];
这些过滤器的指针用到的会被填充. 例如pat 过滤器, pmt 过滤器,及其它过滤器.
mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);
mpegts_open_section_filter(ts, pmt_pid, pmt_cb, ts, 1);
...
该函数的原型如下,ts 是上下文,pid,cb 必须, 后面ts 是作为opaque 传入的,是cb 的参数, 1是是否检查crc。
MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts,
unsigned int pid,
SectionCallback *section_cb,
void *opaque,
int check_crc)
struct MpegTSFilter {
int pid;
int es_id;
...
enum MpegTSFilterType type;
union {
MpegTSPESFilter pes_filter;
MpegTSSectionFilter section_filter;
} u;
}
struct MpegTSSectionFilter {
int section_index; //灵活使用
int section_h_size; // 由数据流中读取填充
...
uint8_t *section_buf; // 数据流中的地址
SectionCallback *section_cb; //回调函数
void *opaque; //回调参数
}
后面就是一个包一个包的扫描了,读一个包read_packet(s, packet, ts->raw_packet_size, &data); 这里就不分析了,
3. 调用handle_packet
处理一个包. ret = handle_packet(ts, data, avio_tell(s->pb)); //从调用可见data 正指向一个包的开头,pos 是文件的数据偏移
再调用 write_section_data, 其参数 tss1 是MpegTSFilter指针,这个指针来于包的pid, tss1 = ts->pids[pid];
此时的包ID肯定是pmt的包ID(pmt的包id 是解析pat包得到的), 因而会调用pmt_cb
tss->section_cb(tss1, cur_section_buf, tss->section_h_size);
MpegTSSectionFilter *tss = &tss1->u.section_filter; // tss1是tsfilter, tss是sectionFilter, tss1更大一些.
uint 8 *cur_section_buf = tss->section_buf;
此处的tss 是 MpegTSSectionFilter
前面注册的回调就是pmt_cb, 因而 tss->section_cb(函数指针) == pmt_cb
4. 调用 pmt_cb
它传入的第一个参数是tss1, MpegTSFilter *, 第二个参数是section数据指针,第三个是大小
pmt 分析会读取pmt包头, 然后进行读取pmt包体找到所有stream. 当找到新的stream 后,要创建一个stream
pes = add_pes_stream(ts, pid, pcr_pid); //添加一个pes 流 , 其中, pes->stream = ts->stream;
if (pes && !pes->st) {
st = avformat_new_stream(pes->stream, NULL); // 传进去的参数就是 AVFormatContext * ,见前述
st->id = pes->pid;
}
5. 调用avformat_new_stream , 然后avf->nb_streams 就会加1, 即扫描pmt表,发现一个流就会创建一个流,然后流个数加1
----------------------------------------
丙 AVFormatContext 中av_class 是何时赋值的 ?
----------------------------------------
这个简单,在内存分配时就已经赋值了.
avformat_alloc_context() -> avformat_get_context_defaults(AVFormatContext *s)
s->av_class = &av_format_context_class; //把av_format_context_class 地址付给av_class
av_opt_set_defaults(s);
把avformat对象中数据依据options中定义的默认值来赋值.
AVClass : 代码中定义了很多个AVClass 变量 , 有400多个,
每个class 有名称,有选项指针, 有的还有child_iterate
它们或作为私有类放入demux对象(属于AVInputFormat 对象)
或mux对象(属于AVOutputFormat 对象中)
这些muxer,demuxer会按数组方式组织在一起供程序使用. 程序往往是通过遍历这个列表,找到输入文件是
哪种输出格式. 并通过文件内容分析来填充AVFormatContext 结构内容.
----------------------------------------
丁: stream->codecpar->code_id 是怎样赋值的? 在read_header中赋值.
----------------------------------------
ts 流中,找到pmt 表时,就能确定stream的ID
streamID 会对应到codec的类型和codec的ID
这需要在合适的位置设置断点,并且要会设置.
1.内存断点观察avf->streams, 断在了avformat_new_stream() 中,跟踪,退出到
ff_raw_video_read_header 中,发现: 其id 来自于 iformat
st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
st->codecpar->codec_id = s->iformat->raw_codec_id; 这是h264视频格式的
对于ts流,则是在pmt表中存储有stream_type, 根据stream_type 可找到AVCODEC_ID 并进而找到codec, 如下调用
#0 mpegts_find_stream_type (st=0x555555560680, stream_type=27, types=0x7ffff6683c08 <ISO_types+72>) at libavformat/mpegts.c:884
#1 0x00007ffff6555240 in mpegts_set_stream_info (st=0x555555560680, pes=0x555555560380, stream_type=27, prog_reg_desc=0) at libavformat/mpegts.c:917
#2 0x00007ffff655a5d2 in pmt_cb (filter=0x55555555fcc0, section=0x55555555ec40 "\002\260\035", section_len=32) at libavformat/mpegts.c:2448
#3 0x00007ffff655402c in write_section_data (ts=0x55555556b0c0, tss1=0x55555555fcc0, buf=0x5555555631dd "\002\260\035", buf_size=183, is_start=1) at libavformat/mpegts.c:466
#4 0x00007ffff655b992 in handle_packet (ts=0x55555556b0c0, packet=0x5555555631d8 "GP", pos=564) at libavformat/mpegts.c:2810
#5 0x00007ffff655c1b4 in handle_packets (ts=0x55555556b0c0, nb_packets=26595) at libavformat/mpegts.c:2975
#6 0x00007ffff655c844 in mpegts_read_header (s=0x555555559fc0) at libavformat/mpegts.c:3093
存储codec_id:
st->codecpar->codec_id = types->codec_id;
(gdb) p *types
$13 = {
stream_type = 27,
codec_type = AVMEDIA_TYPE_VIDEO,
codec_id = AV_CODEC_ID_H264
}
上面数据怎么来的?
pmt表中存储了类型27(0x1b), 从下面表格中就可以找到AV_CODEC_ID_H264
static const StreamType ISO_types[] = {
{ 0x01, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG2VIDEO },
{ 0x02, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG2VIDEO },
{ 0x03, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_MP3 },
{ 0x04, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_MP3 },
{ 0x0f, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC },
{ 0x10, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG4 },
{ 0x1b, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264 },
{ 0x1c, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC },
{ 0x20, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264 },
{ 0x21, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_JPEG2000 },
{ 0x24, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_HEVC },
{ 0x42, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_CAVS },
{ 0xd1, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_DIRAC },
{ 0xd2, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_AVS2 },
{ 0xea, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_VC1 },
{ 0 },
};
如此程序也就分析完毕了. 是的,这是对庞杂代码的简要概括. 保留了骨干,抛弃一些简易的东西.
测试代码:
#include "libavformat/avformat.h"
#include "libavutil/timestamp.h"
AVFormatContext *avf = NULL;
int main(int argc, char **argv)
{
if(argc==1)
{
printf("usage:%s <file>\n",argv[0]);
return 1;
}
const char *filename=argv[1];
avf = avformat_alloc_context();
int ret;
if ((ret = avformat_open_input(&avf, filename, NULL, NULL)) < 0) {
fprintf(stderr, "%s: %s\n", filename, av_err2str(ret));
return 1;
}
if ((ret = avformat_find_stream_info(avf, NULL)) < 0) {
fprintf(stderr, "%s: could not find codec parameters: %s\n", filename,
av_err2str(ret));
return 1;
}
//read packet
AVPacket packet;
ret = av_read_frame(avf, &packet);
if (ret < 0) {
printf("read: %d (%s)\n", ret, av_err2str(ret));
} else {
AVRational *tb = &avf->streams[packet.stream_index]->time_base;
printf("read: size=%d stream=%d dts=%s(%s) pts=%s(%s)\n",
packet.size, packet.stream_index,
av_ts2str(packet.dts), av_ts2timestr(packet.dts, tb),
av_ts2str(packet.pts), av_ts2timestr(packet.pts, tb));
av_packet_unref(&packet);
}
avformat_close_input(&avf);
return 0;
}
运行结果:
./test_open /opt/test/test1.ts
read: size=25709 stream=0 dts=126000(1.4) pts=133200(1.48)
参考1: ffmpeg4.4 源代码
参考2: pmt 表