vs2010MFC D3D播放YUV格式视频详细制作全过程
2014-11-06 14:09
676 查看
1.环境配置
1.1 Microsoft Visual Studio 2010安装
先下载Visual Studio 2010,然后双击setup.exe安装,安装时有一步选择vc++安装就可以了,其他步骤直接点击下一步。1.2DirectX SDK安装
先下载DXSDK_Feb10,然后解压,解压完双击DXSDK_Feb10.exe进行安装,安装时请记住自己的安装路径以便后续操作,一直点下一步直到完成安装即可。2.YUV视频播放器编写
2.1创建项
先运行vs2010,新建项目,模板选择Visual C++ 中MFC中的MFC应用程序,然后输入项目名称“myplayer”,点击确定,弹出一个MFC应用程序向导对话框,点击下一步,应用程序类型选择基于对话框(D),然后点击完成。2.2设置DirectX SDK环境
2.2.1右击刚才创建的项目,选择属性,弹出一个项目属性页。
2.2.2点击配置属性里的VC++目录,然后选择包含目录那一行,在那行后边会出现一个倒立黑色小三角形。如下图:
左击倒立三角,弹出一个单行的框,里边写着编辑,左击编辑两个字,弹出包含目录对话框,如下图,左击下图红色正方形标示的图标,在红色长方形处会弹出一行空白框,空白框后边有个按钮,左击那个按钮。
左击按钮后出现一个选择目录对话框,根据自己安装DirectX SDK时的路径找到如下图红色标示区的文件夹,双击这个文件夹进入里边,找到Include文件夹,双击Include文件夹,然后点击“选择文件夹”按钮,接下来一直点击确定即可。
2.2.3包含d3d9.lib文件
先执行上边的2.2.1操作,如下图找到配置属性下的链接器双击,再双击输入,单击附加依赖项这一行,出现黑色倒立小三角形,单击三角形出现编辑两个字。单击编辑这两个字,出现如下图的附加依赖项对话框,如下图红色标示处自己写上d3d9.lib,点击确定按钮。至此DirectX SDK环境设置完成。
2.3创建一个用于显示YUV视频的窗口。
2.3.1下图使我们创建项目时自动生成的MFC窗口,把下图中红色标示的控件删除。
2.3.2如下图把vs2010窗口右边的红色标示的工具箱打开。
2.3.3展开工具箱后用如下红色标示的两个控件创建窗口。
2.3.4播放窗口如下图:
上图中间是Picture Control控件,用来显示视频,选中这个控件,在vs2010窗口右侧属性那如下图把ID属性改为“IDC_VIDEO”。
2.3.5在自己创建的项目的头文件右击选择“添加”—>“新建项”,弹出添加新项窗口。
在添加新项窗口中选择如下图红色标示的项,然后输入名称,点击“添加”按钮。一共要添加四个*.h的文件,名称分别输入为PlayControl、DEFINE、ColourSpaceConvert 和D3DDisplay。
添加完四个*.h的文件,再按这种方法添加三个*.cpp的文件,如下图单击选择红色区域,然后输入名称,点击“添加”按钮。名称分别为PlayControl、ColourSpaceConvert 和D3DDisplay。
2.3.6代码
2.3.6.1 DEFINE.h
#ifndef MESSAGE_H_ #define MESSAGE_H_ #define D3D_ARGB32 1 #define D3D_YUV422 2 #define IMAGE_WIDTH 352 //分辨率 #define IMAGE_HEIGHT 288//分辨率 #define YUVPLANE IMAGE_WIDTH*IMAGE_HEIGHT*3/2 #define RGBPLANE IMAGE_WIDTH*IMAGE_HEIGHT*4 #endif//MESSAGE_H_
2.3.6.2 ColourSpaceConvert.h
//经典的YUV转换到RGB的函数的头文件 #if !defined(AFX_COLOURSPACECONVERT_H__92911B35_FC87_4C19_9007_1D735942D76E__INCLUDED_) #define AFX_COLOURSPACECONVERT_H__92911B35_FC87_4C19_9007_1D735942D76E__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 class CColourSpaceConvert { public: CColourSpaceConvert(); virtual ~CColourSpaceConvert(); void YUV2RGB(unsigned char *src0,unsigned char *src1,unsigned char *src2,unsigned char *dst_ori, int width,int height); private: //YUV===>RGB long int crv_tab[256]; long int cbu_tab[256]; long int cgu_tab[256]; long int cgv_tab[256]; long int tab_76309[256]; unsigned char clp[1024]; void InitConvertTable(); }; #endif
2.3.6.3 PlayControl.h
#if !defined(AFX_PLAYCONTROL_H__C3D14174_0157_4809_B573_DEA1F90DADB6__INCLUDED_) #define AFX_PLAYCONTROL_H__C3D14174_0157_4809_B573_DEA1F90DADB6__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include "D3DDisplay.h" #include "DEFINE.h" #include <stdio.h> #define WM_MANAGE WM_USER + 1001 class CPlayControl :public CWinThread { DECLARE_DYNCREATE(CPlayControl) public: CPlayControl(); virtual ~CPlayControl(); virtual BOOL InitInstance(); virtual int ExitInstance(); int Init(HWND hwnd, char *filename); void Play(); void Stop(); void Pause(); private: CD3DDisplay m_D3D; BYTE *yuvbuf; BYTE *rgbbuf; FILE *f_in;//输入的文件句柄 int m_State; protected: void OnManage(WPARAM wParam, LPARAM lParam); DECLARE_MESSAGE_MAP() }; #endif
2.3.6.4 D3DDisplay.h
#if !defined(AFX_D3DDISPLAY_H__D2B5461A_AF44_411A_8FEA_8439E0FB5628__INCLUDED_) #define AFX_D3DDISPLAY_H__D2B5461A_AF44_411A_8FEA_8439E0FB5628__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include <d3dx9.h> class CD3DDisplay { public: int GetFormt(); HRESULT RenderSample(BYTE* pSampleBuffer); HRESULT RenderTOSrceen(); HRESULT InitGeometry(); HRESULT CreateTexture(); HRESULT InitD3D(HWND hWnd); void ClearD3D(); CD3DDisplay(); virtual ~CD3DDisplay(); private: LPDIRECT3D9 m_pD3D; LPDIRECT3DDEVICE9 m_pd3dDevice; LPDIRECT3DTEXTURE9 m_pTexturesvideo; LPDIRECT3DVERTEXBUFFER9 m_pVBvideo; //视频矩形的顶点缓存区接口指针 LPDIRECT3DTEXTURE9 m_pTexturesbmp; LPDIRECT3DVERTEXBUFFER9 m_pVBbmp; //位图矩形的顶点缓存区接口指针 HWND m_hWnd; D3DFORMAT m_Format; LPD3DXFONT m_p2Dfont; }; #endif
2.3.6.5 ColourSpaceConvert.cpp
//经典的YUV转换到RGB的函数的实现 #include "stdafx.h" #include "ColourSpaceConvert.h" CColourSpaceConvert::CColourSpaceConvert() { //InitLookupTable(); InitConvertTable(); } CColourSpaceConvert::~CColourSpaceConvert() { } //YUV===>RGB void CColourSpaceConvert::InitConvertTable() { long int crv,cbu,cgu,cgv; int i,ind; crv = 104597; cbu = 132201; cgu = 25675; cgv = 53279; for (i = 0; i < 256; i++) { crv_tab[i] = (i-128) * crv; cbu_tab[i] = (i-128) * cbu; cgu_tab[i] = (i-128) * cgu; cgv_tab[i] = (i-128) * cgv; tab_76309[i] = 76309*(i-16); } for (i=0; i<384; i++) clp[i] =0; ind=384; for (i=0;i<256; i++) clp[ind++]=i; ind=640; for (i=0;i<384;i++) clp[ind++]=255; } void CColourSpaceConvert::YUV2RGB(unsigned char *src0,unsigned char *src1,unsigned char *src2,unsigned char *dst_ori, int width,int height) { int y1,y2,u,v; unsigned char *py1,*py2; int i,j, c1, c2, c3, c4; unsigned char *d1, *d2; py1=src0; py2=py1+width; d1=dst_ori; d2=d1+3*width; for (j = 0; j < height; j += 2) { for (i = 0; i < width; i += 2) { u = *src1++; v = *src2++; c1 = crv_tab[v]; c2 = cgu_tab[u]; c3 = cgv_tab[v]; c4 = cbu_tab[u]; //up-left y1 = tab_76309[*py1++]; *d1++ = clp[384+((y1 + c1)>>16)]; *d1++ = clp[384+((y1 - c2 - c3)>>16)]; *d1++ = clp[384+((y1 + c4)>>16)]; //down-left y2 = tab_76309[*py2++]; *d2++ = clp[384+((y2 + c1)>>16)]; *d2++ = clp[384+((y2 - c2 - c3)>>16)]; *d2++ = clp[384+((y2 + c4)>>16)]; //up-right y1 = tab_76309[*py1++]; *d1++ = clp[384+((y1 + c1)>>16)]; *d1++ = clp[384+((y1 - c2 - c3)>>16)]; *d1++ = clp[384+((y1 + c4)>>16)]; //down-right y2 = tab_76309[*py2++]; *d2++ = clp[384+((y2 + c1)>>16)]; *d2++ = clp[384+((y2 - c2 - c3)>>16)]; *d2++ = clp[384+((y2 + c4)>>16)]; } d1 += 3*width; d2 += 3*width; py1+= width; py2+= width; } }
2.3.6.6 PlayControl.cpp
#include "stdafx.h" #include "PlayControl.h" #include "ColourSpaceConvert.h" #include <afx.h> ///////////////////////////////////////////////////////////////////////////// // CPlayControl IMPLEMENT_DYNCREATE(CPlayControl, CWinThread) CPlayControl::CPlayControl() { m_State = 0; f_in = NULL; rgbbuf = NULL; yuvbuf = NULL; CreateThread(); } CPlayControl::~CPlayControl() { f_in = NULL; rgbbuf = NULL; yuvbuf = NULL; } BOOL CPlayControl::InitInstance() { // TODO: perform and per-thread initialization here return TRUE; } int CPlayControl::ExitInstance() { // TODO: perform any per-thread cleanup here return CWinThread::ExitInstance(); } BEGIN_MESSAGE_MAP(CPlayControl, CWinThread) //{{AFX_MSG_MAP(CPlayControl) // NOTE - the ClassWizard will add and remove mapping macros here. ON_THREAD_MESSAGE(WM_MANAGE,OnManage) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CPlayControl message handlers int CPlayControl::Init(HWND hwnd, char *filename) { m_D3D.InitD3D(hwnd); m_D3D.CreateTexture(); m_D3D.InitGeometry(); if(m_D3D.GetFormt() == D3D_ARGB32) { rgbbuf = new BYTE[RGBPLANE]; yuvbuf = new BYTE[YUVPLANE]; } else { return -1; } if(!rgbbuf || !yuvbuf) { delete [] rgbbuf; delete [] yuvbuf; rgbbuf = NULL; yuvbuf = NULL; return -1; } f_in=fopen(filename,"rb"); if (f_in == NULL) { return -1; } m_State = 1; return 0; } void CPlayControl::Play() { m_State = 1; } void CPlayControl::Stop() { m_State = 0; } void CPlayControl::Pause() { m_State = 2; } void CPlayControl::OnManage(WPARAM wParam, LPARAM lParam) { CColourSpaceConvert m_csc; if(f_in == NULL) return; while(1) { if(m_State == 1) { if(fread(yuvbuf, YUVPLANE, 1, f_in)) { m_csc.YUV2RGB(yuvbuf, yuvbuf + IMAGE_HEIGHT*IMAGE_WIDTH*5/4, yuvbuf + IMAGE_HEIGHT*IMAGE_WIDTH, rgbbuf, IMAGE_WIDTH, IMAGE_HEIGHT); m_D3D.RenderSample(rgbbuf); m_D3D.RenderTOSrceen(); Sleep(33); } else break; } else if(m_State == 2) continue; else break; } fclose(f_in); }
2.3.6.7 D3DDisplay.cpp
#include "stdafx.h" #include "D3DDisplay.h" #include "DEFINE.h" #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif struct CUSTOMVERTEX//顶点缓存 { float x,y,z,rhw; //顶点坐标 DWORD color; //顶点颜色 float tu,tv; //纹理坐标 }; #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1) ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CD3DDisplay::CD3DDisplay() { m_pd3dDevice = NULL; m_pD3D = NULL; m_p2Dfont = NULL; m_pTexturesvideo = NULL; m_pVBvideo = NULL; m_pTexturesbmp = NULL; m_pVBbmp = NULL; m_hWnd = NULL; m_Format = (D3DFORMAT)0; } CD3DDisplay::~CD3DDisplay() { ClearD3D(); } HRESULT CD3DDisplay::InitD3D(HWND hWnd)//初始化d3d { D3DPRESENT_PARAMETERS d3dpp;//显示参数设置 if(hWnd == NULL) return -1; m_hWnd = hWnd; //D3DFMT_A8R8G8B8表示一个32位像素,从左开始,8位为ALPHA通道,8位分配给红色,8位分配给绿色,8位分配给蓝色 m_Format = D3DFMT_A8R8G8B8; if(NULL == (m_pD3D = Direct3DCreate9( D3D_SDK_VERSION )))//创建d3d对象 return -1; ZeroMemory( &d3dpp, sizeof(d3dpp) );//清空显示参数,重新设置参数 d3dpp.Windowed = TRUE;//FALSE,表示要渲染全屏,如果为TRUE,表示要渲染窗口 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;//交换缓冲支持的效果类型,指定表面在交换链中D是如何被交换的 //D3DSWAPEFFECT_DISCARD,则后备缓冲区的东西被复制到屏幕上后,后备缓冲区的东西就没有什么用可以丢弃 d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;//后备缓冲的格式D3DFMT_UNKNOWN,这时候它将使用桌面的格式 d3dpp.EnableAutoDepthStencil = TRUE;//如果要使用缓冲或模板缓冲则把它设为TRUE d3dpp.AutoDepthStencilFormat = D3DFMT_D16;//如果启动了深度缓冲那么这个参数将为深度设定缓冲格式 //D3DFMT_16深度缓冲 d3dpp.BackBufferWidth = IMAGE_WIDTH;//后备缓冲的宽度和高度 d3dpp.BackBufferHeight = IMAGE_HEIGHT;//在全屏模式下这两者的值必需符合显卡所支持的分辨率 if( FAILED( m_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_MULTITHREADED, &d3dpp, &m_pd3dDevice ) ) )//创建d3d设备 return -1; return 0; } void CD3DDisplay::ClearD3D()//释放d3d { if(m_pd3dDevice != NULL) m_pd3dDevice->Release(); if(m_pD3D != NULL) m_pD3D->Release(); if(m_p2Dfont != NULL) m_p2Dfont->Release(); if(m_pVBvideo != NULL) m_pVBvideo->Release(); if(m_pTexturesvideo != NULL) m_pTexturesvideo->Release(); if(m_pVBbmp != NULL) m_pVBbmp->Release(); if(m_pTexturesbmp != NULL) m_pTexturesbmp->Release(); m_hWnd = NULL; m_Format = (D3DFORMAT)0; } HRESULT CD3DDisplay::CreateTexture()//创建纹理 { HRESULT hr = 0; D3DSURFACE_DESC ddsd; hr = m_pd3dDevice->CreateTexture( IMAGE_WIDTH, IMAGE_HEIGHT, 1, 0, m_Format, D3DPOOL_MANAGED, &m_pTexturesvideo, NULL); if( FAILED(hr)) { return -1; } hr = m_pTexturesvideo->GetLevelDesc( 0, &ddsd );//获得加载图片的宽和高及些信息 if(FAILED(hr)) return -1; if ((ddsd.Format != D3DFMT_A8R8G8B8) && (ddsd.Format != D3DFMT_YUY2)) return -1; return 0; } HRESULT CD3DDisplay::InitGeometry()//创建视频几何建立顶点缓存并初始化纹坐标 { HRESULT hr = 0; CUSTOMVERTEX* pVertices = NULL; RECT rect; //创建视频矩形 rect.top = 0; rect.left = 0; rect.right = IMAGE_WIDTH; rect.bottom = IMAGE_HEIGHT; CUSTOMVERTEX vertices[4] = //顶点缓存 { // x, y, z, rhw, color { (float)rect.left, (float)rect.top, 0.0f, 1.0f, D3DCOLOR_XRGB(255,255,255), 0.0f, 0.0f }, { (float)rect.right, (float)rect.top, 0.0f, 1.0f, D3DCOLOR_XRGB(255,255,255), 1.0f, 0.0f }, { (float)rect.left, (float)rect.bottom, 0.0f, 1.0f, D3DCOLOR_XRGB(255,255,255), 0.0f, 1.0f }, { (float)rect.right, (float)rect.bottom, 0.0f, 1.0f, D3DCOLOR_XRGB(255,255,255), 1.0f, 1.0f } }; m_pd3dDevice->CreateVertexBuffer(sizeof(vertices), //指定缓冲区的大小 0, //指定顶点缓冲区的属性 D3DFVF_CUSTOMVERTEX, //开始创建的定点格式 D3DPOOL_DEFAULT, //指定顶点缓冲区的内存类型 &m_pVBvideo, //视频矩形的顶点缓存区接口指针 NULL ); //保留参数 m_pVBvideo->Lock( 0, sizeof(vertices), (void**)&pVertices, 0 ); //填充顶点缓冲区 memcpy( pVertices, vertices, sizeof(vertices)); m_pVBvideo->Unlock(); return hr; } HRESULT CD3DDisplay::RenderSample(BYTE *pSampleBuffer) { HRESULT hr = 0; BYTE * pTextureBuffer = NULL; D3DLOCKED_RECT d3dlr; LONG lTexturePitch; hr = m_pTexturesvideo->LockRect( 0, &d3dlr, 0, 0 ); if( FAILED(hr)) { return -1; } lTexturePitch = d3dlr.Pitch; pTextureBuffer = static_cast<byte *>(d3dlr.pBits); for(int i = 0; i < IMAGE_HEIGHT; i++ ) { BYTE *pBmpBufferOld = pSampleBuffer; BYTE *pTxtBufferOld = pTextureBuffer; for (int j = 0; j < IMAGE_WIDTH; j++) { pTextureBuffer[0] = pSampleBuffer[0]; pTextureBuffer[1] = pSampleBuffer[1]; pTextureBuffer[2] = pSampleBuffer[2]; pTextureBuffer[3] = 0xFF; pTextureBuffer += 4; pSampleBuffer += 3; } pSampleBuffer = pBmpBufferOld + IMAGE_WIDTH *3; pTextureBuffer = pTxtBufferOld + lTexturePitch; } hr = m_pTexturesvideo->UnlockRect(0); if( FAILED(hr)) { return -1; } return 0; } HRESULT CD3DDisplay::RenderTOSrceen()//传递到屏幕 { HRESULT hr = 0; hr = m_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0 ); if( FAILED(hr)) { return -1; } hr = m_pd3dDevice->BeginScene(); if( FAILED(hr)) { return -1; } //显示视频 hr = m_pd3dDevice->SetTexture( 0, m_pTexturesvideo ); m_pd3dDevice->SetStreamSource( 0, m_pVBvideo, 0, sizeof(CUSTOMVERTEX)); hr = m_pd3dDevice->SetVertexShader( NULL );//设置可编程渲染管道的Shader程序 m_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX ); //设置固定渲染管道Ì的顶点格式 m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );//渲染一组非索引 m_pd3dDevice->SetTexture( 0, NULL ); m_pd3dDevice->EndScene(); m_pd3dDevice->Present( NULL, NULL, NULL, NULL ); return hr; } int CD3DDisplay::GetFormt() { return D3D_ARGB32; }
2.3.6.8 按钮点击事件函数
双击你自己创建的用于播放YUV视频的窗体上的那四个按钮,进入单击事件代码区,然后分别写入相应代码。首先在myplayerDlg.h中加入头文件声明#include
"PlayControl.h",在类中加上
private: CPlayControl m_PlayControl;
双击“打开文件”按钮后写入代码:
void CmyplayerDlg::OnBnClickedButton1() { // TODO: 在此添加控件通知处理程序代码 CString file_name = ""; CFileDialog fd(TRUE, NULL, file_name, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR, NULL, NULL); if (fd.DoModal() == IDOK) { m_PlayControl.Init(::GetDlgItem(GetSafeHwnd(),IDC_VIDEO), fd.GetPathName().GetBuffer(fd.GetPathName().GetLength())); m_PlayControl.PostThreadMessage(WM_MANAGE, 0, 0); } }
双击“播放”按钮后写入代码:
void CmyplayerDlg::OnBnClickedButton2() { // TODO: 在此添加控件通知处理程序代码 m_PlayControl.Play(); }
双击“暂停”按钮后写入代码:
void CmyplayerDlg::OnBnClickedButton3() { // TODO: 在此添加控件通知处理程序代码 m_PlayControl.Pause(); } 双击“停止”按钮后写入代码: void CmyplayerDlg::OnBnClickedButton4() { // TODO: 在此添加控件通知处理程序代码 m_PlayControl.Stop(); }
3.程序内部运行过程
运行时,初始化播放界面,操作人员点击“打开文件”按钮,选择自己要播放的YUV格式视频文件,点击一下这个视频文件,程序会先用PlayControl类的对象调用PlayControl.cpp中Init()函数来创建D3D设备(创建D3D设备调用D3DDisplay类),然后PlayControl类的对象传递消息触发消息响应函数PlayControl.cpp中的OnManage()函数,OnManage()函数中使用ColourSpaceConvert类的对象调用ColourSpaceConvert.cpp中的YUV2RGB()函数将YUV视频转换为RGB格式,然后D3D设备调用D3DDisplay.cpp中的RenderSample()函数和RenderToScreen()函数对视频渲染并在屏幕上显示。4.注意事项
4.1在DEFINE.h文件中
#define IMAGE_WIDTH 352//分辨率
#define IMAGE_HEIGHT 288//分辨率
这两句是设置分辨率的,可以更改为自己YUV视频的分辨率来正确播放视频。
4.2字符集错误
若出现不能将参数2 从“wchar_t *”转换为“char *”的错误,右击项目名选择“属性”,按下图红色区域把字符集改为“使用多字节字符集”。5.其他:
本文附带的项目文件“myplayer”需要在安装了DirectX SDK的电脑上运行,请按上文中1.2所述进行安装。若安装完运行出错,请按2.2所述配置SDK环境。
相关文章推荐
- vs2010MFC D3D播放YUV格式视频详细制作全过程
- avi视频格式转yuv格式与播放yuv视频
- 在VS2010下MFC采用ADO方式连接ACCESS数据库(详细过程)
- PPT2003播放多种格式的视频的方法ppt模板制作
- 第一次在VS2010下MFC采用ADO方式连接ACCESS数据库(详细过程)
- 第一次在VS2010下MFC采用ADO方式连接ACCESS数据库(详细过程)
- VS2010MFC对话框程序中使用Windows Media Player播放音频或视频文件
- ffplay播放原始格式的音频文件(PCM)和视频文件(YUV)
- vs2010环境下mfc+ffmpeg+opencv读取任意格式视频的播放器
- 视频采集相关知识之YUV格式详细解释
- avi视频格式转yuv格式与播放yuv视频
- 对YUV格式的详细描述,以及存储形式
- 网页制作之在线视频播放代码
- 第一部分 使用iReport制作报表的详细过程(Windows环境下)
- 武林外传辅助工具详细制作过程[第三篇:显示游戏数据]
- 武林外传辅助工具详细制作过程[第四篇:自动保护]
- 第一部分:使用iReport制作报表的详细过程(Windows环境下)
- 武林外传辅助工具详细制作过程[第六篇:物品过滤]
- 网页制作之在线视频播放代码
- 武林外传辅助工具详细制作过程[第一篇:基址的寻找方法]