【轻量化】在WIN下改进MFCapture对USB摄像头进行读取

83 阅读4分钟

关键词:MFC、 CPU、 WIN、工控机、 性能
链接:github.com/microsoft/w…

一. 引言

  近期为了解决在win工控机中(低端cpu)中的资源消耗问题,此时我想到了微软公司的开源代码库里面一顿翻箱倒柜,可算是让我找到了MFCaptureToFile。在Microsoft中提供了多种API来实现这一功能,其中之一就是Media Foundation(媒体基础)。

MFCaptureToFile简介

  MFCaptureToFile是一个官方示例项目,展示了如何使用Media Foundation来捕获音频和视频流并保存到文件中。这个示例项目是用C++编写的,并且利用了Windows平台提供的强大功能。该项目类似于在linux系统中的V4L2实现对USB摄像头的读取,对资源的消耗降低。

  下载好项目后使用使用VS进行打开,直接进行编译即可。项目文件中将各种环境配置文件都准备好了,做到了开箱即用的状态,非常棒!下面是我编译成功后得到软件属性和运行软件界面ui.

image.png image.png

改进

  由于MFCaptureToFile项目是属于win桌面应用程序,为了更好融于其它项目,这里我们需要将其改为空项目或者控制台下项目,后续将capture.cppcapture.h添加至项目源文件中,然后另创建一个mian.cpp文件进行存放改进后的代码。

  这里我设计为双击exe程序后自动保存30秒的视频文件,利用了Microsoft的Media Foundation API来进行视频捕捉。

代码流程

  1. 堆损坏保护:启用堆损坏时的终止机制,防止因堆损坏导致的应用崩溃。

  2. 初始化:初始化COM库和Media Foundation。

  3. 设备通知:注册设备通知以监听设备插入或移除事件。

  4. 设备枚举:枚举视频捕捉设备列表。

  5. 设备选择与启动

    • 如果存在设备,则选择第一个设备。
    • 创建捕捉实例,并设置输出文件名和编码参数。
    • 开始视频捕捉。
  6. 等待:让程序暂停一定的时间。

  7. 停止捕捉:结束视频捕捉会话。

  8. 资源清理

    • 释放设备激活对象和捕捉实例。
    • 清空设备列表。
    • 取消设备通知注册。
    • 关闭Media Foundation和COM库。

  通过上述步骤,代码实现了从设备列表中选取视频捕捉设备,并开始录制视频的功能。这是一个基础的视频捕捉框架,后续自己可以根据具体需求进一步扩展和完善。

main代码

#include <windows.h>
#include <windowsx.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <assert.h>
#include <strsafe.h>
#include <shlwapi.h>
#include <Dbt.h>
#include <ks.h>
#include <ksmedia.h>
#include <iostream>
// 包含自定义的头文件
#include "capture.h"

// 定义一个模板函数用于安全释放COM对象
template <class T> void SafeRelease(T** ppT)
{
    if (*ppT)
    {
        (*ppT)->Release();
        *ppT = NULL;
    }
}


// 定义目标比特率
const UINT32 TARGET_BIT_RATE = 1920 * 1080 * 3;

// 全局变量定义
DeviceList  g_devices;
CCapture* g_pCapture = NULL;
HDEVNOTIFY  g_hdevnotify = NULL;

// 应用程序的入口点
int main()
{
    // 启用堆损坏时的终止
    (void)HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);

    HRESULT hr = S_OK;

    // 初始化COM库
    hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    if (FAILED(hr))
    {
        std::cerr << "Failed to initialize COM library." << std::endl;
        return -1;
    }

    // 初始化Media Foundation
    if (SUCCEEDED(hr))
    {
        hr = MFStartup(MF_VERSION);
        if (FAILED(hr))
        {
            std::cerr << "Failed to initialize Media Foundation." << std::endl;
            CoUninitialize();
            return -1;
        }
    }

    // 注册设备通知
    DEV_BROADCAST_DEVICEINTERFACE di = { 0 };
    di.dbcc_size = sizeof(di);
    di.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
    di.dbcc_classguid = KSCATEGORY_CAPTURE;

    g_hdevnotify = RegisterDeviceNotification(NULL, &di, DEVICE_NOTIFY_WINDOW_HANDLE);
    if (g_hdevnotify == NULL)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        std::cerr << "Failed to register device notification." << std::endl;
        MFShutdown();
        CoUninitialize();
        return -1;
    }

    // 枚举视频捕获设备
    if (SUCCEEDED(hr))
    {
        hr = g_devices.EnumerateDevices();
        if (FAILED(hr))
        {
            std::cerr << "Failed to enumerate devices." << std::endl;
            UnregisterDeviceNotification(g_hdevnotify);
            MFShutdown();
            CoUninitialize();
            return -1;
        }
    }

    // 选择第一个设备并开始捕获
    if (SUCCEEDED(hr) && g_devices.Count() > 0)
    {
        IMFActivate* pActivate = NULL;
        hr = g_devices.GetDevice(0, &pActivate);
        if (SUCCEEDED(hr))
        {
            hr = CCapture::CreateInstance(NULL, &g_pCapture);
            if (SUCCEEDED(hr))
            {
                WCHAR pszFile[MAX_PATH] = L"capture.mp4";
                EncodingParameters params;
                params.subtype = MFVideoFormat_H264;
                params.bitrate = TARGET_BIT_RATE;

                hr = g_pCapture->StartCapture(pActivate, pszFile, params);
                if (FAILED(hr))
                {
                    std::cerr << "Failed to start capture." << std::endl;
                    SafeRelease(&pActivate);
                    SafeRelease(&g_pCapture);
                    UnregisterDeviceNotification(g_hdevnotify);
                    MFShutdown();
                    CoUninitialize();
                    return -1;
                }

                // 等待30秒
                Sleep(30000);

                // 停止捕获
                hr = g_pCapture->EndCaptureSession();
                if (FAILED(hr))
                {
                    std::cerr << "Failed to stop capture." << std::endl;
                }

                SafeRelease(&pActivate);
                SafeRelease(&g_pCapture);
            }
            else
            {
                std::cerr << "Failed to create capture instance." << std::endl;
                SafeRelease(&pActivate);
            }
        }
        else
        {
            std::cerr << "Failed to get device." << std::endl;
        }
    }
    else
    {
        std::cerr << "No capture devices found." << std::endl;
    }

    // 清除设备列表
    g_devices.Clear();

    // 注销设备通知
    if (g_hdevnotify)
    {
        UnregisterDeviceNotification(g_hdevnotify);
    }

    // 关闭Media Foundation
    MFShutdown();
    CoUninitialize();

    return 0;
}

生成程序详细信息: image.png

总结

  MFCaptureToFile示例为我们提供了一个简单的框架,展示了如何利用现代Windows API来处理摄像头输入。这对于那些希望在其应用程序中集成视频功能的开发者来说,是一个很好的起点。通过学习这个示例项目,开发者可以获得必要的知识来开始创建自己的视频捕获应用。如果是自己以前的套路则为opencv+qt进行解决。