FFmpeg内存IO模式
创始人
2024-01-20 10:27:45
0

ffmpeg 支持从网络流 或者本地文件读取数据,然后拿去丢给解码器解码,但是有一种特殊情况,就是数据不是从网络来的,也不在本地文件里面,而是在某块内存里面的。

这时候 av_read_frame() 函数怎样才能从内存把 AVPacket 读出来呢?

FFmpeg 的开发者一早就考虑到这个问题了,所以他们提供了一个自定义 avio 的功能。利用这个功能,你可以自定义封装层的输入函数,也可以自定义封装层的输出函数。

没错,输出你也可以输出到一块内存里面,而不是保存到本地文件。


本文的代码下载地址:GitHub,编译环境是 Qt 5.15.2 跟 MSVC2019_64bit 。

avio 项目 是从 output 项目改造来的,所以一些代码是类似的。

avio 项目的整个流程是这样的,先用了 readFile() 函数读取本地的 mp4 文件到内存,来模拟内存场景。然后调 avio_alloc_context() 自定义输入跟 seek 函数。

重点讲一下 avio_alloc_context() 函数的用法,定义如下:

AVIOContext *avio_alloc_context(unsigned char *buffer,int buffer_size,int write_flag,void *opaque,int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),int64_t (*seek)(void *opaque, int64_t offset, int whence));

参数如下:

1,unsigned char *buffer,这是一个指针,指向一块 av_malloc() 申请的内存。这是我们的自定义函数跟 FFmpeg 的 API 函数沟通的桥梁。

当 write_flag 为 0 时,由 自定义的回调函数 向 buffer 填充数据,FFmpeg API 函数取走数据。

当 write_flag 为 1 时,由 FFmpeg API 函数 向 buffer 填充数据,自定义的回调函数 取走数据。

补充,我测试的时候,这里的 buffer 指针跟 回调函数 里面的 buf 指针,好像不是同一块内存,FFmpeg 注释说,buffer 可能会被替换成另一块内存。

2,int buffer_sizebuffer 内存的大小,通常设置为 4kb 大小即可,对于一些有固定块大小的格式,例如 TS 格式,TS流的包结构是固定长度188字节的,所以你需要设置为 188 字节大小。如果这个值设置得不对,性能会下降得比较厉害,但是不会报错。

3,int write_flag ,write_flag 可以是 0 或者 1,作用是标记 buffer 内存的用途。

4,void *opaque,传递给我们自定义函数用的。

5,int (*read_packet)(...),输入函数的指针。

6,int (*write_packet)(...),输出函数的指针。

7,int (*seek)(...),seek 函数的指针。


再来看一下我们自定义的输入函数 read_packet(),如下:

buf 跟 buf_size 都是 FFmpeg 告诉我们的,告诉我们的自定义函数,要往哪里(buf)写数据,写多少(buf_size)数据。

read_packet() 函数的返回值是实际写入的数据大小,如果是一些网络流,在某个时间确实没从网络读到数据,不能返回 0 ,需要返回错误码,例如 EAGAIN。


再来看一下自定义的 seek 函数 seek_in_buffer(),如下:

这个 seek 函数是必须实现,如果不实现这个函数,直接传 NULL 给 avio_alloc_context() ,在 mp4 格式下会导致 av_read_frame() 读不出来 AVPacket

但是在 flv 格式下是可以不实现 seek 函数的,需不需要实现 seek 是由封装格式决定的。

seek_in_buffer 函数的 offset 跟 whence 参数特别重要。

whence 代表 seek 的类型,主要有 2 个值,如下:

1,AVSEEK_SIZE,不进行 seek 操作,而是返回 视频 buffer 整体的大小。也就是文件大小。

2,SEEK_SET,要进行 seek 操作,seek 到 offset 参数的位置,也就是需要 seek 到 第 offset 个字节。需要把 bd->ptr 指向第 offset 个字节


后面的逻辑就是 av_read_frame() 从内存读到 AVPacket 之后,就丢给解码器,然后再重新编码,输出成另一个 mp4。

最后需要调 avio_context_free(&avio_ctx) 来释放这个 自定义的 avio 上下文。


在 main.c 里面只定义了输入函数,输出函数是在另一个文件 main_write.c 里面的。你只需要修改一下 avio.pro 文件即可调试,如下:

SOURCES += main.c 
改成
SOURCES += main_write.c

main_write.c 里面的重点代码如下:

main_write.c 的逻辑是申请了 100M 的内存,然后把 av_interleaved_write_frame() 输出的数据,全部保存到这 100M 内存里面,可能没用完100M内存。

上图中我新申请了一个 avio 上下文实例来给输出用,不能跟输入用同一个 avio 实例。然后封装格式指定位 flv 了,因为 flv 格式可以不实现 seek

mp4 格式的输出对应的 seek 函数我也不知道怎么实现。可能跟输入一样吧,这里埋个坑,后面填。

然后直接赋值 fmt_ctx_out->pb 即可

fmt_ctx_out->pb = avio_ctx_out;

内存IO模式输出,是不需要调 avio_open2() 的了。


参考文章:

1,ffmpeg AVIOContext 自定义 IO 及 seek

2,FFmpeg内存IO模式(内存区作输入或输出) - 叶余


推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:

Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...