Vulkan 在 FFmpeg 中的支持

技术答疑,成长进阶,可以加入我的知识星球:音视频领域专业问答的小圈子

周末时候看到一篇推送说 FFmpeg 升级到 5.0 版本了。

其中提到 FFmpeg 引入了 Vulkan 驱动的新滤镜,用于视频水平、垂直翻转。

看到 FFmpeg 引入了 Vulkan ,想着这是要有什么大动作啊,直接利好 Vulkan 嘛?

后来又仔细看了下 FFmpeg 的 Changelog ,原来早在 4.3 版本就已经开始支持 Vulkan 了。

那时候就已经有滤镜支持了,比如 scale_vulkan、chromaber_vulkan 等。

而且还支持在 Linux 平台上通过 Vulkan 使用 AMD 的高级媒体框架(AMF)库,可以用 GPU 来进行 H.264/HEVC 的编码。(Windows 平台用的是 DirectX 接口)

这里提一下 AMF 框架,实际上我也是第一次接触这个。

AMF 全称是 Advanced Media Framework ,翻译为高级媒体框架。它是 AMD 公司出品的,为开发人员提供对 GPU 的访问以进行多媒体处理,通过 AMF 可以进行视频编解码、转码、色彩空间转换等功能。

简单说就是提供了对自家显卡产品能力的调用,可以用它来做编解码的工作。既然 AMD 有了,那么相信 NVIDIA 也有类似的产品。

由此可见后面的趋势:渲染 API 不仅仅是用来做渲染,还是可以用做编解码的,毕竟它是可以直接用 GPU 打交道的。

所以 FFmpeg 5.0 中引入了 Vulkan 新滤镜应该也不是什么大新闻了,毕竟在 4.3 版本就已经有了支持,只是多了几个滤镜,按照开发人员的话来说,就是多了几个 shader 嘛。


接下来就看看这几个新增的 翻转shader 有何不同之处:

如果不了解 Vulkan 流程的话,建议看看 Vulkan 相关的文章,毕竟这里面概念挺多的,但很多流程还是固定的,只要抓到重点就好了。

大概的流程:Vulkan 作为 FFmpeg 中的一个滤镜,那么它肯定要接收代表解码后的 AVFrame 数据,通过将 AVFrame 数据转换为它渲染链结构的输入,经过渲染后,将渲染结果转换为 AVFrame 数据并往下进行传递。

理解上面的流程,剩下的就是去理解 Vulkan 的渲染链了。

核心代码如下:

 1static int process_frames(AVFilterContext *avctx, AVFrame *outframe, AVFrame *inframe)
 2{
 3    // 省略起始代码
 4    // 得到输入数据
 5    AVVkFrame *in = (AVVkFrame *)inframe->data[0];
 6    AVVkFrame *out = (AVVkFrame *)outframe->data[0];
 7    const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
 8    const VkFormat *input_formats = av_vkfmt_from_pixfmt(s->vkctx.input_format);
 9    const VkFormat *output_formats = av_vkfmt_from_pixfmt(s->vkctx.output_format);
10
11    ff_vk_start_exec_recording(vkctx, s->exec);
12    cmd_buf = ff_vk_get_exec_buf(s->exec);
13
14    for (int i = 0; i < planes; i++) {
15        // 将输入数据绑定到 ImageView 上
16        RET(ff_vk_create_imageview(vkctx, s->exec,
17                                   &s->input_images[i].imageView, in->img[i],
18                                   input_formats[i],
19                                   ff_comp_identity_map));
20
21        RET(ff_vk_create_imageview(vkctx, s->exec,
22                                   &s->output_images[i].imageView, out->img[i],
23                                   output_formats[i],
24                                   ff_comp_identity_map));
25
26        s->input_images[i].imageLayout  = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
27        s->output_images[i].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
28    }
29    // 绑定资源描述符
30    ff_vk_update_descriptor_set(vkctx, s->pl, 0);
31    // 设置好内存屏障
32    for (int i = 0; i < planes; i++) {
33        // 省略一大串代码
34        vk->CmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
35                               VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0,
36                               0, NULL, 0, NULL, FF_ARRAY_ELEMS(barriers), barriers);
37        // 省略一大串代码
38    }
39    // 设置好 pipeline 和 资源描述符集 descriptorSet
40    ff_vk_bind_pipeline_exec(vkctx, s->exec, s->pl);
41    vk->CmdDispatch(cmd_buf, FFALIGN(s->vkctx.output_width, CGS)/CGS,
42                    s->vkctx.output_height, 1);
43
44    ff_vk_add_exec_dep(vkctx, s->exec, inframe, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
45    ff_vk_add_exec_dep(vkctx, s->exec, outframe, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
46    // 提交到队列
47    err = ff_vk_submit_exec_queue(vkctx, s->exec);
48    if (err)
49        return err;
50    ff_vk_qf_rotate(&s->qf);
51    return 0;
52}
CPP

以上代码要是看的费劲的话,还是只看核心的 shader 部分吧:

可以看出,做水平或者垂直翻转也只是更改了 texture 采样坐标而已,如果你会 OpenGL 的话,一样可以做出类似的 filter 。

欢迎关注微信公众号:音视频开发进阶

Licensed under CC BY-NC-SA 4.0
粤ICP备20067247号
使用 Hugo 构建    主题 StackedJimmy 设计,Jacob 修改