Linux多路USB摄像头直播画面位置固定


在一些视频直播画面中需要将USB摄像头的通道与视频画面的关系固定下来,什么意思呢?假如一个页面需要播放3路视频,分别对应3个USB摄像头,要将USB的通道序号与播放画面的3路视频一一对应,且每次播放时USB通道序号与画面对应关系保持不变。怎么做呢?下面记录一下我的做法。

产生问题的原因

在嵌入式设备中使用多路USB摄像头时,在/dev目录中会产生/dev/video0、/dev/video1等类似的设备节点;有些USB摄像头支持多种格式的数据输出,例如YUV、mpeg、H264等,因此单个USB摄像头可能被驱动程序创建多个设备节点,那么如何选择需要的设备节点呢?
将同一个USB摄像头连接到同一个USB接口,但是顺序不同,产生设备的节点序号也不相同,假设有3个USB接口,接口1如果先连接则设备节点为/dev/video0,如果最后连接设备节点为/dev/video2
如果仅仅使用设备节点的序号,将导致直播画面的在不同连接顺序时,播放不同摄像头的视频。

那么怎么解决这个问题呢?这就需要根据USB的通道号来确定当前通道下USB摄像头的设备节点。

根据USB通道确定设备节点

/sys/class/video4linux/目录下的文件可以确定USB通道与设备节点的之间的关系
在这里插入图片描述
在此目录下video0节点描述的USB通道与设备节点之间的关系,video0 -> ../../devices/soc0/soc/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0/video4linux/video0
其中2-1.1/2-1.1:1.0描述了USB的通道号,由于此文件是目录链接,因此可以进入video0目录查看
在这里插入图片描述
其中

  • device描述了USB通道序号,可以用读取链接的办法获取通道号,
  • name是USB摄像头的设备名称;可以根据此确定是否是应用连接的USB摄像头。

代码实现

#define MAX_USB_CHANNEL 		(4)
#define VIDEO4LINUX_PATH 		"/sys/class/video4linux"
#define CAMERA_PRODUCT1 		"HD USB Camera"			//可产生多种数据格式的摄像头
#define CAMERA_PRODUCT2 		"SWT USB2.0 Camera"
camera_struct usbcamera[MAX_USB_CHANNEL];
#define USB_VIDEO_BUF_REQ_CNT			16
typedef struct camera_node
{
	char id[16];
	int ch;

	//V4L2
	char devname[16];
	int fd;
	struct v4l2_format fmt;
	struct v4l2_streamparm parm;
	struct v4l2_requestbuffers req;
	struct buffer *buffers;
	int n_buffers;

	unsigned int width;
	unsigned int height;
	unsigned int bitrate;

	unsigned int buf_head;
	unsigned char buf[VIDEO_BUF_SIZE];
}camera_struct;

static char *xreadlink(const char *path)
{
	static const int GROWBY = 80; /* how large we will grow strings by */

	char *buf = NULL;
	int bufsize = 0, readsize = 0;

	do {
		buf = realloc(buf, bufsize += GROWBY);
		readsize = readlink(path, buf, bufsize); /* 1st try */
		if (readsize == -1) {
			free(buf);
			return NULL;
		}
	}
	while (bufsize < readsize + 1);

	buf[readsize] = '\0';

	return buf;
}

//name:video0 or video1 or video2 ...
//port:0 or 1 or 2 ...
static int update_camera_video(char *name, int port)
{
	struct camera_node *usb_node = NULL;

	if(port >= MAX_USB_CHANNEL)
	{
		printf("update_camera_video faild, port:%d > MAX_USB_CHANNEL!\n", port);
		return -1;
	}

	usb_node = &usbcamera[port];
	sprintf(usb_node->devname, "/dev/%s", name);
	usb_node->ch = port;
	usb_node->ch_online = 1;
	printf("update_camera_video name:%s, dev name:%s port:%d\n",
			name, usb_node->devname, port);

	return 0;
}

//path = "/sys/class/video4linux/video0"
int find_camera(char *path, int *port, char *usb_path)
{
	FILE *fp = NULL;
	int ret = -1;
	char name[128] = {0};
	char dev_name[128] = {0};
	char part0[16] = {0};
	char part1[16] = {0};
	char index = -1;
	char *device_link = NULL;
	long int num = 0;
	
	sprintf(name, "%s/name", path);
	fp = fopen(name, "r");
	ret = fread(dev_name, 1, 128, fp);
	dev_name[ret - 1] = '\0';
	fclose(fp);
	//以上步骤用于获取设备节点的名称:/sys/class/video4linux/video0/name
	//printf("dev_name %s CAMERA_PRODUCT %s\n", dev_name, CAMERA_PRODUCT);
	if(strcmp(dev_name, CAMERA_PRODUCT1) == 0 || strcmp(dev_name, CAMERA_PRODUCT2) == 0)
	{
		if(strcmp(dev_name, CAMERA_PRODUCT1) == 0)
		{
			memset(name, 0, sizeof(name));
			sprintf(name, "%s/index", path);
			fp = fopen(name, "r");
			ret = fread(&index, 1, 1, fp);
			fclose(fp);
			num = strtoul(&index, NULL, 10);
			printf("find_camera name %s index %ld %c&&&&&&&&&&&&&&&&&&&&&&&&\n", name, num, index);
			if(num == 0)
			{// 此处返回是因为,当设备节点的index=0时,表示此设备输出数据格式为mpeg格式,不是H264格式
				return 0;
			}
		}
		printf("find camera %s+++++++++++++++++++++++\n", dev_name);
		memset(name, 0, sizeof(name));
		sprintf(name, "%s/device", path);
		printf("name %s\n", name);
		device_link = xreadlink(name);
		printf("device_link %s\n", device_link);	// device_link: "../../../2-1.2:1.0"
		sscanf(strrchr(device_link, '/') + 1, "%[^:]:%s", part0, part1);
		//格式化输出字符串,“%[^:]:%s”,表示将device_link最后一次出现/字符之后的字符按照:分割,分别放到part0、part1,其中part0取到:截止,不包含:
		if(strrchr(part0, '.'))
		{
			num = strtoul(strrchr(part0, '.') + 1, NULL, 10);	//字符.后面的数字就是USB通道号
			*port = num;
			strcpy(usb_path, part0);
			printf("part0 %s part1 %s num %ld\n", part0, part1, num);
		}
		else
		{
			*port = -1;
		}
		free(device_link);
		return 1;
	}

	return 0;
}

int usbcamera_scan(void)
{
	int ret = -1;
	DIR *vdir;
	DIR *adir;
	struct dirent *ptr;
	struct dirent *aptr;
	char path[64] = {0};
	char usb_path[16] = {0};
	int usb_port = -1;
	int cardno = -1;
	char *card_link = NULL;

	vdir = opendir(VIDEO4LINUX_PATH);
	if(vdir == NULL)
	{
		printf("open vdir %s failed\n", VIDEO4LINUX_PATH);
		return -1;
	}

	while((ptr = readdir(vdir)) != NULL)
	{
		//printf("ptr->d_name = %s ptr->d_type = %d strlen(ptr->d_name) = %d\n",ptr->d_name,ptr->d_type, strlen(ptr->d_name));
		if(strcmp(ptr->d_name,".") == 0 || strcmp(ptr->d_name,"..") == 0)
		{
			continue;
		}
		//  "/sys/class/video4linux/video0"
		sprintf(path, "%s/%s", VIDEO4LINUX_PATH, ptr->d_name);
		ret = find_camera(path, &usb_port, usb_path);
		if(ret == 0)
		{
			continue;
		}
		// ptr->d_name = video0
		ret = update_camera_video(ptr->d_name, usb_port - 1);
		if(ret < 0)
		{
			continue;
		}
	}
	closedir(vdir);
	return 0;
}

关键函数说明

void *realloc(void *mem_address, unsigned int newsize)

  • 如果有足够空间用于扩大mem_address指向的内存块,则分配额外内存,并返回mem_address。
    这里说的是“扩大”,我们知道,realloc是从堆上分配内存的,当扩大一块内存空间时, realloc()试图直接从堆上现存的数据后面的那些字节中获得附加的字节,如果能够满足,自然天下太平。也就是说,如果原先的内存大小后面还有足够的空闲空间用来分配,加上原来的空间大小= newsize。那么就ok。得到的是一块连续的内存。
  • 如果原先的内存大小后面没有足够的空闲空间用来分配,那么从堆中另外找一块newsize大小的内存。并把原来大小内存空间中的内容复制到newsize中。返回新的mem_address指针。(数据被移动了),老块被放回堆上。

ssize_t readlink (const char *__restrict __path,char *__restrict __buf, size_t __len)

将参数path的符号链接内容存储到参数buf所指的内存空间,返回的内容不是以\0作字符串结尾,但会将字符串的字符数返回,这使得添加\0变得简单。若参数bufsiz小于符号连接的内容长度,过长的内容会被截断,如果 readlink 第一个参数指向一个文件而不是符号链接时,readlink 设 置errno 为 EINVAL 并返回 -1。 readlink()函数组合了open()、read()和close()的所有操作。

返回值 :执行成功则传符号连接所指的文件路径字符串,失败返回-1, 错误代码存于errno
错误代码:
EACCESS 取文件时被拒绝,权限不够
EINVAL 参数bufsiz为负数
EIO O存取错误
ELOOP 欲打开的文件有过多符号连接问题
ENAMETOOLONG 参数path的路径名称太长
ENOENT 参数path所指定的文件不存在
ENOMEM 核心内存不足
ENOTDIR 参数path路径中的目录存在但却非真正的目录

char *strrchr(const char *str, int c)

  • 功能:参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置
  • str – C 字符串。
  • c – 要搜索的字符。以 int 形式传递,但是最终会转换回 char 形式。

unsigned long int strtoul(const char *str, char **endptr, int base)

  • 功能:参数 str 所指向的字符串根据给定的 base 转换为一个无符号长整数(类型为 unsigned long int 型),base 必须介于 2 和 36(包含)之间,或者是特殊值 0
  • str – 要转换为无符号长整数的字符串。
  • endptr – 对类型为 char* 的对象的引用,其值由函数设置为 str 中数值后的下一个字符。
  • base – 基数,必须介于 2 和 36(包含)之间,或者是特殊值 0
  • 返回值:该函数返回转换后的长整数,如果没有执行有效的转换,则返回一个零值。
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值