使用c++/winrt API获取RGB相机视频流
1、前提条件
该示例使用c++/winrt进行开发,需要编译器支持c++17,本人使用Visual Studio2017,系统版本为Windows10 21H2,由于UWP API是从Windows10系统进行支持的,故而Windows7及Windows8等系统可能不能正常使用。
注:遇到未定义行为请先检查该API是从哪个Windows版本支持的,可能你当前的系统版本还不支持该API。
2、使用MediaCapture获取RGB相机视频的流程
- 使用FindAllAsync接口获取所有的VideoCapture设备,选择你想要的设备;
- 根据选好的设备ID及自定义配置初始化MediaCapture对象;
- 使用刚刚初始化的MediaCapture获取所有的帧源,我们这里选择RGB视频流这个帧源;
- 为选择好的MeidaFrameSource设置指定的format(width,height);
- 获取读取视频流帧对象MediaFrameReader;
- 使用MediaFrameReader读取相机的视频帧;
3、代码演示
注:演示代码中还需要依赖opencv进行nv12->bgr的转换工作,以及使用opencv进行实时显示取到的视频帧工作。
头文件
//MediaFrameCapture.h
#pragma once
#ifdef _WIN32
// winrt
#include <mfapi.h>
#include <mfidl.h>
#include <winrt/base.h>
#include <winrt/Windows.Media.Core.h>
#include <winrt/Windows.Media.Devices.h>
#include <winrt/Windows.Media.Capture.h>
#include <winrt/Windows.Media.Capture.Frames.h>
#include <winrt/Windows.Media.Mediaproperties.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Windows.Devices.Enumeration.h>
#include <winrt/Windows.Graphics.h>
#include <winrt/Windows.Graphics.Imaging.h>
using namespace winrt;
#include "opencv2/opencv.hpp"
// std
#include <vector>
class MediaFrameCapture
{
public:
MediaFrameCapture();
~MediaFrameCapture();
/**
* @brief 获取已连接的设备数
*
* @return 返回已连接的mipi RGB的设备数量
* @note
*/
int listDevices();
/**
* @brief 更新已连接mipi RGB相机列表
*
* @return void
* @note
*/
void updateListDevices();
/**
* @brief 连接指定deviceId的设备
*
* 通过deviceId获取指定设备并初始MediaCapture对象
*
* @param[in] deviceId RGB设备的PID,通过该deviceId查询设备是否存在
* @return 返回设置是否被正确的打开和初始化
* @note
*/
bool setupDevice(const std::string& deviceId);
/**
* @brief 连接指定deviceId的设备并指定帧的分辨率
*
* 通过deviceId获取指定设备并初始MediaCapture对象;指定帧的分辨率,若指定的大小格式不支持则失败
*
* @param[in] deviceId RGB设备的PID,通过该deviceId查询设备是否存在
* @param[in] width 分辨率宽
* @param[in] height 分辨率高
* @return 返回设置是否被正确的打开和初始化
* @note
*/
bool setupDevice(const std::string& deviceId, int width, int height);
bool startCapture();
bool stopCapture();
/**
* @brief 提供给外部调用返回读取到的新帧
*
*
* @param[out] oneFrame 返回读取到的新帧
* @return false没有获取到新帧,true获取到新帧
* @note
*/
bool read(cv::Mat&);
/**
* @brief 用于通过设备PID号选择指定的设备,初始化m_selectedDevice字段
*
*
* @param[in] deviceId 设备的PID号
* @return false指定的设备不存在,获取失败;true指定的设备存在
* @note
*/
bool selectDeviceByDeviceId(const std::string& deviceId);
/**
* @brief 判断已选设备是否被占用
*
* @param[in] selectedDevice 传入选中的设备信息
* @return false设备未被占用,可以使用;true设备已被占用或互斥打开
* @note
*/
bool isDeviceInUse(Windows::Devices::Enumeration::DeviceInformation selectedDevice);
/**
* @brief 获取设备列表
*
* @return 返回设备列表结果集
* @note
*/
Windows::Devices::Enumeration::DeviceInformationCollection getDeviceList();
private:
/**
* @brief 通过选好的设备初始化MediaCapture对象,该对象用于媒体设备流采集
*
*
* @param[in] selectedDevice 选择的设备信息
* @return false初始化失败,true初始化成功
* @note
*/
bool initMediaCapture(const Windows::Devices::Enumeration::DeviceInformation& selectedDevice);
/**
* @brief 选择Color帧源,由于一个设备中可能有color、depth、ir等源,选择color帧
*
* @return false不存在color源,true存在并初始化成功
* @note
*/
bool chooseMediaFrameSource();
/**
* @brief 获取最合适的分辨率
*
* 若width和height为0时,选择最高分辨率,不为0时,选择与width、height对应的分辨率
*
* @param[in] width 需要适配的分辨率宽
* @param[in] height 需要适配的分辨率高
* @return 为nullptr表示没有找到适配的format
* @note
*/
Windows::Media::Capture::Frames::MediaFrameFormat getSupportFormat(int width = 0, int height = 0);
private:
int m_width; // 分辨率-宽
int m_height; // 分辨率-高
int m_deviceNums; // 设备数
std::string m_deviceId; // 设备的PID
param::hstring m_subType;
std::map<uint32_t, std::pair<int, int>> m_supportFormatMap;// 支持的NV12格式
static std::map <std::string, bool> m_deviceOpenedMap;
Windows::Devices::Enumeration::DeviceInformation m_selectedDevice; // 被选择的设备
Windows::Devices::Enumeration::DeviceInformationCollection m_deviceList; // 设备列表
Windows::Media::Capture::MediaCapture m_mediaCapture; // 媒体流采集
Windows::Media::Capture::Frames::MediaFrameSource m_mediaFrameSource; // 媒体流源,从mediacapture获取
Windows::Media::Capture::Frames::MediaFrameFormat m_defaultFormat; // 媒体格式,用于自定义视频流的分辨率
Windows::Media::Capture::Frames::MediaFrameReader m_mediaFrameReader; // 读取视频流
};
#endif
实现
#ifdef _WIN32
#include "MediaFrameCapture.h"
#include <iostream> // 后期使用log代替
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc.hpp"
std::map<std::string,bool> MediaFrameCapture::m_deviceOpenedMap;
MediaFrameCapture::MediaFrameCapture():
m_selectedDevice(nullptr),
m_mediaFrameSource(nullptr),
m_defaultFormat(nullptr),
m_deviceList(nullptr),
m_mediaFrameReader(nullptr),
//m_mediaCapture(nullptr),
m_subType(L"NV12")
{
init_apartment();
updateListDevices();
}
MediaFrameCapture::~MediaFrameCapture()
{
if (m_mediaFrameReader)
{
stopCapture();
m_mediaFrameReader.Close();
m_mediaFrameReader = nullptr;
}
if (m_mediaCapture)
{
m_mediaCapture.Close();
}
m_deviceOpenedMap[m_deviceId] = false;
}
void MediaFrameCapture::updateListDevices()
{
// 选择设备类型为视频
auto selector = Windows::Devices::Enumeration::DeviceClass::VideoCapture;
m_deviceList = Windows::Devices::Enumeration::DeviceInformation::FindAllAsync(selector).get();
}
int MediaFrameCapture::listDevices()
{
updateListDevices();
return m_deviceList.Size();
}
bool MediaFrameCapture::selectDeviceByDeviceId(const std::string& deviceId)
{
bool haveFound = false;
for (const Windows::Devices::Enumeration::DeviceInformation& device : m_deviceList)
{
std::wstring wDeviceId(device.Id().c_str());
std::string devId(wDeviceId.begin(), wDeviceId.end());
if (m_deviceOpenedMap.find(devId) != m_deviceOpenedMap.end())
{
if (m_deviceOpenedMap.at(devId)) continue;
}
if (devId.find("PID_" + deviceId) != std::string::npos)
{
haveFound = true;
m_selectedDevice = device;
m_deviceId = devId;
}
}
return haveFound;
}
bool MediaFrameCapture::initMediaCapture(const Windows::Devices::Enumeration::DeviceInformation& selectedDevice)
{
Windows::Media::Capture::MediaCaptureInitializationSettings settings;
settings.VideoDeviceId(selectedDevice.Id());
settings.SharingMode(winrt::Windows::Media::Capture::MediaCaptureSharingMode::ExclusiveControl);
settings.MemoryPreference(winrt::Windows::Media::Capture::MediaCaptureMemoryPreference::Cpu);
settings.StreamingCaptureMode(winrt::Windows::Media::Capture::StreamingCaptureMode::Video);
try {
m_mediaCapture.InitializeAsync(settings).get();
}
catch (...)
{
std::cout << "MediaCapture初始化失败" << std::endl;
return false;
}
m_deviceOpenedMap[m_deviceId] = true;
return true;
}
bool MediaFrameCapture::isDeviceInUse(Windows::Devices::Enumeration::DeviceInformation selectedDevice)
{
std::wstring wDeviceId(selectedDevice.Id().c_str());
std::string devId(wDeviceId.begin(), wDeviceId.end());
if (m_deviceOpenedMap.find(devId) != m_deviceOpenedMap.end() && m_deviceOpenedMap.at(devId))
return true;
return false;
}
Windows::Devices::Enumeration::DeviceInformationCollection MediaFrameCapture::getDeviceList()
{
updateListDevices();
return m_deviceList;
}
bool MediaFrameCapture::chooseMediaFrameSource()
{
if (m_mediaCapture == nullptr)
{
return false;
}
auto frameSources = m_mediaCapture.FrameSources();
Windows::Media::Capture::Frames::MediaFrameSource mediaFrameSource = nullptr;
for (auto frameSource : frameSources)
{
mediaFrameSource = frameSource.Value();
if (mediaFrameSource.Info().MediaStreamType() == Windows::Media::Capture::MediaStreamType::VideoRecord
&& mediaFrameSource.Info().SourceKind() == Windows::Media::Capture::Frames::MediaFrameSourceKind::Color)
{
m_mediaFrameSource = mediaFrameSource;
return true;
}
}
return false;
}
Windows::Media::Capture::Frames::MediaFrameFormat MediaFrameCapture::getSupportFormat(int t_width, int t_height)
{
if (!m_mediaFrameSource) return nullptr;
int max_width = 0;
int max_height = 0;
Windows::Media::Capture::Frames::MediaFrameFormat preffered_format = nullptr;
for (Windows::Media::Capture::Frames::MediaFrameFormat format : m_mediaFrameSource.SupportedFormats())
{
std::wcout << format.Subtype().data() << " "
<< format.VideoFormat().Width() << " "
<< format.VideoFormat().Height() << std::endl;
std::wstring subType = format.Subtype().data();
auto width = format.VideoFormat().Width();
auto height = format.VideoFormat().Height();
if (L"NV12" == subType)
{
if (t_width == 0 && t_height == 0)
{
if (max_width < width && max_height < height)
{
max_width = width;
max_height = height;
preffered_format = format;
}
}
else if (t_width == width && t_height == height)
{
preffered_format = format;
break;
}
}
}
if (preffered_format)
{
if (t_width == 0 && t_height == 0)
{
m_width = max_width;
m_height = max_height;
}
else
{
m_width = t_width;
m_height = t_height;
}
}
return preffered_format;
}
bool MediaFrameCapture::setupDevice(const std::string& deviceId)
{
if (!initMediaCapture(m_selectedDevice))
{
return false;
}
if (!chooseMediaFrameSource())
{
return false;
}
m_defaultFormat = getSupportFormat();
if (!m_defaultFormat)
{
return false;
}
try
{
m_mediaFrameSource.SetFormatAsync(m_defaultFormat).get();
m_mediaFrameReader = m_mediaCapture.CreateFrameReaderAsync(m_mediaFrameSource, m_subType).get();
}
catch (...)
{
return false;
}
return startCapture();
}
bool MediaFrameCapture::setupDevice(const std::string& deviceId, int t_width, int t_height)
{
if (!initMediaCapture(m_selectedDevice))
{
return false;
}
if (!chooseMediaFrameSource())
{
return false;
}
m_defaultFormat = getSupportFormat(t_width, t_height);
if (!m_defaultFormat)
{
return false;
}
try
{
m_mediaFrameSource.SetFormatAsync(m_defaultFormat).get();
m_mediaFrameReader = m_mediaCapture.CreateFrameReaderAsync(m_mediaFrameSource, m_subType).get();
}
catch (...)
{
return false;
}
return startCapture();
}
bool MediaFrameCapture::startCapture()
{
if (!m_mediaFrameReader) return false;
try {
m_mediaFrameReader.StartAsync().get();
}
catch (...)
{
return false;
}
return true;
}
bool MediaFrameCapture::stopCapture()
{
if (!m_mediaFrameReader) return false;
try {
m_mediaFrameReader.StopAsync().get();
}
catch (...)
{
return false;
}
return true;
}
bool MediaFrameCapture::read(cv::Mat& oneFrame)
{
Windows::Media::Capture::Frames::MediaFrameReference videoMediaFrame = m_mediaFrameReader.TryAcquireLatestFrame();
if (videoMediaFrame)
{
auto videoFrame = videoMediaFrame.VideoMediaFrame().GetVideoFrame();
cv::Mat bgrMat;
if (videoFrame != nullptr)
{
auto ff = videoMediaFrame.Format();
std::wcout << ff.Subtype().data()
<< " " << ff.VideoFormat().Width()
<< " " << ff.VideoFormat().Height() << std::endl;
uint32_t cap = m_width * m_height * 1.5;
Windows::Storage::Streams::Buffer buffer(cap);
videoFrame.SoftwareBitmap().CopyToBuffer(buffer);
uint8_t *data = buffer.data();
cv::Mat yuvMat(m_height * 3 / 2, m_width, CV_8UC1, data);
cv::cvtColor(yuvMat, bgrMat, cv::COLOR_YUV2BGR_NV12);
oneFrame = bgrMat.clone();
return true;
}
}
return false;
}
#endif
测试代码
#include "MediaFrameCapture.h"
#include <opencv2/opencv.hpp>
int main(int argc, char*argv[])
{
std::string PID = "";
if (argc > 1)
{
PID = argv[1];
}
MediaFrameCapture frameCapture;
if (!frameCapture.selectDeviceByDeviceId(PID))
{
std::cout << "没有找到对应PID为<" << PID << ">的相机" << std::endl;
return false;
}
bool ret = frameCapture.setupDevice(PID, 1280, 720);
if (!ret)
{
std::cout << "open PID : " << PID << " failed" << std::endl;
return 0;
}
while (1)
{
cv::Mat readFrame;
ret = frameCapture.read(readFrame);
if (ret)
{
cv::imshow("NV12 Image", readFrame);
cv::waitKey(50);
}
}
return 0;
}