既然使用ffmpeg gdigrab可以实现辅屏的采集,那么必须适配一下DXGI的多屏幕采集(先前实现参考《【音视频】WIN8|WIN10的桌面采集技术-DXGI(一)》)。
1、修改初始化d3d函数
将获取的IDXGIAdapter和IDXGIOutput保存为成员
int DuplicationCaptor::initD3d()
{
int err = ERROR_CODE_OK;
do {
err = getAdapter(&m_adapter, &m_output, m_outputIndex);
HCMDR_ERROR_CODE_BREAK(err);
err = createD3dDevice(m_adapter, &m_d3dDevice);
HCMDR_ERROR_CODE_BREAK(err);
} while (0);
return err;
}
其中getAdapter实现修改如下:
int DuplicationCaptor::getAdapter(IDXGIAdapter** adapter, IDXGIOutput** output, uint32_t& outputIndex)
{
int err = ERROR_CODE_OK;
do {
std::list<IDXGIAdapter*> adapters;
err = getAdapters(adapters);
HCMDR_ERROR_CODE_BREAK(err);
if (adapters.empty()) {
err = ERROR_CODE_DXGI_FOUND_ADAPTER_FAILED;
break;
}
bool found = false;
for (std::list<IDXGIAdapter*>::iterator it = adapters.begin(); it != adapters.end(); it++) {
UINT index = 0;
IDXGIOutput* adapterOutput = nullptr;
DXGI_ADAPTER_DESC adapterDesc = { 0 };
(*it)->GetDesc(&adapterDesc);
for (index = 0; (*it)->EnumOutputs(index, &adapterOutput) != DXGI_ERROR_NOT_FOUND; index++) {
DXGI_OUTPUT_DESC outputDesc = { 0 };
RECT outputRect = { 0 };
HRESULT hr = adapterOutput->GetDesc(&outputDesc);
if (FAILED(hr)) {
continue;
}
outputRect = outputDesc.DesktopCoordinates;
// The target area is within the selected area
if (m_rect.left >= outputRect.left && m_rect.top >= outputRect.top &&
m_rect.right <= outputRect.right && m_rect.bottom <= outputRect.bottom) {
found = true;
break;
}
}
if (found) {
err = ERROR_CODE_OK;
*adapter = *it;
*output = adapterOutput;
outputIndex = index;
break;
}
}
} while (0);
return err;
}
2、修改初始化dxgi函数
使用获取的IDXGIOutput* m_output获取IDXGIOutputDuplication* m_duplication
int DuplicationCaptor::initDuplication()
{
int err = ERROR_CODE_OK;
do {
HRESULT hr;
IDXGIOutput* output = m_output;
output->GetDesc(&m_outputDesc);
IDXGIOutput1* output1 = nullptr;
hr = output->QueryInterface(__uuidof(IDXGIOutput1), reinterpret_cast<void**>(&output1));
output->Release();
output = nullptr;
if (FAILED(hr)) {
err = ERROR_CODE_DXGI_QUERY_INTERFACE_FAILED;
break;
}
// Create desktop duplication
hr = output1->DuplicateOutput(m_d3dDevice, &m_duplication);
output1->Release();
output1 = nullptr;
if (FAILED(hr)) {
err = ERROR_CODE_DXGI_DUPLICATE_FAILED;
if (hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE) {
err = ERROR_CODE_DXGI_DUPLICATE_NOT_AVALIABLE;
}
LOGGER::Logger::log(LOGGER::LOG_TYPE_ERROR, "[%s] duplicate output failed %ld", __FUNCTION__, hr);
break;
}
} while (0);
return err;
}
3、采集桌面时注意重启采集器的情况
在采集线程中添加reinit:
void DuplicationCaptor::captureProcess()
{
/* 省略 */
while (m_running) {
err = reinit();
if (err != ERROR_CODE_OK) {
if (m_onVideoCaptureError != nullptr) {
m_onVideoCaptureError(err, m_index);
}
break;
}
err = getDuplicatedFrame(&frameInfo);
/* 省略 */
}
/* 省略 */
}
reinit的实现如下。当桌面分辨率改变时重启采集器。
int DuplicationCaptor::reinit()
{
int err = ERROR_CODE_OK;
long handle = atol(m_deviceId.c_str());
HMONITOR monitor = reinterpret_cast<HMONITOR>(handle);
MONITORINFO mi;
mi.cbSize = sizeof(mi);
do {
if (!GetMonitorInfo(monitor, &mi)) {
err = ERROR_CODE_DEVICE_GET_MONITOR_FAILED;
break;
}
RECT rect = mi.rcMonitor;
if (rect.left != m_rect.left || rect.top != m_rect.top ||
rect.right - rect.left != m_rect.right - m_rect.left ||
rect.bottom - rect.top != m_rect.bottom - m_rect.top) {
cleanup();
err = init(m_deviceId, m_fps);
HCMDR_ERROR_CODE_BREAK(err);
if (!attatchedDesktop()) {
err = ERROR_CODE_DXGI_ATTATCH_DESKTOP_FAILED;
LOGGER::Logger::log(LOGGER::LOG_TYPE_ERROR, "[%s] attach desktop error: %s, last error: %lu",
__FUNCTION__, HCMDR_GET_ERROR_DESC(err), GetLastError());
if (m_onVideoCaptureError != nullptr) {
m_onVideoCaptureError(err, m_index);
}
break;
}
err = initDuplication();
if (err != ERROR_CODE_OK) {
LOGGER::Logger::log(LOGGER::LOG_TYPE_ERROR, "[%s] init duplication error: %s", __FUNCTION__,
HCMDR_GET_ERROR_DESC(err));
if (m_onVideoCaptureError != nullptr) {
m_onVideoCaptureError(err, m_index);
}
break;
}
}
} while (0);
return err;
}