我们在之前的文章中,通过相机的打开流程,知道,相机的id号,就是我们Camera.open(cameraid)里的这个id,实际不是与设备固定对应的。
当我们机器里有多个设像头的时候,如果其中一个坏了,那么机器启动的时候,它还是按照0、1、2、3这种顺序去对应摄像头驱动的设备文件(就是之前的vedio2,vedio3,vedio4等),那么实际上,这个id号就不知道打开的是哪个摄像头了。
如果要固定对应id号,我们就需要底层告诉我们那些vedio对应的是哪个硬件
我们看下之前的初始化代码
hardware\qcom\camera\QCamera2\stack\mm-camera-interface\src\mm_camera_interface.c
uint8_t get_num_of_cameras()
{
......省略代码......
while (1) {
uint32_t num_entities = 1U;
char dev_name[32];
snprintf(dev_name, sizeof(dev_name), "/dev/media%d", num_media_devices);
dev_fd = open(dev_name, O_RDWR | O_NONBLOCK);
if (dev_fd < 0) {
LOGD("Done discovering media devices\n");
break;
}
num_media_devices++;
rc = ioctl(dev_fd, MEDIA_IOC_DEVICE_INFO, &mdev_info);
......省略代码......
while (1) {
......省略代码......
rc = ioctl(dev_fd, MEDIA_IOC_ENUM_ENTITIES, &entity);
if (rc < 0) {
LOGD("Done enumerating media entities");
rc = 0;
break;
}
LOGD("entity name %s type %d group id %d",
entity.name, entity.type, entity.group_id);
if (entity.type == MEDIA_ENT_T_V4L2_SUBDEV &&
entity.group_id == MSM_CAMERA_SUBDEV_SENSOR_INIT) {
snprintf(subdev_name, sizeof(dev_name), "/dev/%s", entity.name);
break;
}
}
close(dev_fd);
dev_fd = -1;
}
/* Open sensor_init subdev */
sd_fd = open(subdev_name, O_RDWR);
......省略代码......
if (ioctl(sd_fd, VIDIOC_MSM_SENSOR_INIT_CFG, &cfg) < 0) {
LOGE("failed");
}
......省略代码......
while (1) {
......省略代码......
dev_fd = open(dev_name, O_RDWR | O_NONBLOCK);
......省略代码......
rc = ioctl(dev_fd, MEDIA_IOC_DEVICE_INFO, &mdev_info);
......省略代码......
while (1) {
......省略代码......
rc = ioctl(dev_fd, MEDIA_IOC_ENUM_ENTITIES, &entity);
......省略代码......
strlcpy(g_cam_ctrl.video_dev_name[num_cameras],
entity.name, sizeof(entity.name));
......省略代码......
}
......省略代码......
}
......省略代码......
}
把上面的代码简化一下
snprintf(dev_name, sizeof(dev_name), "/dev/media%d", num_media_devices);
dev_fd = open(dev_name, O_RDWR | O_NONBLOCK);
ioctl(dev_fd, MEDIA_IOC_DEVICE_INFO, &mdev_info);
ioctl(dev_fd, MEDIA_IOC_ENUM_ENTITIES, &entity);
snprintf(subdev_name, sizeof(dev_name), "/dev/%s", entity.name);
sd_fd = open(subdev_name, O_RDWR);
ioctl(sd_fd, VIDIOC_MSM_SENSOR_INIT_CFG, &cfg)
dev_fd = open(dev_name, O_RDWR | O_NONBLOCK);
ioctl(dev_fd, MEDIA_IOC_DEVICE_INFO, &mdev_info)
ioctl(dev_fd, MEDIA_IOC_ENUM_ENTITIES, &entity)
首先我们来看下这个ioctl是在哪里定义的
MEDIA_IOC_DEVICE_INFO
搜索一下关键字MEDIA_IOC_DEVICE_INFO
发现在media-device.c中有用到
kernel\msm-3.18\drivers\media\media-device.c
int __must_check __media_device_register(struct media_device *mdev,
struct module *owner)
{
......省略代码......
/* Register the device node. */
mdev->devnode.fops = &media_device_fops;
......省略代码......
}
static const struct media_file_operations media_device_fops = {
.owner = THIS_MODULE,
.open = media_device_open,
//调用ioctl实际是调用方法中的media_device_ioctl
.ioctl = media_device_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = media_device_compat_ioctl,
#endif /* CONFIG_COMPAT */
.release = media_device_close,
};
static long media_device_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct media_devnode *devnode = media_devnode_data(filp);
struct media_device *dev = to_media_device(devnode);
long ret;
switch (cmd) {
case MEDIA_IOC_DEVICE_INFO:
ret = media_device_get_info(dev,
(struct media_device_info __user *)arg);
break;
case MEDIA_IOC_ENUM_ENTITIES:
ret = media_device_enum_entities(dev,
(struct media_entity_desc __user *)arg);
break;
......省略代码......
}
static int media_device_get_info(struct media_device *dev,
struct media_device_info __user *__info)
{
struct media_device_info info;
memset(&info, 0, sizeof(info));
strlcpy(info.driver, dev->dev->driver->name, sizeof(info.driver));
strlcpy(info.model, dev->model, sizeof(info.model));
strlcpy(info.serial, dev->serial, sizeof(info.serial));
strlcpy(info.bus_info, dev->bus_info, sizeof(info.bus_info));
info.media_version = MEDIA_API_VERSION;
info.hw_revision = dev->hw_revision;
info.driver_version = dev->driver_version;
if (copy_to_user(__info, &info, sizeof(*__info)))
return -EFAULT;
return 0;
}
static long media_device_enum_entities(struct media_device *mdev,
struct media_entity_desc __user *uent)
{
struct media_entity *ent;
struct media_entity_desc u_ent;
memset(&u_ent, 0, sizeof(u_ent));
if (copy_from_user(&u_ent.id, &uent->id, sizeof(u_ent.id)))
return -EFAULT;
ent = find_entity(mdev, u_ent.id);
if (ent == NULL)
return -EINVAL;
u_ent.id = ent->id;
if (ent->name)
strlcpy(u_ent.name, ent->name, sizeof(u_ent.name));
u_ent.type = ent->type;
u_ent.revision = ent->revision;
u_ent.flags = ent->flags;
u_ent.group_id = ent->group_id;
u_ent.pads = ent->num_pads;
u_ent.links = ent->num_links - ent->num_backlinks;
memcpy(&u_ent.raw, &ent->info, sizeof(ent->info));
if (copy_to_user(uent, &u_ent, sizeof(u_ent)))
return -EFAULT;
return 0;
}
kernel\msm-3.18\include\uapi\linux\media.h
struct media_entity_desc {
......省略代码......
};
media_device_get_info
代码中看到,把dev中的内容赋值给__info,实际上就是赋值给ioctl(dev_fd, MEDIA_IOC_DEVICE_INFO, &mdev_info);中的mdev_info,看下这个字段的作用
#define MSM_CONFIGURATION_NAME "msm_config"
if (strncmp(mdev_info.model, MSM_CONFIGURATION_NAME,
sizeof(mdev_info.model)) != 0) {
close(dev_fd);
dev_fd = -1;
continue;
}
也就是说,这个mdev_info.model是 "msm_config"时就继续往下走,否则就打开下一个media文件,后面再看原因
media_device_enum_entities
我们看到如果传进来的uent没有id值则报参数错误,然后通过find_entity找到设备的entity信息,实际上最终就是给传进来的uent填充信息,我们接着看
static struct media_entity *find_entity(struct media_device *mdev, u32 id)
{
struct media_entity *entity;
int next = id & MEDIA_ENT_ID_FLAG_NEXT;
id &= ~MEDIA_ENT_ID_FLAG_NEXT;
spin_lock(&mdev->lock);
media_device_for_each_entity(entity, mdev) {
if ((entity->id == id && !next) ||
(entity->id > id && next)) {
spin_unlock(&mdev->lock);
return entity;
}
}
spin_unlock(&mdev->lock);
return NULL;
}
看下media_device_for_each_entity定义
kernel\msm-3.18\include\media\media-device.h
struct media_device {
......省略代码......
u32 entity_id;
struct list_head entities;
......省略代码......
};
#define media_device_for_each_entity(entity, mdev) list_for_each_entry(entity, &(mdev)->entities, list)
可以看到,就是遍历了media_device 中的list_head,然后找到这个entity返回去。现在我们就是要找这个media_device 是怎么赋值的,在上面的代码中我们看到传进来的dev是这么初始化的
struct media_devnode *devnode = media_devnode_data(filp);
struct media_device *dev = to_media_device(devnode);
kernel\msm-3.18\include\media\media-devnode.h
struct media_devnode {
/* device ops */
const struct media_file_operations *fops;
/* sysfs */
struct device dev; /* media device */
struct cdev cdev; /* character device */
struct device *parent; /* device parent */
/* device info */
int minor;
unsigned long flags; /* Use bitops to access flags */
/* callbacks */
void (*release)(struct media_devnode *mdev);
};
static inline struct media_devnode *media_devnode_data(struct file *filp)
{
return filp->private_data;
}
kernel\msm-3.18\include\media\media-device.h
/* media_devnode to media_device */
#define to_media_device(node) container_of(node, struct media_device, devnode)
结果到头来还是通过这个文件句柄来初始化的,这个后面再看
这个方法赋值了ioctl(dev_fd, MEDIA_IOC_ENUM_ENTITIES, &entity);中的entity,看下这个变量的作用
if (entity.type == MEDIA_ENT_T_V4L2_SUBDEV &&
entity.group_id == MSM_CAMERA_SUBDEV_SENSOR_INIT) {
snprintf(subdev_name, sizeof(dev_name), "/dev/%s", entity.name);
break;
}
看到,如果这个设备的group_id为MSM_CAMERA_SUBDEV_SENSOR_INIT,就得到subdev_name,并跳出大循环,通过打印,看到这个subdev_name是 /dev/v4l-subdev13。
结合上面循环的代码,可以得出,前部分这段代码,就是先循环查找 /dev/media* 文件,找到model为MSM_CONFIGURATION_NAME的media文件,然后通过ioctl去读取这个media文件的entity,如果满足type == MEDIA_ENT_T_V4L2_SUBDEV && group_id == MSM_CAMERA_SUBDEV_SENSOR_INIT。就取出entity.name,我们接着往后看
sd_fd = open(subdev_name, O_RDWR);
ioctl(sd_fd, VIDIOC_MSM_SENSOR_INIT_CFG, &cfg)
搜索发现VIDIOC_MSM_SENSOR_INIT_CFG在msm_sensor_init.c中被使用
VIDIOC_MSM_SENSOR_INIT_CFG
kernel\msm-3.18\drivers\media\platform\msm\camera_v2\sensor\msm_sensor_init.c
static struct v4l2_subdev_core_ops msm_sensor_init_subdev_core_ops = {
.ioctl = msm_sensor_init_subdev_ioctl,
};
static long msm_sensor_init_subdev_ioctl(struct v4l2_subdev *sd,
unsigned int cmd, void *arg)
{
long rc = 0;
struct msm_sensor_init_t *s_init = v4l2_get_subdevdata(sd);
CDBG("Enter");
/* Validate input parameters */
if (!s_init) {
pr_err("failed: s_init %pK", s_init);
return -EINVAL;
}
switch (cmd) {
case VIDIOC_MSM_SENSOR_INIT_CFG:
rc = msm_sensor_driver_cmd(s_init, arg);
break;
default:
pr_err_ratelimited("default\n");
break;
}
return rc;
}
static int32_t msm_sensor_driver_cmd(struct msm_sensor_init_t *s_init,
void *arg)
{
int32_t rc = 0;
struct sensor_init_cfg_data *cfg = (struct sensor_init_cfg_data *)arg;
/* Validate input parameters */
if (!s_init || !cfg) {
pr_err("failed: s_init %pK cfg %pK", s_init, cfg);
return -EINVAL;
}
switch (cfg->cfgtype) {
case CFG_SINIT_PROBE:
mutex_lock(&s_init->imutex);
s_init->module_init_status = 0;
rc = msm_sensor_driver_probe(cfg->cfg.setting,
&cfg->probed_info,
cfg->entity_name);
mutex_unlock(&s_init->imutex);
if (rc < 0)
pr_err("%s failed (non-fatal) rc %d", __func__, rc);
break;
case CFG_SINIT_PROBE_DONE:
s_init->module_init_status = 1;
wake_up(&s_init->state_wait);
break;
case CFG_SINIT_PROBE_WAIT_DONE:
rc = msm_sensor_wait_for_probe_done(s_init);
break;
default:
pr_err("default");
break;
}
return rc;
}
我们之前传进去的参数是CFG_SINIT_PROBE_WAIT_DONE,看下这个
static int msm_sensor_wait_for_probe_done(struct msm_sensor_init_t *s_init)
{
int rc;
int tm = 20000;
if (s_init->module_init_status == 1) {
CDBG("msm_cam_get_module_init_status -2\n");
return 0;
}
rc = wait_event_timeout(s_init->state_wait,
(s_init->module_init_status == 1), msecs_to_jiffies(tm));
if (rc == 0) {
pr_err("%s:%d wait timeout\n", __func__, __LINE__);
rc = -1;
}
return rc;
}
很明显是在等待某些操作的完成,看下这个s_init是怎么获取的
struct msm_sensor_init_t *s_init = v4l2_get_subdevdata(sd);
kernel\msm-3.18\include\media\v4l2-subdev.h
static inline void *v4l2_get_subdevdata(const struct v4l2_subdev *sd)
{
return sd->dev_priv;
}
猜测是等待CFG_SINIT_PROBE执行完成,这个命令是在sensor_init.c中执行的
vendor\qcom\proprietary\mm-camera\mm-camera2\media-controller\modules\sensors\module\sensor_init.c
static boolean sensor_probe(module_sensor_ctrl_t *module_ctrl, int32_t fd,
const char *sensor_name, char *path, struct xmlCameraConfigInfo *xmlConfig)
{
......省略代码......
cfg.cfgtype = CFG_SINIT_PROBE;
cfg.cfg.setting = slave_info;
if (ioctl(fd, VIDIOC_MSM_SENSOR_INIT_CFG, &cfg) < 0) {
SERR("[%s]CFG_SINIT_PROBE failed",sensor_name);
ret = FALSE;
goto ERROR;
}
......省略代码......
}
这里就是初始化的sensor,涉及到驱动方面,不深究,我们往后看CFG_SINIT_PROBE,调用的msm_sensor_driver_probe
static int32_t msm_sensor_driver_create_i2c_v4l_subdev
(struct msm_sensor_ctrl_t *s_ctrl)
{
struct i2c_client *client = s_ctrl->sensor_i2c_client->client;
......省略代码......
rc = camera_init_v4l2(&client->dev, &session_id);
......省略代码......
}
static int32_t msm_sensor_driver_create_v4l_subdev
(struct msm_sensor_ctrl_t *s_ctrl)
{
int32_t rc = 0;
uint32_t session_id = 0;
rc = camera_init_v4l2(&s_ctrl->pdev->dev, &session_id);
......省略代码......
}
int32_t msm_sensor_driver_probe(void *setting,
struct msm_sensor_info_t *probed_info, char *entity_name)
{
......省略代码......
struct msm_camera_sensor_slave_info32 *slave_info32 =
kzalloc(sizeof(*slave_info32), GFP_KERNEL);
if (!slave_info32) {
pr_err("failed: no memory for slave_info32 %pK\n",
slave_info32);
rc = -ENOMEM;
goto free_slave_info;
}
if (copy_from_user((void *)slave_info32, setting,
sizeof(*slave_info32))) {
pr_err("failed: copy_from_user");
rc = -EFAULT;
kfree(slave_info32);
goto free_slave_info;
}
......省略代码......
slave_info->addr_type = slave_info32->addr_type;
//这个是底层传上来的设备id,这个是固定的
slave_info->camera_id = slave_info32->camera_id;
......省略代码......
s_ctrl = g_sctrl[slave_info->camera_id];
......省略代码......
if (s_ctrl->sensor_device_type == MSM_CAMERA_PLATFORM_DEVICE)
rc = msm_sensor_driver_create_v4l_subdev(s_ctrl);
else
rc = msm_sensor_driver_create_i2c_v4l_subdev(s_ctrl);
}
上面的代码可以看到,最终都调用了camera_init_v4l2
kernel\msm-3.18\drivers\media\platform\msm\camera_v2\camera\camera.c
int camera_init_v4l2(struct device *dev, unsigned int *session, enum msm_sensor_camera_id_t camera_id)
{
......省略代码......
strlcpy(v4l2_dev->mdev->model, MSM_CAMERA_NAME,
sizeof(v4l2_dev->mdev->model));
v4l2_dev->mdev->dev = dev;
rc = media_device_register(v4l2_dev->mdev);
......省略代码......
pvdev->vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
pvdev->vdev->entity.group_id = QCAMERA_VNODE_GROUP_ID;
v4l2_dev->notify = NULL;
pvdev->vdev->v4l2_dev = v4l2_dev;
rc = v4l2_device_register(dev, pvdev->vdev->v4l2_dev);
strlcpy(pvdev->vdev->name, "msm-sensor", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &camera_v4l2_fops;
pvdev->vdev->ioctl_ops = &camera_v4l2_ioctl_ops;
pvdev->vdev->minor = -1;
pvdev->vdev->vfl_type = VFL_TYPE_GRABBER;
rc = video_register_device(pvdev->vdev,
VFL_TYPE_GRABBER, -1);
......省略代码......
}
上面列出了一些主要代码,我们依次来看
media_device_register 多媒体设备注册
kernel\msm-3.18\drivers\media\media-device.c
int __must_check __media_device_register(struct media_device *mdev,
struct module *owner)
{
int ret;
if (WARN_ON(mdev->dev == NULL || mdev->model[0] == 0))
return -EINVAL;
mdev->entity_id = 1;
INIT_LIST_HEAD(&mdev->entities);
spin_lock_init(&mdev->lock);
mutex_init(&mdev->graph_mutex);
/* Register the device node. */
mdev->devnode.fops = &media_device_fops;
/*下面是绑定父设备 */
mdev->devnode.parent = mdev->dev;
mdev->devnode.release = media_device_release;
ret = media_devnode_register(&mdev->devnode, owner);
if (ret < 0)
return ret;
/*创建了属性文件,我们可以直接通过属性文件来操作这个文件节点 */
ret = device_create_file(&mdev->devnode.dev, &dev_attr_model);
if (ret < 0) {
media_devnode_unregister(&mdev->devnode);
return ret;
}
return 0;
}
v4l2_device_register v4l2设备注册
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
if (v4l2_dev == NULL)
return -EINVAL;
INIT_LIST_HEAD(&v4l2_dev->subdevs);
spin_lock_init(&v4l2_dev->lock);
mutex_init(&v4l2_dev->ioctl_lock);
v4l2_prio_init(&v4l2_dev->prio);
kref_init(&v4l2_dev->ref);
get_device(dev);
/*下面将当前设备的对象,赋值给v4l2_dev->dev中,这样的话当前的设备就成了v4l2设备,由于当前设备已经注册过了设备文件,所以后续不需要在重新注册设备文件。*/
v4l2_dev->dev = dev;
if (dev == NULL) {
/* If dev == NULL, then name must be filled in by the caller */
if (WARN_ON(!v4l2_dev->name[0]))
return -EINVAL;
return 0;
}
/* Set name to driver name + device name if it is empty. */
if (!v4l2_dev->name[0])
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
dev->driver->name, dev_name(dev));
/*将v4l2设备对象,保存到dev设备中,这个dev可以是platform、char、block等设,我们可以使用dev_get_drvdata(dev)获取到v4l2对象。*/
if (!dev_get_drvdata(dev))
dev_set_drvdata(dev, v4l2_dev);
return 0;
}
上面方法的作用就是将当前设备驱动和v4l2对象进行捆绑,便于挂接子设备
video_register_device
kernel\msm-3.18\drivers\media\v4l2-core\v4l2-dev.c
int __video_register_device(struct video_device *vdev, int type, int nr,
int warn_if_nr_in_use, struct module *owner)
{
......省略代码......
/* Part 1: check device type */
switch (type) {
case VFL_TYPE_GRABBER:
name_base = "video";
break;
case VFL_TYPE_VBI:
name_base = "vbi";
break;
case VFL_TYPE_RADIO:
name_base = "radio";
break;
case VFL_TYPE_SUBDEV:
name_base = "v4l-subdev";
break;
case VFL_TYPE_SDR:
/* Use device name 'swradio' because 'sdr' was already taken. */
name_base = "swradio";
break;
default:
printk(KERN_ERR "%s called with unknown type: %d\n",
__func__, type);
return -EINVAL;
}
......省略代码......
/* 查找空闲次设备号*/
nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
if (nr == minor_cnt)
nr = devnode_find(vdev, 0, minor_cnt);
......省略代码......
/* 查找到第一个空闲的次设备号*/
for (i = 0; i < VIDEO_NUM_DEVICES; i++)
if (video_device[i] == NULL)
break;
......省略代码......
/* minor_offset一般是0,i就是查找到的空闲次设备号,这里总的次设备支持到256个*/
vdev->minor = i + minor_offset;
vdev->num = nr;
......省略代码......
/* 这里根据次设备号将当前video_device保存到video_device[]数组中*/
video_device[vdev->minor] = vdev;
mutex_unlock(&videodev_lock);
if (vdev->ioctl_ops)
determine_valid_ioctls(vdev);
/* Part 3: Initialize the character device */
/* 分配字符设备文件*/
vdev->cdev = cdev_alloc();
if (vdev->cdev == NULL) {
ret = -ENOMEM;
goto cleanup;
}
/* 后面子设备中的ioctl都是通过这里查找的*/
vdev->cdev->ops = &v4l2_fops;
vdev->cdev->owner = owner;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
if (ret < 0) {
printk(KERN_ERR "%s: cdev_add failed\n", __func__);
kfree(vdev->cdev);
vdev->cdev = NULL;
goto cleanup;
}
/* Part 4: register the device with sysfs */
vdev->dev.class = &video_class;
/*主设备号为81 */
vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
/*这里一般是v4l2_device对象 */
vdev->dev.parent = vdev->dev_parent;
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
/*注册该video_device到kernel中 */
ret = device_register(&vdev->dev);
......省略代码......
/* Increase v4l2_device refcount */
/*增加v4l2设备对象引用计数*/
if (vdev->v4l2_dev)
v4l2_device_get(vdev->v4l2_dev);
#if defined(CONFIG_MEDIA_CONTROLLER)
/* Part 5: Register the entity. */
if (vdev->v4l2_dev && vdev->v4l2_dev->mdev &&
vdev->vfl_type != VFL_TYPE_SUBDEV) {
vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
vdev->entity.name = vdev->name;
vdev->entity.info.v4l.major = VIDEO_MAJOR;
vdev->entity.info.v4l.minor = vdev->minor;
ret = media_device_register_entity(vdev->v4l2_dev->mdev,
&vdev->entity);
if (ret < 0)
printk(KERN_WARNING
"%s: media_device_register_entity failed\n",
__func__);
}
#endif
......省略代码......
}
调用的时候传入的是VFL_TYPE_GRABBER,所以创建的字符设备是vedio*,
ioctl(sd_fd, VIDIOC_MSM_SENSOR_INIT_CFG, &cfg)这个方法实际是为了等设备初始化完成,该创建的字符设备都创建了,就往下走
我们继续回到get_num_of_cameras往下走
进入下一个while循环
rc = ioctl(dev_fd, MEDIA_IOC_DEVICE_INFO, &mdev_info);
if(strncmp(mdev_info.model, MSM_CAMERA_NAME, sizeof(mdev_info.model)) != 0) {
close(dev_fd);
dev_fd = -1;
continue;
}
这个去media里找model是MSM_CAMERA_NAME的设备,我们在之前camera_init_v4l2中有看到
strlcpy(v4l2_dev->mdev->model, MSM_CAMERA_NAME,
sizeof(v4l2_dev->mdev->model));
所以这里就是要查找camera设备
接着看下
rc = ioctl(dev_fd, MEDIA_IOC_ENUM_ENTITIES, &entity);
和上面一样的MEDIA_IOC_ENUM_ENTITIES,这里就判断
type == MEDIA_ENT_T_DEVNODE_V4L && group_id == QCAMERA_VNODE_GROUP_ID为相机设备,然后拿到entity.name,实际就是vedio*
网友评论