具有音频混合功能的 DirectX 硬件屏幕捕获和编码。 H264/H265/VP80/VP90/FLAC/MP3。

以硬件方式捕获视频和屏幕截图。

介绍

有很多关于它的东西。 这是一个简单的单头文件,硬件加速。 如果使用 Windows 8 或更高版本,您可以轻松地将其包含在您的项目中。

要求

Windows 8 或更高版本。

视频截取

我们需要在 DXGI 的帮助下枚举我们的适配器和监视器的数量:

static void GetAdapters(std::vector<CComPtr<IDXGIAdapter1>>& a)
{CComPtr<IDXGIFactory1> df;CreateDXGIFactory1(__uuidof(IDXGIFactory1),(void**)&df);a.clear();if (!df)return;int L = 0;for (;;){CComPtr<IDXGIAdapter1> lDxgiAdapter;df->EnumAdapters1(L, &lDxgiAdapter);if (!lDxgiAdapter)break;L++;a.push_back(lDxgiAdapter);}
}

然后,我们将使用其中一个或默认值来实例化 DirectX 11 设备:

HRESULT CreateDirect3DDevice(IDXGIAdapter1* g)
{HRESULT hr = S_OK;// 支持的驱动程序类型D3D_DRIVER_TYPE DriverTypes[] ={D3D_DRIVER_TYPE_HARDWARE,D3D_DRIVER_TYPE_WARP,D3D_DRIVER_TYPE_REFERENCE,};UINT NumDriverTypes = ARRAYSIZE(DriverTypes);// 支持的功能级别D3D_FEATURE_LEVEL FeatureLevels[] ={D3D_FEATURE_LEVEL_11_0,D3D_FEATURE_LEVEL_10_1,D3D_FEATURE_LEVEL_10_0,D3D_FEATURE_LEVEL_9_3,D3D_FEATURE_LEVEL_9_2,D3D_FEATURE_LEVEL_9_1};UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels);D3D_FEATURE_LEVEL FeatureLevel;// 创建设备for (UINT DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex){hr = D3D11CreateDevice(g, DriverTypes[DriverTypeIndex], nullptr, D3D11_CREATE_DEVICE_VIDEO_SUPPORT, FeatureLevels, NumFeatureLevels,D3D11_SDK_VERSION, &device, &FeatureLevel, &context);if (SUCCEEDED(hr)){// 设备创建成功,无需循环break;}}if (FAILED(hr))return hr;return S_OK;
}

我们要创建输出的桌面副本,然后:

bool Prepare(UINT Output = 0)
{// 获取 DXGI 设备CComPtr<IDXGIDevice> lDxgiDevice;lDxgiDevice = device;if (!lDxgiDevice)return 0;// 获取 DXGI 适配器CComPtr<IDXGIAdapter> lDxgiAdapter;auto hr = lDxgiDevice->GetParent(__uuidof(IDXGIAdapter),reinterpret_cast<void**>(&lDxgiAdapter));if (FAILED(hr))return 0;lDxgiDevice = 0;// 获取输出CComPtr<IDXGIOutput> lDxgiOutput;hr = lDxgiAdapter->EnumOutputs(Output, &lDxgiOutput);if (FAILED(hr))return 0;lDxgiAdapter = 0;DXGI_OUTPUT_DESC lOutputDesc;hr = lDxgiOutput->GetDesc(&lOutputDesc);// QI for Output 1CComPtr<IDXGIOutput1> lDxgiOutput1;lDxgiOutput1 = lDxgiOutput;if (!lDxgiOutput1)return 0;lDxgiOutput = 0;// 创建桌面副本hr = lDxgiOutput1->DuplicateOutput(device,&lDeskDupl);if (FAILED(hr))return 0;lDxgiOutput1 = 0;// 创建 GUI 绘图纹理lDeskDupl->GetDesc(&lOutputDuplDesc);D3D11_TEXTURE2D_DESC desc = {};desc.Width = lOutputDuplDesc.ModeDesc.Width;desc.Height = lOutputDuplDesc.ModeDesc.Height;desc.Format = lOutputDuplDesc.ModeDesc.Format;desc.ArraySize = 1;desc.BindFlags = D3D11_BIND_FLAG::D3D11_BIND_RENDER_TARGET;desc.MiscFlags = D3D11_RESOURCE_MISC_GDI_COMPATIBLE;desc.SampleDesc.Count = 1;desc.SampleDesc.Quality = 0;desc.MipLevels = 1;desc.CPUAccessFlags = 0;desc.Usage = D3D11_USAGE_DEFAULT;hr = device->CreateTexture2D(&desc, NULL, &lGDIImage);if (FAILED(hr))return 0;if (lGDIImage == nullptr)return 0;// 创建 CPU 访问纹理desc.Width = lOutputDuplDesc.ModeDesc.Width;desc.Height = lOutputDuplDesc.ModeDesc.Height;desc.Format = lOutputDuplDesc.ModeDesc.Format;desc.ArraySize = 1;desc.BindFlags = 0;desc.MiscFlags = 0;desc.SampleDesc.Count = 1;desc.SampleDesc.Quality = 0;desc.MipLevels = 1;desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;desc.Usage = D3D11_USAGE_STAGING;hr = device->CreateTexture2D(&desc, NULL, &lDestImage);if (FAILED(hr))return 0;if (lDestImage == nullptr)return 0;return 1;
}

要获取屏幕截图,我们循环:

hr = cap.lDeskDupl->AcquireNextFrame(0,&lFrameInfo,&lDesktopResource);
if (hr == DXGI_ERROR_WAIT_TIMEOUT)hr = S_OK;
if (FAILED(hr))break;
if (lDesktopResource && !cap.Get(lDesktopResource, dp.Cursor, dp.rx.right && dp.rx.bottom ? &dp.rx : 0))break;

get() 方法将返回位图,可选地包含和裁剪的光标:

bool Get(IDXGIResource* lDesktopResource,bool Curs,RECT* rcx = 0)
{// ID3D11Texture2D 的 QICComPtr<ID3D11Texture2D> lAcquiredDesktopImage;if (!lDesktopResource)return 0;auto hr = lDesktopResource->QueryInterface(IID_PPV_ARGS(&lAcquiredDesktopImage));if (!lAcquiredDesktopImage)return 0;lDesktopResource = 0;// 将图像复制到 GDI 绘图纹理中context->CopyResource(lGDIImage, lAcquiredDesktopImage);// 将光标图像绘制到 GDI 绘图纹理中CComPtr<IDXGISurface1> lIDXGISurface1;lIDXGISurface1 = lGDIImage;if (!lIDXGISurface1)return 0;CURSORINFO lCursorInfo = { 0 };lCursorInfo.cbSize = sizeof(lCursorInfo);auto lBoolres = GetCursorInfo(&lCursorInfo);if (lBoolres == TRUE){if (lCursorInfo.flags == CURSOR_SHOWING && Curs){auto lCursorPosition = lCursorInfo.ptScreenPos;
//                auto lCursorSize = lCursorInfo.cbSize;HDC  lHDC;lIDXGISurface1->GetDC(FALSE, &lHDC);DrawIconEx(lHDC,lCursorPosition.x,lCursorPosition.y,lCursorInfo.hCursor,0,0,0,0,DI_NORMAL | DI_DEFAULTSIZE);lIDXGISurface1->ReleaseDC(nullptr);}}// 将图像复制到 CPU 访问纹理中context->CopyResource(lDestImage, lGDIImage);// Copy from CPU access texture to bitmap bufferD3D11_MAPPED_SUBRESOURCE resource;UINT subresource = D3D11CalcSubresource(0, 0, 0);hr = context->Map(lDestImage, subresource, D3D11_MAP_READ_WRITE, 0, &resource);if (FAILED(hr))return 0;auto sz = lOutputDuplDesc.ModeDesc.Width* lOutputDuplDesc.ModeDesc.Height * 4;auto sz2 = sz;buf.resize(sz);if (rcx){sz2 = (rcx->right - rcx->left) * (rcx->bottom - rcx->top) * 4;buf.resize(sz2);sz = sz2;}UINT lBmpRowPitch = lOutputDuplDesc.ModeDesc.Width * 4;if (rcx)lBmpRowPitch = (rcx->right - rcx->left) * 4;UINT lRowPitch = std::min<UINT>(lBmpRowPitch, resource.RowPitch);BYTE* sptr = reinterpret_cast<BYTE*>(resource.pData);BYTE* dptr = buf.data() + sz - lBmpRowPitch;if (rcx)sptr += rcx->left * 4;for (size_t h = 0; h < lOutputDuplDesc.ModeDesc.Height; ++h){if (rcx && h < (size_t)rcx->top){sptr += resource.RowPitch;continue;}if (rcx && h >= (size_t)rcx->bottom)break;memcpy_s(dptr, lBmpRowPitch, sptr, lRowPitch);sptr += resource.RowPitch;dptr -= lBmpRowPitch;}context->Unmap(lDestImage, subresource);return 1;
}

之后,您可以将“buf”数据输入媒体基金会的接收器写入器。

音频捕捉

您将使用 IAudioClient 获取 IAudioCaptureClient 以在单独的线程中录制音频。

void ThreadLoopCapture()
{UINT64 up, uq;while (Capturing){if (hEv)WaitForSingleObject(hEv, INFINITE);if (!Capturing)break;auto hr = cap->GetBuffer(&pData, &framesAvailable, &flags, &up, &uq);if (FAILED(hr))break;if (framesAvailable == 0)continue;auto ThisAudioBytes = framesAvailable * wfx.Format.nChannels * wfx.Format.wBitsPerSample/8 ;AudioDataX->PushX((const char*)pData, ThisAudioBytes);cap->ReleaseBuffer(framesAvailable);}CapturingFin1 = true;
}

如果录音设备是通过 loopback 的播放设备,则必须确保播放了某些内容,否则 Core Audio API 什么也不记录。 所以我们必须玩沉默:

void PlaySilence(REFERENCE_TIME rt)
{// nsrt /= 10000;// in SR , 1000 ms//  ?    , rt msauto ns = (wfx.Format.nSamplesPerSec * rt);ns /= 1000;while (Capturing){if (!ren)break;Sleep((DWORD)(rt / 2));if (!Capturing)break;// 查看有多少可用的缓冲区空间。UINT32 numFramesPadding = 0;auto hr = ac2->GetCurrentPadding(&numFramesPadding);if (FAILED(hr))break;auto numFramesAvailable = ns - numFramesPadding;if (!numFramesAvailable)continue;BYTE* db = 0;hr = ren->GetBuffer((UINT32)numFramesAvailable, &db);if (FAILED(hr))break;auto bs = numFramesAvailable * wfx.Format.nChannels * wfx.Format.wBitsPerSample / 8;memset(db, 0,(size_t) bs);ren->ReleaseBuffer((UINT32)numFramesAvailable, 0); //AUDCLNT_BUFFERFLAGS_SILENT}CapturingFin2 = true;
}

当有许多音频流时,您必须将它们混合在一个缓冲区中。 这是使用我自己的 REBUFFER 和 MIXBUFFER 完成的:

struct REBUFFER
{std::recursive_mutex m;std::vector<char> d;AHANDLE Has = CreateEvent(0, TRUE, 0, 0);MIXBUFFER<float> mb;void FinMix(size_t sz, float* A = 0){mb.Fin(sz / sizeof(float), A);}size_t PushX(const char* dd, size_t sz, float* A = 0, float V = 1.0f){REBUFFERLOCK l(m);auto s = d.size();d.resize(s + sz);if (dd)memcpy(d.data() + s, dd, sz);elsememset(d.data() + s, 0, sz);char* a1 = d.data();a1 += s;mb.Set((float*)a1);mb.count = 1;SetEvent(Has);float* b = (float*)(d.data() + s);if (V > 1.01f || V < 0.99f){auto st = sz / sizeof(float);for (size_t i = 0; i < st; i++)b[i] *= V;}if (A){*A = Peak<float>(b, sz / sizeof(float));}return s + sz;}size_t Av(){REBUFFERLOCK l(m);return d.size();}size_t PopX(char* trg, size_t sz, DWORD wi = 0, bool NR = false){if (wi)WaitForSingleObject(Has, wi);REBUFFERLOCK l(m);if (sz >= d.size())sz = d.size();if (sz == 0)return 0;if (trg)memcpy(trg, d.data(), sz);if (NR == false)d.erase(d.begin(), d.begin() + sz);if (d.size() == 0)ResetEvent(Has);return sz;}void Clear(){REBUFFERLOCK l(m);d.clear();}
};

如果您有音频,则视频会与之同步。

使用库

#include "stdafx.h"
#include "capture.hpp"
#include <iostream>int wmain()
{CoInitializeEx(0, COINIT_APARTMENTTHREADED);MFStartup(MF_VERSION);std::cout << "Capturing screen for 10 seconds...";DESKTOPCAPTUREPARAMS dp;dp.f = L"capture.mp4";dp.EndMS = 10000;DesktopCapture(dp);std::cout << "Done.\r\n";return 0;
}

DESKTOPCAPTUREPARAMS 的定义如下:

struct DESKTOPCAPTUREPARAMS
{bool HasVideo = 1;bool HasAudio = 1;std::vector<std::tuple<std::wstring, std::vector<int>>> AudioFrom;GUID VIDEO_ENCODING_FORMAT = MFVideoFormat_H264;GUID AUDIO_ENCODING_FORMAT = MFAudioFormat_MP3;std::wstring f;void* cb = 0;std::function<HRESULT(const BYTE* d, size_t sz,void* cb)> Streamer;std::function<HRESULT(const BYTE* d, size_t sz,void* cb)> Framer;std::function<void(IMFAttributes* a)> PrepareAttributes;int fps = 25;int NumThreads = 0;int Qu = -1;int vbrm = 0;int vbrq = 0;int BR = 4000;int NCH = 2;int SR = 44100;int ABR = 192;bool Cursor = true;RECT rx = { 0,0,0,0 };HWND hWnd = 0;IDXGIAdapter1* ad = 0;UINT nOutput = 0;unsigned long long StartMS = 0; // 0, noneunsigned long long EndMS = 0; // 0, nonebool MustEnd = false;bool Pause = false;
};

说明:

HasVideo = 1 -> 您正在捕捉视频。如果设置了此项,则无论您是否有音频,输出文件都必须是 MP4 或 ASF。
HasAudio = 1 -> 您正在捕获音频。如果已设置并且您没有视频,则输出文件必须是 MP3 或 FLAC。
AudioFrom = 要捕获的音频设备的向量。每个元素都是设备唯一 ID 的元组(由枚举返回,请参阅 VISTAMIXERS::EnumVistaMixers())和您要记录的通道的向量。
该库还可以在环回中从播放设备(例如您的扬声器)进行录制。您可以指定多个录制源,库会将它们全部混合到最终的音频流中。

VIDEO_ENCODING_FORMAT -> MFVideoFormat_H264、MFVideoFormat_HEVC、MFVideoFormat_VP90、MFVideoFormat_VP80 之一。
AUDIO_ENCODING_FORMAT -> MFAudioFormat_MP3 或 MFAudioFormat_FLAC 或 MFAudioFormat_AAC 之一。 MP3 和 AAC 仅支持 44100/48000 2 通道输出。
f -> 目标文件名(MP3/FLAC 仅用于音频,MP4/ASF 其他)
fps -> 每秒帧数
NumThreads -> 视频编码器的线程,默认为 0。可以是 0-16。
Qu -> 如果 >= 0 且 <= 0,质量与速度的视频因素
vbrm 和 vbrq -> 如果为 2,则 vbrq 是介于 0 和 100 之间的质量值(BR 被忽略)
BR -> 以 KBps 为单位的视频比特率,默认 4000。如果 vbrm 为 2,则忽略 BR
NCH -> 音频输出通道
SR -> 音频输出采样率
ABR -> MP3 的音频比特率(Kbps)
Cursor -> true 捕获光标
rx -> 如果不是 {0},则仅捕获此特定矩形
hWnd -> 如果不是 {0},则仅捕获此 HWND。如果 HWND 为 0 且 rx = {0},则捕获整个屏幕
ad -> 如果不是 0,则指定如果您有超过 1 个适配器,您要捕获哪个适配器
nOutput -> 要捕获的监视器的索引。 0 是第一个监视器。对于多个监视器,这指定了监视器。
EndMS -> 如果不为 0,则库在 EndMs 毫秒被捕获时停止。否则,您必须通过将“MustEnd”设置为 true 来停止库。
MustEnd -> 设置为 true 以使库停止捕获
暂停 -> 如果为真,则暂停捕获
如果要捕获到缓冲区,则必须将“f”参数留空并使用 Streamer 参数。只要您返回 S_OK,这就会调用您的回调。如果您使用 ASF 容器,则无需执行任何操作。如果您想使用 MP4 流,则必须准备流示例描述(请参阅此帖子)。您可以使用它通过 HTTP 流式传输您的桌面。

捕获帧

您可以使用“Framer”回调,而不是捕获压缩视频。 只要您返回 S_FALSE,这将返回您请求的分辨率的原始 RGBA 倒置数组。 一旦你返回 S_OK,函数就会返回。

ScreenCapture:通过DirectX 库进行屏幕捕获相关推荐

  1. Spring 实现屏幕捕获-屏幕共享

    Spring 实现屏幕捕获-屏幕共享 使用服务端推送技术SSE+屏幕截屏,实现一个简单的屏幕共享功能 SseEmitter 实现服务端推送功能 java.awt.Toolkit 获取屏幕截屏 屏幕截屏 ...

  2. java robot 控制 不用用户 界面_编写一个基于Java Robot类的屏幕捕获工具

    Fun and Games(娱乐和游戏)提供了通过Java的Robot类捕获主屏幕设备的功能,并且可以将整个屏幕或者选定的一部分保存为jpeg文件.这篇文章以Swing应用的形式实现了屏幕捕获工具. ...

  3. VC++ 屏幕捕获(DirectDraw)

    1.初始化DirectDraw接口 #include <ddraw.h> LPDIRECTDRAW m_lpDDraw; LPDIRECTDRAWSURFACE m_lpDDSPrime; ...

  4. MindMapper屏幕捕获功能该如何使用

    MindMapper屏幕捕获功能可以让您创建主题时事半功倍,截取的图片可以作为分支添加到导图中,大大的节省添加图片的操作.下面,小编就这个功能,和大家共享运用时的一些小秘诀. 在MindMapper中 ...

  5. 物联网国赛LORA模块开发基础教程(通用库)—OLED屏幕

    LORA模块开发基础教程目录 物联网国赛LORA模块开发基础教程-开发环境配置 物联网国赛LORA模块开发基础教程-输出(LED) 物联网国赛LORA模块开发基础教程-输入(按键) 物联网国赛LORA ...

  6. Bytescout屏幕捕获,SDK ActiveX的主要功能

    Bytescout屏幕捕获,SDK ActiveX的主要功能 如果您构建的应用程序旨在将屏幕动作记录到WMV,AVI电影文件中,请利用Bytescout屏幕捕捉SDK向您的程序快速添加显示视频记录功能 ...

  7. VC++屏幕捕获并保存成图片(附源码)

    目录 1.屏幕捕获(截取桌面) 2.将内存中的位图保存成图片文件 3.完整功能的屏幕截图

  8. C# DirectX.DirectShow-音视频播放或捕获

    英文原文:https://msdn.microsoft.com/en-us/library/windows/desktop/dd375454(v=vs.85).aspx Microsoft Direc ...

  9. VB中使用DirectX库的简明教程(3)

    二. Direct3D Direct3D类对象可以说是DirectX下最重要的同时也是最复杂的对象集合.基本说来,Direct3D可以分为立即模式( Immediate Mode)和保留模式(Reta ...

最新文章

  1. vue中一个组件导入另一个组件
  2. 最优二叉树(哈夫曼树)知识点
  3. mariadb 和mysql主从_MariaDB主从同步
  4. 用MOS管防止电源反接的原理
  5. YbtOJ#20239-[冲刺NOIP2020模拟赛Day10]连边方案【状压dp】
  6. MATLAB凸包Convex hull运算
  7. mxnet img2rec的使用,生成数据文件
  8. Zend Studio 实用快捷键一览表
  9. 群签名和环签名的区别_苹果企业签名和苹果超级签名的区别
  10. oracle 消除块竞争(hot blocks)
  11. excel模板 基金账本_专项基金拨款记录EXCEL图表
  12. 路径规划:RRT算法在ROS中的实现
  13. 灵格斯怎么屏幕取词_灵格斯词霸怎么用?灵格斯词霸使用手册
  14. 水中机器人电控方案设计
  15. 2021最新Java JDK1.8的安装教程
  16. html关于圣诞节主题的网页,灵感: 8个以圣诞节为主题的网站欣赏
  17. 使用usb tplink无线网卡搭建无线热点AP
  18. 计算机在投资审计中应用方法,计算机辅助审计技术在投资审计中的应用研究(原稿)_0...
  19. centos7安装python3.7.4_基于centos7 安装python3.6.4出错的解决方法
  20. 如何在shell脚本中定义数组及遍历

热门文章

  1. allegro 输出gerber文件
  2. 载誉前行 创新发展 | 拓保软件三项技术获评“深圳企业创新纪录”
  3. 高新技术企业认定的价值
  4. 成为一流软件开发者的 34 条建议
  5. 音影系统linux,搭建基于linux桌面环境的影音平台_linux教程
  6. 前端工程化之模块化基础
  7. 求内切圆的圆心和半径(已知三个点的坐标)
  8. win10 服务器远程连接数量限制修改
  9. 动力电池系统介绍(十)——电压采样
  10. 一步步学习Python----10