屏幕捕获RGB数据回调函数(CVideoCapture.h):
#ifndef _VIDEODATA_CALLBACK_
#define _VIDEODATA_CALLBACK_
typedef void(*LPVideoDataRealCallback)(unsigned char *pRgbData, int size, int width, int height, void* pContext);
#endif
调用接口定义(CVideoCapture.h):
// GDI方式,设置视频回调函数,并开始捕获视频数据
AVFILTER_API void* GDI_VideoSourceStartCapture(int left, int top, int width, int height, LPVideoDataRealCallback pCallback, void* pUser);
// 停止视频数据捕获
AVFILTER_API bool GDI_VideoSourceStopCapture(void* pSource);
实现源码(CVideoCapture.c):
#include <stdio.h>
#include "CVideoCapture.h"
#include <stdlib.h>
#include <windows.h>
#include <process.h>
#pragma comment(lib,"user32")
#pragma comment(lib,"gdi32")
typedef struct tVideoSource
{
int m_nLeft;//捕获区域坐标值-左
int m_nTop;//捕获区域坐标值-上
int m_nWidth;//捕获区域-宽度
int m_nHeight;//捕获区域-高度
bool m_bCaptureCursor;//是否抓取光标
bool m_bActive;//是否激活,即是否获取到视频帧
HANDLE m_hCaptureThread;//捕获线程句柄
HANDLE m_hStopSignal;//线程停止信号
CRITICAL_SECTION m_csMemLock;//数据访问互斥锁
void* m_memRawBuffer;//RGB数据存放缓冲区
int m_nMemSize;//RGB数据缓冲区大小
BITMAPINFO m_bmpInfo;//位图信息
HDC m_hScreenDC;//屏幕上下文-DC
HDC m_hMemDC;//内存上下文-DC,此处采用的双缓存(其实没必要,只有绘图才需要双缓存,避免闪烁)
HBITMAP m_hBitmap;//位图句柄
HBITMAP m_hOldBitmap;//旧位图句柄
LPVideoDataRealCallback m_pVideoDataRealCalBack;//实时捕获数据回调函数
void* m_pVideoDataRealCalBackUser;//实时捕获数据用户接受对象
}VideoSource, *LPVideoSource;
//申请一个视频源对象
void AllocVideoResource(LPVideoSource* ppSource);
//释放视频源对象
void ReleaseVideoResource(LPVideoSource pSource);
//开始捕获视频数据
bool StartVideoCapture(LPVideoSource pSource);
//停止捕获视频数据
void StopVideoCapture(LPVideoSource pSource);
//视频数据循环捕获线程
DWORD WINAPI OnVideoCaptureThread(LPVOID param);
//视频数据捕获函数
bool DoVideoCapture(LPVideoSource pSource);
//视频数据捕获函数
bool ProcessVideoCaptureData(LPVideoSource pSource);
//申请一个视频源对象
void AllocVideoResource(LPVideoSource* ppSource)
{
//参数初始化
*ppSource = (LPVideoSource)malloc(sizeof(VideoSource));
((LPVideoSource)*ppSource)->m_bActive = false;
((LPVideoSource)*ppSource)->m_bCaptureCursor = false;
((LPVideoSource)*ppSource)->m_hCaptureThread = NULL;
((LPVideoSource)*ppSource)->m_hStopSignal = CreateEvent(NULL, TRUE, FALSE, NULL);
((LPVideoSource)*ppSource)->m_memRawBuffer = NULL;
((LPVideoSource)*ppSource)->m_pVideoDataRealCalBack = NULL;
((LPVideoSource)*ppSource)->m_pVideoDataRealCalBackUser = NULL;
DeleteObject(((LPVideoSource)*ppSource)->m_hOldBitmap);
DeleteObject(((LPVideoSource)*ppSource)->m_hBitmap);
DeleteDC(((LPVideoSource)*ppSource)->m_hMemDC);
ReleaseDC(NULL, ((LPVideoSource)*ppSource)->m_hScreenDC);
InitializeCriticalSection(&((LPVideoSource)*ppSource)->m_csMemLock);
}
//释放视频源对象
void ReleaseVideoResource(LPVideoSource pSource)
{
StopVideoCapture(pSource);
ReleaseDC(NULL, pSource->m_hScreenDC);
DeleteDC(pSource->m_hMemDC);
free(((LPVideoSource)pSource)->m_memRawBuffer);
((LPVideoSource)pSource)->m_memRawBuffer = NULL;
DeleteCriticalSection(&((LPVideoSource)pSource)->m_csMemLock);
free(pSource);
pSource = NULL;
}
//开始捕获视频数据
bool StartVideoCapture(LPVideoSource pSource)
{
// 释放之前申请的的存放图像的缓存
free(((LPVideoSource)pSource)->m_memRawBuffer);
((LPVideoSource)pSource)->m_memRawBuffer = NULL;
((LPVideoSource)pSource)->m_nMemSize = 0;
//计算所需存放图像的缓存大小
((LPVideoSource)pSource)->m_nMemSize = ((LPVideoSource)pSource)->m_nWidth * ((LPVideoSource)pSource)->m_nHeight * 3;//获取的图像位图深度24位,所以是*3
((LPVideoSource)pSource)->m_memRawBuffer = (char*)malloc(((LPVideoSource)pSource)->m_nMemSize);
memset(((LPVideoSource)pSource)->m_memRawBuffer, 0, ((LPVideoSource)pSource)->m_nMemSize);
//创建捕获图像的线程
unsigned int dwThreadId;
pSource->m_hCaptureThread = (HANDLE)_beginthreadex(NULL,
0,
&OnVideoCaptureThread,
pSource,
THREAD_PRIORITY_NORMAL,
&dwThreadId);
if (!pSource->m_hCaptureThread)
return false;
((LPVideoSource)pSource)->m_bActive = true;
return true;
}
//停止捕获视频数据
void StopVideoCapture(LPVideoSource pSource)
{
//发送线程停止工作信号
SetEvent(pSource->m_hStopSignal);
//等待线程安全退出
if (pSource->m_bActive)
{
WaitForSingleObject(pSource->m_hCaptureThread, INFINITE);
}
pSource->m_nMemSize = 0;
}
//视频数据循环捕获线程
DWORD WINAPI OnVideoCaptureThread(LPVOID param)
{
LPVideoSource pSource = (LPVideoSource)param;
DoVideoCapture(pSource);
_endthreadex(0);
return 0;
}
//视频数据循环捕获线程-处理线程
bool DoVideoCapture(LPVideoSource pSource)
{
bool bReconnect = false;
DWORD dwTimeout = 1;
// 等待超时进入下一次图像数据获取
while (WaitForSingleObject(pSource->m_hStopSignal, dwTimeout) == WAIT_TIMEOUT)
{
if (ProcessVideoCaptureData(pSource) )
{
//获取图像成功,回调数据给应用层用户处理
if (pSource->m_pVideoDataRealCalBack )
{
pSource->m_pVideoDataRealCalBack((LPBYTE)pSource->m_memRawBuffer, pSource->m_nMemSize, pSource->m_nWidth, pSource->m_nHeight, pSource->m_pVideoDataRealCalBackUser);
}
}
}
//关闭信号
CloseHandle(pSource->m_hStopSignal);
pSource->m_hStopSignal = NULL;
//关闭线程句柄
CloseHandle(pSource->m_hCaptureThread);
pSource->m_hCaptureThread = NULL;
((LPVideoSource)pSource)->m_bActive = false;
return true;
}
//视频数据捕获函数
bool ProcessVideoCaptureData(LPVideoSource pSource)
{
POINT ptCursor; //光标位置
HCURSOR hCurSor; //光标句柄
ICONINFO icoInfo; //图标信息
CURSORINFO cursorInfo;//光标信息
//将屏幕信息绘制到内存上下文里面去
BitBlt(pSource->m_hMemDC, 0, 0, pSource->m_nWidth, pSource->m_nHeight, pSource->m_hScreenDC, pSource->m_nLeft, pSource->m_nTop, SRCCOPY);
//抓取屏幕鼠标信息
if (pSource->m_bCaptureCursor)
{
GetCursorPos(&ptCursor);
ptCursor.x -= pSource->m_nLeft;
ptCursor.y -= pSource->m_nTop;
memset(&cursorInfo, 0, sizeof(cursorInfo));
cursorInfo.cbSize = sizeof(cursorInfo);
GetCursorInfo(&cursorInfo);
hCurSor = cursorInfo.hCursor;
if (hCurSor)
{
if (GetIconInfo(hCurSor, &icoInfo))
{
ptCursor.x -= icoInfo.xHotspot;
ptCursor.y -= icoInfo.yHotspot;
if (icoInfo.hbmMask) DeleteObject(icoInfo.hbmMask);
if (icoInfo.hbmColor) DeleteObject(icoInfo.hbmColor);
}
DrawIcon(pSource->m_hMemDC, ptCursor.x, ptCursor.y, hCurSor);
}
}
//从内存上下文里面获取图像数据信息
GetDIBits(pSource->m_hMemDC, pSource->m_hBitmap, 0, pSource->m_nHeight, (PRGBTRIPLE)pSource->m_memRawBuffer, (LPBITMAPINFO)&pSource->m_bmpInfo, DIB_RGB_COLORS);
return true;
}
//设置图像数据回调函数
void SetVideoCaptureDataRealCallback(LPVideoSource pSource, LPVideoDataRealCallback pVideoDataRealCalBack, void* pUser)
{
pSource->m_pVideoDataRealCalBack = pVideoDataRealCalBack;
pSource->m_pVideoDataRealCalBackUser = pUser;
}
// GDI方式,设置视频回调函数,并开始捕获视频数据
AVFILTER_API void* GDI_VideoSourceStartCapture(int left, int top, int width, int height, LPVideoDataRealCallback pCallback, void* pUser)
{
RECT rect = { left, top, left + width, top + height };
int nWidth = rect.right - rect.left;
int nHeight = rect.bottom - rect.top;
//
// 对齐4字节边界
// 因为GDI的原因,分辨率必须要是4字节的边界,否则抓出来的图像重新渲染的话,
// 会出现花屏现象,这里处理的目的就是为了保证是4字节的边界。
//
int nMod = nWidth % 4;
if (nMod)
{
rect.left += (nMod / 2);
rect.right -= (nMod / 2);
}
nMod = nHeight % 4;
if (nMod)
{
rect.top += (nMod / 2);
rect.bottom -= (nMod / 2);
}
// 申请一个视频数据源
LPVideoSource pSource = NULL;
AllocVideoResource(&pSource);
//保存捕获区域参数
pSource->m_nLeft = rect.left;
pSource->m_nTop = rect.top;
pSource->m_nWidth = rect.right - rect.left;
pSource->m_nHeight = rect.bottom - rect.top;
pSource->m_bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pSource->m_bmpInfo.bmiHeader.biWidth = rect.right - rect.left;
pSource->m_bmpInfo.bmiHeader.biHeight = rect.bottom - rect.top;
pSource->m_bmpInfo.bmiHeader.biPlanes = 1;
pSource->m_bmpInfo.bmiHeader.biBitCount = 24;
pSource->m_bmpInfo.bmiHeader.biCompression = BI_RGB;
pSource->m_hScreenDC = GetDC(NULL);
if (pSource->m_hScreenDC == NULL)
goto exit;
//创建一个与指定设备兼容的内存设备上下文环境(DC)
pSource->m_hMemDC = CreateCompatibleDC(pSource->m_hScreenDC);
if (pSource->m_hMemDC == NULL)
goto exit;
//创建与指定的设备环境相关的设备兼容的位图
pSource->m_hBitmap = CreateCompatibleBitmap(pSource->m_hScreenDC, pSource->m_nWidth, pSource->m_nHeight);
if (pSource->m_hBitmap == NULL)
goto exit;
//这里把m_hBitmap的位图选择到m_hMemDC,之后这个m_hMemDC就拥有和m_hBitmap同样大小的绘图区域
pSource->m_hOldBitmap = (HBITMAP)SelectObject(pSource->m_hMemDC, pSource->m_hBitmap);
SetVideoCaptureDataRealCallback(pSource, pCallback, pUser);
if (pSource && StartVideoCapture(pSource))
{
return pSource;
}
else
{
goto exit;
}
exit:
ReleaseVideoResource(pSource);
pSource = NULL;
return pSource;
}
// 停止视频数据捕获
AVFILTER_API bool GDI_VideoSourceStopCapture(void* pSource)
{
if (pSource == NULL )
return true;
SetVideoCaptureDataRealCallback(pSource, NULL, NULL);
StopVideoCapture(pSource);
return true;
}
GDI方式在1920*1080分辨率下,实时性较差(帧率大概20帧/秒左右)。