基础知识
AVFormatContext->pb
是一个AVIOContext结构体,负责IO操作。
一般情况下,我们通过avio_open函数创建并初始化AVFormatContext->pb;通过avio_closep函数关闭AVFormatContext->pb。
1. // 创建AVIOContext,用于读写url标识的文件
2. int avio_open(AVIOContext **s, const char *url, int flags);
3. // 关闭AVIOContext资源
4. int avio_closep(AVIOContext **s);
Mux流程:
- avio_open(打开AVIOContext)
- avformat_write_header(写封装格式头信息)
- av_interleaved_write_frame(写入AVPacket)
- av_write_trailer(写封装格式尾信息)
- avio_closep(关闭AVIOContext)
边合成边上传
所谓边合成边上传,就是在写文件的同时,通过回调向外抛出一份Buffer数据,业务侧可以基于Buffer数据同步做文件上传。
核心逻辑就是创建自定义AVIOContext,接管文件IO。
1. // 创建Buffer
2. uint8_t *avio_buffer = (uint8_t*)av_malloc(avio_ctx_buffer_size);
3. // 创建AVIOContext,read_packet函数负责读buffer,write_packet负责写buffer,seek_user负责seek
4. AVIOContext io = avio_alloc_context(avio_buffer, avio_ctx_buffer_size, 1, opaque, &read_packet, &write_packet, &seek_user);
5. io->seekable = AVIO_SEEKABLE_NORMAL;
7. AVFormatContext->pb = io;
Mux时,AVIOContext->write_packet
收到Buffer数据,可以一边写文件,一边向外回调Buffer,业务侧进行同步上传。
下面的48字节,一开始就会输出,其中ftyp和free box是固定的40字节,最后8字节表示mdat box header,在所有AVPacket输出完之后(只有所有AVPacket都输出完了,才知道mdat box size),会更新40~43字节的mdat box size。
1. `ftyp box header`
2. `0, 0, 0, 20, // ftyp box 共32字节`
3. `66, 74, 79, 70,`
4. `69, 73, 6f, 6d,`
5. `0, 0, 2, 0,`
6. `69, 73, 6f, 6d,`
7. `69, 73, 6f, 32,`
8. `61, 76, 63, 31,`
9. `6d, 70, 34, 31,`
11. `free box header`
12. `0, 0, 0, 8, // free box 共8字节`
13. `66, 72, 65, 65,`
15. `mdat box header`
16. `0, d, a1, af,`
17. `6d, 64, 61, 74,`
最后生成的Mp4文件,正好可以与前48字节对应起来:
输出完所有AVPacket之后,调用av_write_trailer输出尾部数据,例如:Mp4的moov box。
调用av_write_trailer之后,AVIOContext->write_packet首先重写mdat box size(40~43字节),然后输出moov数据。
1. `moov box header`
2. `0, 0, 10, 2d, // moov box size`
3. `6d, 6f, 6f, 76,`
5. `// 后续为moov具体内容`
6. `0, 0, 0, 6c,`
7. `6d, 76, 68, 64`
8. `............`
最终生成的Mp4文件,正好与尾部数据一致:
总结
AVIOContext->write_packet
函数首先输出48字节,包含ftyp box、free box、mdat box header,然后输出所有的AVPacket,最后调用av_write_trailer
函数后,AVIOContext->write_packet
函数先调整40~43字节的mdat size,然后输出moov box。
在边合成边上传场景中,可以先缓存48字节的文件头和MOOV文件尾,等到所有AVPacket向外回调之后,再单独回调文件头Buffer和文件尾Buffer。
因为会存在修改已上传数据(40~43)的情况,并且上传SDK并不支持这种操作,所以才分为文件主内容(AVPacket)、文件头和文件尾三部分向外回调。