在 Windows 上捕获和显示实时摄像头内容

如何解决在 Windows 上捕获和显示实时摄像头内容

我正在开发一个 Windows 应用程序,该应用程序能够显示高质量的视频源、录制视频或从中拍摄照片,并在以后进行编辑(最高可达 4K,在不久的将来可能会达到 8K)。 我目前有一个工作产品,使用 WPF (C#)。为了捕获和显示视频,我使用了 AForge.NET 库。

我的问题是应用程序真的很慢,主要的性能影响来自视频渲染。显然,唯一的方法是从 AForge 库中进行回调,每次可用时提供一个新框架。然后将该框架作为图像放置在 Image 元素内。我相信您可以看到性能下降的来源,尤其是高分辨率图像。

我使用 WPF 和这些庞大的库的经验让我重新思考了我想如何编程;我不想制作因为速度慢而占用每个人时间的糟糕软件(我参考手工制作网络了解更多“为什么?”。

问题是,在 WPF C# 中,相机捕获和显示是地狱,但我似乎没有比其他任何地方更好(在 Windows 上)。我的一个选择是主要使用 C++ 和 DirectShow。这是一个不错的解决方案,但在性能方面感觉已经过时,并且建立在 Microsoft 的 COM 系统上,我更愿意避免使用它。可以选择使用 Direct3D 使用硬件进行渲染,但 DirectShow 和 Direct3D 不能很好地配合使用。

我研究了其他应用程序是如何实现这一点的。 VLC 使用 DirectShow,但这只能说明 DirectShow 存在较大的延迟。我认为这是因为 VLC 并非用于实时目的。 OBS 工作室使用 QT 使用的任何东西,但我无法找到他们是如何做到的。 OpenCV 抓取帧并将它们传送到屏幕上,根本没有效率,但这对于 OpenCV 观众来说已经足够了。 最后,来自 Windows 的集成网络摄像头应用程序。 出于某种原因,这个应用程序能够实时记录和回放,而不会对性能造成很大的影响。我无法弄清楚他们是如何做到这一点的,我也没有找到任何其他解决方案可以实现与该工具相当的结果。

TLDR; 所以我的问题是:我将如何有效地捕获和渲染相机流,最好是硬件加速;是否可以在不通过 Directshow 的情况下在 Windows 上执行此操作?最后,当我希望它们实时处理 4K 素材时,我是否会要求很多商品设备?

我没有发现任何人以足以满足我需求的方式这样做;这让我同时感到绝望和内疚。我宁愿不要因为这个问题打扰 StackOverflow。

非常感谢您提供有关此主题的一般性回答或建议。

解决方法

这是一个完整的可重现示例代码,它使用 GDI+ 进行渲染并使用 MediaFoundation 捕获视频。它应该在 Visual Studio 上开箱即用,并且由于使用 unique_ptr 和 CComPtr 的自动内存管理,不应该有任何类型的内存泄漏。此外,您的相机将使用此代码输出其默认视频格式。如果需要,您始终可以使用以下内容设置视频格式:https://docs.microsoft.com/en-us/windows/win32/medfound/how-to-set-the-video-capture-format

#include <windows.h>
#include <mfapi.h>
#include <iostream>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <dshow.h>
#include <dvdmedia.h>
#include <gdiplus.h>
#include <atlbase.h>
#include <thread>
#include <vector>

#pragma comment(lib,"mfplat")
#pragma comment(lib,"mf")
#pragma comment(lib,"mfreadwrite")
#pragma comment(lib,"mfuuid")
#pragma comment(lib,"gdiplus")

void BackgroundRecording(HWND hWnd,CComPtr<IMFSourceReader> pReader,int videoWidth,int videoHeight) {
    DWORD streamIndex,flags;
    LONGLONG llTimeStamp;

    Gdiplus::PixelFormat pixelFormat = PixelFormat24bppRGB;
    Gdiplus::Graphics* g = Gdiplus::Graphics::FromHWND(hWnd,FALSE);

    while (true) {
        CComPtr<IMFSample> pSample;

        HRESULT hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,&streamIndex,&flags,&llTimeStamp,&pSample);
        if (!FAILED(hr)) {
            if (pSample != NULL) {
                CComPtr<IMFMediaBuffer> pBuffer;
                hr = pSample->ConvertToContiguousBuffer(&pBuffer);
                if (!FAILED(hr)) {
                    DWORD length;
                    hr = pBuffer->GetCurrentLength(&length);
                    if (!FAILED(hr)) {
                        unsigned char* data;
                        hr = pBuffer->Lock(&data,NULL,&length);
                        if (!FAILED(hr)) {
                            std::unique_ptr<unsigned char[]> reversedData(new unsigned char[length]);
                            int counter = length - 1;
                            for (int i = 0; i < length; i += 3) {
                                reversedData[i] = data[counter - 2];
                                reversedData[i + 1] = data[counter - 1];
                                reversedData[i + 2] = data[counter];
                                counter -= 3;
                            }
                            std::unique_ptr<Gdiplus::Bitmap> bitmap(new Gdiplus::Bitmap(videoWidth,videoHeight,3 * videoWidth,pixelFormat,reversedData.get()));
                            g->DrawImage(bitmap.get(),0);
                        }
                    }
                }
            }
        }
    }
}

LRESULT CALLBACK WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd,&ps);
        FillRect(hdc,&ps.rcPaint,(HBRUSH)(COLOR_WINDOW + 1));
        EndPaint(hwnd,&ps);
    }
    break;
    case WM_CLOSE:
    {
        DestroyWindow(hwnd);
    }
    break;
    case WM_DESTROY:
    {
        PostQuitMessage(0);
    }
    break;
    default:
        return DefWindowProc(hwnd,uMsg,wParam,lParam);
        break;
    }
}

int WINAPI wWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PWSTR pCmdLine,int nCmdShow) {
    HRESULT hr = MFStartup(MF_VERSION);

    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken,&gdiplusStartupInput,NULL);

    CComPtr<IMFSourceReader> pReader = NULL;
    CComPtr<IMFMediaSource> pSource = NULL;
    CComPtr<IMFAttributes> pConfig = NULL;
    IMFActivate** ppDevices = NULL;

    hr = MFCreateAttributes(&pConfig,1);
    if (FAILED(hr)) {
        std::cout << "Failed to create attribute store" << std::endl;
    }

    hr = pConfig->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
    if (FAILED(hr)) {
        std::cout << "Failed to request capture devices" << std::endl;
    }

    UINT32 count = 0;
    hr = MFEnumDeviceSources(pConfig,&ppDevices,&count);
    if (FAILED(hr)) {
        std::cout << "Failed to enumerate capture devices" << std::endl;
    }

    hr = ppDevices[0]->ActivateObject(IID_PPV_ARGS(&pSource));
    if (FAILED(hr)) {
        std::cout << "Failed to connect camera to source" << std::endl;
    }

    hr = MFCreateSourceReaderFromMediaSource(pSource,pConfig,&pReader);
    if (FAILED(hr)) {
        std::cout << "Failed to create source reader" << std::endl;
    }

    for (unsigned int i = 0; i < count; i++) {
        ppDevices[i]->Release();
    }
    CoTaskMemFree(ppDevices);

    CComPtr<IMFMediaType> pType = NULL;
    DWORD dwMediaTypeIndex = 0;
    DWORD dwStreamIndex = 0;
    hr = pReader->GetNativeMediaType(dwStreamIndex,dwMediaTypeIndex,&pType);
    LPVOID representation;
    pType->GetRepresentation(AM_MEDIA_TYPE_REPRESENTATION,&representation);
    GUID subType = ((AM_MEDIA_TYPE*)representation)->subtype;
    BYTE* pbFormat = ((AM_MEDIA_TYPE*)representation)->pbFormat;
    GUID formatType = ((AM_MEDIA_TYPE*)representation)->formattype;
    int videoWidth = ((VIDEOINFOHEADER2*)pbFormat)->bmiHeader.biWidth;
    int videoHeight = ((VIDEOINFOHEADER2*)pbFormat)->bmiHeader.biHeight;

    WNDCLASS wc = { };
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = L"Window";
    RegisterClass(&wc);
    HWND hWnd = CreateWindowExW(NULL,L"Window",WS_OVERLAPPEDWINDOW,videoWidth,hInstance,NULL);
    ShowWindow(hWnd,nCmdShow);

    std::thread th(BackgroundRecording,hWnd,pReader,videoHeight);
    th.detach();

    MSG msg = { };
    while (GetMessage(&msg,0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    pSource->Shutdown();
    Gdiplus::GdiplusShutdown(gdiplusToken);
    return 0;
}
,

您的问题是关于多种技术的组合:视频捕获、视频演示以及将两者连接在一起需要什么。

在 Windows 上有两个与视频相关的 API(如果我们不考虑古老的 VfW):DirectShow 和 Media Foundation。这两个 API 都有底层,它们大多是共享的,因此 DirectShow 和 Media Foundation 都提供类似的视频捕获功能和性能。这两个 API 都为您提供了良好的视频捕获延迟,而且相当低。就目前情况而言,不建议使用 DirectShow,因为该 API 已接近其生命周期,并且大部分已被废弃。同时,您可能会发现 DirectShow 的文档更完善、功能更丰富,并且提供了数量级更好的补充材料和第三方软件项目。您提到了一些库,它们都建立在上述技术之一(VfW、DirectShow、Media Foundation)之上,其实现质量不如原始操作系统 API。

实际上,您可以使用两者之一来捕捉视频,最好使用 Media Foundation 作为当前技术。

在我看来,您问题中最重要的部分是如何组织视频渲染。在性能方面,利用硬件加速至关重要,在这种情况下,您的应用程序所构建的技术以及视频演示/嵌入的可用集成选项很重要。对于 .NET 桌面应用程序,您可能对将 Direct3D 11/12 与 .NET 混合或使用 MediaPlayerElement 控件以及研究如何将视频帧注入其中感兴趣。如上所述,即使第三方库可用,您也不应该期望它们以适当的方式解决问题。您有兴趣至少了解视频管道中的数据流。

那么你有一个问题,如何连接视频捕获(不是由视频硬件加速)和硬件加速的视频渲染。这里可以有多种解决方案,但重要的是 DirectShow 对硬件加速的支持是有限的,并且在 Direct3D 9 中停止了它的发展,现在听起来已经过时了。这是告别这项 - 毫无疑问 - 优秀技术的另一个原因。您有兴趣研究将捕获的视频内容尽快放入 Direct3D 11/Direct3D 12/Direct2D 的选项,并利用当前的标准技术进行以下处理。实际技术可能取决于:它可以是 Media Foundation、Direct3D 11/12 或提到的 MediaPlayerElement 控件,以及一些其他选项,如 Direct2D,它们也不错。在获得出色或至少合理的性能的过程中,您有兴趣尽量减少使用第三方库(即使它们很受欢迎),即使它们的标题中有流行语。

可以实时捕获和处理 4K 实时镜头,但是您通常拥有专业的视频捕获硬件或压缩的内容,您应该使用硬件加速进行解压缩。

,

我发现我目前拥有的软件似乎是唯一的解决方案。

为了提高性能,我当然需要硬件加速,但问题是与 WPF 兼容的选项并不多。我发现 Direct3D9 可以完成这项工作,尽管已经过时了。可以在 D3D11 或更远的地方做所有事情并与 D3D9 表面共享结果,但我选择一直使用 D3D9。

为了捕获自身,我现在使用 MediaFoundation 本身而不是 DirectShow 或任何捕获库。这似乎运行良好,并且可以轻松访问音频和视频组合。

回调收集视频帧并将它们写入 D3D9 纹理,该纹理又用作像素着色器(= 片段着色器)的输入,并渲染为矩形。这样做的原因是为了能够从相机的原生 NV12 格式(其他格式也可以)进行格式转换。

如果有人对如何更详细地完成此操作感兴趣,请随时在此答案的评论中提问。这可以节省很多时间:)

TL;博士: WPF 仅允许 D3D9 内容,我使用 MediaFoundation 进行捕获。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res