使用c++/winrt API获取RGB相机视频流

文章介绍了如何使用C++/WinRTAPI在Windows10系统上获取RGB相机的视频流。首先,确保编译器支持C++17和VisualStudio2017。接着,通过MediaCapture类找到并选择视频捕获设备,设置帧源和格式,然后启动和停止视频捕获。代码示例中还涉及了OpenCV库进行NV12到BGR的转换和帧的显示。
摘要由CSDN通过智能技术生成

使用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;
}
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值