赞
踩
具有音频混合功能的 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 1
- CComPtr<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 的 QI
- CComPtr<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 buffer
- D3D11_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)
- {
- // ns
- rt /= 10000;
- // in SR , 1000 ms
- // ? , rt ms
- auto 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);
- else
- memset(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, none
- unsigned long long EndMS = 0; // 0, none
- bool 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,函数就会返回。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。