精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

在 Windows 下玩轉多媒體處理框架 BMF

系統 Windows
本文將沿三個步驟,全面介紹 BMF 框架在 Windows 端的能力建設與技術實踐,首先介紹 BMF 框架在 Windows 環境下如何配置與編譯,其次介紹如何在 Windows 環境配置 BMF 開發環境,并展示一個簡單 Python 模塊的運行過程,最后展示一個基于 DirectX 的全鏈路圖像縮放模塊的開發與部署案例...

一、簡介

現代科技網絡日益發達,視頻已經成為人們生活中不可或缺的一部分。隨著互聯網和移動設備的普及,視頻內容在傳播和分享方面發揮著越來越重要的作用。從社交媒體到在線教育,從數字廣告到遠程工作,視頻已經成為人們獲取信息、娛樂和交流的主要方式之一。在這樣一個視頻日益普及的超視頻時代,開發一套跨語言、跨設備、跨系統的多媒體處理框架顯得尤為重要,這樣的框架可以為開發人員提供統一的解決方案,幫助他們在不同的平臺上快速、高效地處理多媒體內容,從而提供一致的用戶體驗和功能,是迎接未來的必然趨勢。

在當今數字化的世界中,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 框架需要依賴以下環境:

  1. MSYS2。提供了一個基于開源軟件的本機構建環境,可以在 Windows 上用 Linux 的方式使用多種不同的環境和工具來執行不同的任務。
  2. CMake。管理框架構建過程,推薦版本 3.27。
  3. Visual Studio 2013 - 2022。BMF 在 Win 端選用兼容性較好的 msvc 編譯工具鏈,目前支持版本 2013 - 2022。

以上三個依賴是必須項,下面還有 2 個依賴可供選擇是否打開相關的框架能力:

  1. Python 3.7 - 3.10。用于編譯 BMF Python SDK,如果不提供,則框架無法編譯 Python 相關的調用能力
  2. FFmpeg 4.4 - 5.1。用于編譯 BMF built-in modules(ffmpeg_decoder、ffmpeg_encoder、ffmpeg_filter),如果不提供,默認取消編譯相關產物

本文在介紹編譯過程時,默認會打開以上兩個選項,實現一次全鏈路的編譯構建,下面用圖文方式介紹 BMF 框架編譯過程:

  1. 打開 Visual Studio 的命令提示環境,建議以管理員方式

圖片

  1. 找到 MSYS2 的安裝目錄,執行命令: .\msys2_shell.cmd -use-full-path,攜帶宿主機環境進入 msys2(注意:這里需要保證 CMake 工具已經成功配置進系統環境變量,本文的實驗環境默認配置了 CMake 3.27、FFmpeg 4.4、Python 3.7.9 的環境變量)。
  2. 克隆 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 目錄,命令如下:

export INCLUDE="$INCLUDE;C:\path\to\include_for_ffmpeg"

以 msvc 2022 編譯 x64-Debug 版本為例,腳本執行命令:

./build_win_lite.sh --msvc=2022 --preset=x64-Debug bmf_ffmpeg

執行后會在項目目錄的 build_win_lite/x64-Debug 目錄生成 BMF.sln 解決方案

圖片

之后,您可以雙擊 sln 文件使用 Visual Studio 友好的界面進行項目構建和編譯,您也可以使用以下 CMake 命令直接通過命令行進行項目構建:

cmake --build build_win_lite/x64-Debug --config Debug --target ALL_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 運行環境

export PYTHONHOME="$(dirname "$(which python)")"
export PYTHONPATH=$(pwd)/output/bmf/lib:$(pwd)/output

配置完畢后,進入 python 環境應該可以正常 import bmf 框架,如圖所示

圖片

我們要運行的 customize_module 文件在 output/test/customize_module 目錄下的 my_module.py 文件,模塊的實現代碼如下:

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

可以看到,模塊僅僅將幀從輸入隊列取出,不做任何處理,直接傳遞至輸出隊列,我們將要執行的測試程序 test_customize_module.py 的實現如下:

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()

在測試程序的 33 - 37 行中,我們構建了一個 BMF Graph,首先對輸入視頻進行解碼,隨后調起事先我們寫好的 Python 模塊 my_module 對解碼后的視頻幀進行一次處理,最后調用 encode 模塊對視頻幀進行編碼,產出輸出文件,剩余部分是框架使用 Google Test 框架所進行的一些轉碼指標的驗證,這里不深入追溯,程序使用的輸入視頻是 BMF 框架為集成測試預先準備好的一組測試資源包,您可以通過https://github.com/BabitMF/bmf/releases/download/files/files.tar.gz 進行下載和使用,本文將使用這個資源包,下載命令如下

(cd output && wget https://github.com/BabitMF/bmf/releases/download/files/files.tar.gz && tar xvf files.tar.gz && rm -rf files.tar.gz)

至此,所有執行該程序的前置依賴均已準備完畢,切換到 customize_module 目錄執行程序

cd test/customize_module
python test_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 或更高版本執行計算著色器時,通常需要以下步驟:

  1. 創建設備和設備上下文:
    a.  創建 DirectX 設備對象,通常通過調用 D3D11CreateDevice() 函數。
    b.  為了執行計算著色器,設備需要支持 DirectCompute 功能,因此需要檢查設備是否支持 DirectCompute。可以通過檢查設備屬性來實現。
  2. 創建計算著色器:
    a.  創建計算著色器對象,通常通過編譯 HLSL(High-Level Shading Language)代碼而獲得。可以使用 HLSL 編譯器將計算著色器代碼編譯為字節碼形式。
    b.  使用 ID3D11Device::CreateComputeShader() 函數創建計算著色器對象。
  3. 創建常量緩沖區和資源:
    a.  如果計算著色器需要常量或者其他資源作為輸入,則需要創建對應的常量緩沖區或者資源。
    b.  常量緩沖區通常通過 ID3D11Device::CreateBuffer() 函數創建,然后通過 ID3D11DeviceContext::CSSetConstantBuffers() 函數將常量緩沖區綁定到計算著色器上下文。
    c.  其他資源,如紋理、UAV、SRV 視圖、常亮緩沖區等,可以通過相應的創建函數創建,并通過 ID3D11DeviceContext::CSSetShaderResources() 函數將其綁定到計算著色器上下文。
  4. 設置執行參數:
    a.  在執行計算著色器之前,需要設置執行參數,包括計算著色器的線程組數等。
    b.  使用 ID3D11DeviceContext::Dispatch() 函數設置計算著色器執行的線程組數。
  5. 執行計算著色器:
    a.  調用 ID3D11DeviceContext::CSSetShader() 函數將計算著色器綁定到設備上下文。
    b.  調用 ID3D11DeviceContext::Dispatch() 函數執行計算著色器。
  6. 等待執行完成:可以通過插入事件或者查詢設備上下文的執行狀態來等待計算著色器的執行完成。
  7. 清理資源:在完成計算著色器的使用后,需要釋放相關資源,包括計算著色器對象、常量緩沖區、資源等。

基于以上流程,本節將構建一個 BMF 模塊,命名為 d3dresizemodule ,d3dresizemodule 擁有兩個輸入流(InputStream),編號 0、1,0 號輸入流負責接收 Device、Devicecontext 等基礎資源管理對象,并控制 DirectX 側的初始化流程,在初始化流程中需要完成紋理、SRV/UAV 視圖、著色器、采樣器等資源的創建和初始化,因此 0 號輸入流也被命名為“配置流”(config_stream)。1 號流負責在 DirectX 資源成功被初始化后,接收外界調用方傳入的輸入紋理數據,并職責,因此也被命名為“數據流”(data_stream),模塊整體架構如下圖所示:

圖片image.png

d3dresizemodule 模塊的聲明文件實現如下所示:

#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 側初始化函數,需通過配置流成功配置 device_、device_context_ 后觸發
  int32_t unsafe_init();
  int32_t init_d3d11();
  // 模塊處理函數,由 BMF 框架驅動調用
  int32_t process(Task &task);
  int32_t unsafe_process(Task &task);
  int32_t close();
  // UAV 功能檢測函數
  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 的調用能力,用戶可以調用這些接口輕易地實現 DirectX 相關資源的創建和管理,文件內部主要封裝了一些與 DirectX 11 相關的常用操作和數據結構。讓我們逐一解析:

1.包含頭文件:

包含了一些與 DirectX 11 相關的頭文件,如 <d3d11.h>, <wrl/client.h>, <d3dcompiler.h> 等。這些頭文件包含了 DirectX 11 中定義的接口和數據結構。

2.定義了一些結構體:

a.D3D11DeviceWrapper 結構體用于封裝了一個 DirectX 11 設備對象的指針。

b.D3D11DeviceContextWrapper 結構體用于封裝了一個 DirectX 11 設備上下文對象的指針。

c.D3D11TextureWrapper 結構體用于封裝了一個 DirectX 11 紋理對象的指針。

d.InputSizeBuffer 結構體用于定義輸入尺寸緩沖區的數據結構,主要用于 DirectX 常量緩沖區。

e.OutputSizeBuffer 結構體用于定義輸出尺寸緩沖區的數據結構,主要用于 DirectX 常量緩沖區。
3.定義了一個模板別名:

a.BMFComPtr 是一個模板別名,用于簡化使用 Microsoft::WRL::ComPtr類型的代碼。

4.聲明了一些外部函數:

a.CreateTexture 函數用于創建一個 DirectX 11 紋理對象。

b.CreateUAV 函數用于創建一個 DirectX 11 無序訪問視圖對象。

c.CreateSRV 函數用于創建一個 DirectX 11 著色器資源視圖對象。

d.createComputeShader 函數用于創建一個 DirectX 11 計算著色器對象。

e.CreateStagingTexture 函數用于創建一個用于數據傳輸的臨時紋理對象。

f.CreateSampleState 函數用于創建一個 DirectX 11 采樣器狀態對象

總的來說,這個頭文件封裝了一些常用的 DirectX 11 操作函數和數據結構,提供了一種簡化 DirectX 11 編程的方式,使得開發者可以更方便地使用 DirectX 11 相關功能,下面是 d3d11_common.h 文件的實現:

#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

關于 Shader 的編譯,默認的方式是將 shader 代碼寫在 HLSL 文件中,在程序初始化時調用編譯程序讀取文件進行編譯,這種方式會要求強制暴露 HLSL 代碼實現,外界調用方才可以通過 BMF 框架正確加載模塊,不利于 Shader 代碼的封裝,Demo 中使用 map 封裝每個 Shader 的字符二進制,并分類管理,這樣的好處是在模塊編譯出 dll 時,相關 hlsl 代碼已經成功被封裝進模塊內部,無需額外附上 hlsl 代碼文件,下面關于是 gpuresize Shader 的實現:

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 定義了采樣器、輸入輸出紋理、常量緩沖區等資源,在計算主邏輯中,首先判斷當前線程是否處于輸出圖像范圍內,若超出范圍則直接返回。然后,根據當前線程的索引計算對應的采樣點坐標,并使用 SampleLevel 方法從輸入紋理中進行線性采樣,獲取采樣到的 RGBA 像素值,并將其寫入輸出紋理中。

關于模塊的具體實現,這里重點分析三個函數:init_d3d11、process、unsafe_process,首先來看 process 函數:

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 函數由 BMF 框架層調用,本模塊將函數執行邏輯主體封裝在 unsafe_process 函數中,process 函數使用 try catch 捕獲異常,起到兜底作用,unsafe_process 的實現如下:

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;
}

這段代碼實現的主要功能如下:

1.初始化階段(未初始化時):

通過從模塊的配置流中獲取數據包,初始化 D3D11 設備和設備上下文對象。

如果獲取到了設備和設備上下文對象,則調用 init_d3d11 函數進行 DirectX 11 的初始化。

2.處理階段:

  • 清空之前的著色器資源視圖和無序訪問視圖。
  • 設置輸入紋理的著色器資源視圖和輸出紋理的無序訪問視圖。
  • 設置計算著色器,并執行計算著色器的調度,實現了一個 D3D11 RGBA 紋理的 resize 功能

  a .通過循環從模塊的數據流獲取數據包,直到獲取到結束標志(BMF_EOF)為止。

  b.如果獲取到的數據包類型為 D3D11TextureWrapper,則表示獲取到了 D3D11 紋理對象。

  c.根據獲取到的輸入紋理,創建一個新的輸出紋理對象。

  d.調用上文描述的接口,創建輸入紋理的著色器資源視圖(SRV)和輸出紋理的無序訪問視圖(UAV)。

  e.執行一系列的 DirectX 11 操作:

  f.封裝輸出紋理對象為數據包,并 push 到模塊的輸出隊列中,供外界調用代碼獲取


init_d3d11 函數的實現如下:

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;
    }
}

代碼主要實現的邏輯如下:

  • 檢查設備和設備上下文對象是否已經初始化,如果沒有則拋出異常。
  • 獲取當前設備的特性級別(Feature Level),并檢查是否支持 D3D11 特性級別 11.0 以上,如果不支持則拋出異常。
  • 檢查硬件是否支持 UAV(Unordered Access View)加載特性,如果不支持則拋出異常。
  • 編譯創建計算著色器,用于圖像的 resize 操作。
  • 創建采樣器狀態(Sampler State),用于計算著色器中的紋理采樣。
  • 創建輸出大小的常量緩沖區(Constant Buffer),用于傳遞輸出圖像的寬度和高度信息給計算著色器。
  • 將輸出大小的緩沖區數據初始化,并創建 D3D11 緩沖區對象。
  • 設置標志位 inited 為 true,表示 D3D11 初始化完成。

從上文 unsafe_process 函數的邏輯可以獲知:init_d3d11 函數的調用時機是當模塊成功接收了外界傳入的 Device 和 DeviceContext 之后。

以上便是基于 DirectX 的圖像縮放模塊 D3DResizeModule 的設計與實現,通過集成 BMF 開發環境,可以編譯出對應的 BMF 模塊

調用程序

本節將實現一個測試 demo 程序,用于測試和調用上文中所構建的圖像縮放模塊,測試程序實現如下:

#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 設備和上下文

使用 D3D11CreateDevice 函數創建 D3D11 設備和上下文對象。

2.準備輸入圖像數據

從文件中讀取 RGBA 格式的測試圖像數據。

3.創建輸入圖像的 D3D11 紋理對象

使用 CreateTexture2D 函數創建輸入圖像的 D3D11 紋理對象。

4.構建 BMF Graph

使用生成器模式創建并初始化 bmf::builder::Graph

添加 2 個輸入流(配置流和數據流)和輸出流,以及需要的參數,通過 json 數據創建要縮放的圖像寬高。

導入名為 d3dresizemodule 的多媒體處理模塊,并連接輸入流和輸出流,調用 Start 函數啟動 Graph

5.填充輸入數據

將創建的 D3D11 設備和上下文對象填充到配置流中,驅動模塊內部完成 DirectX 側的初始化

將創建的輸入圖像紋理送入到數據流中。

6.執行 BMF Graph

通過調用 graph.Generate 方法驅動模塊執行處理流程,獲取數據幀

7.讀取輸出數據并寫入文件

從生成的數據中獲取輸出圖像的 D3D11 紋理對象。

使用自定義函數 ReadDataFromTexture 讀取紋理數據。

將輸出圖像數據寫入文件 output.rgb 中。

8.調用 Flush 方法清空任務隊列,同步釋放 DirectX 相關資源

9.異常處理

在程序執行過程中捕獲可能出現的異常,并輸出錯誤信息。

通過以上步驟,測試程序實現了對 Direct3D 11 實現的圖像 resize 模塊的測試,并將處理結果保存到文件中,用于后續的分析和驗證。

構建腳本與運行

本節主要介紹構建并運行圖像縮放處理程序 Demo 的流程與步驟,同樣需要進入 msys2 環境,本文實現的 Demo 可以通過構建腳本實現多種不同 Module 的選擇性構建,Demo 項目的目錄結構如下所示:

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 是一個 cmake 配置文件,其中包括 BMF 適配 Windows msvc 環境的編譯選項配置,modules 文件夾下包含了諸多用戶實現的 modules,其中 common 文件夾為公共文件目錄,其中實現了上文所述的 DirectX API,如果用戶想添加新實現的 module,可以在 modules 文件夾下再新建一個文件夾,命名為 module 名稱,內部文件布局保持與 d3d11resizemodule 一致即可。

build.sh 腳本用于構建項目,并支持一些參數來配置構建過程。這個解釋使用方法和每個參數的意義和作用

--module

指定要編譯構建的 module 名稱,需要與 modules 文件夾下的某個目錄對齊命名

--msvc

指定 msvc 版本,支持[2013, 2015, 2017, 2019, 2022]

--bmf_lite

指定 lite 控制標識位,用于控制內部的一些 Win 的特化配置項

--test

控制是否編譯對應測試 demo 程序,內部通過 cmake 控制

對于本例,本文使用的編譯構建命令如下:

./build.sh --msvc=2022 --module=d3dresizemodule --bmf-lite --test

在構建項目前,需要設置 BMF 庫的 INCLUDE 和 LIB 環境變量

export BMF_INCLUDE_DIR=/path/to/bmf/include
export BMF_LIBRARY_DIR=/path/to/bmf/lib

在 msys 執行腳本后,會在本地生成 build_windows_lite_{preset} 文件夾,與 BMF 框架相同,可以使用 Visual Studio 界面交互進行項目構建,或使用命令行直接構建

圖片圖片

構建完成后,在項目目錄本地會生成 output 文件夾,切換至 output/bin/{preset}/Release/,執行 ./test_d3dresizemodule.exe 程序,程序輸出如下

圖片

發現在本地生成了 output.rgb 文件

圖片

使用 ffplay 查看生成的圖像

ffplay -pix_fmt rgba -s 1080x1920 output.rgb

結果如下

圖片


責任編輯:龐桂玉 來源: 字節跳動技術團隊
相關推薦

2023-08-15 13:57:08

開發者

2013-08-28 16:08:19

多媒體Windows8.1

2011-06-09 10:07:28

Qt phonon

2010-01-27 13:52:15

Android多媒體框

2014-07-16 16:17:00

2012-05-02 13:22:46

JavaJava設計

2010-08-01 15:34:27

Android

2011-06-24 10:21:11

Qt phonon 多媒體

2010-06-30 10:38:05

2009-02-09 23:29:40

兼容硬件驅動

2015-09-20 20:13:55

2009-12-25 17:02:33

WPF多媒體

2010-10-27 11:27:50

MAS視頻監控H3C

2013-12-17 11:18:53

iOS開發多媒體API

2013-12-17 13:29:04

iOS開發多媒體

2023-05-15 07:28:48

2010-05-05 21:56:37

融合通信

2009-02-20 19:56:26

WindowsLinux雙系統

2009-12-22 16:29:51

Linux多媒體軟件
點贊
收藏

51CTO技術棧公眾號

国产丶欧美丶日本不卡视频| 欧美日韩123| 亚洲精品视频在线看| 亚洲a在线播放| 久久免费在线观看视频| 亚洲1区在线| 婷婷综合在线观看| 欧美高清视频一区| 亚洲 小说区 图片区| 午夜精品毛片| 精品91自产拍在线观看一区| 欧美日韩黄色一级片| 亚洲av片一区二区三区| 日韩va亚洲va欧美va久久| 色婷婷综合成人av| 黑森林av导航| 日韩中文影院| 亚洲精品免费看| 久久99精品久久久久子伦| 精品视频久久久久| 久久av资源| 欧美日韩国内自拍| 亚洲午夜精品一区二区三区| 精品人妻久久久久一区二区三区| 亚洲激情不卡| 最近更新的2019中文字幕| 不卡的一区二区| 欧美第一视频| 一区二区三区四区不卡在线| 欧美精品七区| 99国产精品久久久久久久成人| 亚洲激情偷拍| 久久久精品在线观看| 中文字幕 亚洲一区| 欧美一区=区三区| 国产精品成人午夜| 麻豆av一区二区三区| 国产又黄又粗又硬| 久久久久久久尹人综合网亚洲| www日韩中文字幕在线看| 亚洲精品乱码久久久久久不卡 | 欧美成人激情在线| 久久久视频6r| 国产成人夜色高潮福利影视| 欧美亚洲一区二区在线| 国精产品一区一区三区视频| 日本在线视频站| av在线这里只有精品| 国产精品网站大全| 六月丁香婷婷综合| 狠狠入ady亚洲精品经典电影| 在线观看日韩专区| 中文字幕一区二区人妻在线不卡| 精品91福利视频| 欧美三级电影一区| 四虎永久在线精品无码视频| free性m.freesex欧美| 综合色中文字幕| 色99中文字幕| www.在线视频.com| 久久精品在线免费观看| 日本精品一区二区三区高清 久久| 天天操天天干天天舔| 成人深夜视频在线观看| 精品视频在线观看| 毛片在线播放网址| 国产欧美日韩在线看| 亚洲电影免费| 国产成人l区| 一区二区三区日韩欧美| 日韩国产一级片| 欧美激情网站| 欧美亚洲自拍偷拍| 91香蕉国产线在线观看| 盗摄牛牛av影视一区二区| 精品视频久久久久久久| 日本少妇xxxxx| 99精品视频在线| 欧美激情va永久在线播放| 亚洲欧美在线视频免费| 日韩精品一二三| 91久久久久久久久久久久久| 国产男男gay体育生白袜| 成人免费毛片app| 区一区二区三区中文字幕| 日本高清中文字幕在线| 一区二区三区成人| 国产精品亚洲αv天堂无码| 成人国产一区| 精品国产乱码久久久久久久| 91精品人妻一区二区三区蜜桃欧美| 青青草成人影院| 久久久久久成人精品| 亚洲 欧美 日韩 在线| 韩国欧美国产1区| 久久精品99| 麻豆系列在线观看| 香蕉加勒比综合久久| 爱情岛论坛亚洲首页入口章节| 精品国产麻豆| 亚洲美女av黄| 久久网一区二区| 日韩电影免费在线看| 99视频在线免费观看| 岛国在线视频| 亚洲一二三区在线观看| xxxx一级片| 国产香蕉精品| 麻豆成人在线看| 人人妻人人爽人人澡人人精品| 国产一区二区在线看| 欧美视频1区| 黄视频在线免费看| 欧美一区二区三区在线看 | 国产日韩欧美麻豆| 99在线观看视频免费| 久久91视频| 亚洲精品小视频在线观看| 欧美成人精品一区二区免费看片| 日韩精品电影一区亚洲| 国产在线一区二区三区四区| 91中文在线| 欧美日韩国产色站一区二区三区| 人妻无码中文久久久久专区| 综合久久婷婷| 成人在线小视频| 97视频在线观看网站| 欧美日韩激情视频8区| 丰满熟女人妻一区二区三区| 68国产成人综合久久精品| 国产黑人绿帽在线第一区| 午夜视频在线免费播放| 亚洲国产婷婷综合在线精品| 国产探花一区二区三区| 91精品一区国产高清在线gif| 国产精品久久久久福利| 你懂的好爽在线观看| 精品国产乱码久久久久久婷婷 | 99a精品视频在线观看| 久久精品国产成人精品| 一级片免费观看视频| 日本一二三不卡| 超碰在线97免费| 国产一区网站| 国产精品极品尤物在线观看| 国产福利在线| 欧美色图天堂网| 日本黄区免费视频观看| 久久机这里只有精品| 致1999电视剧免费观看策驰影院| 国产91精品在线| 日韩最新在线视频| 国产三级漂亮女教师| 亚洲老妇xxxxxx| 亚洲成人av免费观看| 欧美在线三级| 国产精品自拍首页| 免费一二一二在线视频| 亚洲精品自产拍| 亚洲天堂五月天| 国产精品污www在线观看| 色www免费视频| 欧美在线免费| 国产在线观看一区| 国精产品一区二区三区有限公司| 中文字幕久久久av一区| 国产精品国产三级国产普通话对白| 亚洲欧美一区二区三区久本道91| 日韩精品视频网址| 亚洲二区在线| 日韩欧美一区二区三区四区| 久草综合在线| 国产69精品久久久久9| 天堂中文在线资| 欧美午夜精品久久久| 国产黄在线免费观看| jlzzjlzz国产精品久久| 青青在线免费观看视频| 五月精品视频| 国产专区一区二区三区| 国产亚洲欧美日韩精品一区二区三区| 中文字幕精品网| 后入内射欧美99二区视频| 色婷婷综合在线| 日韩a级片在线观看| 99国产欧美另类久久久精品| 午夜久久久精品| 亚洲国产99| 亚洲高清在线播放| 福利欧美精品在线| 国产精品免费福利| 欧美videossex| 亚洲人成电影网站色| 国产精品久久综合青草亚洲AV| 亚洲成av人片在线观看无码| 中文字幕第二区| 成人免费视频一区二区| 四季av一区二区| 亚洲激情二区| 国产又爽又黄ai换脸| 一区三区在线欧| 99蜜桃在线观看免费视频网站| 成人看片网站| 97福利一区二区| 婷婷在线播放| 日韩在线精品视频| 亚洲av成人精品一区二区三区在线播放 | 伊甸园亚洲一区| 懂色中文一区二区三区在线视频| 日本精品裸体写真集在线观看| 久久男人av资源网站| 免费看美女视频在线网站| 亚洲国产日韩精品在线| 国产男女无套免费网站| 色爱区综合激月婷婷| 国产乡下妇女做爰视频| 亚洲丝袜另类动漫二区| 熟女少妇内射日韩亚洲| 99国产精品久久久久久久久久 | 日本中文字幕影院| 日韩中文字幕亚洲一区二区va在线| 国产天堂视频在线观看| 98精品视频| 亚洲电影免费| 日韩国产欧美一区二区| 日本一区二区三不卡| 亚洲精品国产setv| 精品视频一区二区三区四区| 国产精品毛片视频| 不卡视频一区二区| 日本一区二区三区视频在线看| 成人免费视频网| 亚洲福利影视| 国产一区视频在线| 国产91在线精品| 国产精品久久97| 影视一区二区三区| 国产精品mp4| 亚洲涩涩在线| 日本aⅴ大伊香蕉精品视频| 美女扒开腿让男人桶爽久久软| 久久久久国色av免费观看性色 | 午夜影院福利社| 成人久久18免费网站麻豆| 日本一区二区三区在线免费观看| 韩日精品视频一区| 久久综合在线观看| 国产乱淫av一区二区三区 | 91麻豆6部合集magnet| 182在线视频| 91色porny在线视频| 久久无码人妻精品一区二区三区| 99久久免费精品| 老牛影视av老牛影视av| 久久久久久久性| 日本精品在线观看视频| 亚洲国产成人午夜在线一区| 国产精品1区2区3区4区| 综合久久给合久久狠狠狠97色| 中文字幕在线观看2018| 亚洲激情校园春色| 国产无遮挡免费视频| 欧美视频专区一二在线观看| 精品久久久久久久久久久久久久久久| 91福利精品视频| 国产精品九九九九| 亚洲成人精品视频| 欧美偷拍视频| 中文字幕一区二区三区电影| 欧美69xxx| 久久久视频在线| 阿v视频在线观看| 国产精品高精视频免费| 国产精品igao视频网网址不卡日韩| 亚洲xxxx做受欧美| 欧美精品中文| 在线一区高清| 亚洲精品孕妇| 91小视频网站| 成人看片黄a免费看在线| 国产精品国产三级国产专业不| 最近中文字幕一区二区三区| 97人人澡人人爽人人模亚洲| 欧美视频日韩视频| 国产 日韩 欧美 精品| 亚洲欧洲一区二区三区久久| 黄av在线免费观看| 668精品在线视频| 四虎影视成人精品国库在线观看| 国产精品免费在线播放| 成人国产精品一级毛片视频| 日韩免费在线观看av| 日韩精品欧美成人高清一区二区| 日韩欧美中文在线视频| 久久奇米777| 久久久久无码国产精品不卡| 欧美影片第一页| 懂色av一区二区三区四区| 一本色道久久88精品综合| 在线你懂的视频| 国产精品视频色| 美女呻吟一区| 无码人妻aⅴ一区二区三区日本| 久久久蜜桃一区二区人| 95视频在线观看| 中文字幕一区二区视频| 欧美日韩一级黄色片| 欧美成人艳星乳罩| 久操视频在线| 欧美日韩亚洲丝袜制服| 日韩经典在线视频| 黄色网页在线播放| 91精品国产成人| 日本99精品| 伊人av成人| 久久精品免费| 538国产视频| 亚洲国产一区二区三区 | 日韩丝袜美女视频| 999国产在线视频| 国产成人avxxxxx在线看| 精品欧美午夜寂寞影院| 永久免费看av| 精一区二区三区| 亚洲天堂最新地址| 色婷婷精品久久二区二区蜜臂av| 四虎在线视频免费观看| 久久人91精品久久久久久不卡| 成人在线视频www| 亚洲日本精品| 麻豆高清免费国产一区| www色com| 欧美午夜精品久久久久久超碰| 国产在线资源| 热久久这里只有精品| 亚洲精品合集| 男人天堂网视频| 26uuu国产电影一区二区| 中文字幕第15页| 日韩精品在线第一页| 深夜av在线| 欧美精品一区二区三区在线四季 | 亚洲品质自拍视频| 国产强伦人妻毛片| 欧美精品一区在线播放| 国产视频一区二| 五月天激情图片| 高清久久久久久| 日韩欧美亚洲国产| 日韩av在线网页| 亚洲欧洲日本韩国| 欧美三级电影在线播放| 青青草成人在线观看| 毛片aaaaaa| 6080午夜不卡| 日本不卡影院| 激情一区二区三区| 每日更新成人在线视频| 国产jk精品白丝av在线观看| 91极品美女在线| 午夜精品一区| 3d动漫啪啪精品一区二区免费 | 能看的毛片网站| 国产精品三级av在线播放| 国产剧情久久久| 韩国美女主播一区| 亚洲色图美女| 亚洲综合婷婷久久| 亚洲在线视频网站| 青青草在线播放| 国产视频福利一区| 亚洲欧美综合| 中文字幕免费看| 欧美日韩国产乱码电影| 亚洲小说区图片区都市| 国产精品视频免费一区| 噜噜噜91成人网| 亚洲人做受高潮| 亚洲国产精品字幕| 国产成人精品123区免费视频| 亚洲一区三区| 成人爱爱电影网址| 波多野结衣电影在线播放| 久久成人国产精品| 日韩有码中文字幕在线| 一区二区三区视频网| 亚洲一区二区高清| 久蕉在线视频| y111111国产精品久久婷婷| 久久伊人亚洲| 久久久国产精华液| 尤物精品国产第一福利三区| 视频一区中文字幕精品| 日韩一级片播放| 一区二区三区欧美亚洲| 国产黄在线观看| 国产精品免费看一区二区三区| 另类人妖一区二区av| 日本中文字幕免费| 久久精品视频亚洲|