教学、会议、信息发布同屏直播满足流媒体录播、直播软件之 libEasyScreenLive通过D3D方式实现屏幕采集
随着信息化时代的到来,同屏技术在教学、会议、大型活动中的应用越来越广泛。同屏结束简单说来,就是将手机、平板等一些移动设备上面的音视频资料通过无线或有线网络同步到显示设备上。换言之,就是将移动设备中的音视频资料景象到显示频幕上面。就像照镜子一样,移动设备上显示什么,显示屏上就会显示什么。
libEasyScreenLive通过D3D方式实现屏幕采集
在libEasyScreenLive中我们已经实现了通过GDI方式采集屏幕,但是,GDI采集方式效率低下,这就导致其采集的桌面视频帧比实际的桌面刷新画面延迟,经测试这个延时能达到近50ms,这在我们同屏要求极低延时的需求不符,所以,我们通过D3D方式采集,这种方式可以调度硬件驱动,其性能更好,而且采集帧率能达到60fps以上,采集延时也变得极低。
在libEasyScreenLive通过D3D方式采集屏幕主要通过CD3DCaptureScreem类实现,该类声明如下:
#include <d3d9.h>
#include <WinError.h>
typedef struct IDirect3D9* LPDIRECT3D9, *PDIRECT3D9;
typedef struct IDirect3DDevice9* LPDIRECT3DDevice9, *PDIRECT3DDevice9;
typedef struct IDirect3DSurface9* LPDIRECT3DSurface9, *PDIRECT3DSurface9;
typedef int (WINAPI *CaptureScreenCallback)(int nDevId, unsigned char *pBuffer, int nBufSize, int nRealDatatYPE, void* realDataInfo, void* pMaster);
class CD3DCaptureScreem
{
public:
CD3DCaptureScreem(void);
~CD3DCaptureScreem(void);
//接口函数
// 初始化
HRESULT InitD3DCapture(HWND hShowWnd);
//direct实现的截图
void DirectScreenCapture(LPVOID screenCaptureData);
//创建线程进行屏幕捕获
int CreateCaptureScreenThread();
static UINT WINAPI CaptureScreenThread(LPVOID pParam);
void CaptureVideoProcess();
//设置捕获数据回调函数
void SetCaptureScreenCallback(CaptureScreenCallback callBack, void * pMaster);
BOOL IsInCapturing()
{
return m_bCaptureScreen;
}
void GetCaptureScreenSize(int& nWidth, int& nHeight );
bool Convert24Image(BYTE *p32Img, BYTE *p24Img,DWORD dwSize32);
void StopD3DScreenCapture();
private:
HWND m_hMainWnd;
/*IDirect3D9**/ LPDIRECT3D9 m_pD3DScreenCapture;
/*IDirect3DDevice9* */LPDIRECT3DDevice9 m_pd3dDevice;
/*IDirect3DSurface9**/ LPDIRECT3DSurface9 m_pSurface;
CaptureScreenCallback m_pCallback;
void* m_pMaster;
int m_nCapWidth;
int m_nCapHeight;
LPVOID m_pScreenCaptureData;
RECT m_ScreenRect;
HANDLE m_hScreenCaptureThread;
BOOL m_bCaptureScreen;
};
从接口我们可以看出通过D3D方式采集实现比GDI方式更为简单,我们只需要把D3D9的设备捕获渲染流程走一遍即可(暂时没有采集鼠标),实现函数如下:
HRESULT CD3DCaptureScreem:: InitD3DCapture(HWND hShowWnd)
{
m_hMainWnd = hShowWnd;
BITMAPINFO bmpInfo;
ZeroMemory(&bmpInfo,sizeof(BITMAPINFO));
bmpInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biBitCount=32;
bmpInfo.bmiHeader.biCompression = BI_RGB;
bmpInfo.bmiHeader.biWidth=GetSystemMetrics(SM_CXSCREEN);
bmpInfo.bmiHeader.biHeight=GetSystemMetrics(SM_CYSCREEN);
bmpInfo.bmiHeader.biPlanes=1;
bmpInfo.bmiHeader.biSizeImage=abs(bmpInfo.bmiHeader.biHeight)*bmpInfo.bmiHeader.biWidth*bmpInfo.bmiHeader.biBitCount/8;
HDC hdc=GetDC(GetDesktopWindow());
HDC hBackDC=NULL;
HBITMAP hBackBitmap=NULL;
hBackDC=CreateCompatibleDC(hdc);
hBackBitmap=CreateDIBSection(hdc,&bmpInfo,DIB_RGB_COLORS,&m_pScreenCaptureData,NULL,0);
if(hBackBitmap==NULL)
{
return 0 ;
}
ReleaseDC(GetDesktopWindow(),hdc);
HWND hWnd = hShowWnd;
D3DDISPLAYMODE ddm;
D3DPRESENT_PARAMETERS d3dpp;
if((m_pD3DScreenCapture=Direct3DCreate9(D3D_SDK_VERSION))==NULL)
{
return E_FAIL;
}
if(FAILED(m_pD3DScreenCapture->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&ddm)))
{
return E_FAIL;
}
ZeroMemory(&d3dpp,sizeof(D3DPRESENT_PARAMETERS));
d3dpp.Windowed=true;
d3dpp.Flags=D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
d3dpp.BackBufferFormat=ddm.Format;
d3dpp.BackBufferHeight=m_ScreenRect.bottom =ddm.Height;
d3dpp.BackBufferWidth=m_ScreenRect.right =ddm.Width;
d3dpp.MultiSampleType=D3DMULTISAMPLE_NONE;
d3dpp.SwapEffect=D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow=hWnd;
d3dpp.PresentationInterval=D3DPRESENT_INTERVAL_DEFAULT;
d3dpp.FullScreen_RefreshRateInHz=D3DPRESENT_RATE_DEFAULT;
if(FAILED(m_pD3DScreenCapture->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,hWnd,D3DCREATE_HARDWARE_VERTEXPROCESSING ,&d3dpp,&m_pd3dDevice)))
{
return E_FAIL;
}
m_nCapWidth = ddm.Width;
m_nCapHeight = ddm.Height;
if(FAILED(m_pd3dDevice->CreateOffscreenPlainSurface(m_nCapWidth, m_nCapHeight, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &m_pSurface, NULL)))
{
return E_FAIL;
}
CreateCaptureScreenThread();
return S_OK;
}
void CD3DCaptureScreem::StopD3DScreenCapture()
{
m_bCaptureScreen = 0;
//等待线程结束
//Sleep(300);
//获取线程结束代码 ,如果线程还在运行就等她结束
while (1)
{
DWORD dwExitCode ;
::GetExitCodeThread(m_hScreenCaptureThread,&dwExitCode);
if(dwExitCode == STILL_ACTIVE)
{
WaitForSingleObject(m_hScreenCaptureThread, 100);
}
else
{
break;
}
}
CloseHandle(m_hScreenCaptureThread);
m_hScreenCaptureThread = INVALID_HANDLE_VALUE;
m_pSurface->Release();
m_pd3dDevice->Release();
m_pD3DScreenCapture->Release();
}
//direct实现的截图
void CD3DCaptureScreem::DirectScreenCapture(LPVOID screenCaptureData)
{
m_pd3dDevice->GetFrontBufferData(0, m_pSurface);
D3DLOCKED_RECT lockedRect;
if(FAILED(m_pSurface->LockRect(&lockedRect,&m_ScreenRect,D3DLOCK_NO_DIRTY_UPDATE|D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)))
{
return;
}
// for(int i=0;i<m_ScreenRect.bottom;i++)
// {
// }
memcpy((BYTE*)screenCaptureData/*+(i)*m_ScreenRect.right*32/8*/, (BYTE*)lockedRect.pBits/*+i*lockedRect.Pitch*/, m_ScreenRect.bottom*m_ScreenRect.right<<2);
m_pSurface->UnlockRect();
//printf("%d\n" , l3);
}
//创建线程进行屏幕捕获
int CD3DCaptureScreem::CreateCaptureScreenThread()
{
//创建线程
m_bCaptureScreen = TRUE;
m_hScreenCaptureThread=(HANDLE)_beginthreadex(NULL,0,(&CD3DCaptureScreem::CaptureScreenThread),
this,THREAD_PRIORITY_NORMAL,NULL);
return 1;
}
UINT WINAPI CD3DCaptureScreem::CaptureScreenThread(LPVOID pParam)
{
if (pParam)
{
CD3DCaptureScreem* pMaster = (CD3DCaptureScreem*)pParam;
if (pMaster)
{
pMaster->CaptureVideoProcess();
}
}
return 0;
}
void CD3DCaptureScreem::CaptureVideoProcess()
{
unsigned char * screenData = new unsigned char[1920*1080*4];
unsigned char * screenData24 = new unsigned char[1920*1080*3];
while (m_bCaptureScreen)
{
DirectScreenCapture(screenData);
Convert24Image(screenData, screenData24, 1920*1080*4);
#if 0
//显示图像
HWND hWnd=m_hMainWnd;
HDC hDC=::GetDC(hWnd);
::SetTextColor( hDC, RGB( 255, 255, 255 ) );
::SetBkMode( hDC, TRANSPARENT );
::SetStretchBltMode( hDC, COLORONCOLOR );
CRect rect;
::GetClientRect(m_hMainWnd, &rect);
BITMAPINFO bmi;
memset(&bmi, 0, sizeof(bmi));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = 1920;
bmi.bmiHeader.biHeight = -1080; // top-down image
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;//m_nColorBits;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = 0;
unsigned char * pData = (unsigned char *)screenData ;
::SetDIBitsToDevice( hDC, 0, 0, /*rect.Width()*/640, /*rect.Height()*/480, 0, 0, 0, 1080, (LPBYTE)pData , &bmi, DIB_RGB_COLORS );
#endif
if (m_pCallback&&m_pMaster)
{
ScreenCapDataInfo sCapScreenInfo;
sCapScreenInfo.nWidth = m_nCapWidth;
sCapScreenInfo.nHeight = m_nCapHeight;
sCapScreenInfo.nDataType = 24;
strcpy(sCapScreenInfo.strDataType, "RGB24");
m_pCallback(0, (unsigned char*)(screenData24), /*alpbi->biSizeImage*/m_nCapWidth*m_nCapHeight*3, 1, &sCapScreenInfo, m_pMaster);
}
//Sleep(30);
}
if (screenData)
{
delete screenData;
screenData = NULL;
}
if(screenData24)
{
delete screenData24;
screenData24 = NULL;
}
}
bool CD3DCaptureScreem::Convert24Image(BYTE *p32Img, BYTE *p24Img,DWORD dwSize32)
{
if(p32Img != NULL && p24Img != NULL && dwSize32>0)
{
DWORD dwSize24;
dwSize24=(dwSize32 * 3)/4;
BYTE *pTemp,*ptr;
pTemp=p32Img;
ptr = p24Img;
int ival=0;
for (DWORD index = 0; index < dwSize32/4 ; index++)
{
unsigned char r = *(pTemp++);
unsigned char g = *(pTemp++);
unsigned char b = *(pTemp++);
(pTemp++);//skip alpha
*(ptr++) = r;
*(ptr++) = g;
*(ptr++) = b;
}
}
else
{
return false;
}
return true;
}
//设置捕获数据回调函数
void CD3DCaptureScreem::SetCaptureScreenCallback(CaptureScreenCallback callBack, void * pMaster)
{
m_pCallback = callBack;
m_pMaster = pMaster;
}
void CD3DCaptureScreem::GetCaptureScreenSize(int& nWidth, int& nHeight )
{
nWidth = m_nCapWidth;
nHeight = m_nCapHeight;
}
EasyScreenlive应用场景
EasyScreenlive广泛应用于大屏显示投屏,无纸化会议同屏演示,课堂同屏等,可以配合全屏显示,反向模拟触控实现远程控制功能(Android控制Windows,Windows控制Android,Windows控制Windows等)