赞
踩
AvIOContext使用场景是: 使用 ffmpeg 相关解码代码要编译成 wasm 在浏览器端使用,js 层面拿到视频 buffer 数据(拉取的 m3u8 分片也好,本地上传的视频文件等等),将 buffer 传递给 c 解封装、解码,这时候就用到 AVIOContext AVIOContext AVIOContext 主要使用逻辑: 我们有一块大的视频文件 buffer,然后 ffmpeg 对这块数据的访问借助于 io 上下文,io 上下文上自己维护了一个小的 buffer(注意: 先看此上下文结构体中一些重要的属性: avio.h 中关于 io 上下文 buffer 主要概念的图示: avio_alloc_context()方法签名: 这几个参数分别对应了上面结构体介绍中的对应属性,看实际使用流程: io 上下文使用流程
主要实现功能就是从视频 buffer 某个位置开始,copy 一段数据到 io 上下文小 buffer 参数介绍: opaque: 指向 自定义 BufferData 结构体的实例,因为要从视频 buffer 数据中不断通过 read_packet 读数据到 io 上下文小 buffer。 buf: io 上下文小 buffer 的开始位置,也就是上面定义的 buf_size: 就是 io 上下文小 buffer 的大小,如上定义的
至此,就通过自定义 io 上下文,让 AVFormatContext 可以解封装提供的视频 buffer 数据了,之后的解封装、解码流程和不使用 io 上下文一样 seekseek 功能研究卡住了几天,原因有二,1: AVIOContext 自定义 seek 函数的实现逻辑不清楚, 2: 对 ts 格式文件 精准 seek 存在花屏或解码失败问题,原以为自己实现逻辑存在问题,实际上对于 ts 格式,没有像 mp4 一样有地方存储所有关键帧的位置偏移信息,ffmpeg 也无能为力 ffmpeg 中 seek 功能通过 av_seek_frame()方法来进行 AVIOContext 自定义 seek 函数 io 上下文 seek 函数的主要逻辑是: 原始视频大 buffer 和长度知道,通过 seek 方法来把大 buffer 的要读取位置指定到某个位置 回调签名: 参数介绍: opaque: 同 read_packet 回调,原始视频 buffer 信息的结构体 offset: 要 seek 到的位置,可以是相对原始视频的起始位置,可以是相对 io 上下文小 buffer 的起始位置,取决于 whence whence: seek 的类型,取值为 AVSEEK_SIZE 、SEEK_CUR 、SEEK_SET、SEEK_END, AVSEEK_SIZE: 不进行 seek 操作,而是要求返回 视频 buffer 的长度大小 SEEK_CUR: 表示 offset 是相对 io 上下文小 buffer 开始位置的 SEEK_SET: 表示 offset 是相对 原始 buffer 开始位置的 SEEK_END: 表示 offset 是相对 原始 buffer 结束位置的
所以自定义 io seek 函数实现如下即可: 精准 seek av_seek_frame 要想不花屏需要设置 flag 对于 mp4 格式没毛病,seek 到里指定 pts 之前最近的关键帧,然后开始解码,从关键帧到指定的 pts 之前的视频帧可以手动丢弃,然后从指定 pts 位置开始展示 对于 ts 格式效果就没那么好,av_seek_frame() 对 ts 是会精确的 seek 到指定的 pts 位置的,但找不到 pts 之前最近的关键帧,指定 AVSEEK_FLAG_BACKWARD 也不行,效果就是: 从指定的 pts 位置开始解码,花屏直到遇到下一个关键帧,对于单个 ts 分片,只在开头有一个关键帧的这种,seek 后可能从指定的 pts 位置开始直接全部解码失败了。测试对一个 ts 文件 通过 ffplay 最后,av_seek_frame()后需要刷新解码器上下文 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。