最近在研究程序窗口截流,因为之前用过ffmpeg的gdigrab,所以知道gdigrab也可以截取窗口图片,但实际上没用过。经过研究,通过将avformat_open_input中url设置为“title={程序名}"获取format context从而得到图片。
1、初始化gdigrab采集器
int GdigrabCaptor::init(const std::string& deviceId, const int fps)
{
int err = ERROR_CODE_OK;
if (m_inited) {
return err;
}
do {
if (deviceId.empty()) {
err = ERROR_CODE_PARAMS_ERROR;
break;
}
m_deviceId = deviceId;
RECT rect = {
};
err = initDevice(m_deviceId, m_deviceName, rect);
HCMDR_ERROR_CODE_BREAK(err);
avdevice_register_all();
avformat_network_init();
m_inputFmt = av_find_input_format("gdigrab");
if (m_inputFmt == nullptr) {
err = ERROR_CODE_FFMPEG_FIND_INPUT_FORMAT_FAILED;
break;
}
m_rect = rect;
m_fps = fps;
if (m_fps < 0) {
m_fps = GDIGRAB_CAPTOR_MIN_FRAMERATE;
}
else if (m_fps > GDIGRAB_CAPTOR_MAX_FRAMERATE) {
m_fps = GDIGRAB_CAPTOR_MAX_FRAMERATE;
}
err = initGdigrab(m_deviceName, m_rect, m_fps);
HCMDR_ERROR_CODE_BREAK(err);
m_pixelFmt = AV_PIX_FMT_BGRA;
m_timebase = {
1, AV_TIME_BASE };
m_inited = true;
} while (0);
if (err != ERROR_CODE_OK) {
LOGGER::Logger::log(LOGGER::LOG_TYPE_ERROR, "[%s] init camera captor error: %s, last error: %lu",
__FUNCTION__, HCMDR_GET_ERROR_DESC(err), GetLastError());
cleanup();
}
return err;
}
其中initDevice中,根据deviceId获取HMONITOR或者HWND,然后获取显示器或者窗口大小、名称。需要注意的是,采集窗口时发现窗口最小化了,先将其显示出来,再获取其大小,也便于采集编码。
int GdigrabCaptor::initDevice(const std::string& deviceId, std::string& deviceName, RECT& deviceRect)
{
int err = ERROR_CODE_OK;
do {
long handle = atol(deviceId.c_str());
HMONITOR monitor = reinterpret_cast<HMONITOR>(handle);
HWND hwnd = reinterpret_cast<HWND>(handle);
MONITORINFO mi;
mi.cbSize = sizeof(mi);
if (GetMonitorInfo(monitor, &mi)) {
deviceName = "desktop";
deviceRect = mi.rcMonitor;
m_isDesktop = true;
}
else {
TCHAR title[_MAX_FNAME] = {
0 };
RECT rect = {
};
// 将最小化窗口显示出来
if (::IsIconic(hwnd)) {
::ShowWindow(hwnd, SW_SHOWNOACTIVATE);
}
BOOL ret = GetWindowText(hwnd, title, sizeof(title)) && GetClientRect(hwnd, &rect);
if (!ret) {
err = ERROR_CODE_DEVICE_GET_MONITOR_FAILED;
break;
}
#ifdef UNICODE
deviceName = HELPER::StringConverter::convertUnicodeToAscii(title);
#else
deviceName = title;
#endif // UNICODE
deviceRect = rect;
m_isDesktop = false;
}