• FFmpeg内存IO模式


    FFmpeg 的社群来了,想加入微信社群的朋友请购买《FFmpeg原理》VIP版 电子书,里有更高级的内容与答疑服务。


    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() 函数的用法,定义如下:

    1. AVIOContext *avio_alloc_context(
    2. unsigned char *buffer,
    3. int buffer_size,
    4. int write_flag,
    5. void *opaque,
    6. int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
    7. int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
    8. 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 文件即可调试,如下:

    1. SOURCES += main.c
    2. 改成
    3. 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模式(内存区作输入或输出) - 叶余

  • 相关阅读:
    经典矩阵试题(一)
    记录:2022-9-19 螺旋矩阵 球会落何处 分页分区
    nacos
    Java 8 新特性 Stream API 介绍与使用
    「运维有小邓」合规审计报表
    R语言绘制矩形树状图
    基于libmpv内核设计开发的视频播放器-高级版(四)
    Rust所有权(非常重要)
    激光雷达与自动驾驶详解
    Leetcode 1619. 删除某些元素后的数组均值
  • 原文地址:https://blog.csdn.net/u012117034/article/details/127760815