一个专注音视频领域的小圈子

周末时候看到一篇推送说 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 的渲染链了。

核心代码如下:

static int process_frames(AVFilterContext *avctx, AVFrame *outframe, AVFrame *inframe)
{
    // 省略起始代码
    // 得到输入数据
    AVVkFrame *in = (AVVkFrame *)inframe->data[0];
    AVVkFrame *out = (AVVkFrame *)outframe->data[0];
    const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
    const VkFormat *input_formats = av_vkfmt_from_pixfmt(s->vkctx.input_format);
    const VkFormat *output_formats = av_vkfmt_from_pixfmt(s->vkctx.output_format);

    ff_vk_start_exec_recording(vkctx, s->exec);
    cmd_buf = ff_vk_get_exec_buf(s->exec);

    for (int i = 0; i < planes; i++) {
        // 将输入数据绑定到 ImageView 上
        RET(ff_vk_create_imageview(vkctx, s->exec,
                                   &s->input_images[i].imageView, in->img[i],
                                   input_formats[i],
                                   ff_comp_identity_map));

        RET(ff_vk_create_imageview(vkctx, s->exec,
                                   &s->output_images[i].imageView, out->img[i],
                                   output_formats[i],
                                   ff_comp_identity_map));

        s->input_images[i].imageLayout  = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
        s->output_images[i].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
    }
    // 绑定资源描述符
    ff_vk_update_descriptor_set(vkctx, s->pl, 0);
    // 设置好内存屏障
    for (int i = 0; i < planes; i++) {
        // 省略一大串代码
        vk->CmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
                               VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0,
                               0, NULL, 0, NULL, FF_ARRAY_ELEMS(barriers), barriers);
        // 省略一大串代码
    }
    // 设置好 pipeline 和 资源描述符集 descriptorSet
    ff_vk_bind_pipeline_exec(vkctx, s->exec, s->pl);
    vk->CmdDispatch(cmd_buf, FFALIGN(s->vkctx.output_width, CGS)/CGS,
                    s->vkctx.output_height, 1);

    ff_vk_add_exec_dep(vkctx, s->exec, inframe, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
    ff_vk_add_exec_dep(vkctx, s->exec, outframe, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
    // 提交到队列
    err = ff_vk_submit_exec_queue(vkctx, s->exec);
    if (err)
        return err;
    ff_vk_qf_rotate(&s->qf);
    return 0;
}

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

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

原创文章,转载请注明来源:    Vulkan 在 FFmpeg 中的支持