应用背景
投屏和显示技术的升级,使视频远程控制的功能日益更新,EasyScreenlive广泛应用于大屏显示投屏,无纸化会议同屏演示,课堂同屏等,可以配合全屏显示,反向模拟触控实现远程控制功能(Android控制Windows,Windows控制Android,Windows控制Windows等)
EasyScreenlive支持DXGI采集桌面
之前文章中我们已经提到了通过GDI和D3D来采集windows桌面,通过D3D采集效率已经提高了不少,那么有没有更加高效的方法呢?答案是有的,那就是我们现在要讲的DXGI桌面采集技术。
该技术是基于DirectX技术,所以你需要简单熟悉DirectX技术才能使用DXGI,他通过一系列DX设备的创建和查询各种接口,最终获取到 IDXGIOutputDuplication 接口。
截屏的时候,使用这个接口的AcquireNextFrame 函数获取当前桌面图像, 当然这个接口还提供 GetFrameDirtyRects等函数获取发生了变化的矩形区域。 这应该算是一个比较好的截屏方法,CPU占用也极低,可惜只支持windows 8 以上的平台,而win7,windows xp等系统不支持。
下面我们通过代码简单讲解下采集实现流程,首先,通过DXGI接口获取桌面设备:
// Get desktop
DUPL_RETURN Ret;
HDESK CurrentDesktop = nullptr;
CurrentDesktop = OpenInputDesktop(0, FALSE, GENERIC_ALL);
if (!CurrentDesktop)
{
// We do not have access to the desktop so request a retry
SetEvent(TData->ExpectedErrorEvent);
Ret = DUPL_RETURN_ERROR_EXPECTED;
goto Exit;
}
// Attach desktop to this thread
bool DesktopAttached = SetThreadDesktop(CurrentDesktop) != 0;
CloseDesktop(CurrentDesktop);
CurrentDesktop = nullptr;
if (!DesktopAttached)
{
// We do not have access to the desktop so request a retry
Ret = DUPL_RETURN_ERROR_EXPECTED;
goto Exit;
}
}
// New display manager
DispMgr.InitD3D(&TData->DxRes);
// Obtain handle to sync shared Surface
HRESULT hr = TData->DxRes.Device->OpenSharedResource(TData->TexSharedHandle, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&SharedSurf));
if (FAILED (hr))
{
Ret = ProcessFailure(TData->DxRes.Device, L"Opening shared texture failed", L"Error", hr, SystemTransitionsExpectedErrors);
goto Exit;
}
hr = SharedSurf->QueryInterface(__uuidof(IDXGIKeyedMutex), reinterpret_cast<void**>(&KeyMutex));
if (FAILED(hr))
{
Ret = ProcessFailure(nullptr, L"Failed to get keyed mutex interface in spawned thread", L"Error", hr);
goto Exit;
}
// Make duplication manager
Ret = DuplMgr.InitDupl(TData->DxRes.Device, TData->Output);
if (Ret != DUPL_RETURN_SUCCESS)
{
goto Exit;
}
// Get output description
DXGI_OUTPUT_DESC DesktopDesc;
RtlZeroMemory(&DesktopDesc, sizeof(DXGI_OUTPUT_DESC));
DuplMgr.GetOutputDesc(&DesktopDesc);
然后,通过大循环GetFrame采集桌面图像数据源,实现代码如下:
while ((WaitForSingleObjectEx(TData->TerminateThreadsEvent, 0, FALSE) == WAIT_TIMEOUT))
{
if (!WaitToProcessCurrentFrame)
{
// Get new frame from desktop duplication
bool TimeOut;
Ret = DuplMgr.GetFrame(&CurrentData, &TimeOut);
if (Ret != DUPL_RETURN_SUCCESS)
{
// An error occurred getting the next frame drop out of loop which
// will check if it was expected or not
break;
}
// Check for timeout
if (TimeOut)
{
// No new frame at the moment
continue;
}
}
// We have a new frame so try and process it
// Try to acquire keyed mutex in order to access shared surface
hr = KeyMutex->AcquireSync(0, 1000);
if (hr == static_cast<HRESULT>(WAIT_TIMEOUT))
{
// Can't use shared surface right now, try again later
WaitToProcessCurrentFrame = true;
continue;
}
else if (FAILED(hr))
{
// Generic unknown failure
Ret = ProcessFailure(TData->DxRes.Device, L"Unexpected error acquiring KeyMutex", L"Error", hr, SystemTransitionsExpectedErrors);
DuplMgr.DoneWithFrame();
break;
}
// We can now process the current frame
WaitToProcessCurrentFrame = false;
// Get mouse info
Ret = DuplMgr.GetMouse(TData->PtrInfo, &(CurrentData.FrameInfo), TData->OffsetX, TData->OffsetY);
if (Ret != DUPL_RETURN_SUCCESS)
{
DuplMgr.DoneWithFrame();
KeyMutex->ReleaseSync(1);
break;
}
// Process new frame
Ret = DispMgr.ProcessFrame(&CurrentData, SharedSurf, TData->OffsetX, TData->OffsetY, &DesktopDesc);
if (Ret != DUPL_RETURN_SUCCESS)
{
DuplMgr.DoneWithFrame();
KeyMutex->ReleaseSync(1);
break;
}
// Release acquired keyed mutex
hr = KeyMutex->ReleaseSync(1);
if (FAILED(hr))
{
Ret = ProcessFailure(TData->DxRes.Device, L"Unexpected error releasing the keyed mutex", L"Error", hr, SystemTransitionsExpectedErrors);
DuplMgr.DoneWithFrame();
break;
}
// Release frame back to desktop duplication
Ret = DuplMgr.DoneWithFrame();
if (Ret != DUPL_RETURN_SUCCESS)
{
break;
}
}