找回密码
 会员注册
查看: 11|回复: 0

在Windows下玩转多媒体处理框架BMF

[复制链接]

4

主题

0

回帖

13

积分

新手上路

积分
13
发表于 2024-9-30 19:38:01 | 显示全部楼层 |阅读模式
一、简介现代科技网络日益发达,视频已经成为人们生活中不可或缺的一部分。随着互联网和移动设备的普及,视频内容在传播和分享方面发挥着越来越重要的作用。从社交媒体到在线教育,从数字广告到远程工作,视频已经成为人们获取信息、娱乐和交流的主要方式之一。在这样一个视频日益普及的超视频时代,开发一套跨语言、跨设备、跨系统的多媒体处理框架显得尤为重要,这样的框架可以为开发人员提供统一的解决方案,帮助他们在不同的平台上快速、高效地处理多媒体内容,从而提供一致的用户体验和功能,是迎接未来的必然趋势。在当今数字化的世界中,Windows 平台的重要性和关键性无可置疑。作为普通用户的首要选择,Windows 提供了广泛的硬件和软件支持,为用户提供了丰富多彩的体验。特别是在多媒体处理领域,Windows 平台凭借其强大的生态系统和稳定的性能,基本是普通用户的首选。Windows 平台拥有庞大而完善的 DirectX 能力体系,这使得在Windows 环境下可以很方便地实现通过 GPU 加速图像视频处理的性能,这种强大的图形处理能力可以更高效地处理和渲染视频、音频等多媒体内容。特别是对于游戏主播、视频编辑等相关领域的从业者,Windows 平台提供了一个稳定而强大的开发环境,为他们的创作和工作带来了极大的便利和效率。因此,开发一套兼容 Windows 平台的多媒体处理框架具有重要的意义。这不仅可以满足普通用户对于多媒体内容的需求,还可以为专业从业者提供强大的工具和支持。无论是游戏行业、直播行业还是视频编辑领域,都可以受益于这样一套高效、稳定的多媒体处理框架,为用户带来更优质的体验和服务。基于以上两个前提,2023 年 8 月 22 日,火山引擎视频云与NVIDIA正式开源多媒体处理框架 Babit Multimedia Framework (以下统称 BMF 框架),BMF 在 Windows 侧对齐 Linux,目前已经打通了框架的编译、构建、同时支持模块自定义开发,在字节跳动内部,BMF 在 Win 侧已集成多种使用 CPU/GPU 的图像处理算法,服务于抖音直播伴侣业务,目前已有 5 个算法已被成功集成,BMF 框架作为搭建算法与业务的桥梁,通过自定义模块实现算法逻辑与业务的完全解耦,其内部可以很方便地在 Win 侧集成不同图像处理算法。本文将沿三个步骤,全面介绍 BMF 框架在 Windows 端的能力建设与技术实践,首先介绍 BMF 框架在 Windows 环境下如何配置与编译,其次介绍如何在 Windows 环境配置 BMF 开发环境,并展示一个简单 Python 模块的运行过程,最后展示一个基于 DirectX 的全链路图像缩放模块的开发与部署案例,展示 BMF 在 Windows 端友好的兼容性和强大的功能适配能力,助您在 Windows 下玩转BMF!二、编译与构建编译 BMF 框架需要依赖以下环境:MSYS2。提供了一个基于开源软件的本机构建环境,可以在 Windows 上用 Linux 的方式使用多种不同的环境和工具来执行不同的任务。CMake。管理框架构建过程,推荐版本 3.27。Visual Studio 2013 - 2022。BMF 在 Win 端选用兼容性较好的 msvc 编译工具链,目前支持版本 2013 - 2022。以上三个依赖是必须项,下面还有 2 个依赖可供选择是否打开相关的框架能力:Python 3.7 - 3.10。用于编译 BMF Python SDK,如果不提供,则框架无法编译 Python 相关的调用能力FFmpeg 4.4 - 5.1。用于编译 BMF built-in modules(ffmpeg_decoder、ffmpeg_encoder、ffmpeg_filter),如果不提供,默认取消编译相关产物本文在介绍编译过程时,默认会打开以上两个选项,实现一次全链路的编译构建,下面用图文方式介绍 BMF 框架编译过程:打开 Visual Studio 的命令提示环境,建议以管理员方式找到 MSYS2 的安装目录,执行命令: .\msys2_shell.cmd -use-full-path,携带宿主机环境进入 msys2(注意:这里需要保证 CMake 工具已经成功配置进系统环境变量,本文的实验环境默认配置了 CMake 3.27、FFmpeg 4.4、Python 3.7.9 的环境变量)。克隆 BMF 项目,在根目录运行 build_win_lite 脚本,项目设有以下编译控制选项:Build Options:--msvc 设定 msvc 版本,包括[2013, 2015, 2017, 2019, 2022]bmf_ffmpeg 控制是否集成 FFmpeg 相关能力并编译 built-in module--preset 编译配置,包括[x86-Debug, x86-Release, x64-Debug, x64-Release]注意:如果您需要编译 FFmpeg 相关的能力,且您本地的 CMake 版本高于等于 3.28,你还需要设置 ffmpeg 的 include 目录,命令如下:exportINCLUDE="$INCLUDE;C:\path\to\include_for_ffmpeg"以 msvc 2022 编译 x64-Debug 版本为例,脚本执行命令:./build_win_lite.sh--msvc=2022--preset=x64-Debugbmf_ffmpeg执行后会在项目目录的 build_win_lite/x64-Debug 目录生成 BMF.sln 解决方案之后,您可以双击 sln 文件使用 Visual Studio 友好的界面进行项目构建和编译,您也可以使用以下 CMake 命令直接通过命令行进行项目构建:cmake--buildbuild_win_lite/x64-Debug--configDebug--targetALL_BUILD至此,您便可以在 Windows 环境中完成对 BMF 框架的编译与构建,在 Visual Studio 的编译过程如图所示三、开发环境配置与模块运行本章介绍如何搭建 BMF 开发环境,继上文所述,当我们完成 BMF 框架的编译构建后,会生成 output 文件夹,我们需要将 bin 目录与 lib 目录配置进系统环境变量,与此同时,BMF 开发环境还需依赖一个 Win 相关的依赖集合 win_rootfs(https://github.com/BabitMF/bmf/releases/download/files/win_rootfs.tar.gz),需配置环境变量,如图所示:配置完 BMF 环境变量后,我们需要重启 msys2 环境,目的是让 BMF 的环境变量生效,下面展示如何运行一个 Python Module 的测试程序:test_customize_module。首先,需要将 msys2 当前目录切换至编译产物的上级目录,设置一些 msys2 环境的环境变量,配置 BMF 框架的 Python 运行环境exportPYTHONHOME="$(dirname"$(whichpython)")"exportPYTHONPATH=$(pwd)/output/bmf/lib(pwd)/output配置完毕后,进入 python 环境应该可以正常 import bmf 框架,如图所示我们要运行的 customize_module 文件在 output/test/customize_module 目录下的 my_module.py 文件,模块的实现代码如下:frombmfimportModule,Log,LogLevel,InputType,ProcessResult,Packet,Timestamp,scale_av_pts,av_time_base,\BmfCallBackType,VideoFrame,AudioFrameclassmy_module(Module):def__init__(self,node,option=None):self.node_=nodeself.option_=optionpassdefprocess(self,task):for(input_id,input_packets)intask.get_inputs().items():##outputqueueoutput_packets=task.get_outputs()[input_id]whilenotinput_packets.empty():pkt=input_packets.get()##processEOSifpkt.timestamp==Timestamp.EOFog.log_node(LogLevel.DEBUG,task.get_node(),"ReceiveEOF")output_packets.put(Packet.generate_eof_packet())task.timestamp=Timestamp.DONEreturnProcessResult.OK##copyinputpackettooutputifpkt.defined()andpkt.timestamp!=Timestamp.UNSETutput_packets.put(pkt)returnProcessResult.OK可以看到,模块仅仅将帧从输入队列取出,不做任何处理,直接传递至输出队列,我们将要执行的测试程序 test_customize_module.py 的实现如下:importsysimporttimeimportunittestsys.path.append("../../..")sys.path.append("../../c_module_sdk/build/bin/lib")importbmfimportosifos.name=='nt':##Weredefinetimeout_decoratoronwindowsclasstimeout_decoratorstaticmethoddeftimeout(*args,**kwargs):returnlambdaf:f##returnano-opdecoratorelse:importtimeout_decoratorsys.path.append("../../test/")frombase_test.base_test_caseimportBaseTestCasefrombase_test.media_infoimportMediaInfoclassTestCustomizeModule(BaseTestCase)timeout_decorator.timeout(seconds=120)deftest_customize_module(self):input_video_path="../../files/big_bunny_10s_30fps.mp4"output_path="./output.mp4"expect_result='|1080|1920|10.0|MOV,MP4,M4A,3GP,3G2,MJ2|1783292|2229115|h264|'\'{"fps":"30.0662251656"}'self.remove_result_data(output_path)(bmf.graph().decode({'input_path':input_video_path})['video'].module('my_module').encode(None,{"output_path"utput_path}).run())self.check_video_diff(output_path,expect_result)if__name__=='__main__':unittest.main()在测试程序的 33 - 37 行中,我们构建了一个 BMF Graph,首先对输入视频进行解码,随后调起事先我们写好的 Python 模块 my_module 对解码后的视频帧进行一次处理,最后调用 encode 模块对视频帧进行编码,产出输出文件,剩余部分是框架使用 Google Test 框架所进行的一些转码指标的验证,这里不深入追溯,程序使用的输入视频是 BMF 框架为集成测试预先准备好的一组测试资源包,您可以通过https://github.com/BabitMF/bmf/releases/download/files/files.tar.gz 进行下载和使用,本文将使用这个资源包,下载命令如下(cdoutput&wgethttps://github.com/BabitMF/bmf/releases/download/files/files.tar.gz&tarxvffiles.tar.gz&rm-rffiles.tar.gz)至此,所有执行该程序的前置依赖均已准备完毕,切换到 customize_module 目录执行程序cdtest/customize_modulepythontest_customize_module.py程序执行结果如图所示,可以看到在本地成功产出了 output.mp4 文件四、实践案例本章将从 0 到 1 带你实现一个 RGBA 图像的 GPU 缩放功能,基于 DirectX Compute Shader 机制完成算法能力建设,并使用 BMF 框架的模块机制将算法能力封装进 BMF 模块中,同时实现一套 Host 端的 BMF 调用测试程序,实现对 GPU 图像缩放模块的调用,并提供构建脚本的实现,整体链路将算法层与调用层解耦,充分发挥并展示 BMF 框架在 Windows 端的良好的兼容性、适配性与易用性,本节流程主要分为三个部分:1. GPU 图像缩放算法模块的实现 2. 调用程序的实现。3. 构建脚本的实现图像缩放模块与其他可编程着色器(例如顶点和几何着色器)一样,计算着色器(Compute Shader)是使用 HLSL 设计和实现的,但相似之处仅此而已。计算着色器提供高速通用计算,并利用图形处理单元 (GPU) 上的大量并行处理器。计算着色器提供内存共享和线程同步功能,这些特性让 Win 端用户具备轻易调用跨平台框架 DirectX 调用 GPU 高效处理音视频图像领域的诸多计算任务。一个完整的 DirectX Compute Shader 调用过程分为 Host 端和 Device 端,下面简要阐述 Host 端的调用步骤:当使用 DirectX 11 或更高版本执行计算着色器时,通常需要以下步骤:创建设备和设备上下文:a. 创建 DirectX 设备对象,通常通过调用 D3D11CreateDevice() 函数。b. 为了执行计算着色器,设备需要支持 DirectCompute 功能,因此需要检查设备是否支持 DirectCompute。可以通过检查设备属性来实现。创建计算着色器:a. 创建计算着色器对象,通常通过编译 HLSL(High-Level Shading Language)代码而获得。可以使用 HLSL 编译器将计算着色器代码编译为字节码形式。b. 使用 ID3D11Device::CreateComputeShader() 函数创建计算着色器对象。创建常量缓冲区和资源:a. 如果计算着色器需要常量或者其他资源作为输入,则需要创建对应的常量缓冲区或者资源。b. 常量缓冲区通常通过 ID3D11Device::CreateBuffer() 函数创建,然后通过 ID3D11DeviceContext::CSSetConstantBuffers() 函数将常量缓冲区绑定到计算着色器上下文。c. 其他资源,如纹理、UAV、SRV 视图、常亮缓冲区等,可以通过相应的创建函数创建,并通过 ID3D11DeviceContext::CSSetShaderResources() 函数将其绑定到计算着色器上下文。设置执行参数:a. 在执行计算着色器之前,需要设置执行参数,包括计算着色器的线程组数等。b. 使用 ID3D11DeviceContext:ispatch() 函数设置计算着色器执行的线程组数。执行计算着色器:a. 调用 ID3D11DeviceContext::CSSetShader() 函数将计算着色器绑定到设备上下文。b. 调用 ID3D11DeviceContext:ispatch() 函数执行计算着色器。等待执行完成:可以通过插入事件或者查询设备上下文的执行状态来等待计算着色器的执行完成。清理资源:在完成计算着色器的使用后,需要释放相关资源,包括计算着色器对象、常量缓冲区、资源等。基于以上流程,本节将构建一个 BMF 模块,命名为 d3dresizemodule ,d3dresizemodule 拥有两个输入流(InputStream),编号 0、1,0 号输入流负责接收 Device、Devicecontext 等基础资源管理对象,并控制 DirectX 侧的初始化流程,在初始化流程中需要完成纹理、SRV/UAV 视图、着色器、采样器等资源的创建和初始化,因此 0 号输入流也被命名为“配置流”(config_stream)。1 号流负责在 DirectX 资源成功被初始化后,接收外界调用方传入的输入纹理数据,并职责,因此也被命名为“数据流”(data_stream),模块整体架构如下图所示:image.pngd3dresizemodule 模块的声明文件实现如下所示:#ifndefROI_Module_H#defineROI_Module_H#include#include#includeUSE_BMF_SDK_NSclassD3DResizeModule:publicModule{public3DResizeModule(intnode_id,JsonParamoption);int32_tinit();//DirectX侧初始化函数,需通过配置流成功配置device_、device_context_后触发int32_tunsafe_init();int32_tinit_d3d11();//模块处理函数,由BMF框架驱动调用int32_tprocess(Task&task);int32_tunsafe_process(Task&task);int32_tclose();//UAV功能检测函数boolcheckUAVFeature();~D3DResizeModule();JsonParamoption_;boolinited=false;intwidth_=0;intheight_=0;intinputWidth_=0;intinputHeight_=0;BMFComPtrprocessShader=nullptr;BMFComPtroutputSizebuf=nullptr;BMFComPtrsampleState=nullptr;ID3D11Device*device_=nullptr;ID3D11DeviceContext*device_context_=nullptr;};#endif其中,d3d11_common.h 是一组基于 Windows DirectX 的调用能力,用户可以调用这些接口轻易地实现 DirectX 相关资源的创建和管理,文件内部主要封装了一些与 DirectX 11 相关的常用操作和数据结构。让我们逐一解析:包含头文件:包含了一些与 DirectX 11 相关的头文件,如 , , 等。这些头文件包含了 DirectX 11 中定义的接口和数据结构。定义了一些结构体:D3D11DeviceWrapper 结构体用于封装了一个 DirectX 11 设备对象的指针。D3D11DeviceContextWrapper 结构体用于封装了一个 DirectX 11 设备上下文对象的指针。D3D11TextureWrapper 结构体用于封装了一个 DirectX 11 纹理对象的指针。InputSizeBuffer 结构体用于定义输入尺寸缓冲区的数据结构,主要用于 DirectX 常量缓冲区。OutputSizeBuffer 结构体用于定义输出尺寸缓冲区的数据结构,主要用于 DirectX 常量缓冲区。定义了一个模板别名:BMFComPtr 是一个模板别名,用于简化使用 Microsoft::WRL::ComPtr类型的代码。声明了一些外部函数:CreateTexture 函数用于创建一个 DirectX 11 纹理对象。CreateUAV 函数用于创建一个 DirectX 11 无序访问视图对象。CreateSRV 函数用于创建一个 DirectX 11 着色器资源视图对象。createComputeShader 函数用于创建一个 DirectX 11 计算着色器对象。CreateStagingTexture 函数用于创建一个用于数据传输的临时纹理对象。CreateSampleState 函数用于创建一个 DirectX 11 采样器状态对象总的来说,这个头文件封装了一些常用的 DirectX 11 操作函数和数据结构,提供了一种简化 DirectX 11 编程的方式,使得开发者可以更方便地使用 DirectX 11 相关功能,下面是 d3d11_common.h 文件的实现:#ifndefD3D11_COMMON__H#defineD3D11_COMMON__H#include#include#include#include#includestructD3D11DeviceWrapper{ID3D11Device*device;};structD3D11DeviceContextWrapper{ID3D11DeviceContext*device_context;};structD3D11TextureWrapper{ID3D11Texture2D*texture;};structInputSizeBuffer{uint32_tinWidth;uint32_tinHeight;floatpadding[2];};structOutputSizeBuffer{uint32_toutWidth;uint32_toutHeight;floatpadding[2];};templateusingBMFComPtr=Microsoft::WRL::ComPtr;externboolCreateTexture(ID3D11Texture2D**texture,ID3D11Device*d3dDevice,intwidth,intheight,DXGI_FORMATformat,constvoid*initData,D3D11_BIND_FLAGbindflag,intpixelbit,intbitSize);externboolCreateUAV(ID3D11UnorderedAccessView**uav,ID3D11Device*d3dDevice,D3D11_UNORDERED_ACCESS_VIEW_DESC*desc,ID3D11Texture2D*texture);externboolCreateSRV(ID3D11ShaderResourceView**srv,ID3D11Device*d3dDevice,D3D11_SHADER_RESOURCE_VIEW_DESC*desc,ID3D11Texture2D*texture);externboolcreateComputeShader(ID3D11ComputeShader**shader_,conststd::string&shader,ID3D11Device*device);externboolCreateStagingTexture(ID3D11Texture2D**stagingTexture,ID3D11Device*device,intwidth,intheight,DXGI_FORMATformat);externboolCreateSampleState(ID3D11SamplerState**state,ID3D11Device*device);//...#endif关于 Shader 的编译,默认的方式是将 shader 代码写在 HLSL 文件中,在程序初始化时调用编译程序读取文件进行编译,这种方式会要求强制暴露 HLSL 代码实现,外界调用方才可以通过 BMF 框架正确加载模块,不利于 Shader 代码的封装,Demo 中使用 map 封装每个 Shader 的字符二进制,并分类管理,这样的好处是在模块编译出 dll 时,相关 hlsl 代码已经成功被封装进模块内部,无需额外附上 hlsl 代码文件,下面关于是 gpuresize Shader 的实现:staticstd::maphlslMap={{"gpuresize",R"(//DefinealinearsamplerstateSamplerStateLinearSampler:register(s0);Texture2DRGBATexture:register(t0);cbufferOutputSize:register(b0){uintoutWidth;uintoutHeight;};RWTexture2DRGBOutput:register(u0);//bgra->scale->rgba[numthreads(16,16,1)]voidCSMain(uint3dtid:SV_DispatchThreadID){if(dtid.x>=outWidth||dtid.y>=outHeight)return;float2samplepoint=(float2(dtid.xy)+float2(0.5,0.5))/float2(outWidth,outHeight);float4rgba=RGBATexture.SampleLevel(LinearSampler,samplepoint,0);RGBOutput[dtid.xy]=rgba;})"}};首先,Shader 定义了采样器、输入输出纹理、常量缓冲区等资源,在计算主逻辑中,首先判断当前线程是否处于输出图像范围内,若超出范围则直接返回。然后,根据当前线程的索引计算对应的采样点坐标,并使用 SampleLevel 方法从输入纹理中进行线性采样,获取采样到的 RGBA 像素值,并将其写入输出纹理中。关于模块的具体实现,这里重点分析三个函数:init_d3d11、process、unsafe_process,首先来看 process 函数:int32_tD3DResizeModule::process(Task&task){try{int32_tres=unsafe_process(task);returnres;}catch(std::exception&e){BMFLOG(BMF_INFO)<<"ROImoduleprocessthrowsstd::exception:"<
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-14 03:30 , Processed in 0.935100 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表