libudev+V4L2 linux usb摄像头列表发现以及热拔插事件

2023-03-10 13:09:10 浏览数 (2)

libudev V4L2 linux usb摄像头列表发现以及热拔插事件

简介

最近工作计划本来是重写CameraCtrl 控制类以及实现推流。但是由于需求变动导致之前调研废弃,就暂时放这吧。

libudev

详细地址: libudev Reference Manul

v4l2

相关地址:

v4l2-ctl

Video for Linux Two API Specification

v4l-utils

思路

设计初衷,当时设计思路为将CameraManager作为一个摄像头管理模块,用来管理摄像头列表,主要为热拔插事件对应的增删以及推流的管理。并且实现跨平台,兼容windows&&linux(X86_64,arm,aarch64)。目前只讲解event相关部分,管理模块需要根据实际业务需求来自己进行针对性设计。

源码讲解

文件结构

代码语言:javascript复制
src/deviceManager/cameraManager
├── cameraEvent.cpp
├── cameraEvent.h
├── cameraManager.cpp
├── cameraManager.h
└── README.md
tests
├── camera.cpp
├── CMakeLists.txt
└── conanfile.py

详解

  • 相关结构定义

主要定义信息结构如下:

代码语言:javascript复制
FormatInfo: 帧信息 . 帧宽 . 帧高 . 帧率
CameraCardBindDeviceName: 摄像头绑定名称: 摄像头原始名称, 摄像头驱动名称
CameraInfo: 摄像头信息:摄像头名称(摄像头对外名称),摄像头原始名称,摄像头驱动名称,摄像头pid,摄像头vid,帧列表
EventInfo: 事件信息(主要记录日插拔事件,但是还有其他事件,所以没有命名为热插拔事件) 事件类型,摄像头名称,摄像头驱动名称,摄像头vid,摄像头pid
代码语言:javascript复制
struct FormatInfo
{
    unsigned int width;
    unsigned int height;
    unsigned int rate;
};

struct CameraCardBindDeviceName
{
    std::string cardNameOld;
    std::string cameraDeviceName;
};

//因为摄像头设备存在着同名摄像头设备,无法从摄像头名称区分所需要的摄像头,所以另起了一个别名,用来区分管理摄像头设备。
//命名规则: eg. 存在两个摄像头设备为CameraLog,在遍历过程中第一个获取的为 CameraLog,使用原始名称,第二个获取到的设备名为 CameraLog(1)。即使用别名
struct CameraInfo
{
    std::string cameraCardName; 	//摄像头别名即摄像头名称
    std::string cameraCardNameOld;  //摄像头原始名称
    std::string cameraDeviceName;
    std::string pid;
    std::string vid;
    std::list<FormatInfo> formats;
};

struct EventInfo
{
    bool action; //true:add false:remove
    std::string cameraName;
    std::string cameraDeviceName;
    std::string vid;
    std::string pid;
};
  • 摄像头事件类

摄像头事件类,主要工作类,提供对外接口。负责摄像头事件的相关业务(列表获取,热拔插事件,摄像头信息)

推流函数接口,没什么实际意义就不具体放了。

代码语言:javascript复制
class CameraEvent
{
private:
    /* data */
public:
    ~CameraEvent();
    static CameraEvent* instance();
    /**
     * @name: 获取摄像头列表
     * @msg: 
     * @param {*}
     * @return 返回摄像头列表—map
     */    
    std::map<std::string, CameraCardBindDeviceName> getCameraList();
    /**
     * @name: 获取摄像头分辨率列表
     * @msg: 
     * @param {string} devicename - 摄像头驱动名称
     * @return {*} 摄像头分辨率列表
     */    
    std::list<FormatInfo> getCameraResolutions(std::string devicename);
    /**
     * @name: 获取摄像头图像格式列表
     * @msg: 
     * @param {*}
     * @return {*} 返回所有信息列表
     */    
    std::list<CameraInfo> getCameraFormats();
    /**
     * @name: 获取摄像头输入事件设备vid.pid列表
     * @msg: 
     * @param {*}
     * @return {*} 摄像头输入事件vid,pid列表
     */    
    std::map<std::string, std::string> getInputVPIDs();
    /**
     * @name: 摄像头列表添加摄像头
     * @msg: 
     * @param {string} devicename 摄像头驱动名称
     * @param {string} vid 
     * @param {string} pid
     * @param {list<CameraInfo>} &cameras 摄像头列表
     * @return {*} 插入结果
     */    
    bool addCameraInfo(std::string devicename, std::string vid, std::string pid, std::list<CameraInfo> &cameras);
    /**
     * @name: 摄像头列表移除摄像头
     * @msg: 
     * @param {string} devicename 摄像头驱动名称
     * @param {list<CameraInfo>} &cameras 摄像头列表
     * @return {*} 移除结果
     */    
    bool removeCameraInfo(std::string devicename, std::list<CameraInfo> &cameras);
    /**
     * @name: 摄像头推流启动
     * @msg: 
     * @param {string} devicename 摄像头驱动名称
     * @param {int} width   分辨率-宽
     * @param {int} height 分辨率-高
     * @param {string} host 目标主机地址
     * @param {string} port 目标主机端口
     * @return {*}
     */    
    void cameraGstPushStreamStart(std::string devicename, int width, int height, std::string host, std::string port);
    /**
     * @name: 摄像头推流停止
     * @msg: 
     * @param {string} devicename 摄像头驱动名称
     * @return {*}
     */    
    void cameraGstPushStreamStop(std::string devicename);
#ifdef __linux__
    /**
     * @name: udev admMonitor 初始化
     * @msg: 
     * @param {udev} *udev udev指针
     * @param {udev_monitor*} &kernelMonitor
     * @param {fd_set} &readFds
     * @return {*}
     */    
    int udevadmMonitor(struct udev *udev,  struct udev_monitor* &kernelMonitor, fd_set &readFds);
    /**
     * @name: udev admmonitor item 子元素事件
     * @msg: 
     * @param {udev_monitor*} &kernelMonitor
     * @param {fd_set} &readFds
     * @return {*}
     */    
    EventInfo udevadmMonitorItem(struct udev_monitor* &kernelMonitor, fd_set &readFds);
#endif
private:
    CameraEvent(/* args */);
    //摄像头推流句柄管理容器
    boost::unordered_map<std::string, Poco::ProcessHandle> _mapHandle;
};
  • instance()
代码语言:javascript复制
CameraEvent* CameraEvent::instance()
{
    static CameraEvent cameraEvent;
    return &cameraEvent;
}

getCameraList()

主要工作流程为:遍历 /dev/文件夹下 video 类,存储,过滤,排序。遍历,过滤,获取所需要的摄像头列表。

代码语言:javascript复制
在ubuntu18.04中。摄像头插入会有两个/dev/video 文件,按照顺序排序。其中原因为在18.04中,摄像头没有区分 V4L2_CAP_META_CAPTURE 和 V4L2_CAP_VIDEO_CAPTURE 导致输出两个 /dev/video 文件,在摄像头推流实际使用中, 是使用 V4L2_CAP_VIDEO_CAPTURE 类型摄像头,所以在过滤的时候需要区分。
Video Capture = V4L2_CAP_STREAMING   V4L2_CAP_EXT_PIX_FORMAT   V4L2_CAP_VIDEO_CAPTURE
Metadata Capture = V4L2_CAP_STREAMING   V4L2_CAP_EXT_PIX_FORMAT   V4L2_CAP_META_CAPTURE
ioctl(fd, VIDIOC_QUERYCAP, &vcap) 获取到 vcap.device_caps 值 来进行过滤区分。

相关链接:ubuntu18.04一个摄像头在/dev下对应两个video怎么回事

代码语言:javascript复制
std::map<std::string, CameraCardBindDeviceName> CameraEvent::getCameraList()
{
    std::map<std::string, CameraCardBindDeviceName> cameras;
#ifdef _WIN32

#elif __linux__
	DIR *dp;
	struct dirent *ep;
	dev_vec files;
	struct v4l2_capability vcap;

	dp = opendir("/dev");
	if (dp == nullptr) {
		perror ("Couldn't open the directory");
		return {};
	}
	while ((ep = readdir(dp)))
		if (is_v4l_dev(ep->d_name) && std::string(ep->d_name).find("video") != std::string::npos)
			files.push_back(std::string("/dev/")   ep->d_name);
	closedir(dp);

	std::sort(files.begin(), files.end(), sort_on_device_name);

	for (const auto &file : files) {
		int fd = open(file.c_str(), O_RDWR);
		std::string bus_info;
		std::string card;

		if (fd < 0)
			continue;
		int err = ioctl(fd, VIDIOC_QUERYCAP, &vcap);
		bool is_mate = 0;
		if (err) {

		} else {
			if(vcap.device_caps == (V4L2_CAP_STREAMING   V4L2_CAP_EXT_PIX_FORMAT   V4L2_CAP_VIDEO_CAPTURE))
			{				
				bus_info = reinterpret_cast<const char *>(vcap.bus_info);
				card = reinterpret_cast<const char *>(vcap.card);
				#ifdef service_debug
				std::cout << std::string(card) <<" "<< std::string(file) << " " << std::string(bus_info) << std::endl;
				#elif
				LOG(INFO) << std::string(card) << std::string(file) << std::string(bus_info);
				#endif
			}else if(vcap.device_caps == (V4L2_CAP_STREAMING   V4L2_CAP_META_CAPTURE   V4L2_CAP_EXT_PIX_FORMAT))
				is_mate = true;
		}
		close(fd);
		if (err || is_mate)
			continue;
		std::string cameraNameOld = card;
		if(cameras.count(std::string(card)))
			card = getCardName(cameras, card, 1);
		cameras[std::string(card)] = {cameraNameOld, file};
	}
#endif
	#ifdef service_debug
		for(auto it : cameras)
		{
			std::cout << it.first << " " << it.second.cardNameOld << " " << it.second.cameraDeviceName << std::endl;
		}
	#endif
    return cameras;
}
  • getCameraResolutions(std::string devicename)

根据devicename 获取摄像头分辨率,eg. /dev/video0 获取对应摄像头分辨率。

代码语言:javascript复制
在实际中,摄像头通常支持多种视频格式,yuyv,mjeg等其他格式,但是在博主的实际使用中,只是用yuyv格式,所以只获取了yuyv对应分辨率列表。
if(fmt.pixelformat == V4L2_PIX_FMT_YUYV) 来进行过滤。
代码语言:javascript复制
std::list<FormatInfo> CameraEvent::getCameraResolutions(std::string dev)
{
	std::list<FormatInfo> resolutions = {};
#ifdef _WIN32

#elif __linux__
	int fd = open(dev.c_str(), O_RDONLY);
	if (fd < 0)
	{
		#ifdef service_debug
		std::cout << dev << ":Open fail!!!" << std::endl;;
		#elif
		LOG(ERROR) << dev << ":Open fail!!!";
		#endif
	}
	struct v4l2_format vfmt = {.type=V4L2_BUF_TYPE_VIDEO_OUTPUT};
	if(ioctl(fd,VIDIOC_G_FMT, &vfmt))
	{
		#ifdef service_debug
		std::string format = (vfmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) ? "YUYV" : std::to_string(vfmt.fmt.pix.pixelformat);
		std::cout << format  <<  " " << vfmt.fmt.pix.width  << " " << vfmt.fmt.pix.height << std::endl;
		#elif
		LOG(ERROR) << dev << ":Open fail!!!";
		#endif
	}else
	{
		#ifdef service_debug
		std::cout << "vfmt:get fail!" << std::endl;
		#elif
		LOG(ERROR) << dev << ":Open fail!!!";
		#endif
	}

	struct v4l2_fmtdesc fmt = {.index=0, .type=V4L2_BUF_TYPE_VIDEO_CAPTURE};
	#ifdef service_debug
	std::cout << "Start Search format resolutions" << std::endl;;
	#elif
	LOG(ERROR) << << "Start Search format resolutions";
	#endif
	while(ioctl(fd, VIDIOC_ENUM_FMT, &fmt) >=0 )
	{
		#ifdef service_debug
		std::cout << "Picture Format:" << fmt.description << std::endl;
		#elif
		LOG(INFO) << "Picture Format:" << fmt.description;
		#endif
		if(fmt.pixelformat == V4L2_PIX_FMT_YUYV)
		{
			#ifdef service_debug
			std::cout << "Picture Format is YUYV" << std::endl;
			#elif
			LOG(INFO) << "Picture Format is YUYV";
			#endif
			FormatInfo fmtInfo;
			struct v4l2_frmsizeenum frmsize = {.index=0, .pixel_format=fmt.pixelformat};
			while(ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0)
			{
				fmtInfo.width = frmsize.discrete.width;
				fmtInfo.height = frmsize.discrete.height;
				fmtInfo.rate = 0;
				#ifdef service_debug
				std::cout << "Resolution: " << fmtInfo.width << "X" << fmtInfo.height << std::endl;
				#elif
				LOG(INFO) << "Resolution: " << fmtInfo.width << "X" << fmtInfo.height;
				#endif				
				struct v4l2_frmivalenum frmival = {.index=0, .pixel_format=frmsize.pixel_format, .width=frmsize.discrete.width, .height=frmsize.discrete.height};				
				while(ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival) >= 0)
				{
					unsigned int maxRate = 0;
					if(frmival.type == V4L2_FRMIVAL_TYPE_DISCRETE)
					{
					#ifdef service_debug
					std::cout << 't' << "frmival.discrete: " << fract2fps_int(frmival.discrete) << std::endl;
					#elif
					LOG(INFO) << 't' << "frmival.discrete: " << fract2fps_int(frmival.stepwise.max);
					#endif
					maxRate = fract2fps_int(frmival.discrete);
					}
					else if (frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS || frmival.type == V4L2_FRMIVAL_TYPE_STEPWISE)
					{
					#ifdef service_debug
					std::cout << "stepwise.max: " << fract2fps(frmival.stepwise.max) << std::endl;
					#elif
					LOG(INFO) << "stepwise.max: " << fract2fps_int(frmival.stepwise.max);
					#endif
					maxRate = fract2fps_int(frmival.stepwise.max);
					}
					fmtInfo.rate = (maxRate > fmtInfo.rate) ? maxRate : fmtInfo.rate;
					frmival.index   ;
				}
				resolutions.push_back(fmtInfo);
				frmsize.index  ;
			}
			break;
		}
		else
			fmt.index   ;
	}
	#ifdef service_debug
	std::cout << "End Search format resolutions" << std::endl;;
	#elif
	LOG(ERROR) << "End Search format resolutions";
	#endif
	close(fd);
#endif
    return resolutions;
}
  • getCameraFormats()

获取摄像头格式信息列表。

代码语言:javascript复制
std::list<CameraInfo> CameraEvent::getCameraFormats()
{
	std::list<CameraInfo> cameraInfos;
    std::map<std::string, CameraCardBindDeviceName> cameras = getCameraList();
	#ifdef service_debug
	std::cout << "Start Search USB Camera VID and PID"  << std::endl;
	#endif
#ifdef _WIN32

#elif __linux__
	std::map<std::string, std::string> vpIDs = getInputVPIDs();	
	for(auto it=cameras.begin(); it != cameras.end(); it  )
	{
		CameraInfo camInfo = {.cameraCardName = it->first, .cameraCardNameOld = it->second.cardNameOld, .cameraDeviceName=it->second.cameraDeviceName};
		camInfo.formats = getCameraResolutions(camInfo.cameraDeviceName);
		camInfo.vid = vpIDs[camInfo.cameraCardNameOld].substr(0, 4);
		camInfo.pid = vpIDs[camInfo.cameraCardNameOld].substr(5, 4);
		cameraInfos.push_back(camInfo);
	}
#endif
	#ifdef service_debug
	std::cout << "Output Cameras Info:" << std::endl;
	for(auto it: cameraInfos)
	{
		std::cout << it.cameraCardName << " " << it.cameraCardNameOld << " " << it.cameraDeviceName << " " << it.vid << " " << it.pid << std::endl;
		for(auto itr : it.formats)
		{
			std::cout << itr.height << " " << itr.width << " " << itr.rate << std::endl;
		}
	}
	#endif	
	return cameraInfos;
}
  • CameraEvent::getInputVPIDs()

获取摄像头设备的VID,PID。在查相关资料的时候,经常能看到通过 摄像头名称获取VID.PID信息,但是在实际中打印摄像头名称全量并没有获取到,另一个种方法是 通过Input 事件 过滤获取,具体链接与源码不贴了。但是直接过滤为错误的。‘vid’,‘pid’。

代码语言:javascript复制
博主获取VID,PID为两种情况 一种为 现有的video设备。通过 ioctl(fd, EVIOCGID, &inputId)  获取对应的信息。
第二种为 热拔插事件的时候,通过input事件来过滤vid,pid。
代码语言:javascript复制
static int get_intputdevice_info(std::string file, std::string &cardname, std::string &vpID)
{
	int fd = open(file.c_str(), O_RDWR);
	std::string bus_info;
	char cardName[256] = "";
	struct input_id inputId;
	if (fd < 0)
		return 1;
	int err_id = ioctl(fd, EVIOCGID, &inputId);
	if (err_id) {
		#ifdef service_debug
		std::cout << "err_id:" << err_id << std::endl;
		#elif
		LOG(INFO) << "err_id:" << err_id << std::string(cardName);
		#endif
	} else {
		std::stringstream buf;
		buf << std::hex << std::setw(4) << std::setfill('0') << inputId.vendor << ":" << std::setw(4) << std::setfill('0') << inputId.product;
		buf >> vpID;
		int len = ioctl(fd, EVIOCGNAME(sizeof(cardName)), cardName);
		#ifdef service_debug
		std::cout << "cardName:" << std::string(cardName, len) << " VID/PID value:" << vpID << std::endl;
		#elif
		LOG(INFO) << "cardName:" << std::string(cardName, len) << " VID/PID value:" << vpID;
		#endif
	}
	close(fd);
	cardname = reinterpret_cast<const char *>(cardName);;
	return err_id;
}

std::map<std::string, std::string> CameraEvent::getInputVPIDs()
{
	std::map<std::string, std::string> inputInfos;
#ifdef _WIN32

#elif __linux__
	DIR *dp;
	struct dirent *ep;
	dev_vec files;

	dp = opendir("/dev/input");
	if (dp == nullptr) {
		perror ("Couldn't open the directory");
		return {};
	}
	while ((ep = readdir(dp)))
		if (std::string(ep->d_name).find("event") != std::string::npos)
			files.push_back(std::string("/dev/input/")   ep->d_name);
	closedir(dp);

	std::sort(files.begin(), files.end(), sort_on_device_name);

	for (const auto &file : files) {
		std::string card,vpID;
		int err_id = get_intputdevice_info(file, card, vpID);
		if(err_id)
			continue;
		if(!inputInfos.count(card)){
				inputInfos[card] = vpID;
		}
	}
#endif
	return inputInfos;
}
  • addCameraInfo(std::string devicename, std::string vid, std::string pid, std::list &cameras)

热拔插 :插入事件

代码语言:javascript复制
bool CameraEvent::addCameraInfo(std::string devicename, std::string vid, std::string pid, std::list<CameraInfo> &cameras)
{
	CameraInfo info = {.cameraCardName = "", .cameraCardNameOld = "", .cameraDeviceName = devicename, .pid = pid, .vid = vid};
#ifdef _WIN32

#elif __linux__
	int fd = open(devicename.c_str(), O_RDWR);
	std::string bus_info;
	std::string card;
	struct v4l2_capability vcap;

	if (fd < 0){
		#ifdef service_debug
		std::cout << "Open file fail:" << devicename << std::endl;
		#elif
		LOG(WARNING) << "Open file fail:" << devicename << std::string(bus_info);
		#endif
		return false;
	}
	int err = ioctl(fd, VIDIOC_QUERYCAP, &vcap);
	bool is_mate = 0;
	if (err) {

	} else {
		if(vcap.device_caps == (V4L2_CAP_STREAMING   V4L2_CAP_EXT_PIX_FORMAT   V4L2_CAP_VIDEO_CAPTURE))
		{
			bus_info = reinterpret_cast<const char *>(vcap.bus_info);
			card = reinterpret_cast<const char *>(vcap.card);
			#ifdef service_debug
			std::cout << std::string(card) <<" "<< std::string(devicename) << " " << std::string(bus_info) << std::endl;
			#elif
			LOG(INFO) << std::string(card) << std::string(devicename) << std::string(bus_info);
			#endif
		}else if(vcap.device_caps == (V4L2_CAP_STREAMING   V4L2_CAP_META_CAPTURE   V4L2_CAP_EXT_PIX_FORMAT))
			is_mate = true;
	}
	close(fd);
	if(err || is_mate)
	{
		#ifdef service_debug
		std::cout << "Open devicename fail:" << devicename << " or deveice type is mate" <<std::endl;
		#elif
		LOG(WARNING) << "Open devicename fail:" << devicename << " or deveice type is mate";
		#endif
		return false;
	}
	bool InsertFlags = false; 
	int count = 0;;
	for(auto it: cameras)
	{
		if(std::string(card) == it.cameraCardNameOld)
		{
			count  ;
			InsertFlags = true;
		}
	}
	info.cameraCardName = (count) ? card   "("   std::to_string(count)   ")" : card;
	info.cameraCardNameOld = card;
	info.formats = getCameraResolutions(devicename);
#endif
	cameras.push_back(info);
	return true;
}
  • removeCameraInfo(std::string cardName, std::list &cameras)

热拔插:拔出事件

代码语言:javascript复制
bool CameraEvent::removeCameraInfo(std::string cardName, std::list<CameraInfo> &cameras)
{
	auto it = cameras.begin();
	bool ret = false;
	for(it; it != cameras.end(); it  )
	{
		if((*it).cameraCardName == cardName)
		{
			ret = true;
			it = cameras.erase(it);
			cameraGstPushStreamStop((*it).cameraDeviceName);
		}
	}
	return ret;
}
  • udevadmMonitor(struct udev udev, struct udev_monitor &kernelMonitor, fd_set &readFds)

热拔插检测事件

代码语言:javascript复制
#ifdef __linux__
int CameraEvent::udevadmMonitor(struct udev *udev, struct udev_monitor* &kernelMonitor, fd_set &readFds)
{
	if(getuid() != 0)
	{
		#ifdef service_debug
		std::cout << "root privileges needed to subscribe to kernel events." << std::endl;
		#elif
		LOG(INFO) << "root privileges needed to subscribe to kernel events." ;
		#endif
		udev_monitor_unref(kernelMonitor);
		return 0;
	}

	#ifdef service_debug
	std::cout << "monitor will print the received events." << std::endl;
	#elif
	LOG(INFO) << "monitor will print the received events." ;
	#endif

	kernelMonitor = udev_monitor_new_from_netlink(udev, "udev");
	if(kernelMonitor == nullptr) {
		udev_monitor_unref(kernelMonitor);
		return 3;
	}
	//事件过滤器:过滤 video4linux 事件
	udev_monitor_filter_add_match_subsystem_devtype(kernelMonitor, "video4linux", nullptr);

	if(udev_monitor_enable_receiving(kernelMonitor) < 0) {
		udev_monitor_unref(kernelMonitor);
		return 4;
	}

	#ifdef service_debug
	std::cout << "UEVENT the kernel uevent:" << std::endl;
	#elif
	LOG(INFO) << "UEVENT the kernel uevent:" ;
	#endif
	return 1;
}
//单次事件具体信息获取
EventInfo CameraEvent::udevadmMonitorItem(struct udev_monitor* &kernelMonitor, fd_set &readFds)
{
	EventInfo info;
	int fdCount = 0;
	FD_ZERO(&readFds);
	if(kernelMonitor != nullptr) {
		FD_SET(udev_monitor_get_fd(kernelMonitor), &readFds);
	}
	fdCount = select(udev_monitor_get_fd(kernelMonitor) 1, &readFds, nullptr, nullptr, nullptr);
	if(!fdCount)
	{
		if(errno != EINTR) {
			#ifdef service_debug
			std::cout << "error receiving uevent message" << std::endl;
			#elif
			LOG(INFO) << "error receiving uevent message:" ;
			#endif
		}
		return {};
	}

	if((kernelMonitor != nullptr) && FD_ISSET(udev_monitor_get_fd(kernelMonitor), &readFds))
	{
		struct udev_device *device = udev_monitor_receive_device(kernelMonitor);
		if(device == nullptr)
		{
			return {};
		}
		if(std::string(udev_device_get_action(device)) == std::string("add") || std::string(udev_device_get_action(device)) == std::string("remove"))
		{
			struct udev_list_entry *devAttributes;
			udev_list_entry_foreach(devAttributes, udev_device_get_properties_list_entry(device))
			{
				std::string name(udev_list_entry_get_name(devAttributes));
				std::string value(udev_list_entry_get_value(devAttributes));
				if(name == std::string("ACTION"))	
					info.action = (value == std::string("add")) ? true : false;
				if(name == std::string("ID_V4L_PRODUCT"))
					info.cameraName = value;
				if(name == std::string("ID_MODEL_ID"))
					info.pid = value;
				if(name == std::string("ID_VENDOR_ID"))
					info.vid = value;
			}
			info.cameraDeviceName = reinterpret_cast<const char *>(udev_device_get_devnode(device));
			#ifdef service_debug
			std::cout   << udev_device_get_action(device) << " " 
						<< udev_device_get_devpath(device) << " "
						<< udev_device_get_subsystem(device) << " " 
						<< udev_device_get_devnode(device) << " "
						<< info.cameraName << " "
						<< info.vid << " "
						<< info.pid << " "
						<< std::endl;
			#elif
			LOG(INFO) 	<< std::string(udev_device_get_action(device)) << " " 
						<< std::string(udev_device_get_devpath(device)) << " "
						<< std::string(udev_device_get_subsystem(device)) << " " 
						<< std::string(udev_device_get_devnode(device)) << " " 
						<< info.cameraName << " "
						<< info.vid << " "
						<< info.pid;
			#endif	
		}		
		udev_device_unref(device);
	}

	return info;
}
#endif

测试样例

  • camera.cpp
代码语言:javascript复制
#include "cameraEvent.h"
#include <libudev.h>
#include <signal.h>

#undef asmlinkage
#ifdef __i386__
#define asmlinkage __attribute__((regparm(0)))
#else
#define asmlinkage 
#endif

static int udev_exit = 0;
static void asmlinkage sig_handler(int signum)
{
	if (signum == SIGINT || signum == SIGTERM)
		udev_exit = 1;
}

int main()
{
    struct sigaction act;
    memset(&act, 0x00, sizeof(struct sigaction));
	act.sa_handler = (void (*)(int)) sig_handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_RESTART;
	sigaction(SIGINT, &act, nullptr);
	sigaction(SIGTERM, &act, nullptr);

    std::list<CameraInfo> cameras = CameraEvent::instance()->getCameraFormats();


    CameraEvent::instance()->cameraGstPushStreamStart((*cameras.begin()).cameraDeviceName, 640, 480, "172.26.106.87", "5600");

    struct udev *udev = udev_new();
    if(udev == nullptr) {
        udev_unref(udev);
        return 0;
    }
    fd_set readFds;
    struct udev_monitor* kernelMonitor = nullptr;
    int ret = CameraEvent::instance()->udevadmMonitor(udev, kernelMonitor, readFds);
	if(kernelMonitor == nullptr)
	{
        std::cout << "kernelMonitor == nullptr" << std::endl;
        return 0;
	}
    while(!udev_exit)
    {    
        EventInfo info = CameraEvent::instance()->udevadmMonitorItem(kernelMonitor, readFds);
        if(info.cameraName == "")
            continue;
        if(info.action)
        {
            bool ret = CameraEvent::instance()->addCameraInfo(info.cameraDeviceName, info.vid, info.pid, cameras);
            std::cout << "Add: " << cameras.size() << std::endl;
        }
        else
        {
            bool ret = CameraEvent::instance()->removeCameraInfo(info.cameraName, cameras);
            std::cout << "Remove: " << cameras.size() << std::endl;
        }
    }
    std::cout << "Ouput result: " << ret << std::endl;
    udev_unref(udev);
    CameraEvent::instance()->cameraGstPushStreamStop((*cameras.begin()).cameraDeviceName);
    return 0;
}
  • cmakelists.txt
代码语言:javascript复制
cmake_minimum_required(VERSION 3.10)

# 工程名称
project(CameraTest)

include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -fpermissive -g")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c  11 -fPIC -fpermissive -g")

set(CMAKE_INCLUDE_CURRENT_DIR ON)

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)

set(service_debug true)
add_definitions(-Dservice_debug)

find_package(Poco)

# 包含头文件
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../src/deviceManager/cameraManager) # 头文件包含目录
include_directories(${Poco_INCLUDE_DIRS})

aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCE_FILES)                     
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/../src/deviceManager/cameraManager camer_FILES)

add_executable(CameraTest
    ${camer_FILES}
    ${SOURCE_FILES}
)

target_link_libraries(CameraTest udev ${CONAN_LIBS})
  • conanfile.py
代码语言:javascript复制
from conans import ConanFile, CMake, tools
import os

class CameraManager(ConanFile):
    name = "CameraManger"
    version = "1.0.0"
    settings = "os", "compiler", "build_type", "arch"
    generators = [("cmake"), ("cmake_find_package"), ("gcc")]
    build_policy = "always"

    def export_sources(self):
        self.copy("CMakeLists.txt")
        self.copy("*", dst="src", src="src")
        self.copy("*", dst="config", src="config")
        self.copy("*", dst="deployment", src="deployment")

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

    def imports(self):
        self.copy("*.dll", dst="bin", src="bin")
        self.copy("*.dylib*", dst="bin", src="lib")
        self.copy('*.so*', dst='bin', src='lib')

    def package(self):
        self.copy("*", dst='bin', src='bin')

    def requirements(self):
        self.requires("boost/1.69.0")
        self.requires("caf/0.17.6")
        self.requires("yaml-cpp/0.7.0")
        self.requires("poco/1.11.0")

其他:并没有提供所有接口,只是提供了测试样例,以及关键函数接口说明。CMakeLists.txt 和 conanfile.py 并不通用,需要根据自己的实际配置来更改

0 人点赞