文章目录
在一些视频直播画面中需要将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
- 返回值:该函数返回转换后的长整数,如果没有执行有效的转换,则返回一个零值。