截图代码
bool Monitor::get()
{
DXGI_OUTDUPL_FRAME_INFO info;
IDXGIResource* desktop = nullptr;
HRESULT hr = this->m_duplication->AcquireNextFrame(0, &info, &desktop);
if (hr == DXGI_ERROR_WAIT_TIMEOUT) {
return false;
}
if (this->m_image != nullptr) {
this->m_image->Release();
this->m_image = nullptr;
}
hr = desktop->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&this->m_image));
if (FAILED(hr)) {
desktop->Release();
this->m_duplication->ReleaseFrame();
return false;
}
/*if (!this->copy_frame_data(this->m_image, _buffer, _size)) {
desktop->Release();
this->m_duplication->ReleaseFrame();
return false;
}*/
desktop->Release();
desktop = nullptr;
if (FAILED(hr)) {
return false;
}
return true;
}
以上是gxdi截图流程,前期需要做些工作拿到显示器adapter,根据adapter创建dxdevice。
bool Monitor::copy_frame_data(ID3D11Texture2D* _texture, uint8_t* _buffer, size_t _size)
{
D3D11_TEXTURE2D_DESC desc;
_texture->GetDesc(&desc);
ID3D11Texture2D* _target = nullptr;
D3D11_TEXTURE2D_DESC target_desc;
memcpy_s(&target_desc, sizeof(target_desc), &desc, sizeof(desc));
target_desc.Usage = D3D11_USAGE_STAGING;
target_desc.BindFlags = 0;
target_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
target_desc.MiscFlags = 0;
HRESULT hr = this->m_device->CreateTexture2D(&target_desc, nullptr, &_target);
if (FAILED(hr)) {
return false;
}
this->m_context->CopyResource(_target, _texture);
D3D11_MAPPED_SUBRESOURCE target_map_res;
UINT subresource = D3D11CalcSubresource(0, 0, 0);
hr = this->m_context->Map(_target, subresource, D3D11_MAP_READ, 0, &target_map_res);
if (FAILED(hr)) {
return false;
}
memcpy_s(_buffer, _size, target_map_res.pData, _size);
this->m_context->Unmap(_target, subresource);
_target->Release();
this->m_duplication->ReleaseFrame();
return true;
}
以上将截图所得的纹理拷贝出去,这些都是常规写法,网络到处是源码。
帧数控制及计算
double Timer::nextFps() const {
const long long target = mulDiv64(this->m_last + this->m_interval, this->m_clock_freq->QuadPart);
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
const long long wait = (((target - now.QuadPart) * 1000.0) / this->m_clock_freq->QuadPart);
if (now.QuadPart < target) {
if (wait > 1) {
Sleep(wait - 1);
}
for (;;) {
QueryPerformanceCounter(&now);
if (now.QuadPart >= target) {
break;
}
YieldProcessor();
}
return this->m_fps;
}
return this->m_fps / (1 - wait * this->m_fps/1000.0);
}
void Timer::mark()
{
this->m_last = this->now();
}
long long Timer::now() const {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
}
这里参考了obs的帧数控制,now()方法采用chrono库获取时间,帧数控制函数中采用QueryPerformanceCounter获取时间,结合obs帧数控制源码是可以正确运行的。
如果now()方法也是用QueryPerformanceCounter获取,计算出来结果不正确。具体obs的last_time那块每太理解,这里贴一下。
const uint64_t udiff = os_gettime_ns() - cur_time;
int64_t diff;
memcpy(&diff, &udiff, sizeof(diff));
const uint64_t clamped_diff = (diff > (int64_t)interval_ns)
? (uint64_t)diff
: interval_ns;
count = (int)(clamped_diff / interval_ns);
*p_time = cur_time + interval_ns * count;
uint64_t os_gettime_ns(void)
{
LARGE_INTEGER current_time;
QueryPerformanceCounter(¤t_time);
return util_mul_div64(current_time.QuadPart, 1000000000,
get_clockfreq());
}
它其实gettime_ns是把QueryPerformanceCounter的时间转换出去(转换后位数与chrono库获取的时间位数相同),在帧数控制计算的时候再转换回来,不太理解这一步的作用。
绘制鼠标及渲染
将纹理转为Qimage进行绘制及渲染
void MainWindow::drawCursor(QImage* _image)
{
QPainter painter(_image);
CURSORINFO cursorInfo;
memset(&cursorInfo, 0, sizeof(cursorInfo));
cursorInfo.cbSize = sizeof(cursorInfo);
if (GetCursorInfo(&cursorInfo)) {
HICON hIcon = CopyIcon(cursorInfo.hCursor);
if (hIcon) {
ICONINFO iconInfo;
if (GetIconInfo(hIcon, &iconInfo)) {
this->m_cursorImage = QImage::fromHBITMAP(iconInfo.hbmColor);
this->m_cursorPixmap = QPixmap::fromImage(this->m_cursorImage);
this->m_cursorPixmap.setMask(QBitmap::fromImage(QImage::fromHBITMAP(iconInfo.hbmMask)));
DeleteObject(iconInfo.hbmColor);
DeleteObject(iconInfo.hbmMask);
}
painter.drawPixmap(cursorInfo.ptScreenPos.x, cursorInfo.ptScreenPos.y, this->m_cursorPixmap);
}
}
}
void PlayWindow::showMonitor(QImage& image)
{
QPixmap pixmap = QPixmap::fromImage(image.scaled(this->m_imageLabel->width(), this->m_imageLabel->height(), Qt::IgnoreAspectRatio));
this->m_imageLabel->setPixmap(pixmap);
}
绘制鼠标后将图片转为pixmap放入label中既可,此处有个bug。鼠标在可选中文字上的形状,就是类似于大写i的形状,绘制不出来,不确定是不是mask的原因,也可能是应用窗口太小没看到。
但这不是重点,考虑不转为QImage,参考obs源码,还在研究。
性能测试
单纯截图性能达到要求,可以稳定60,经测试144也可以基本稳定。
如果在截图线程中加入渲染,则无法稳定60。
单纯渲染性能测试也是,几乎稳定不到60帧。
后续需要考虑直接采用dx11渲染实时显示组件。
另外,编码线程性能严重不足。
编码参数设置:
this->m_encode_pCodecCtx = avcodec_alloc_context3(m_encode_pCodec);
this->m_encode_pCodecCtx->codec_id = AV_CODEC_ID_H264;
// this->m_encode_pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
this->m_encode_pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
this->m_encode_pCodecCtx->bit_rate = 25000 * 1000;
this->m_encode_pCodecCtx->width = width;
this->m_encode_pCodecCtx->height = height;
this->m_encode_pCodecCtx->time_base.num = 1;
this->m_encode_pCodecCtx->time_base.den = fps;
this->m_encode_pCodecCtx->framerate.num = fps;
this->m_encode_pCodecCtx->framerate.den = 1;
this->m_encode_pCodecCtx->gop_size = 10;
av_dict_set(&output_param, "preset", "veryfast", 0);
// av_dict_set(&output_param, "preset", "ultrafast", 0);
av_dict_set(&output_param, "tune", "zerolatency", 0);
av_dict_set(&output_param, "profile", "Main", 0);
这里首先尝试软编,性能无法稳定60帧,不太清楚远控软件144帧是怎么达到的,难道不需要编码压缩直接传?
首先还是需要解决渲染性能问题。