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

标签 windows video-capture directshow hardware-acceleration video-recording

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

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

我使用 WPF 和这些庞大的库的经验让我重新思考我想要如何进行一般的编程;我不想制作糟糕的软件,因为速度慢而占用每个人的时间(我引用 Handmade 网络 了解更多关于“为什么?”的信息。

问题是,摄像头捕获和显示在 WPF C# 中非常糟糕,但我似乎并没有比其他任何地方(在 Windows 上)更好。我可以选择主要使用 C++ 和 DirectShow。这是一个不错的解决方案,但在性能方面感觉已经过时,并且是建立在微软的 COM 系统之上的,我宁愿避免这种情况。有一些选项可以使用 Direct3D 使用硬件进行渲染,但 DirectShow 和 Direct3D 不能很好地协同工作。

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

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

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

非常感谢您就此主题的一般性回答或建议。

最佳答案

这是一个完整的可重现示例代码,它使用 GDI+ 进行渲染并使用 MediaFoundation 捕获视频。它应该在 visual studio 上开箱即用,并且不应由于使用 unique_ptr 和 CComPtr 的自动内存管理而出现任何类型的内存泄漏。此外,您的相机将使用此代码输出其默认视频格式。如果需要,您始终可以使用以下设置视频格式:https://learn.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, 0, &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, 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", L"Window", WS_OVERLAPPEDWINDOW, 0, 0, videoWidth, videoHeight, NULL, NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);

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

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

关于windows - 在 Windows 上捕获和显示实时摄像头内容,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66140642/

相关文章:

ruby - 如何在 Windows 控制台中禁用标准输入回显

python - 将多个视频中的帧提取到多个文件夹中

opencv - 逐帧组合两个不同长度的视频,使它们出现在一帧中,有点像视频平铺

c# - 我怎样才能学会 DirectShow 编程?

c++ - 使用基于 DirectShow 的虚拟相机和 Electron 框架来渲染 <div> 元素的内容

c++ - FindFirstFile 问题无法让任何示例正常工作。

windows - 在 Windows 中,如何找出哪个进程位于本地网络套接字的另一端?

c - 验证签名/证书的最佳方法是什么?

ubuntu - Linux 中的视频编程与 DirectShow 相比如何?

c++ - 如何使用 DirectShow 和网络摄像头预览图像