欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

d3d9播放yuv

程序员文章站 2022-03-04 23:45:17
...

一、几个概念

后台缓冲surface,前台surface,交换链,离屏surface

后台缓冲surface和前台缓冲surface总是成对出现的,当我们进行绘图操作时,画面有可能出现闪烁,这是因为当前绘制的一幅图像没有同时出现在屏幕上导致的,更详细的可以看这篇文章。前台surface + 后台surface就可以解决这个问题,前台surface表示我们可以看到的画面,后台缓冲surface相当于一个缓冲区,每次绘图是在后台surface上进行的,然后完了再把两个surface做一个交换,这样后台surface就可以显示到屏幕上了。

交换链:就是将前台surface和后台surface做交换的

离屏surface:离屏surface是一个永远看不到的表面,通常用来存放位图,并对其中的一些数据做一些处理。

二、d3d9渲染yuv流程

1、创建D3D9:Direct3DCreate9

IDirect3D9 * WINAPI Direct3DCreate9(UINT SDKVersion);

SDKVersion设置为D3D_SDK_VERSION 

IDirect3D9是一个显示3D图形的物理设备的C++对象。它可以用于获得物理设备的信息和创建一个IDirect3DDevice9接口

可以通过它的GetAdapterDisplayMode()函数获取当前主显卡输出的分辨率,刷新频率等参数

D3DDISPLAYMODE d3dDisplayMode;
d3d9->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3dDisplayMode );

2、创建D3D9Device:CreateDevice

HRESULT CreateDevice(
  UINT                  Adapter,
  D3DDEVTYPE            DeviceType,
  HWND                  hFocusWindow,
  DWORD                 BehaviorFlags,
  D3DPRESENT_PARAMETERS *pPresentationParameters,
  IDirect3DDevice9      **ppReturnedDeviceInterface
);

参数介绍: 

Adapter:表示显示适配器的序号。D3DADAPTER_DEFAULT始终是主要的显示适配器
DeviceType:D3DDEVTYPE枚举类型的成员,它表示所需的设备类型。如果所需的设备类型不可用,则该方法将失败
            D3DDEVTYPE_HAL         :硬件加速
            D3DDEVTYPE_REF         :
            D3DDEVTYPE_SW          :软件
            D3DDEVTYPE_NULLREF     :
            D3DDEVTYPE_FORCE_DWORD :
hFocusWindow:Windows窗口句柄
BehaviorFlags:设置为软件顶点处理D3DCREATE_SOFTWARE_VERTEXPROCESSING,或者硬件顶点处理D3DCREATE_HARDWARE_VERTEXPROCESSING
pPresentationParameters:用于D3D9Device的一些参数
ppReturnedDeviceInterface:返回创建的device

下来我们看一下关于 D3DPRESENT_PARAMETERS 的设置

typedef struct _D3DPRESENT_PARAMETERS_
{
    UINT                BackBufferWidth;
    UINT                BackBufferHeight;
    D3DFORMAT           BackBufferFormat;
    UINT                BackBufferCount;
    D3DMULTISAMPLE_TYPE MultiSampleType;
    DWORD               MultiSampleQuality;
    D3DSWAPEFFECT       SwapEffect;
    HWND                hDeviceWindow;
    BOOL                Windowed;
    BOOL                EnableAutoDepthStencil;
    D3DFORMAT           AutoDepthStencilFormat;
    DWORD               Flags;
    /* FullScreen_RefreshRateInHz must be zero for Windowed mode */
    UINT                FullScreen_RefreshRateInHz;
    UINT                PresentationInterval;
} D3DPRESENT_PARAMETERS;

 成员介绍:

BackBufferWidth:后台缓冲surface的宽
BackBufferHeight:后台缓冲surface的高
一般将其设置为frame width和frame height
BackBufferFormat:后台缓冲surface的像素格式
BackBufferCount:后台缓冲surface的个数
MultiSampleType:全屏抗锯齿的类型
MultiSampleQuality:全屏抗锯齿的质量等级
SwapEffect:指定表面在交换链中是如何被交换的
            D3DSWAPEFFECT_DISCARD:后台缓冲表面区的东西被复制到屏幕上后,后台缓冲表面区的东西就没有什么用了,可以丢弃了。
            D3DSWAPEFFECT_FLIP:  后台缓冲表面拷贝到前台表面,保持后台缓冲表面内容不变。当后台缓冲表面大于1个时使用。
            D3DSWAPEFFECT_COPY:  同上。当后台缓冲表面等于1个时使用。
一般设置为D3DSWAPEFFECT_DISCARD
hDeviceWindow:窗口句柄
Windowed:设为true则为窗口模式,false则为全屏模式
EnableAutoDepthStencil:设为true,D3D将自动创建深度/模版缓冲
AutoDepthStencilFormat:深度/模版缓冲的格式
Flags:
FullScreen_RefreshRateInHz:刷新率,设定D3DPRESENT_RATE_DEFAULT使用默认刷新率
PresentationInterval:设置刷新的间隔,可以用以下方式:
                      D3DPRENSENT_INTERVAL_DEFAULT,则说明在显示一个渲染画面的时候必要等候显示器刷新完一次屏幕。例如显示器刷新率设为80Hz的话,则一秒最多可以显示80个渲染画面。
                      D3DPRENSENT_INTERVAL_IMMEDIATE:表示可以以实时的方式来显示渲染画面

3、创建D3D9Surface:CreateOffscreenPlainSurface创建一个离屏surface

HRESULT CreateOffscreenPlainSurface(
  UINT              Width,    // 离屏surface的宽
  UINT              Height,   // 离屏surface的高
  D3DFORMAT         Format,   // 离屏surface的格式YV12
  D3DPOOL           Pool,     // D3DPOOL定义了资源对应的内存类型
                              // D3D3POOL_DEFAULT: 默认值,表示存在于显卡的显存中。
                              // D3D3POOL_MANAGED:由Direct3D*调度内存的位置(显存或者缓存中)。
                              // D3DPOOL_SYSTEMMEM: 表示位于内存中。
  IDirect3DSurface9 **ppSurface,  // 要创建的surface
  HANDLE            *pSharedHandle
);

4、显示画面,显示画面的流程为:LockRect(获取离屏的surface) --> 拷贝数据到离屏的surface --> UnlockRect --> Clear --> BeginScene --> GetBackBuffer(获取后台缓冲区的buffer) --> StretchRect(将一个矩形区域的像素从设备内存的一个Surface转移到另一个Surface上) --> EndScene --> Present (显示)--> Release

三、具体代码

d3d9_player.h

#pragma once

#include <cstdint>
#include <d3d9.h>

enum PixFormat {
    I420,
    NV12,
    NV21
};

class D3D9Player {
public:
    D3D9Player();

    ~D3D9Player();

    void Init(uint32_t width, uint32_t height, PixFormat pix_format);

    void CreateRenderWindow();

    void SetWindow(void* handle);

    void Render(uint8_t* data);

private:
    void InitRender();

    void CreateSurface();

    void DesrtoySurface();

    void Cleanup();

private:
    uint32_t width_{};
    uint32_t height_{};
    PixFormat pix_format_ = I420;

    CRITICAL_SECTION critical_{};
    IDirect3D9* d3d9_{};
    IDirect3DDevice9* d3d9_device_{};
    IDirect3DSurface9* d3d9_surface_{};
    RECT view_rect_{};
    HWND render_window_{};
};

d3d9_palyer.cpp

#include "d3d9_player.h"

#include <iostream>

#pragma comment(lib, "d3d9.lib")

D3D9Player::D3D9Player() {

}

D3D9Player::~D3D9Player() {
    DesrtoySurface();
    Cleanup();
}

void D3D9Player::Init(uint32_t width, uint32_t height, PixFormat pix_format) {
    width_ = width;
    height_ = height;
    pix_format_ = pix_format;

    view_rect_.left = 0;
    view_rect_.top = 0;
    view_rect_.right = width;
    view_rect_.bottom = height;
}

void D3D9Player::CreateRenderWindow() {

}

void D3D9Player::SetWindow(void* handle) {
    render_window_ = (HWND)(handle);
    InitRender();
    CreateSurface();
}

void D3D9Player::Render(uint8_t* data) {
    D3DLOCKED_RECT d3d_rect;
    if (d3d9_surface_ == nullptr) {
        return;
    }
    auto hr = d3d9_surface_->LockRect(&d3d_rect, NULL, D3DLOCK_DONOTWAIT);
    if (FAILED(hr)) {
        std::cout << "lock rect error" << std::endl;
        return;
    }

    int y_stride = d3d_rect.Pitch;
    int v_stride = y_stride / 2;
    int u_stride = y_stride / 2;
    
    uint8_t* y_dest = (uint8_t*)d3d_rect.pBits;
    uint8_t* v_dest = y_dest + height_ * y_stride;
    uint8_t* u_dest = v_dest + height_ / 2 * v_stride;

    for (uint32_t i = 0; i < height_; i++) {
        memcpy(y_dest + i * y_stride, data + i * width_, width_);
    }

    for (uint32_t i = 0; i < height_ / 2; i++) {
        memcpy(v_dest + i * v_stride, data + width_*height_ + i * width_ / 2,
            width_ / 2);
    }

    for (uint32_t i = 0; i < height_ / 2; i++) {
        memcpy(u_dest + i * u_stride, data + width_ * height_ * 5 / 4 + i * width_ / 2,
            width_ / 2);
    }

    hr = d3d9_surface_->UnlockRect();
    if (FAILED(hr)) {
        std::cout << "unlock rect error" << std::endl;
        return;
    }
    d3d9_device_->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0),
        1.0f, 0);
    d3d9_device_->BeginScene();

    IDirect3DSurface9* d3d_surface = NULL;
    d3d9_device_->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO,
        &d3d_surface);

    d3d9_device_->StretchRect(d3d9_surface_, NULL, d3d_surface, &view_rect_,
        D3DTEXF_LINEAR);
    d3d9_device_->EndScene();
    d3d9_device_->Present(NULL, NULL, NULL, NULL);
    d3d_surface->Release();
}

void D3D9Player::InitRender() {
    std::cout << "init render" << std::endl;
    InitializeCriticalSection(&critical_);
    Cleanup();

    d3d9_ = Direct3DCreate9(D3D_SDK_VERSION);
    if (d3d9_ == nullptr) {
        return;
    }
    
    RECT r;
    GetClientRect(render_window_, &r);
    int x = GetSystemMetrics(SM_CXSCREEN);
    int y = GetSystemMetrics(SM_CYSCREEN);
   
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = TRUE;
    d3dpp.hDeviceWindow = (HWND)render_window_;
    d3dpp.Flags = D3DPRESENTFLAG_VIDEO;
    d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferWidth = width_;
    d3dpp.BackBufferHeight = height_;

    HRESULT hr = d3d9_->CreateDevice(
        D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, render_window_,
        D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3d9_device_);
}

void D3D9Player::Cleanup() {
    EnterCriticalSection(&critical_);
    if (d3d9_surface_) {
        d3d9_surface_->Release();
        d3d9_surface_ = nullptr;
    }
    if (d3d9_device_) {
        d3d9_device_->Release();
        d3d9_device_ = nullptr;
    }
    if (d3d9_) {
        d3d9_->Release();
        d3d9_ = nullptr;
    }
    LeaveCriticalSection(&critical_);
}

void D3D9Player::CreateSurface() {
    if (d3d9_device_ == nullptr) {
        return;
    }
    HRESULT hr = d3d9_device_->CreateOffscreenPlainSurface(
        width_, height_, (D3DFORMAT)MAKEFOURCC('Y', 'V', '1', '2'),
        D3DPOOL_DEFAULT, &d3d9_surface_, NULL);
    if (FAILED(hr)) {
        std::cout << "create plain surface error" << std::endl;
        return;
    }
    std::cout << "create surface" << std::endl;
}

void D3D9Player::DesrtoySurface() {
    EnterCriticalSection(&critical_);
    if (d3d9_surface_) {
        d3d9_surface_->Release();
        d3d9_surface_ = nullptr;
    }
    LeaveCriticalSection(&critical_);
}

main.cpp

#include <Windows.h>

#include <iostream>
#include <fstream>
#include <thread>
#include <chrono>

#include "d3d9_player.h"

HWND CreateMyWindow(uint32_t width, uint32_t height) {
    WNDCLASS window;
    memset(&window, 0, sizeof(window));
    window.lpfnWndProc = (WNDPROC)DefWindowProc;
    window.hInstance = GetModuleHandle(NULL);
    window.hCursor = LoadCursor(NULL, IDC_ARROW);
    window.lpszClassName = "yuv_player";
    if (!RegisterClass(&window)) {
        return nullptr;
    }
    DWORD dwStyle = NULL;
    HWND wnd = CreateWindowEx(NULL, window.lpszClassName, "YUV Player", dwStyle,
        100, 100, width, height, nullptr, nullptr,
        GetModuleHandle(NULL), nullptr);
    ShowWindow(wnd, SW_SHOWDEFAULT);
    UpdateWindow(wnd);
    return wnd;
}

int main(int argc,char* argv[]) {
    std::string filename = "480x272_yuv420p.yuv";
    uint32_t width = 480;
    uint32_t height = 272;
    
    std::shared_ptr<D3D9Player> yuv_player = std::make_shared<D3D9Player>();
    yuv_player->Init(width, height, I420);
    // yuv_player->CreateRenderWindow();
    HWND window = CreateMyWindow(width,height);
    yuv_player->SetWindow(window);
    uint8_t* data = new uint8_t[width * height * 3 / 2];
    std::ifstream fin(filename, std::ios::in | std::ios::binary);
    while (!fin.eof()) {
        fin.read((char*)data, width * height * 3 / 2);
        yuv_player->Render(data);
        std::this_thread::sleep_for(std::chrono::milliseconds(33));
    }
    delete[] data;
    fin.close();
    getchar();
    return 0;
}

四、参考资料

https://blog.csdn.net/leixiaohua1020/article/details/40279297

相关标签: 音视频 音视频