=====================================================
最简单的视音频播放示例系列文章列表:
最简单的视音频播放示例1:总述
最简单的视音频播放示例2:GDI播放YUV, RGB
最简单的视音频播放示例3:Direct3D播放YUV,RGB(通过Surface)
最简单的视音频播放示例4:Direct3D播放RGB(通过Texture)
最简单的视音频播放示例5:OpenGL播放RGB/YUV
最简单的视音频播放示例6:OpenGL播放YUV420P(通过Texture,使用Shader)
最简单的视音频播放示例7:SDL2播放RGB/YUV
最简单的视音频播放示例8:DirectSound播放PCM
最简单的视音频播放示例9:SDL2播放PCM
=====================================================
本文接着上一篇文章继续记录Direct3D(简称D3D)播放视频的技术。上一篇文章中已经记录了使用Direct3D中的Surface渲染视频的技术。本文记录一种稍微复杂但是更加灵活的渲染视频的方式:使用Direct3D中的Texture(纹理)渲染视频。
在记录使用Direct3D的Texture渲染视频的技术之前,首先记录一下有关纹理的基础知识。我自己归纳总结了以下几点知识。
刚开始学习Direct3D显示视频技术的人一定会有一个疑问:“像GDI那样直接指定一下像素数据,然后画在窗口上不就行了?为什么又是渲染又是纹理,搞得这么复杂?”。确实,相比于GDI,Direct3D的入门的代码要复杂很多。其实Ditect3D中的很多概念并不是出自于视频领域,而是出自于3D制作。下面简单记录一下Direct3D这些概念的意义。
纹理(Texture)纹理实际上就是一张图片。个人感觉这个词的英文Texture其实也可以翻译成“材质”(“纹理”总给人一种有很多花纹的感觉 =_=)。在3D制作过程中,如果单靠计算机绘制生成3D模型,往往达不到很真实的效果。如果可以把一张2D图片“贴”到3D模型的表面上,则不但节约了计算机绘图的计算量,而且也能达到更真实的效果。纹理就是这张“贴图”。例如,下面这张图就是把一张“木箱表面”的纹理贴在了一个正六面体的六个面上,从而达到了一个“木箱”的效果(还挺像CS里面的木箱的)。
渲染(Render)输入像素数据的分辨率是320x180。把它作为纹理的时候,它的纹理坐标如下图所示。
我们可以映射整张纹理到目标物体表面。把纹理坐标(0,0),(0,1)(1,0),(1,1)映射到坐标(0,0), (0,180), (320,0), (320,180)后的结果如下图所示。
可以试着修改一下目标物体表面的坐标。把纹理坐标(0,0),(0,1)(1,0),(1,1)映射到坐标(80,0), (0,135), (320,45), (240,180)后的结果如下图所示。相当于把纹理“旋转”了一下。
也可以试着修改一下纹理的坐标。把纹理坐标(0,0),(0,1)(0.5,0),(0.5,1)映射到坐标(0,0), (0,180), (320,0), (320,180)后的结果如下图所示。由图可见,“故宫”只有左半边了。
此外纹理映射还有其他更多的功能,使用起来非常灵活,就不一一记录了。
上文记录了纹理到目标物体表面坐标的映射,但究竟是什么变量建立起了这二者之间的联系呢?就是灵活顶点格式。灵活顶点格式(Flexible Vertex Format,FVF)在Direct3D中用来描述一个顶点。灵活顶点格式可以让我们随心所欲地自定义其中所包含的顶点属性信息。例如,指定顶点的三维坐标、颜色、顶点法线和纹理坐标等等。比如我们可以定义一个只包含顶点三维坐标和颜色的结构体:
struct CUSTOMVERTEX
{
float x, y, z; //顶点的三维坐标值,x,y,z
DWORD color; //顶点的颜色值
};
struct NormalTexVertex
{
float x, y, z; // 顶点坐标
float nx, ny, nz; // 法线向量
float u, v; // 纹理坐标
};
比如刚刚我定义的CUSTOMVERTEX结构体就可以通过以下方式来描述:
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
需要注意的是:上述的定义是有顺序的。Direct3D支持下述定义。
序号 |
标示 |
含义 |
1 |
D3DFVF_XYZ |
包含未经过坐标变换的顶点坐标值,不可以和D3DFVF_XYZRHW一起使用 |
2 |
D3DFVF_XYZRHW |
包含经过坐标变换的顶点坐标值,不可以和D3DFVF_XYZ以及D3DFVF_NORMAL一起使用 |
3 |
D3DFVF_XYZB1~5 |
标示顶点混合的权重值 |
4 |
D3DFVF_NORMAL |
包含法线向量的数值 |
5 |
D3DFVF_DIFFUSE |
包含漫反射的颜色值 |
6 |
D3DFVF_SPECULAR |
包含镜面反射的数值 |
7 |
D3DFVF_TEX1~8 |
表示包含1~8个纹理坐标信息,是几重纹理后缀就用几,最多8层纹理 |
typedef struct
{
FLOAT x,y,z;
FLOAT rhw;
D3DCOLOR diffuse;
FLOAT tu, tv;
} CUSTOMVERTEX;
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1)
从上述代码中可以看出,其中包含了一个顶点在目标物体表面的坐标(x, y),以及它的纹理坐标(tu, tv)。修改上述两个坐标的值,就可以实现各种各样的映射了。
PS:z坐标在这里用不到。
下文将会结合代码记录Direct3D中与视频显示相关的功能。
函数分析1) 创建一个Device3. 循环显示画面
2) 设置一些参数(非必须)
3) 基于Device创建一个 Texture
4) 基于Device创建一个 VertexBuffer
5) 填充VertexBuffer
1) 清理下面结合Direct3D的Texture播放RGB的示例代码,详细分析一下上文的流程。注意有一部分代码和上一篇文章中介绍的Direct3D的Surface播放视频的代码是一样的,这里再重复一下。
2) 一帧视频数据拷贝至 Texture
3) 开始一个Scene
4) Device需要启用的 Texture
5) Device绑定 VertexBuffer
6) 渲染
7) 显示
这一步完成的时候,可以得到一个IDirect3DDevice9接口的指针。创建一个Device又可以分成以下几个详细的步骤:
(a) 通过 Direct3DCreate9()创建一个IDirect3D9接口。
获取IDirect3D9接口的关键实现代码只有一行:IDirect3D9 *m_pDirect3D9 = Direct3DCreate9( D3D_SDK_VERSION );
IDirect3D9接口是一个代表我们显示3D图形的物理设备的C++对象。它可以用于获得物理设备的信息和创建一个IDirect3DDevice9接口。例如,可以通过它的GetAdapterDisplayMode()函数获取当前主显卡输出的分辨率,刷新频率等参数,实现代码如下。
D3DDISPLAYMODE d3dDisplayMode;
lRet = m_pDirect3D9->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3dDisplayMode );
由代码可以看出,获取的信息存储在D3DDISPLAYMODE结构体中。D3DDISPLAYMODE结构体中包含了主显卡的分辨率等信息:
/* Display Modes */
typedef struct _D3DDISPLAYMODE
{
UINT Width;
UINT Height;
UINT RefreshRate;
D3DFORMAT Format;
} D3DDISPLAYMODE;
D3DCAPS9 d3dcaps;
lRet=m_pDirect3D9->GetDeviceCaps(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,&d3dcaps);
int hal_vp = 0;
if( d3dcaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ){
// yes, save in ‘vp’ the fact that hardware vertex
// processing is supported.
hal_vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
}
由代码可以看出,获取的设备信息存储在D3DCAPS9结构体中。D3DCAPS9定义比较长包含了各种各样的信息,不再列出来。从该结构体的DevCaps字段可以判断得出该设备是否支持硬件顶点处理。
(b) 设置D3DPRESENT_PARAMETERS结构体,为创建Device做准备。
接下来填充一个D3DPRESENT_PARAMETERS结构的实例。这个结构用于设定我们将要创建的IDirect3DDevice9对象的一些特性,它的定义如下。
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;
//D3DPRESENT_PARAMETERS Describes the presentation parameters.
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.BackBufferWidth = lWidth;
d3dpp.BackBufferHeight = lHeight;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferCount = 1;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.SwapEffect = D3DSWAPEFFECT_COPY;
d3dpp.hDeviceWindow = hwnd;
d3dpp.Windowed = TRUE;
d3dpp.EnableAutoDepthStencil = FALSE;
d3dpp.Flags = D3DPRESENTFLAG_VIDEO;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
(c) 通过IDirect3D9的CreateDevice ()创建一个Device。
最后就可以调用IDirect3D9的CreateDevice()方法创建Device了。
CreateDevice()的函数原型如下:HRESULT CreateDevice(
UINT Adapter,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS *pPresentationParameters,
IDirect3DDevice9** ppReturnedDeviceInterface
);
IDirect3DDevice9 *m_pDirect3DDevice;
D3DPRESENT_PARAMETERS d3dpp;
…
m_pDirect3D9->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, NULL,
D3DCREATE_SOFTWARE_VERTEXPROCESSING|D3DCREATE_MULTITHREADED, &d3dpp, &m_pDirect3DDevice );
这一步不是必需的。创建完成IDirect3DDevice9之后,就可以针对该Device设置一些参数了。IDirect3DDevice9提供了一系列设置参数的API,在这里无法一一列举,仅列出几个在视频播放过程中设置参数需要用到的API:SetSamplerState(),SetRenderState(),SetTextureStageState()。
Direct3D函数命名
在记录着几个函数的作用之前,先说一下IDirect3D中函数的命名特点:所有接口中的函数名称都重新定义了一遍。原来形如XXXX->YYYY(….)的函数,名称改为了IDirect3DXXXX_YYYY(XXXX,…)。即原本接口的指针移动到了函数内部成为了第一个参数,同时函数名称前面加上了“IDirect3DXXXX_”。这样描述说不清楚,举个具体的例子吧。比如说调用IDirect3DDevice9中的SetSamplerState可以采用如下方法:IDirect3DDevice9 * m_pDirect3DDevice;
…
m_pDirect3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
IDirect3DDevice9_SetSamplerState(m_pDirect3DDevice, 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
IDirect3DDevice9 * m_pDirect3DDevice;
…
IDirect3DDevice9_SetSamplerState (m_pDirect3DDevice, 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
IDirect3DDevice9_SetSamplerState(m_pDirect3DDevice, 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
设置参数
设置参数这里用到了3个API:SetSamplerState(),SetRenderState(),SetTextureStageState()。其中SetSamplerState()可以设置枚举类型D3DSAMPLERSTATETYPE的属性的值。SetRenderState()可以设置枚举类型D3DRENDERSTATETYPE的属性的值。SetTextureStageState()可以设置枚举类型D3DTEXTURESTAGESTATETYPE的属性的值。以上3个枚举类型中的数据太多,无法一一列举。在这里直接列出Direct3D播放视频的时候的设置代码:
//SetSamplerState()
// Texture coordinates outside the range [0.0, 1.0] are set
// to the texture color at 0.0 or 1.0, respectively.
IDirect3DDevice9_SetSamplerState(m_pDirect3DDevice, 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
IDirect3DDevice9_SetSamplerState(m_pDirect3DDevice, 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
// Set linear filtering quality
IDirect3DDevice9_SetSamplerState(m_pDirect3DDevice, 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
IDirect3DDevice9_SetSamplerState(m_pDirect3DDevice, 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
//SetRenderState()
//set maximum ambient light
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_AMBIENT, D3DCOLOR_XRGB(255,255,0));
// Turn off culling
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_CULLMODE, D3DCULL_NONE);
// Turn off the zbuffer
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ZENABLE, D3DZB_FALSE);
// Turn off lights
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_LIGHTING, FALSE);
// Enable dithering
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_DITHERENABLE, TRUE);
// disable stencil
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_STENCILENABLE, FALSE);
// manage blending
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ALPHABLENDENABLE, TRUE);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_SRCBLEND,D3DBLEND_SRCALPHA);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ALPHATESTENABLE,TRUE);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ALPHAREF, 0x10);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ALPHAFUNC,D3DCMP_GREATER);
// Set texture states
IDirect3DDevice9_SetTextureStageState(m_pDirect3DDevice, 0, D3DTSS_COLOROP,D3DTOP_MODULATE);
IDirect3DDevice9_SetTextureStageState(m_pDirect3DDevice, 0, D3DTSS_COLORARG1,D3DTA_TEXTURE);
IDirect3DDevice9_SetTextureStageState(m_pDirect3DDevice, 0, D3DTSS_COLORARG2,D3DTA_DIFFUSE);
// turn off alpha operation
IDirect3DDevice9_SetTextureStageState(m_pDirect3DDevice, 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
3) 基于Device创建一个Texture
通过IDirect3DDevice9接口的CreateTexture()方法可以创建一个Texture。CreateTexture()的函数原型如下所示:
HRESULT CreateTexture(
UINT Width,
UINT Height,
UINT Levels,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DTexture9 **ppTexture,
HANDLE *pSharedHandle
);
D3D3POOL_DEFAULT:默认值,表示存在于显卡的显存中。ppTexture:得到的Texture。
D3D3POOL_MANAGED:由Direct3D自由调度内存的位置(显存或者缓存中)。
D3DPOOL_SYSTEMMEM: 表示位于内存中。
IDirect3DDevice9 * m_pDirect3DDevice;
IDirect3DTexture9 *m_pDirect3DTexture;
…
m_pDirect3DDevice->CreateTexture(lWidth, lHeight, 1, D3DUSAGE_SOFTWAREPROCESSING,
D3DFMT_X8R8G8B8,
D3DPOOL_MANAGED,
&m_pDirect3DTexture, NULL );
4) 基于Device创建一个VertexBuffer
通过IDirect3DDevice9接口的CreateVertexBuffer()方法即可创建一个VertexBuffer。CreateVertexBuffer ()的函数原型如下所示:
HRESULT IDirect3DDevice9::CreateVertexBuffer(
UINT Length,
DWORD Usage,
DWORD FVF,
D3DPOOL Pool,
IDirectVertexBuffer9** ppVertexBuffer,
HANDLE pHandle
);
D3D3POOL_DEFAULT: 默认值,表示顶点缓存存在于显卡的显存中。ppVertexBuffer:是一个指向创建的顶点缓冲区地址的指针,用于返回顶点缓冲区的地址
D3D3POOL_MANAGED:由Direct3D自由调度顶点缓冲区内存的位置(显存或者缓存中)。
D3DPOOL_SYSTEMMEM: 表示顶点缓存位于内存中。
IDirect3DDevice9 * m_pDirect3DDevice;
IDirect3DVertexBuffer9 *m_pDirect3DVertexBuffer;
typedef struct
{
FLOAT x,y,z;
FLOAT rhw;
D3DCOLOR diffuse;
FLOAT tu, tv;
} CUSTOMVERTEX;
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1)
…
m_pDirect3DDevice->CreateVertexBuffer( 4 * sizeof(CUSTOMVERTEX),
0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &m_pDirect3DVertexBuffer, NULL );
//Flexible Vertex Format, FVF
typedef struct{
FLOAT x,y,z; // vertex untransformed position
FLOAT rhw; // eye distance
D3DCOLOR diffuse; // diffuse color
FLOAT tu, tv; // texture relative coordinates
} CUSTOMVERTEX;
IDirect3DVertexBuffer9 *m_pDirect3DVertexBuffer;
…
CUSTOMVERTEX *pVertex;
lRet = m_pDirect3DVertexBuffer->Lock( 0, 4 * sizeof(CUSTOMVERTEX), (void**)&pVertex, 0 );
pVertex[0].x = 0.0f; // left
pVertex[0].y = 0.0f; // top
pVertex[0].z = 0.0f;
pVertex[0].diffuse = D3DCOLOR_ARGB(255, 255, 255, 255);
pVertex[0].rhw = 1.0f;
pVertex[0].tu = 0.0f;
pVertex[0].tv = 0.0f;
pVertex[1].x = lWidth; // right
pVertex[1].y = 0.0f; // top
pVertex[1].z = 0.0f;
pVertex[1].diffuse = D3DCOLOR_ARGB(255, 255, 255, 255);
pVertex[1].rhw = 1.0f;
pVertex[1].tu = 1.0f;
pVertex[1].tv = 0.0f;
pVertex[2].x = lWidth; // right
pVertex[2].y = lHeight; // bottom
pVertex[2].z = 0.0f;
pVertex[2].diffuse = D3DCOLOR_ARGB(255, 255, 255, 255);
pVertex[2].rhw = 1.0f;
pVertex[2].tu = 1.0f;
pVertex[2].tv = 1.0f;
pVertex[3].x = 0.0f; // left
pVertex[3].y = lHeight; // bottom
pVertex[3].z = 0.0f;
pVertex[3].diffuse = D3DCOLOR_ARGB(255, 255, 255, 255);
pVertex[3].rhw = 1.0f;
pVertex[3].tu = 0.0f;
pVertex[3].tv = 1.0f;
数据设定完后,调用Unlock()即可。
m_pDirect3DVertexBuffer->Unlock();
填充VertexBuffer完成之后,初始化工作就完成了。
循环显示画面就是一帧一帧的读取YUV/RGB数据,然后显示在屏幕上的过程,下面详述一下步骤。
1) 清理
在显示之前,通过IDirect3DDevice9接口的Clear()函数可以清理Surface。个人感觉在播放视频的时候用不用这个函数都可以。因为视频本身就是全屏显示的。显示下一帧的时候自然会覆盖前一帧的所有内容。Clear()函数的原型如下所示:
HRESULT Clear(
DWORD Count,
const D3DRECT *pRects,
DWORD Flags,
D3DCOLOR Color,
float Z,
DWORD Stencil
);
IDirect3DDevice9 *m_pDirect3DDevice;
m_pDirect3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 255), 1.0f, 0);
上述代码运行完后,屏幕会变成蓝色(R,G,B取值为0,0,255)。
2) 一帧视频数据拷贝至Texture
操作Texture的像素数据,需要使用IDirect3DTexture9的LockRect()和UnlockRect()方法。使用LockRect()锁定纹理上的一块矩形区域,该矩形区域被映射成像素数组。利用函数返回的D3DLOCKED_RECT结构体,可以对数组中的像素进行直接存取。LockRect()函数原型如下。
HRESULT LockRect(
UINT Level,
D3DLOCKED_RECT *pLockedRect,
const RECT *pRect,
DWORD Flags
);
typedef struct _D3DLOCKED_RECT
{
INT Pitch;
void* pBits;
} D3DLOCKED_RECT;
结构如下图所示。
使用LockRect()函数之后,就可以对其返回的D3DLOCKED_RECT中的数据进行操作了。例如memcpy()等。操作完成后,调用UnlockRect()方法。下面给出一个使用Direct3D播放视频的时候IDirect3DTexture9的数据拷贝的典型代码。
IDirect3DTexture9 *m_pDirect3DTexture;
...
D3DLOCKED_RECT d3d_rect;
// Copy pixel data to texture
m_pDirect3DTexture->LockRect( 0, &d3d_rect, 0, 0 );
byte *pSrc = buffer;
byte *pDest = (byte *)d3d_rect.pBits;
int stride = d3d_rect.Pitch;
int pixel_w_size=pixel_w*bpp/8;
for(unsigned long i=0; i< pixel_h; i++){
memcpy( pDest, pSrc, pixel_w_size );
pDest += stride;
pSrc += pixel_w_size;
}
m_pDirect3DTexture->UnlockRect( 0 );
从代码中可以看出,是一行一行的拷贝像素数据的。
4) Device设置需要启用的Texture
使用IDirect3DDevice9接口的SetTexture ()设置我们当前需要启用的纹理。SetTexture()函数的原型如下。
HRESULT SetTexture( DWORD Sampler,IDirect3DBaseTexture9 *pTexture );
其中每个参数的含义如下所列:
Sampler:指定了应用的纹理是哪一层。Direct3D中最多可以设置8层纹理,所以这个参数取值就在0~7之间了。
IDirect3DDevice9 *m_pDirect3DDevice;
IDirect3DTexture9 *m_pDirect3DTexture;
…
m_pDirect3DDevice->SetTexture( 0, m_pDirect3DTexture );
5) Device绑定VertexBuffer
使用IDirect3DDevice9接口的SetStreamSource()绑定VertexBuffer。SetStreamSource()方法把一个顶点缓存绑定到一个设备数据流,这样就在顶点数据和一个顶点数据流端口之间建立了联系。
而后使用SetFVF()设置顶点格式。即告知系统如何解读VertexBuffer中的数据。HRESULT SetStreamSource(
UINT StreamNumber,
IDirect3DVertexBuffer9 *pStreamData,
UINT OffsetInBytes,
UINT Stride
);
IDirect3DDevice9 *m_pDirect3DDevice;
//Flexible Vertex Format, FVF
typedef struct
{
FLOAT x,y,z; // vertex untransformed position
FLOAT rhw; // eye distance
D3DCOLOR diffuse; // diffuse color
FLOAT tu, tv; // texture relative coordinates
} CUSTOMVERTEX;
// Custom flexible vertex format (FVF), which describes custom vertex structure
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1)
//...
m_pDirect3DDevice->SetStreamSource( 0, m_pDirect3DVertexBuffer,0, sizeof(CUSTOMVERTEX) );
m_pDirect3DDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
HRESULT DrawPrimitive(
D3DPRIMITIVETYPE PrimitiveType,
UINT StartVertex,
UINT PrimitiveCount
);
D3DPT_POINTLIST对应的代码如下所示
点列表:将一连串的顶点作为像素进行绘制
D3DPT_LINELIST
线列表:彼此孤立(彼此没有发生连接)的一些直线
D3DPT_LINESTRIP
线带:一连串连接的直线。每条直线都是从前一个顶点到当前顶点绘制而成,很像连接点
D3DPT_TRIANGLELIST
三角形列表:这个设置比较简单,索引区每隔三个一个三角形。
D3DPT_TRIANGLESTRIP
三角形带:索引区中每三个一个三角形,前一个三角形的后两个顶点和后一个三角形的前两个顶点重合。即绘制的第一个三边形使用3个顶点,后面绘制的每一个三角形只使用一个额外的顶点。
D3DPT_TRIANGLEFAN
三角扇形:索引区中第一个点为公共顶点,后面依次展开,每两个点和公共定点组成三角形。
下图显示了几种不同的渲染物件的方式。图中分别按顺序1,3,5,7,9…渲染这些点。
//从第0个点开始用D3DPT_POINTLIST模式渲染6个点
m_pDirect3DDevice->DrawPrimitive(D3DPT_POINTLIST,0,6);
//从第0个点开始用D3DPT_LINELIST 模式渲染3条线
m_pDirect3DDevice->DrawPrimitive(D3DPT_LINELIST,0,3);
//从第0个点开始用D3DPT_TRIANGLESTRIP 模式渲染4个三角形
m_pDirect3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP,0,4);
//从第0个点开始用D3DPT_TRIANGLEFAN模式渲染3个三角形
m_pDirect3DDevice->DrawPrimitive(D3DPT_TRIANGLEFAN,0,3);
//从第0个点开始用D3DPT_LINESTRIP 模式渲染5条线
m_pDirect3DDevice->DrawPrimitive(D3DPT_LINELIST,0,5);
//从第0个点开始用D3DPT_TRIANGLELIST 模式渲染2个三角形
m_pDirect3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST,0,2);
下面给出一个使用Direct3D播放视频的时候IDirect3DDevice9的DrawPrimitive ()的典型代码。
IDirect3DDevice9 *m_pDirect3DDevice;
…
m_pDirect3DDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );
HRESULT Present(
const RECT *pSourceRect,
const RECT *pDestRect,
HWND hDestWindowOverride,
const RGNDATA *pDirtyRegion
);
IDirect3DDevice9 *m_pDirect3DDevice;
…
m_pDirect3DDevice->Present( NULL, NULL, NULL, NULL );
文章至此,使用Direct3D显示YUV/RGB的全部流程就记录完毕了。最后贴一张图总结上述流程。
在Direct3D播放YUV/RGB的流程中,用到了许多的数据结构,现在理一下它们之间的关系,如下图所示。
接口如下所列
IDirect3D9
IDirect3DDevice9结构体如下所列
D3DCAPS9
D3DDISPLAYMODECUSTOMVERTEX
/**
* 最简单的Direct3D播放视频的例子(Direct3D播放RGB)[Texture]
* Simplest Video Play Direct3D (Direct3D play RGB)[Texture]
*
* 雷霄骅 Lei Xiaohua
* [email protected]
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 本程序使用Direct3D播放RGB/YUV视频像素数据。使用D3D中的Texture渲染数据。
* 相对于使用Surface渲染视频数据来说,使用Texture渲染视频数据功能更加灵活,
* 但是学习起来也会相对复杂一些。
*
* 函数调用步骤如下:
*
* [初始化]
* Direct3DCreate9():获得IDirect3D9
* IDirect3D9->CreateDevice():通过IDirect3D9创建Device(设备)
* IDirect3DDevice9->CreateTexture():通过Device创建一个Texture(纹理)。
* IDirect3DDevice9->CreateVertexBuffer():通过Device创建一个VertexBuffer(顶点缓存)。
* IDirect3DVertexBuffer9->Lock():锁定顶点缓存。
* memcpy():填充顶点缓存。
* IDirect3DVertexBuffer9->Unlock():解锁顶点缓存。
*
* [循环渲染数据]
* IDirect3DTexture9->LockRect():锁定纹理。
* memcpy():填充纹理数据
* IDirect3DTexture9->UnLockRect():解锁纹理。
* IDirect3DDevice9->BeginScene():开始绘制。
* IDirect3DDevice9->SetTexture():设置当前要渲染的纹理。
* IDirect3DDevice9->SetStreamSource():绑定VertexBuffer。
* IDirect3DDevice9->SetFVF():设置Vertex格式。
* IDirect3DDevice9->DrawPrimitive():渲染。
* IDirect3DDevice9->EndScene():结束绘制。
* IDirect3DDevice9->Present():显示出来。
*
* This software plays RGB/YUV raw video data using Direct3D.
* It uses Texture in D3D to render the pixel data.
* Compared to another method (use Surface), it's more flexible
* but a little difficult.
*
* The process is shown as follows:
*
* [Init]
* Direct3DCreate9():Get IDirect3D9.
* IDirect3D9->CreateDevice():Create a Device.
* IDirect3DDevice9->CreateTexture():Create a Texture.
* IDirect3DDevice9->CreateVertexBuffer():Create a VertexBuffer.
* IDirect3DVertexBuffer9->Lock():Lock VertexBuffer.
* memcpy():Fill VertexBuffer.
* IDirect3DVertexBuffer9->Unlock():UnLock VertexBuffer.
*
* [Loop to Render data]
* IDirect3DTexture9->LockRect():Lock Texture.
* memcpy():Fill pixel data...
* IDirect3DTexture9->UnLockRect():UnLock Texture.
* IDirect3DDevice9->BeginScene():Begin to draw.
* IDirect3DDevice9->SetTexture():Set current Texture.
* IDirect3DDevice9->SetStreamSource():Bind VertexBuffer.
* IDirect3DDevice9->SetFVF():Set Vertex Format.
* IDirect3DDevice9->DrawPrimitive():Render.
* IDirect3DDevice9->EndScene():End drawing.
* IDirect3DDevice9->Present():Show on the screen.
*/
#include
#include
#include
//Flexible Vertex Format, FVF
typedef struct
{
FLOAT x,y,z;
FLOAT rhw;
D3DCOLOR diffuse;
FLOAT tu, tv;
} CUSTOMVERTEX;
CRITICAL_SECTION m_critial;
HWND m_hVideoWnd; // 视频窗口
IDirect3D9 *m_pDirect3D9= NULL;
IDirect3DDevice9 *m_pDirect3DDevice= NULL;
IDirect3DTexture9 *m_pDirect3DTexture= NULL;
IDirect3DVertexBuffer9 *m_pDirect3DVertexBuffer= NULL;
// Custom flexible vertex format (FVF), which describes custom vertex structure
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1)
//Select one of the Texture mode (Set '1'):
#define TEXTURE_DEFAULT 0
//Rotate the texture
#define TEXTURE_ROTATE 1
//Show half of the Texture
#define TEXTURE_HALF 0
//Width, Height
const int screen_w=500,screen_h=500;
const int pixel_w=320,pixel_h=180;
FILE *fp=NULL;
//Bit per Pixel
const int bpp=32;
unsigned char buffer[pixel_w*pixel_h*bpp/8];
void Cleanup()
{
EnterCriticalSection(&m_critial);
if(m_pDirect3DVertexBuffer)
m_pDirect3DVertexBuffer->Release();
if(m_pDirect3DTexture)
m_pDirect3DTexture->Release();
if(m_pDirect3DDevice)
m_pDirect3DDevice->Release();
if(m_pDirect3D9)
m_pDirect3D9->Release();
LeaveCriticalSection(&m_critial);
}
int InitD3D( HWND hwnd, unsigned long lWidth, unsigned long lHeight )
{
HRESULT lRet;
InitializeCriticalSection(&m_critial);
Cleanup();
EnterCriticalSection(&m_critial);
// Create IDirect3D
m_pDirect3D9 = Direct3DCreate9( D3D_SDK_VERSION );
if ( m_pDirect3D9 == NULL ){
LeaveCriticalSection(&m_critial);
return -1;
}
if ( lWidth == 0 || lHeight == 0 ){
RECT rt;
GetClientRect( hwnd, &rt );
lWidth = rt.right-rt.left;
lHeight = rt.bottom-rt.top;
}
/*
//Get Some Info
//Retrieves device-specific information about a device.
D3DCAPS9 d3dcaps;
lRet=m_pDirect3D9->GetDeviceCaps(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,&d3dcaps);
int hal_vp = 0;
if( d3dcaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ){
//save in hal_vp the fact that hardware vertex processing is supported.
hal_vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
}
// get D3DDISPLAYMODE
D3DDISPLAYMODE d3dDisplayMode;
lRet = m_pDirect3D9->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3dDisplayMode );
if ( FAILED(lRet) ){
LeaveCriticalSection(&m_critial);
return -1;
}
*/
//D3DPRESENT_PARAMETERS Describes the presentation parameters.
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.BackBufferWidth = lWidth;
d3dpp.BackBufferHeight = lHeight;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
//d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferCount = 1;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.SwapEffect = D3DSWAPEFFECT_COPY;
d3dpp.hDeviceWindow = hwnd;
d3dpp.Windowed = TRUE;
d3dpp.EnableAutoDepthStencil = FALSE;
d3dpp.Flags = D3DPRESENTFLAG_VIDEO;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
m_hVideoWnd = hwnd;
//Creates a device to represent the display adapter.
//Adapter: Ordinal number that denotes the display adapter. D3DADAPTER_DEFAULT is always the primary display
//D3DDEVTYPE: D3DDEVTYPE_HAL((Hardware Accelerator), or D3DDEVTYPE_SW(SoftWare)
//BehaviorFlags:D3DCREATE_SOFTWARE_VERTEXPROCESSING, or D3DCREATE_HARDWARE_VERTEXPROCESSING
lRet = m_pDirect3D9->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, NULL,
D3DCREATE_SOFTWARE_VERTEXPROCESSING|D3DCREATE_MULTITHREADED, &d3dpp, &m_pDirect3DDevice );
/*
//Set some property
//SetSamplerState()
// Texture coordinates outside the range [0.0, 1.0] are set
// to the texture color at 0.0 or 1.0, respectively.
IDirect3DDevice9_SetSamplerState(m_pDirect3DDevice, 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
IDirect3DDevice9_SetSamplerState(m_pDirect3DDevice, 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
// Set linear filtering quality
IDirect3DDevice9_SetSamplerState(m_pDirect3DDevice, 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
IDirect3DDevice9_SetSamplerState(m_pDirect3DDevice, 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
//SetRenderState()
//set maximum ambient light
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_AMBIENT, D3DCOLOR_XRGB(255,255,0));
// Turn off culling
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_CULLMODE, D3DCULL_NONE);
// Turn off the zbuffer
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ZENABLE, D3DZB_FALSE);
// Turn off lights
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_LIGHTING, FALSE);
// Enable dithering
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_DITHERENABLE, TRUE);
// disable stencil
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_STENCILENABLE, FALSE);
// manage blending
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ALPHABLENDENABLE, TRUE);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_SRCBLEND,D3DBLEND_SRCALPHA);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ALPHATESTENABLE,TRUE);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ALPHAREF, 0x10);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ALPHAFUNC,D3DCMP_GREATER);
// Set texture states
IDirect3DDevice9_SetTextureStageState(m_pDirect3DDevice, 0, D3DTSS_COLOROP,D3DTOP_MODULATE);
IDirect3DDevice9_SetTextureStageState(m_pDirect3DDevice, 0, D3DTSS_COLORARG1,D3DTA_TEXTURE);
IDirect3DDevice9_SetTextureStageState(m_pDirect3DDevice, 0, D3DTSS_COLORARG2,D3DTA_DIFFUSE);
// turn off alpha operation
IDirect3DDevice9_SetTextureStageState(m_pDirect3DDevice, 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
*/
//Creates a texture resource.
//Usage:
//D3DUSAGE_SOFTWAREPROCESSING: If this flag is used, vertex processing is done in software.
// If this flag is not used, vertex processing is done in hardware.
//D3DPool:
//D3D3POOL_DEFAULT: Resources are placed in the hardware memory (Such as video memory)
//D3D3POOL_MANAGED: Resources are placed automatically to device-accessible memory as needed.
//D3DPOOL_SYSTEMMEM: Resources are placed in system memory.
lRet = m_pDirect3DDevice->CreateTexture(lWidth, lHeight, 1, D3DUSAGE_SOFTWAREPROCESSING,
D3DFMT_X8R8G8B8,
D3DPOOL_MANAGED,
&m_pDirect3DTexture, NULL );
if ( FAILED(lRet) ){
LeaveCriticalSection(&m_critial);
return -1;
}
// Create Vertex Buffer
lRet = m_pDirect3DDevice->CreateVertexBuffer( 4 * sizeof(CUSTOMVERTEX),
0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &m_pDirect3DVertexBuffer, NULL );
if ( FAILED(lRet) ){
LeaveCriticalSection(&m_critial);
return -1;
}
#if TEXTURE_HALF
CUSTOMVERTEX vertices[] ={
{0.0f, 0.0f, 0.0f, 1.0f,D3DCOLOR_ARGB(255, 255, 255, 255),0.0f,0.0f},
{lWidth, 0.0f, 0.0f, 1.0f,D3DCOLOR_ARGB(255, 255, 255, 255),0.5f,0.0f},
{lWidth, lHeight, 0.0f, 1.0f,D3DCOLOR_ARGB(255, 255, 255, 255),0.5f,1.0f},
{0.0f, lHeight, 0.0f, 1.0f,D3DCOLOR_ARGB(255, 255, 255, 255),0.0f,1.0f}
};
#elif TEXTURE_ROTATE
//Rotate Texture?
CUSTOMVERTEX vertices[] ={
{lWidth/4, 0.0f, 0.0f, 1.0f,D3DCOLOR_ARGB(255, 255, 255, 255),0.0f,0.0f},
{lWidth, lHeight/4, 0.0f, 1.0f,D3DCOLOR_ARGB(255, 255, 255, 255),1.0f,0.0f},
{lWidth*3/4, lHeight, 0.0f, 1.0f,D3DCOLOR_ARGB(255, 255, 255, 255),1.0f,1.0f},
{0.0f, lHeight*3/4,0.0f, 1.0f,D3DCOLOR_ARGB(255, 255, 255, 255),0.0f,1.0f}
};
#else
CUSTOMVERTEX vertices[] ={
{0.0f, 0.0f, 0.0f, 1.0f,D3DCOLOR_ARGB(255, 255, 255, 255),0.0f,0.0f},
{lWidth, 0.0f, 0.0f, 1.0f,D3DCOLOR_ARGB(255, 255, 255, 255),1.0f,0.0f},
{lWidth, lHeight, 0.0f, 1.0f,D3DCOLOR_ARGB(255, 255, 255, 255),1.0f,1.0f},
{0.0f, lHeight, 0.0f, 1.0f,D3DCOLOR_ARGB(255, 255, 255, 255),0.0f,1.0f}
};
#endif
// Fill Vertex Buffer
CUSTOMVERTEX *pVertex;
lRet = m_pDirect3DVertexBuffer->Lock( 0, 4 * sizeof(CUSTOMVERTEX), (void**)&pVertex, 0 );
if ( FAILED(lRet) ){
LeaveCriticalSection(&m_critial);
return -1;
}
memcpy(pVertex, vertices, sizeof(vertices));
m_pDirect3DVertexBuffer->Unlock();
LeaveCriticalSection(&m_critial);
return 0;
}
bool Render()
{
LRESULT lRet;
//Read Data
//RGB
if (fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp) != pixel_w*pixel_h*bpp/8){
// Loop
fseek(fp, 0, SEEK_SET);
fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp);
}
if(buffer == NULL || m_pDirect3DDevice == NULL)
return false;
//Clears one or more surfaces
lRet = m_pDirect3DDevice->Clear(0, NULL, D3DCLEAR_TARGET,
D3DCOLOR_XRGB(0, 255, 0), 1.0f, 0);
D3DLOCKED_RECT d3d_rect;
//Locks a rectangle on a texture resource.
//And then we can manipulate pixel data in it.
lRet = m_pDirect3DTexture->LockRect( 0, &d3d_rect, 0, 0 );
if ( FAILED(lRet) ){
return false;
}
// Copy pixel data to texture
byte *pSrc = buffer;
byte *pDest = (byte *)d3d_rect.pBits;
int stride = d3d_rect.Pitch;
int pixel_w_size=pixel_w*bpp/8;
for(unsigned long i=0; i< pixel_h; i++){
memcpy( pDest, pSrc, pixel_w_size );
pDest += stride;
pSrc += pixel_w_size;
}
m_pDirect3DTexture->UnlockRect( 0 );
//Begin the scene
if ( FAILED(m_pDirect3DDevice->BeginScene()) ){
return false;
}
lRet = m_pDirect3DDevice->SetTexture( 0, m_pDirect3DTexture );
//Binds a vertex buffer to a device data stream.
m_pDirect3DDevice->SetStreamSource( 0, m_pDirect3DVertexBuffer,
0, sizeof(CUSTOMVERTEX) );
//Sets the current vertex stream declaration.
lRet = m_pDirect3DDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
//Renders a sequence of nonindexed, geometric primitives of the
//specified type from the current set of data input streams.
m_pDirect3DDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );
m_pDirect3DDevice->EndScene();
//Presents the contents of the next buffer in the sequence of back
//buffers owned by the device.
m_pDirect3DDevice->Present( NULL, NULL, NULL, NULL );
return true;
}
LRESULT WINAPI MyWndProc(HWND hwnd, UINT msg, WPARAM wparma, LPARAM lparam)
{
switch(msg){
case WM_DESTROY:
Cleanup();
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wparma, lparam);
}
int WINAPI WinMain( __in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in LPSTR lpCmdLine, __in int nShowCmd )
{
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpfnWndProc = (WNDPROC)MyWndProc;
wc.lpszClassName = L"D3D";
wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClassEx(&wc);
HWND hwnd = NULL;
hwnd = CreateWindow(L"D3D", L"Simplest Video Play Direct3D (Texture)", WS_OVERLAPPEDWINDOW, 100, 100, screen_w, screen_h, NULL, NULL, hInstance, NULL);
if (hwnd==NULL){
return -1;
}
if(InitD3D( hwnd, pixel_w, pixel_h)==E_FAIL){
return -1;
}
ShowWindow(hwnd, nShowCmd);
UpdateWindow(hwnd);
fp=fopen("../test_bgra_320x180.rgb","rb+");
if(fp==NULL){
printf("Cannot open this file.\n");
return -1;
}
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while (msg.message != WM_QUIT){
//PeekMessage, not GetMessage
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else{
Sleep(40);
Render();
}
}
UnregisterClass(L"D3D", hInstance);
return 0;
}
//Width, Height
const int screen_w=500,screen_h=500;
const int pixel_w=320,pixel_h=180;
//Select one of the Texture mode (Set '1'):
#define TEXTURE_DEFAULT 1
//Rotate the texture
#define TEXTURE_ROTATE 0
//Show half of the Texture
#define TEXTURE_HALF 0
下面展示一下三种纹理映射方式的结果:
(1) 正常代码位于“Simplest Media Play”中
注:
该项目会不定时的更新并修复一些小问题,最新的版本请参考该系列文章的总述页面:
《最简单的视音频播放示例1:总述》