d3d9播放yuv

一、几个概念

后台缓冲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

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值