在 Windows 下玩轉(zhuǎn)多媒體處理框架 BMF
一、簡(jiǎn)介
現(xiàn)代科技網(wǎng)絡(luò)日益發(fā)達(dá),視頻已經(jīng)成為人們生活中不可或缺的一部分。隨著互聯(lián)網(wǎng)和移動(dòng)設(shè)備的普及,視頻內(nèi)容在傳播和分享方面發(fā)揮著越來(lái)越重要的作用。從社交媒體到在線教育,從數(shù)字廣告到遠(yuǎn)程工作,視頻已經(jīng)成為人們獲取信息、娛樂(lè)和交流的主要方式之一。在這樣一個(gè)視頻日益普及的超視頻時(shí)代,開(kāi)發(fā)一套跨語(yǔ)言、跨設(shè)備、跨系統(tǒng)的多媒體處理框架顯得尤為重要,這樣的框架可以為開(kāi)發(fā)人員提供統(tǒng)一的解決方案,幫助他們?cè)诓煌钠脚_(tái)上快速、高效地處理多媒體內(nèi)容,從而提供一致的用戶(hù)體驗(yàn)和功能,是迎接未來(lái)的必然趨勢(shì)。
在當(dāng)今數(shù)字化的世界中,Windows 平臺(tái)的重要性和關(guān)鍵性無(wú)可置疑。作為普通用戶(hù)的首要選擇,Windows 提供了廣泛的硬件和軟件支持,為用戶(hù)提供了豐富多彩的體驗(yàn)。特別是在多媒體處理領(lǐng)域,Windows 平臺(tái)憑借其強(qiáng)大的生態(tài)系統(tǒng)和穩(wěn)定的性能,基本是普通用戶(hù)的首選。Windows 平臺(tái)擁有龐大而完善的 DirectX 能力體系,這使得在Windows 環(huán)境下可以很方便地實(shí)現(xiàn)通過(guò) GPU 加速圖像視頻處理的性能,這種強(qiáng)大的圖形處理能力可以更高效地處理和渲染視頻、音頻等多媒體內(nèi)容。特別是對(duì)于游戲主播、視頻編輯等相關(guān)領(lǐng)域的從業(yè)者,Windows 平臺(tái)提供了一個(gè)穩(wěn)定而強(qiáng)大的開(kāi)發(fā)環(huán)境,為他們的創(chuàng)作和工作帶來(lái)了極大的便利和效率。因此,開(kāi)發(fā)一套兼容 Windows 平臺(tái)的多媒體處理框架具有重要的意義。這不僅可以滿足普通用戶(hù)對(duì)于多媒體內(nèi)容的需求,還可以為專(zhuān)業(yè)從業(yè)者提供強(qiáng)大的工具和支持。無(wú)論是游戲行業(yè)、直播行業(yè)還是視頻編輯領(lǐng)域,都可以受益于這樣一套高效、穩(wěn)定的多媒體處理框架,為用戶(hù)帶來(lái)更優(yōu)質(zhì)的體驗(yàn)和服務(wù)。
基于以上兩個(gè)前提,2023 年 8 月 22 日,火山引擎視頻云與NVIDIA正式開(kāi)源多媒體處理框架 Babit Multimedia Framework (以下統(tǒng)稱(chēng) BMF 框架),BMF 在 Windows 側(cè)對(duì)齊 Linux,目前已經(jīng)打通了框架的編譯、構(gòu)建、同時(shí)支持模塊自定義開(kāi)發(fā),在字節(jié)跳動(dòng)內(nèi)部,BMF 在 Win 側(cè)已集成多種使用 CPU/GPU 的圖像處理算法,服務(wù)于抖音直播伴侶業(yè)務(wù),目前已有 5 個(gè)算法已被成功集成,BMF 框架作為搭建算法與業(yè)務(wù)的橋梁,通過(guò)自定義模塊實(shí)現(xiàn)算法邏輯與業(yè)務(wù)的完全解耦,其內(nèi)部可以很方便地在 Win 側(cè)集成不同圖像處理算法。本文將沿三個(gè)步驟,全面介紹 BMF 框架在 Windows 端的能力建設(shè)與技術(shù)實(shí)踐,首先介紹 BMF 框架在 Windows 環(huán)境下如何配置與編譯,其次介紹如何在 Windows 環(huán)境配置 BMF 開(kāi)發(fā)環(huán)境,并展示一個(gè)簡(jiǎn)單 Python 模塊的運(yùn)行過(guò)程,最后展示一個(gè)基于 DirectX 的全鏈路圖像縮放模塊的開(kāi)發(fā)與部署案例,展示 BMF 在 Windows 端友好的兼容性和強(qiáng)大的功能適配能力,助您在 Windows 下玩轉(zhuǎn)BMF!
二、編譯與構(gòu)建
編譯 BMF 框架需要依賴(lài)以下環(huán)境:
- MSYS2。提供了一個(gè)基于開(kāi)源軟件的本機(jī)構(gòu)建環(huán)境,可以在 Windows 上用 Linux 的方式使用多種不同的環(huán)境和工具來(lái)執(zhí)行不同的任務(wù)。
- CMake。管理框架構(gòu)建過(guò)程,推薦版本 3.27。
- Visual Studio 2013 - 2022。BMF 在 Win 端選用兼容性較好的 msvc 編譯工具鏈,目前支持版本 2013 - 2022。
以上三個(gè)依賴(lài)是必須項(xiàng),下面還有 2 個(gè)依賴(lài)可供選擇是否打開(kāi)相關(guān)的框架能力:
- Python 3.7 - 3.10。用于編譯 BMF Python SDK,如果不提供,則框架無(wú)法編譯 Python 相關(guān)的調(diào)用能力
- FFmpeg 4.4 - 5.1。用于編譯 BMF built-in modules(ffmpeg_decoder、ffmpeg_encoder、ffmpeg_filter),如果不提供,默認(rèn)取消編譯相關(guān)產(chǎn)物
本文在介紹編譯過(guò)程時(shí),默認(rèn)會(huì)打開(kāi)以上兩個(gè)選項(xiàng),實(shí)現(xiàn)一次全鏈路的編譯構(gòu)建,下面用圖文方式介紹 BMF 框架編譯過(guò)程:
- 打開(kāi) Visual Studio 的命令提示環(huán)境,建議以管理員方式
- 找到 MSYS2 的安裝目錄,執(zhí)行命令: .\msys2_shell.cmd -use-full-path,攜帶宿主機(jī)環(huán)境進(jìn)入 msys2(注意:這里需要保證 CMake 工具已經(jīng)成功配置進(jìn)系統(tǒng)環(huán)境變量,本文的實(shí)驗(yàn)環(huán)境默認(rèn)配置了 CMake 3.27、FFmpeg 4.4、Python 3.7.9 的環(huán)境變量)。
- 克隆 BMF 項(xiàng)目,在根目錄運(yùn)行 build_win_lite 腳本,項(xiàng)目設(shè)有以下編譯控制選項(xiàng):
Build Options:
--msvc 設(shè)定 msvc 版本,包括[2013, 2015, 2017, 2019, 2022]
bmf_ffmpeg 控制是否集成 FFmpeg 相關(guān)能力并編譯 built-in module
--preset 編譯配置,包括[x86-Debug, x86-Release, x64-Debug, x64-Release]
注意:如果您需要編譯 FFmpeg 相關(guān)的能力,且您本地的 CMake 版本高于等于 3.28,你還需要設(shè)置 ffmpeg 的 include 目錄,命令如下:
export INCLUDE="$INCLUDE;C:\path\to\include_for_ffmpeg"
以 msvc 2022 編譯 x64-Debug 版本為例,腳本執(zhí)行命令:
./build_win_lite.sh --msvc=2022 --preset=x64-Debug bmf_ffmpeg
執(zhí)行后會(huì)在項(xiàng)目目錄的 build_win_lite/x64-Debug 目錄生成 BMF.sln 解決方案
之后,您可以雙擊 sln 文件使用 Visual Studio 友好的界面進(jìn)行項(xiàng)目構(gòu)建和編譯,您也可以使用以下 CMake 命令直接通過(guò)命令行進(jìn)行項(xiàng)目構(gòu)建:
cmake --build build_win_lite/x64-Debug --config Debug --target ALL_BUILD
至此,您便可以在 Windows 環(huán)境中完成對(duì) BMF 框架的編譯與構(gòu)建,在 Visual Studio 的編譯過(guò)程如圖所示
三、開(kāi)發(fā)環(huán)境配置與模塊運(yùn)行
本章介紹如何搭建 BMF 開(kāi)發(fā)環(huán)境,繼上文所述,當(dāng)我們完成 BMF 框架的編譯構(gòu)建后,會(huì)生成 output 文件夾,我們需要將 bin 目錄與 lib 目錄配置進(jìn)系統(tǒng)環(huán)境變量,與此同時(shí),BMF 開(kāi)發(fā)環(huán)境還需依賴(lài)一個(gè) Win 相關(guān)的依賴(lài)集合 win_rootfs(https://github.com/BabitMF/bmf/releases/download/files/win_rootfs.tar.gz),需配置環(huán)境變量,如圖所示:
配置完 BMF 環(huán)境變量后,我們需要重啟 msys2 環(huán)境,目的是讓 BMF 的環(huán)境變量生效,下面展示如何運(yùn)行一個(gè) Python Module 的測(cè)試程序:test_customize_module。首先,需要將 msys2 當(dāng)前目錄切換至編譯產(chǎn)物的上級(jí)目錄,設(shè)置一些 msys2 環(huán)境的環(huán)境變量,配置 BMF 框架的 Python 運(yùn)行環(huán)境
export PYTHONHOME="$(dirname "$(which python)")"
export PYTHONPATH=$(pwd)/output/bmf/lib:$(pwd)/output
配置完畢后,進(jìn)入 python 環(huán)境應(yīng)該可以正常 import bmf 框架,如圖所示
我們要運(yùn)行的 customize_module 文件在 output/test/customize_module 目錄下的 my_module.py 文件,模塊的實(shí)現(xiàn)代碼如下:
from bmf import Module, Log, LogLevel, InputType, ProcessResult, Packet, Timestamp, scale_av_pts, av_time_base, \
BmfCallBackType, VideoFrame, AudioFrame
class my_module(Module):
def __init__(self, node, optinotallow=None):
self.node_ = node
self.option_ = option
pass
def process(self, task):
for (input_id, input_packets) in task.get_inputs().items():
## output queue
output_packets = task.get_outputs()[input_id]
while not input_packets.empty():
pkt = input_packets.get()
## process EOS
if pkt.timestamp == Timestamp.EOF:
Log.log_node(LogLevel.DEBUG, task.get_node(),
"Receive EOF")
output_packets.put(Packet.generate_eof_packet())
task.timestamp = Timestamp.DONE
return ProcessResult.OK
## copy input packet to output
if pkt.defined() and pkt.timestamp != Timestamp.UNSET:
output_packets.put(pkt)
return ProcessResult.OK
可以看到,模塊僅僅將幀從輸入隊(duì)列取出,不做任何處理,直接傳遞至輸出隊(duì)列,我們將要執(zhí)行的測(cè)試程序 test_customize_module.py 的實(shí)現(xiàn)如下:
import sys
import time
import unittest
sys.path.append("../../..")
sys.path.append("../../c_module_sdk/build/bin/lib")
import bmf
import os
if os.name == 'nt':
## We redefine timeout_decorator on windows
class timeout_decorator:
@staticmethod
def timeout(*args, **kwargs):
return lambda f: f ## return a no-op decorator
else:
import timeout_decorator
sys.path.append("../../test/")
from base_test.base_test_case import BaseTestCase
from base_test.media_info import MediaInfo
class TestCustomizeModule(BaseTestCase):
@timeout_decorator.timeout(secnotallow=120)
def test_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": output_path
}).run())
self.check_video_diff(output_path, expect_result)
if __name__ == '__main__':
unittest.main()
在測(cè)試程序的 33 - 37 行中,我們構(gòu)建了一個(gè) BMF Graph,首先對(duì)輸入視頻進(jìn)行解碼,隨后調(diào)起事先我們寫(xiě)好的 Python 模塊 my_module 對(duì)解碼后的視頻幀進(jìn)行一次處理,最后調(diào)用 encode 模塊對(duì)視頻幀進(jìn)行編碼,產(chǎn)出輸出文件,剩余部分是框架使用 Google Test 框架所進(jìn)行的一些轉(zhuǎn)碼指標(biāo)的驗(yàn)證,這里不深入追溯,程序使用的輸入視頻是 BMF 框架為集成測(cè)試預(yù)先準(zhǔn)備好的一組測(cè)試資源包,您可以通過(guò)https://github.com/BabitMF/bmf/releases/download/files/files.tar.gz 進(jìn)行下載和使用,本文將使用這個(gè)資源包,下載命令如下
(cd output && wget https://github.com/BabitMF/bmf/releases/download/files/files.tar.gz && tar xvf files.tar.gz && rm -rf files.tar.gz)
至此,所有執(zhí)行該程序的前置依賴(lài)均已準(zhǔn)備完畢,切換到 customize_module 目錄執(zhí)行程序
cd test/customize_module
python test_customize_module.py
程序執(zhí)行結(jié)果如圖所示,可以看到在本地成功產(chǎn)出了 output.mp4 文件
四、實(shí)踐案例
本章將從 0 到 1 帶你實(shí)現(xiàn)一個(gè) RGBA 圖像的 GPU 縮放功能,基于 DirectX Compute Shader 機(jī)制完成算法能力建設(shè),并使用 BMF 框架的模塊機(jī)制將算法能力封裝進(jìn) BMF 模塊中,同時(shí)實(shí)現(xiàn)一套 Host 端的 BMF 調(diào)用測(cè)試程序,實(shí)現(xiàn)對(duì) GPU 圖像縮放模塊的調(diào)用,并提供構(gòu)建腳本的實(shí)現(xiàn),整體鏈路將算法層與調(diào)用層解耦,充分發(fā)揮并展示 BMF 框架在 Windows 端的良好的兼容性、適配性與易用性,本節(jié)流程主要分為三個(gè)部分:1. GPU 圖像縮放算法模塊的實(shí)現(xiàn) 2. 調(diào)用程序的實(shí)現(xiàn)。3. 構(gòu)建腳本的實(shí)現(xiàn)
圖像縮放模塊
與其他可編程著色器(例如頂點(diǎn)和幾何著色器)一樣,計(jì)算著色器(Compute Shader)是使用 HLSL 設(shè)計(jì)和實(shí)現(xiàn)的,但相似之處僅此而已。計(jì)算著色器提供高速通用計(jì)算,并利用圖形處理單元 (GPU) 上的大量并行處理器。計(jì)算著色器提供內(nèi)存共享和線程同步功能,這些特性讓 Win 端用戶(hù)具備輕易調(diào)用跨平臺(tái)框架 DirectX 調(diào)用 GPU 高效處理音視頻圖像領(lǐng)域的諸多計(jì)算任務(wù)。
一個(gè)完整的 DirectX Compute Shader 調(diào)用過(guò)程分為 Host 端和 Device 端,下面簡(jiǎn)要闡述 Host 端的調(diào)用步驟:
當(dāng)使用 DirectX 11 或更高版本執(zhí)行計(jì)算著色器時(shí),通常需要以下步驟:
- 創(chuàng)建設(shè)備和設(shè)備上下文:
a. 創(chuàng)建 DirectX 設(shè)備對(duì)象,通常通過(guò)調(diào)用D3D11CreateDevice()
函數(shù)。
b. 為了執(zhí)行計(jì)算著色器,設(shè)備需要支持 DirectCompute 功能,因此需要檢查設(shè)備是否支持 DirectCompute??梢酝ㄟ^(guò)檢查設(shè)備屬性來(lái)實(shí)現(xiàn)。 - 創(chuàng)建計(jì)算著色器:
a. 創(chuàng)建計(jì)算著色器對(duì)象,通常通過(guò)編譯 HLSL(High-Level Shading Language)代碼而獲得??梢允褂?HLSL 編譯器將計(jì)算著色器代碼編譯為字節(jié)碼形式。
b. 使用ID3D11Device::CreateComputeShader()
函數(shù)創(chuàng)建計(jì)算著色器對(duì)象。 - 創(chuàng)建常量緩沖區(qū)和資源:
a. 如果計(jì)算著色器需要常量或者其他資源作為輸入,則需要?jiǎng)?chuàng)建對(duì)應(yīng)的常量緩沖區(qū)或者資源。
b. 常量緩沖區(qū)通常通過(guò)ID3D11Device::CreateBuffer()
函數(shù)創(chuàng)建,然后通過(guò)ID3D11DeviceContext::CSSetConstantBuffers()
函數(shù)將常量緩沖區(qū)綁定到計(jì)算著色器上下文。
c. 其他資源,如紋理、UAV、SRV 視圖、常亮緩沖區(qū)等,可以通過(guò)相應(yīng)的創(chuàng)建函數(shù)創(chuàng)建,并通過(guò)ID3D11DeviceContext::CSSetShaderResources()
函數(shù)將其綁定到計(jì)算著色器上下文。 - 設(shè)置執(zhí)行參數(shù):
a. 在執(zhí)行計(jì)算著色器之前,需要設(shè)置執(zhí)行參數(shù),包括計(jì)算著色器的線程組數(shù)等。
b. 使用ID3D11DeviceContext::Dispatch()
函數(shù)設(shè)置計(jì)算著色器執(zhí)行的線程組數(shù)。 - 執(zhí)行計(jì)算著色器:
a. 調(diào)用ID3D11DeviceContext::CSSetShader()
函數(shù)將計(jì)算著色器綁定到設(shè)備上下文。
b. 調(diào)用ID3D11DeviceContext::Dispatch()
函數(shù)執(zhí)行計(jì)算著色器。 - 等待執(zhí)行完成:可以通過(guò)插入事件或者查詢(xún)?cè)O(shè)備上下文的執(zhí)行狀態(tài)來(lái)等待計(jì)算著色器的執(zhí)行完成。
- 清理資源:在完成計(jì)算著色器的使用后,需要釋放相關(guān)資源,包括計(jì)算著色器對(duì)象、常量緩沖區(qū)、資源等。
基于以上流程,本節(jié)將構(gòu)建一個(gè) BMF 模塊,命名為 d3dresizemodule ,d3dresizemodule 擁有兩個(gè)輸入流(InputStream),編號(hào) 0、1,0 號(hào)輸入流負(fù)責(zé)接收 Device、Devicecontext 等基礎(chǔ)資源管理對(duì)象,并控制 DirectX 側(cè)的初始化流程,在初始化流程中需要完成紋理、SRV/UAV 視圖、著色器、采樣器等資源的創(chuàng)建和初始化,因此 0 號(hào)輸入流也被命名為“配置流”(config_stream)。1 號(hào)流負(fù)責(zé)在 DirectX 資源成功被初始化后,接收外界調(diào)用方傳入的輸入紋理數(shù)據(jù),并職責(zé),因此也被命名為“數(shù)據(jù)流”(data_stream),模塊整體架構(gòu)如下圖所示:
image.png
d3dresizemodule 模塊的聲明文件實(shí)現(xiàn)如下所示:
#ifndef ROI_Module_H
#define ROI_Module_H
#include <bmf/sdk/module.h>
#include <bmf/sdk/task.h>
#include <d3d11_common.h>
USE_BMF_SDK_NS
class D3DResizeModule : public Module {
public:
D3DResizeModule(int node_id, JsonParam option);
int32_t init();
// DirectX 側(cè)初始化函數(shù),需通過(guò)配置流成功配置 device_、device_context_ 后觸發(fā)
int32_t unsafe_init();
int32_t init_d3d11();
// 模塊處理函數(shù),由 BMF 框架驅(qū)動(dòng)調(diào)用
int32_t process(Task &task);
int32_t unsafe_process(Task &task);
int32_t close();
// UAV 功能檢測(cè)函數(shù)
bool checkUAVFeature();
~D3DResizeModule();
JsonParam option_;
bool inited = false;
int width_ = 0;
int height_ = 0;
int inputWidth_ = 0;
int inputHeight_ = 0;
BMFComPtr<ID3D11ComputeShader> processShader = nullptr;
BMFComPtr<ID3D11Buffer> outputSizebuf = nullptr;
BMFComPtr<ID3D11SamplerState> sampleState = nullptr;
ID3D11Device *device_ = nullptr;
ID3D11DeviceContext *device_context_ = nullptr;
};
#endif
其中,d3d11_common.h 是一組基于 Windows DirectX 的調(diào)用能力,用戶(hù)可以調(diào)用這些接口輕易地實(shí)現(xiàn) DirectX 相關(guān)資源的創(chuàng)建和管理,文件內(nèi)部主要封裝了一些與 DirectX 11 相關(guān)的常用操作和數(shù)據(jù)結(jié)構(gòu)。讓我們逐一解析:
1.包含頭文件:
包含了一些與 DirectX 11 相關(guān)的頭文件,如 <d3d11.h>
, <wrl/client.h>
, <d3dcompiler.h>
等。這些頭文件包含了 DirectX 11 中定義的接口和數(shù)據(jù)結(jié)構(gòu)。
2.定義了一些結(jié)構(gòu)體:
a.D3D11DeviceWrapper
結(jié)構(gòu)體用于封裝了一個(gè) DirectX 11 設(shè)備對(duì)象的指針。
b.D3D11DeviceContextWrapper
結(jié)構(gòu)體用于封裝了一個(gè) DirectX 11 設(shè)備上下文對(duì)象的指針。
c.D3D11TextureWrapper
結(jié)構(gòu)體用于封裝了一個(gè) DirectX 11 紋理對(duì)象的指針。
d.InputSizeBuffer
結(jié)構(gòu)體用于定義輸入尺寸緩沖區(qū)的數(shù)據(jù)結(jié)構(gòu),主要用于 DirectX 常量緩沖區(qū)。
e.OutputSizeBuffer
結(jié)構(gòu)體用于定義輸出尺寸緩沖區(qū)的數(shù)據(jù)結(jié)構(gòu),主要用于 DirectX 常量緩沖區(qū)。
3.定義了一個(gè)模板別名:
a.BMFComPtr
是一個(gè)模板別名,用于簡(jiǎn)化使用 Microsoft::WRL::ComPtr類(lèi)型的代碼。
4.聲明了一些外部函數(shù):
a.CreateTexture
函數(shù)用于創(chuàng)建一個(gè) DirectX 11 紋理對(duì)象。
b.CreateUAV
函數(shù)用于創(chuàng)建一個(gè) DirectX 11 無(wú)序訪問(wèn)視圖對(duì)象。
c.CreateSRV
函數(shù)用于創(chuàng)建一個(gè) DirectX 11 著色器資源視圖對(duì)象。
d.createComputeShader
函數(shù)用于創(chuàng)建一個(gè) DirectX 11 計(jì)算著色器對(duì)象。
e.CreateStagingTexture
函數(shù)用于創(chuàng)建一個(gè)用于數(shù)據(jù)傳輸?shù)呐R時(shí)紋理對(duì)象。
f.CreateSampleState
函數(shù)用于創(chuàng)建一個(gè) DirectX 11 采樣器狀態(tài)對(duì)象
總的來(lái)說(shuō),這個(gè)頭文件封裝了一些常用的 DirectX 11 操作函數(shù)和數(shù)據(jù)結(jié)構(gòu),提供了一種簡(jiǎn)化 DirectX 11 編程的方式,使得開(kāi)發(fā)者可以更方便地使用 DirectX 11 相關(guān)功能,下面是 d3d11_common.h 文件的實(shí)現(xiàn):
#ifndef D3D11_COMMON__H
#define D3D11_COMMON__H
#include <d3d11.h>
#include <wrl/client.h>
#include <d3dcompiler.h>
#include <string>
#include <vector>
struct D3D11DeviceWrapper {
ID3D11Device *device;
};
struct D3D11DeviceContextWrapper {
ID3D11DeviceContext *device_context;
};
struct D3D11TextureWrapper {
ID3D11Texture2D *texture;
};
struct InputSizeBuffer
{
uint32_t inWidth;
uint32_t inHeight;
float padding[2];
};
struct OutputSizeBuffer
{
uint32_t outWidth;
uint32_t outHeight;
float padding[2];
};
template <class T>
using BMFComPtr = Microsoft::WRL::ComPtr<T>;
extern bool CreateTexture(ID3D11Texture2D** texture, ID3D11Device* d3dDevice, int width, int height, DXGI_FORMAT format, const void* initData, D3D11_BIND_FLAG bindflag, int pixelbit, int bitSize);
extern bool CreateUAV(ID3D11UnorderedAccessView** uav, ID3D11Device* d3dDevice, D3D11_UNORDERED_ACCESS_VIEW_DESC* desc, ID3D11Texture2D* texture);
extern bool CreateSRV(ID3D11ShaderResourceView** srv, ID3D11Device* d3dDevice, D3D11_SHADER_RESOURCE_VIEW_DESC* desc, ID3D11Texture2D* texture);
extern bool createComputeShader(ID3D11ComputeShader** shader_, const std::string& shader, ID3D11Device* device);
extern bool CreateStagingTexture(ID3D11Texture2D** stagingTexture, ID3D11Device* device, int width, int height, DXGI_FORMAT format);
extern bool CreateSampleState(ID3D11SamplerState** state, ID3D11Device* device);
// ...
#endif
關(guān)于 Shader 的編譯,默認(rèn)的方式是將 shader 代碼寫(xiě)在 HLSL 文件中,在程序初始化時(shí)調(diào)用編譯程序讀取文件進(jìn)行編譯,這種方式會(huì)要求強(qiáng)制暴露 HLSL 代碼實(shí)現(xiàn),外界調(diào)用方才可以通過(guò) BMF 框架正確加載模塊,不利于 Shader 代碼的封裝,Demo 中使用 map 封裝每個(gè) Shader 的字符二進(jìn)制,并分類(lèi)管理,這樣的好處是在模塊編譯出 dll 時(shí),相關(guān) hlsl 代碼已經(jīng)成功被封裝進(jìn)模塊內(nèi)部,無(wú)需額外附上 hlsl 代碼文件,下面關(guān)于是 gpuresize Shader 的實(shí)現(xiàn):
static std::map<std::string, std::string> hlslMap = {
{ "gpuresize", R"(
// Define a linear sampler state
SamplerState LinearSampler : register(s0);
Texture2D RGBATexture : register(t0);
cbuffer OutputSize : register(b0)
{
uint outWidth;
uint outHeight;
};
RWTexture2D<float4> RGBOutput : register(u0);
// bgra -> scale -> rgba
[numthreads(16, 16, 1)]
void CSMain(uint3 dtid : SV_DispatchThreadID)
{
if (dtid.x >= outWidth || dtid.y >= outHeight)
return;
float2 samplepoint = (float2(dtid.xy) + float2(0.5, 0.5)) / float2(outWidth, outHeight);
float4 rgba = RGBATexture.SampleLevel(LinearSampler, samplepoint, 0);
RGBOutput[dtid.xy] = rgba;
}
)" }
};
首先,Shader 定義了采樣器、輸入輸出紋理、常量緩沖區(qū)等資源,在計(jì)算主邏輯中,首先判斷當(dāng)前線程是否處于輸出圖像范圍內(nèi),若超出范圍則直接返回。然后,根據(jù)當(dāng)前線程的索引計(jì)算對(duì)應(yīng)的采樣點(diǎn)坐標(biāo),并使用 SampleLevel
方法從輸入紋理中進(jìn)行線性采樣,獲取采樣到的 RGBA 像素值,并將其寫(xiě)入輸出紋理中。
關(guān)于模塊的具體實(shí)現(xiàn),這里重點(diǎn)分析三個(gè)函數(shù):init_d3d11、process、unsafe_process,首先來(lái)看 process 函數(shù):
int32_t D3DResizeModule::process(Task &task) {
try {
int32_t res = unsafe_process(task);
return res;
} catch (std::exception &e) {
BMFLOG(BMF_INFO) << "ROI module process throws std::exception: "
<< e.what();
throw e;
return -1;
}
}
模塊的 process 函數(shù)由 BMF 框架層調(diào)用,本模塊將函數(shù)執(zhí)行邏輯主體封裝在 unsafe_process 函數(shù)中,process 函數(shù)使用 try catch 捕獲異常,起到兜底作用,unsafe_process 的實(shí)現(xiàn)如下:
int32_t D3DResizeModule::unsafe_process(Task& task) {
if (!inited) {
bmf_sdk::Packet d3d11_packet;
while (task.pop_packet_from_input_queue(0, d3d11_packet)) {
if (d3d11_packet.timestamp() == bmf_sdk::BMF_EOF) {
task.set_timestamp(bmf_sdk::DONE);
task.fill_output_packet(0, bmf_sdk::Packet::generate_eof_packet());
break;
}
if (d3d11_packet.is<D3D11DeviceWrapper>()) {
device_ = d3d11_packet.get<D3D11DeviceWrapper>().device;
}
else if (d3d11_packet.is<D3D11DeviceContextWrapper>()) {
device_context_ = d3d11_packet.get<D3D11DeviceContextWrapper>().device_context;
}
else {
BMFLOG_NODE(BMF_WARNING, node_id_) << "get unexpected data:" << d3d11_packet.type_info().name << std::endl;
}
}
if (device_ && device_context_) {
init_d3d11();
}
}
bmf_sdk::Packet frame_packet;
int textureStyle = -1;
while (task.pop_packet_from_input_queue(1, frame_packet)) {
if (frame_packet.timestamp() == bmf_sdk::BMF_EOF) {
task.set_timestamp(bmf_sdk::DONE);
task.fill_output_packet(0, bmf_sdk::Packet::generate_eof_packet());
break;
}
if (!frame_packet.is<D3D11TextureWrapper>()) {
BMFLOG_NODE(BMF_ERROR, node_id_) << "get unexpected data:" << frame_packet.type_info().name << std::endl;
}
D3D11TextureWrapper inputPkt = frame_packet.get<D3D11TextureWrapper>();
ID3D11Texture2D* input_texture = inputPkt.texture;
if (!input_texture) {
throw std::exception("null texture input!");
}
ID3D11Texture2D *outputTexture = nullptr;
CreateTexture(&outputTexture, device_, width_, height_, DXGI_FORMAT_R8G8B8A8_UNORM, nullptr, (D3D11_BIND_FLAG)(D3D11_BIND_UNORDERED_ACCESS), 4, sizeof(uint8_t));
BMFComPtr<ID3D11ShaderResourceView> input_srv;
BMFComPtr<ID3D11UnorderedAccessView> output_texture_uav;
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc, sizeof(srvDesc));
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = 1;
if (!CreateSRV(input_srv.GetAddressOf(), device_, &srvDesc, input_texture)) {
throw std::exception("input_srv create failed");
}
if (!CreateUAV(output_texture_uav.GetAddressOf(), device_, nullptr, outputTexture)) {
throw std::exception("output_texture_uav create failed!");
}
// flush the DirectX resource
ID3D11ShaderResourceView* null_srv = nullptr;
ID3D11UnorderedAccessView* null_uav = nullptr;
ID3D11Buffer* null_buf = nullptr;
ID3D11SamplerState* null_sample = nullptr;
ID3D11ComputeShader* null_shader = nullptr;
device_context_->CSSetShaderResources(0, 1, &null_srv);
device_context_->CSSetUnorderedAccessViews(0, 1, &null_uav, nullptr);
device_context_->CSSetConstantBuffers(0, 1, &null_buf);
device_context_->CSSetSamplers(0, 1, &null_sample);
device_context_->CSSetShader(null_shader, nullptr, 0);
// execute the resize shader
device_context_->CSSetShaderResources(0, 1, input_srv.GetAddressOf());
device_context_->CSSetUnorderedAccessViews(0, 1, output_texture_uav.GetAddressOf(), nullptr);
device_context_->CSSetShader(processShader.Get(), nullptr, 0);
device_context_->CSSetConstantBuffers(0, 1, outputSizebuf.GetAddressOf());
device_context_->CSSetSamplers(0, 1, sampleState.GetAddressOf());
device_context_->Dispatch((width_ - 1 + 16) / 16, (height_ - 1 + 16) / 16, 1);
D3D11TextureWrapper outputTextureWrapper;
outputTextureWrapper.texture = outputTexture;
bmf_sdk::Packet output_packet(outputTextureWrapper);
task.fill_output_packet(0, output_packet);
}
return 0;
}
這段代碼實(shí)現(xiàn)的主要功能如下:
1.初始化階段(未初始化時(shí)):
通過(guò)從模塊的配置流中獲取數(shù)據(jù)包,初始化 D3D11 設(shè)備和設(shè)備上下文對(duì)象。
如果獲取到了設(shè)備和設(shè)備上下文對(duì)象,則調(diào)用 init_d3d11
函數(shù)進(jìn)行 DirectX 11 的初始化。
2.處理階段:
- 清空之前的著色器資源視圖和無(wú)序訪問(wèn)視圖。
- 設(shè)置輸入紋理的著色器資源視圖和輸出紋理的無(wú)序訪問(wèn)視圖。
- 設(shè)置計(jì)算著色器,并執(zhí)行計(jì)算著色器的調(diào)度,實(shí)現(xiàn)了一個(gè) D3D11 RGBA 紋理的 resize 功能
a .通過(guò)循環(huán)從模塊的數(shù)據(jù)流獲取數(shù)據(jù)包,直到獲取到結(jié)束標(biāo)志(BMF_EOF
)為止。
b.如果獲取到的數(shù)據(jù)包類(lèi)型為 D3D11TextureWrapper
,則表示獲取到了 D3D11 紋理對(duì)象。
c.根據(jù)獲取到的輸入紋理,創(chuàng)建一個(gè)新的輸出紋理對(duì)象。
d.調(diào)用上文描述的接口,創(chuàng)建輸入紋理的著色器資源視圖(SRV)和輸出紋理的無(wú)序訪問(wèn)視圖(UAV)。
e.執(zhí)行一系列的 DirectX 11 操作:
f.封裝輸出紋理對(duì)象為數(shù)據(jù)包,并 push 到模塊的輸出隊(duì)列中,供外界調(diào)用代碼獲取
init_d3d11 函數(shù)的實(shí)現(xiàn)如下:
int32_t D3DResizeModule::init_d3d11() {
if (!inited) {
if (!device_ || !device_context_) {
throw std::exception("d3d11 device or context is not inited!");
}
D3D_FEATURE_LEVEL featureLevel = device_->GetFeatureLevel();
if (featureLevel < D3D_FEATURE_LEVEL_11_0) {
throw std::exception("local d3d11 feature level < 11! computeshader cannot work!");
}
if (!checkUAVFeature()) {
throw std::exception("UAV load is not supported in this hardware");
}
if (!createComputeShader(processShader.GetAddressOf(), "gpuresize", device_)) {
throw std::exception("shader compile failed!");
}
if (!CreateSampleState(sampleState.GetAddressOf(), device_)) {
throw std::exception("sample state create failed!");
}
OutputSizeBuffer sizeBuffer;
sizeBuffer.outWidth = width_;
sizeBuffer.outHeight = height_;
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.ByteWidth = sizeof(OutputSizeBuffer);
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
desc.MiscFlags = 0;
desc.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA initData;
initData.pSysMem = &sizeBuffer;
initData.SysMemPitch = 0;
initData.SysMemSlicePitch = 0;
HRESULT hr = device_->CreateBuffer(&desc, &initData, outputSizebuf.GetAddressOf());
if (FAILED(hr)) {
throw std::exception("constant buffer create failed!");
}
inited = true;
}
}
代碼主要實(shí)現(xiàn)的邏輯如下:
- 檢查設(shè)備和設(shè)備上下文對(duì)象是否已經(jīng)初始化,如果沒(méi)有則拋出異常。
- 獲取當(dāng)前設(shè)備的特性級(jí)別(Feature Level),并檢查是否支持 D3D11 特性級(jí)別 11.0 以上,如果不支持則拋出異常。
- 檢查硬件是否支持 UAV(Unordered Access View)加載特性,如果不支持則拋出異常。
- 編譯創(chuàng)建計(jì)算著色器,用于圖像的 resize 操作。
- 創(chuàng)建采樣器狀態(tài)(Sampler State),用于計(jì)算著色器中的紋理采樣。
- 創(chuàng)建輸出大小的常量緩沖區(qū)(Constant Buffer),用于傳遞輸出圖像的寬度和高度信息給計(jì)算著色器。
- 將輸出大小的緩沖區(qū)數(shù)據(jù)初始化,并創(chuàng)建 D3D11 緩沖區(qū)對(duì)象。
- 設(shè)置標(biāo)志位
inited
為 true,表示 D3D11 初始化完成。
從上文 unsafe_process 函數(shù)的邏輯可以獲知:init_d3d11 函數(shù)的調(diào)用時(shí)機(jī)是當(dāng)模塊成功接收了外界傳入的 Device 和 DeviceContext 之后。
以上便是基于 DirectX 的圖像縮放模塊 D3DResizeModule 的設(shè)計(jì)與實(shí)現(xiàn),通過(guò)集成 BMF 開(kāi)發(fā)環(huán)境,可以編譯出對(duì)應(yīng)的 BMF 模塊
調(diào)用程序
本節(jié)將實(shí)現(xiàn)一個(gè)測(cè)試 demo 程序,用于測(cè)試和調(diào)用上文中所構(gòu)建的圖像縮放模塊,測(cè)試程序?qū)崿F(xiàn)如下:
#include <bmf/sdk/log.h>
#include <bmf/sdk/video_frame.h>
#include <nlohmann/json.hpp>
#include <builder.hpp>
#include <chrono>
#include <d3d11_common.h>
#include <fstream>
using json = nlohmann::json;
using namespace std::chrono;
namespace fs = std::filesystem;
static const int TESTINPUTWIDTH = 720;
static const int TESTINPUTHEIGHT = 1280;
static const int TESTOUTPUTWIDTH = 1080;
static const int TESTOUTPUTHEIGHT = 1920;
static void readRGBAFile(std::vector<uint8_t>& data, const std::string& filename) {
std::ifstream file(filename, std::ios::binary);
if (!file) {
throw std::runtime_error("cannot open yuv file!");
}
file.seekg(0, std::ios::end);
std::streampos fileSize = file.tellg();
file.seekg(0, std::ios::beg);
if (fileSize != (TESTINPUTWIDTH * TESTINPUTHEIGHT * 4)) {
throw std::runtime_error("file size not compared with TESTINPUTWIDTH and TESTINPUTHEIGHT! it should be a RGBA data");
}
file.read(reinterpret_cast<char*>(data.data()), fileSize);
file.close();
}
int main(int argc, char const *argv[])
{
static const int profile_time = 1;
HRESULT hr = S_OK;
BMFComPtr<ID3D11Device> device = nullptr;
BMFComPtr<ID3D11DeviceContext> context = nullptr;
BMFComPtr<ID3D11ComputeShader> computeShader = nullptr;
BMFComPtr<ID3D11Texture2D> input_rgba = nullptr;
// Initialize device and context
hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0,
D3D11_SDK_VERSION, &device, nullptr, &context);
if (FAILED(hr)) {
std::cerr << "Failed to create D3D11 device" << std::endl;
exit(EXIT_FAILURE);
}
std::vector<uint8_t> filedata(TESTINPUTWIDTH * TESTINPUTHEIGHT * 4, 0);
readRGBAFile(filedata, "../../../files/test_opencv_lenna_720x1280.rgb");
ID3DBlob* pBlob = nullptr;
D3D11_SUBRESOURCE_DATA sd;
sd.pSysMem = filedata.data();
sd.SysMemPitch = TESTINPUTWIDTH * sizeof(uint8_t) * 4;
sd.SysMemSlicePitch = TESTINPUTWIDTH * TESTINPUTHEIGHT * sizeof(uint8_t) * 4;
CD3D11_TEXTURE2D_DESC texDesC(DXGI_FORMAT_R8G8B8A8_UNORM, TESTINPUTWIDTH, TESTINPUTHEIGHT, 1, 1, (D3D11_BIND_FLAG)(D3D11_BIND_SHADER_RESOURCE), D3D11_USAGE_DEFAULT, 0, 1, 0, D3D11_RESOURCE_MISC_SHARED);
texDesC.CPUAccessFlags = 0;
texDesC.MipLevels = 1;
texDesC.ArraySize = 1;
hr = device->CreateTexture2D(&texDesC, &sd, &input_rgba);
if (FAILED(hr)) {
std::cerr << "Failed to CreateTexture2D" << std::endl;
exit(EXIT_FAILURE);
}
{
try {
bmf::builder::Graph graph = bmf::builder::Graph(bmf::builder::GeneratorMode);
auto input_stream0 = graph.InputStream(
"config_stream", "stream0", "");
auto input_stream1 = graph.InputStream(
"data_stream", "stream1", "");
json roi_option = {
{"width", TESTOUTPUTWIDTH},
{"height", TESTOUTPUTHEIGHT}
};
std::string moduleName;
#ifdef _DEBUG
moduleName = "d3dresizemoduled.dll";
#else
moduleName = "d3dresizemodule.dll";
#endif
auto resize_module = graph.CppModule({ input_stream0, input_stream1 }, "d3dresizemodule", bmf_sdk::JsonParam(roi_option),
"", moduleName, "d3dresizemodule:D3DResizeModule");
auto resize_output_stream = resize_module.Stream(0);
std::vector<bmf::builder::Stream> enhanceGenerateStreams;
enhanceGenerateStreams.emplace_back(resize_output_stream);
graph.Start(enhanceGenerateStreams);
D3D11DeviceWrapper device_wrapper;
D3D11DeviceContextWrapper device_context_wrapper;
device_wrapper.device = device.Get();
device_context_wrapper.device_context = context.Get();
graph.FillPacket(input_stream0.GetName(), bmf_sdk::Packet(device_wrapper));
graph.FillPacket(input_stream0.GetName(), bmf_sdk::Packet(device_context_wrapper));
D3D11TextureWrapper input;
input.texture = input_rgba.Get();
graph.FillPacket(input_stream1.GetName(), bmf_sdk::Packet(input));
Packet output_pkt = graph.Generate(resize_output_stream.GetName());
ID3D11Texture2D* out_texture = nullptr;
D3D11TextureWrapper output_ = output_pkt.get<D3D11TextureWrapper>();
out_texture = output_.texture;
// Read Output and write file local
std::vector<uint8_t> outdata(TESTOUTPUTWIDTH * TESTOUTPUTHEIGHT * 4, 1);
ReadDataFromTexture(device.Get(), context.Get(), out_texture, TESTOUTPUTWIDTH, TESTOUTPUTHEIGHT, DXGI_FORMAT_R8G8B8A8_UNORM, outdata, 4);
std::ofstream outputFile("output.rgb", std::ios::binary);
if (!outputFile.is_open()) {
printf("open outputfile error!\n");
}
outputFile.write((const char*)outdata.data(), sizeof(uint8_t) * TESTOUTPUTWIDTH * TESTOUTPUTHEIGHT * 4);
outputFile.close();
out_texture->Release();
out_texture = nullptr;
context->Flush();
}
catch (const fs::filesystem_error& e) {
std::cerr << "FileSystem error: " << e.what() << std::endl;
}
catch (const std::exception& e) {
std::cerr << "General error: " << e.what() << std::endl;
}
}
return 0;
}
以下是程序的主要步驟和功能:
1.初始化 DirectX 11 設(shè)備和上下文:
使用 D3D11CreateDevice
函數(shù)創(chuàng)建 D3D11 設(shè)備和上下文對(duì)象。
2.準(zhǔn)備輸入圖像數(shù)據(jù):
從文件中讀取 RGBA 格式的測(cè)試圖像數(shù)據(jù)。
3.創(chuàng)建輸入圖像的 D3D11 紋理對(duì)象:
使用 CreateTexture2D
函數(shù)創(chuàng)建輸入圖像的 D3D11 紋理對(duì)象。
4.構(gòu)建 BMF Graph:
使用生成器模式創(chuàng)建并初始化 bmf::builder::Graph
添加 2 個(gè)輸入流(配置流和數(shù)據(jù)流)和輸出流,以及需要的參數(shù),通過(guò) json 數(shù)據(jù)創(chuàng)建要縮放的圖像寬高。
導(dǎo)入名為 d3dresizemodule
的多媒體處理模塊,并連接輸入流和輸出流,調(diào)用 Start 函數(shù)啟動(dòng) Graph
5.填充輸入數(shù)據(jù):
將創(chuàng)建的 D3D11 設(shè)備和上下文對(duì)象填充到配置流中,驅(qū)動(dòng)模塊內(nèi)部完成 DirectX 側(cè)的初始化
將創(chuàng)建的輸入圖像紋理送入到數(shù)據(jù)流中。
6.執(zhí)行 BMF Graph:
通過(guò)調(diào)用 graph.Generate
方法驅(qū)動(dòng)模塊執(zhí)行處理流程,獲取數(shù)據(jù)幀
7.讀取輸出數(shù)據(jù)并寫(xiě)入文件:
從生成的數(shù)據(jù)中獲取輸出圖像的 D3D11 紋理對(duì)象。
使用自定義函數(shù) ReadDataFromTexture
讀取紋理數(shù)據(jù)。
將輸出圖像數(shù)據(jù)寫(xiě)入文件 output.rgb
中。
8.調(diào)用 Flush 方法清空任務(wù)隊(duì)列,同步釋放 DirectX 相關(guān)資源
9.異常處理:
在程序執(zhí)行過(guò)程中捕獲可能出現(xiàn)的異常,并輸出錯(cuò)誤信息。
通過(guò)以上步驟,測(cè)試程序?qū)崿F(xiàn)了對(duì) Direct3D 11 實(shí)現(xiàn)的圖像 resize 模塊的測(cè)試,并將處理結(jié)果保存到文件中,用于后續(xù)的分析和驗(yàn)證。
構(gòu)建腳本與運(yùn)行
本節(jié)主要介紹構(gòu)建并運(yùn)行圖像縮放處理程序 Demo 的流程與步驟,同樣需要進(jìn)入 msys2 環(huán)境,本文實(shí)現(xiàn)的 Demo 可以通過(guò)構(gòu)建腳本實(shí)現(xiàn)多種不同 Module 的選擇性構(gòu)建,Demo 項(xiàng)目的目錄結(jié)構(gòu)如下所示:
bmf_demo/
│
├── cmake/
│ ├── win-toolchain.cmake
├── modules/
│ ├── common/
│ │ ├── d3d11/
│ │ │ ├── include/
│ │ │ ├ ├── d3d11_common.h
│ │ │ ├── src/
│ │ │ ├ ├── d3d11_common.cpp
│ ├── d3d11resizemodule/
│ │ ├── include/
│ │ ├ ├── d3dresizemodule.h
│ │ ├── src/
│ │ ├ ├── d3dresizemodule.cpp
│ │ ├── test/
│ │ ├ ├── test_d3dresizemodule.cpp
│ │ ├── CMakeLists.txt
│ └── ...
│
├── build.sh
├── CMakeLists.txt
└── CMakePresets.json
其中 cmake/win-toolchain.cmake 是一個(gè) cmake 配置文件,其中包括 BMF 適配 Windows msvc 環(huán)境的編譯選項(xiàng)配置,modules 文件夾下包含了諸多用戶(hù)實(shí)現(xiàn)的 modules,其中 common 文件夾為公共文件目錄,其中實(shí)現(xiàn)了上文所述的 DirectX API,如果用戶(hù)想添加新實(shí)現(xiàn)的 module,可以在 modules 文件夾下再新建一個(gè)文件夾,命名為 module 名稱(chēng),內(nèi)部文件布局保持與 d3d11resizemodule 一致即可。
build.sh
腳本用于構(gòu)建項(xiàng)目,并支持一些參數(shù)來(lái)配置構(gòu)建過(guò)程。這個(gè)解釋使用方法和每個(gè)參數(shù)的意義和作用
--module
指定要編譯構(gòu)建的 module 名稱(chēng),需要與 modules 文件夾下的某個(gè)目錄對(duì)齊命名
--msvc
指定 msvc 版本,支持[2013, 2015, 2017, 2019, 2022]
--bmf_lite
指定 lite 控制標(biāo)識(shí)位,用于控制內(nèi)部的一些 Win 的特化配置項(xiàng)
--test
控制是否編譯對(duì)應(yīng)測(cè)試 demo 程序,內(nèi)部通過(guò) cmake 控制
對(duì)于本例,本文使用的編譯構(gòu)建命令如下:
./build.sh --msvc=2022 --module=d3dresizemodule --bmf-lite --test
在構(gòu)建項(xiàng)目前,需要設(shè)置 BMF 庫(kù)的 INCLUDE 和 LIB 環(huán)境變量
export BMF_INCLUDE_DIR=/path/to/bmf/include
export BMF_LIBRARY_DIR=/path/to/bmf/lib
在 msys 執(zhí)行腳本后,會(huì)在本地生成 build_windows_lite_{preset} 文件夾,與 BMF 框架相同,可以使用 Visual Studio 界面交互進(jìn)行項(xiàng)目構(gòu)建,或使用命令行直接構(gòu)建
構(gòu)建完成后,在項(xiàng)目目錄本地會(huì)生成 output 文件夾,切換至 output/bin/{preset}/Release/,執(zhí)行 ./test_d3dresizemodule.exe 程序,程序輸出如下
發(fā)現(xiàn)在本地生成了 output.rgb 文件
使用 ffplay 查看生成的圖像
ffplay -pix_fmt rgba -s 1080x1920 output.rgb
結(jié)果如下