您的位置:首页 > 其它

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环境。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: