avformat_open_input() 代码分析
----------------------------------------
avformat_open_input() 代码分析
author:hjjdebug
date: 2022年 10月 05日 星期三
----------------------------------------
我不想把代码抄下来一行行分析,有人已经这么做了.
而是想就关键问题进行分析.
文件被成功打开后,会返回一个 AVFormatContext 结构.
这是一个复杂的结构,构建也需要分步进行.
下面从3个方面来分析
说明: 我调试的时候是用ffmpeg 库打开了一个ts 流, 测试代码见后.
就从ts流格式入手吧.
----------------------------------------
甲. 输入文件格式是如何确定的? 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 是如何探测的.
while ((fmt1 = av_demuxer_iterate(&i))) { //这下会枚举几百种格式
score = fmt1->read_probe(&lpd);
}
原来它是枚举每一种输入格式,调用输入格式的探测函数,返回得分,得分高者即为探测的格式.
----------------------------------------
乙: avf->nb_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.,然后分配私有数据.并初始化这个私有类.
/* 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) {
*(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, tss = ts->pids[pid];
此时的包ID肯定是pmt的包ID(pmt的包id 是解析pat包得到的), 因而会调用pmt_cb
tss->section_cb(tss1, cur_section_buf, tss->section_h_size);
此处的tss 是 MpegTSSectionFilter
MpegTSSectionFilter *tss = &tss1->u.section_filter;
uint 8 *cur_section_buf = tss->section_buf;
前面注册的回调就是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 是何时赋值的 ?
----------------------------------------
这个简单,在内存分配时就已经赋值了. 而且固定的付给av_format_context_class, 对于该类的av_option也付给了初始值
avformat_alloc_context() -> avformat_get_context_defaults(AVFormatContext *s)
s->av_class = &av_format_context_class;
av_opt_set_defaults(s);
如此程序也就分析完毕了. 是的,这是对庞杂代码的简要概括. 保留了骨干,抛弃一些简易的东西.
测试代码:
#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 源代码