Chinese translated version of Documentation/media/media_kapi.rst If you have any comment or update to the content, please contact the original document maintainer directly. However, if you have a problem communicating in English you can also ask the Chinese maintainer for help. Contact the Chinese maintainer if this translation is outdated or if there is a problem with the translation. Maintainer: Mauro Carvalho Chehab <mchehab@kernel.org> Chinese maintainer: Fu Wei <tekkamanninja@gmail.com> --------------------------------------------------------------------- Documentation/media/media_kapi.rst çš„ä¸æ–‡ç¿»è¯‘ 如果想评论或更新本文的内容,请直接è”ç³»åŽŸæ–‡æ¡£çš„ç»´æŠ¤è€…ã€‚å¦‚æžœä½ ä½¿ç”¨è‹±æ–‡ 交æµæœ‰å›°éš¾çš„è¯ï¼Œä¹Ÿå¯ä»¥å‘ä¸æ–‡ç‰ˆç»´æŠ¤è€…求助。如果本翻译更新ä¸åŠæ—¶æˆ–者翻 译å˜åœ¨é—®é¢˜ï¼Œè¯·è”ç³»ä¸æ–‡ç‰ˆç»´æŠ¤è€…。 英文版维护者: Mauro Carvalho Chehab <mchehab@kernel.org> ä¸æ–‡ç‰ˆç»´æŠ¤è€…: å‚…ç‚œ Fu Wei <tekkamanninja@gmail.com> ä¸æ–‡ç‰ˆç¿»è¯‘者: å‚…ç‚œ Fu Wei <tekkamanninja@gmail.com> ä¸æ–‡ç‰ˆæ ¡è¯‘者: å‚…ç‚œ Fu Wei <tekkamanninja@gmail.com> 以下为æ£æ–‡ --------------------------------------------------------------------- V4L2 驱动框架概览 ============== 本文档æè¿° V4L2 框架所æ供的å„ç§ç»“构和它们之间的关系。 ä»‹ç» ---- 大部分现代 V4L2 设备由多个 IC 组æˆï¼Œåœ¨ /dev 下导出多个设备节点, 并åŒæ—¶åˆ›å»ºéž V4L2 设备(如 DVBã€ALSAã€FBã€I2C 和红外输入设备)。 由于这ç§ç¡¬ä»¶çš„å¤æ‚性,V4L2 驱动也å˜å¾—éžå¸¸å¤æ‚。 尤其是 V4L2 å¿…é¡»æ”¯æŒ IC 实现音视频的多路å¤ç”¨å’Œç¼–解ç ï¼Œè¿™å°±æ›´å¢žåŠ äº†å…¶ å¤æ‚性。通常这些 IC 通过一个或多个 I2C æ€»çº¿è¿žæŽ¥åˆ°ä¸»æ¡¥é©±åŠ¨å™¨ï¼Œä½†ä¹Ÿå¯ ä½¿ç”¨å…¶ä»–æ€»çº¿ã€‚è¿™äº›è®¾å¤‡ç§°ä¸ºâ€œå设备â€ã€‚ 长期以æ¥ï¼Œè¿™ä¸ªæ¡†æž¶ä»…é™äºŽé€šè¿‡ video_device 结构体创建 V4L 设备节点, 并使用 video_buf 处ç†è§†é¢‘缓冲(注:本文ä¸è®¨è®º video_buf 框架)。 è¿™æ„味ç€æ‰€æœ‰é©±åŠ¨å¿…须自己设置设备实例并连接到å设备。其ä¸ä¸€éƒ¨åˆ†è¦æ£ç¡®åœ° 完æˆæ˜¯æ¯”较å¤æ‚的,使得许多驱动都没有æ£ç¡®åœ°å®žçŽ°ã€‚ 由于框架的缺失,有很多通用代ç 都ä¸å¯é‡å¤åˆ©ç”¨ã€‚ å› æ¤ï¼Œè¿™ä¸ªæ¡†æž¶æž„建所有驱动都需è¦çš„基本结构å—,而统一的框架将使通用代ç 创建æˆå®žç”¨å‡½æ•°å¹¶åœ¨æ‰€æœ‰é©±åŠ¨ä¸å…±äº«å˜å¾—æ›´åŠ å®¹æ˜“ã€‚ 驱动结构 ------- 所有 V4L2 驱动都有如下结构: 1) æ¯ä¸ªè®¾å¤‡å®žä¾‹çš„结构体--包å«å…¶è®¾å¤‡çŠ¶æ€ã€‚ 2) åˆå§‹åŒ–和控制å设备的方法(如果有)。 3) 创建 V4L2 设备节点 (/dev/videoXã€/dev/vbiX å’Œ /dev/radioX) 并跟踪设备节点的特定数æ®ã€‚ 4) 特定文件å¥æŸ„结构体--包å«æ¯ä¸ªæ–‡ä»¶å¥æŸ„çš„æ•°æ®ã€‚ 5) 视频缓冲处ç†ã€‚ 以下是它们的åˆç•¥å…³ç³»å›¾ï¼š device instances(设备实例) | +-sub-device instances(å设备实例) | \-V4L2 device nodes(V4L2 设备节点) | \-filehandle instances(文件å¥æŸ„实例) 框架结构 ------- 该框架éžå¸¸ç±»ä¼¼é©±åŠ¨ç»“构:它有一个 v4l2_device 结构用于ä¿å˜è®¾å¤‡ 实例的数æ®ï¼›ä¸€ä¸ª v4l2_subdev 结构体代表å设备实例;video_device 结构体ä¿å˜ V4L2 设备节点的数æ®ï¼›å°†æ¥ v4l2_fh 结构体将跟踪文件å¥æŸ„ 实例(暂未尚未实现)。 V4L2 框架也å¯ä¸Žåª’体框架整åˆï¼ˆå¯é€‰çš„)。如果驱动设置了 v4l2_device 结构体的 mdev 域,å设备和视频节点的入å£å°†è‡ªåŠ¨å‡ºçŽ°åœ¨åª’体框架ä¸ã€‚ v4l2_device 结构体 ---------------- æ¯ä¸ªè®¾å¤‡å®žä¾‹éƒ½é€šè¿‡ v4l2_device (v4l2-device.h)结构体æ¥è¡¨ç¤ºã€‚ 简å•è®¾å¤‡å¯ä»¥ä»…分é…这个结构体,但在大多数情况下,都会将这个结构体 嵌入到一个更大的结构体ä¸ã€‚ ä½ å¿…é¡»æ³¨å†Œè¿™ä¸ªè®¾å¤‡å®žä¾‹ï¼š v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev); 注册æ“作将会åˆå§‹åŒ– v4l2_device 结构体。如果 dev->driver_data 域 为 NULLï¼Œå°±å°†å…¶æŒ‡å‘ v4l2_dev。 需è¦ä¸Žåª’体框架整åˆçš„驱动必须手动设置 dev->driver_data,指å‘åŒ…å« v4l2_device 结构体实例的驱动特定设备结构体。这å¯ä»¥åœ¨æ³¨å†Œ V4L2 设备 实例å‰é€šè¿‡ dev_set_drvdata() 函数完æˆã€‚åŒæ—¶å¿…须设置 v4l2_device 结构体的 mdev 域,指å‘适当的åˆå§‹åŒ–并注册过的 media_device 实例。 如果 v4l2_dev->name 为空,则它将被设置为从 dev ä¸è¡ç”Ÿå‡ºçš„值(为了 æ›´åŠ ç²¾ç¡®ï¼Œå½¢å¼ä¸ºé©±åŠ¨ååŽè·Ÿ bus_idï¼‰ã€‚å¦‚æžœä½ åœ¨è°ƒç”¨ v4l2_device_register å‰å·²ç»è®¾ç½®å¥½äº†ï¼Œåˆ™ä¸ä¼šè¢«ä¿®æ”¹ã€‚如果 dev 为 NULLï¼Œåˆ™ä½ *å¿…é¡»*在调用 v4l2_device_register å‰è®¾ç½® v4l2_dev->name。 ä½ å¯ä»¥åŸºäºŽé©±åŠ¨å和驱动的全局 atomic_t 类型的实例编å·ï¼Œé€šè¿‡ v4l2_device_set_name() 设置 nameã€‚è¿™æ ·ä¼šç”Ÿæˆç±»ä¼¼ ivtv0ã€ivtv1 ç‰ åå—。若驱动å以数å—结尾,则会在编å·å’Œé©±åŠ¨åé—´æ’å…¥ä¸€ä¸ªç ´æŠ˜å·ï¼Œå¦‚: cx18-0ã€cx18-1 ç‰ã€‚æ¤å‡½æ•°è¿”回实例编å·ã€‚ 第一个 “dev†å‚æ•°é€šå¸¸æ˜¯ä¸€ä¸ªæŒ‡å‘ pci_devã€usb_interface 或 platform_device 的指针。很少使其为 NULL,除éžæ˜¯ä¸€ä¸ªISA设备或者 当一个设备创建了多个 PCI 设备,使得 v4l2_dev æ— æ³•ä¸Žä¸€ä¸ªç‰¹å®šçš„çˆ¶è®¾å¤‡ å…³è”。 ä½ ä¹Ÿå¯ä»¥æ供一个 notify() 回调,使å设备å¯ä»¥è°ƒç”¨å®ƒå®žçŽ°äº‹ä»¶é€šçŸ¥ã€‚ 但这个设置与å设备相关。å设备支æŒçš„任何通知必须在 include/media/<subdevice>.h ä¸å®šä¹‰ä¸€ä¸ªæ¶ˆæ¯å¤´ã€‚ 注销 v4l2_device 使用如下函数: v4l2_device_unregister(struct v4l2_device *v4l2_dev); 如果 dev->driver_data åŸŸæŒ‡å‘ v4l2_dev,将会被é‡ç½®ä¸º NULL。注销åŒæ—¶ 会自动从设备ä¸æ³¨é”€æ‰€æœ‰å设备。 å¦‚æžœä½ æœ‰ä¸€ä¸ªçƒæ’拔设备(如USB设备),则当æ–å¼€å‘ç”Ÿæ—¶ï¼Œçˆ¶è®¾å¤‡å°†æ— æ•ˆã€‚ 由于 v4l2_device 有一个指å‘父设备的指针必须被清除,åŒæ—¶æ ‡å¿—父设备 已消失,所以必须调用以下函数: v4l2_device_disconnect(struct v4l2_device *v4l2_dev); 这个函数并*ä¸*注销åè®¾å¤‡ï¼Œå› æ¤ä½ ä¾ç„¶è¦è°ƒç”¨ v4l2_device_unregister() å‡½æ•°ã€‚å¦‚æžœä½ çš„é©±åŠ¨å™¨å¹¶éžçƒæ’拔的,就没有必è¦è°ƒç”¨ v4l2_device_disconnect()。 æœ‰æ—¶ä½ éœ€è¦é历所有被特定驱动注册的设备。这通常å‘生在多个设备驱动使用 åŒä¸€ä¸ªç¡¬ä»¶çš„情况下。如:ivtvfb 驱动是一个使用 ivtv 硬件的帧缓冲驱动, åŒæ—¶ alsa 驱动也使用æ¤ç¡¬ä»¶ã€‚ ä½ å¯ä»¥ä½¿ç”¨å¦‚下例程é历所有注册的设备: static int callback(struct device *dev, void *p) { struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); /* 测试这个设备是å¦å·²ç»åˆå§‹åŒ– */ if (v4l2_dev == NULL) return 0; ... return 0; } int iterate(void *p) { struct device_driver *drv; int err; /* 在PCI 总线上查找ivtv驱动。 pci_bus_type是全局的. 对于USB总线使用usb_bus_type。 */ drv = driver_find("ivtv", &pci_bus_type); /* é历所有的ivtv设备实例 */ err = driver_for_each_device(drv, NULL, p, callback); put_driver(drv); return err; } æœ‰æ—¶ä½ éœ€è¦ä¸€ä¸ªè®¾å¤‡å®žä¾‹çš„è¿è¡Œè®¡æ•°ã€‚è¿™ä¸ªé€šå¸¸ç”¨äºŽæ˜ å°„ä¸€ä¸ªè®¾å¤‡å®žä¾‹åˆ°ä¸€ä¸ª 模å—选择数组的索引。 推è方法如下: static atomic_t drv_instance = ATOMIC_INIT(0); static int drv_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) { ... state->instance = atomic_inc_return(&drv_instance) - 1; } å¦‚æžœä½ æœ‰å¤šä¸ªè®¾å¤‡èŠ‚ç‚¹ï¼Œå¯¹äºŽçƒæ’拔设备,知é“何时注销 v4l2_device 结构体 å°±æ¯”è¾ƒå›°éš¾ã€‚ä¸ºæ¤ v4l2_device 有引用计数支æŒã€‚当调用 video_register_device æ—¶å¢žåŠ å¼•ç”¨è®¡æ•°ï¼Œè€Œè®¾å¤‡èŠ‚ç‚¹é‡Šæ”¾æ—¶å‡å°å¼•ç”¨è®¡æ•°ã€‚当引用计数为零,则 v4l2_device çš„release() å›žè°ƒå°†è¢«æ‰§è¡Œã€‚ä½ å°±å¯ä»¥åœ¨æ¤æ—¶åšæœ€åŽçš„清ç†å·¥ä½œã€‚ 如果创建了其他设备节点(比如 ALSAï¼‰ï¼Œåˆ™ä½ å¯ä»¥é€šè¿‡ä»¥ä¸‹å‡½æ•°æ‰‹åŠ¨å¢žå‡ 引用计数: void v4l2_device_get(struct v4l2_device *v4l2_dev); 或: int v4l2_device_put(struct v4l2_device *v4l2_dev); 由于引用技术åˆå§‹åŒ–为 1 ï¼Œä½ ä¹Ÿéœ€è¦åœ¨ disconnect() 回调(对于 USB è®¾å¤‡ï¼‰ä¸ è°ƒç”¨ v4l2_device_put,或者 remove() 回调(例如对于 PCI 设备),å¦åˆ™ 引用计数将永远ä¸ä¼šä¸º 0 。 v4l2_subdev结构体 ------------------ 许多驱动需è¦ä¸Žå设备通信。这些设备å¯ä»¥å®Œæˆå„ç§ä»»åŠ¡ï¼Œä½†é€šå¸¸ä»–们负责 音视频å¤ç”¨å’Œç¼–解ç 。如网络摄åƒå¤´çš„åè®¾å¤‡é€šå¸¸æ˜¯ä¼ æ„Ÿå™¨å’Œæ‘„åƒå¤´æŽ§åˆ¶å™¨ã€‚ 这些一般为 I2C 接å£è®¾å¤‡ï¼Œä½†å¹¶ä¸ä¸€å®šéƒ½æ˜¯ã€‚为了给驱动æ供调用å设备的 统一接å£ï¼Œv4l2_subdev 结构体(v4l2-subdev.h)产生了。 æ¯ä¸ªå设备驱动都必须有一个 v4l2_subdev 结构体。这个结构体å¯ä»¥å•ç‹¬ 代表一个简å•çš„å设备,也å¯ä»¥åµŒå…¥åˆ°ä¸€ä¸ªæ›´å¤§çš„结构体ä¸ï¼Œä¸Žæ›´å¤šè®¾å¤‡çŠ¶æ€ ä¿¡æ¯ä¿å˜åœ¨ä¸€èµ·ã€‚通常有一个下级设备结构体(比如:i2c_client)包å«äº† å†…æ ¸åˆ›å»ºçš„è®¾å¤‡æ•°æ®ã€‚建议使用 v4l2_set_subdevdata() 将这个结构体的 指针ä¿å˜åœ¨ v4l2_subdev çš„ç§æœ‰æ•°æ®åŸŸ(dev_priv)ä¸ã€‚这使得通过 v4l2_subdev 找到实际的低层总线特定设备数æ®å˜å¾—容易。 ä½ åŒæ—¶éœ€è¦ä¸€ä¸ªä»Žä½Žå±‚ç»“æž„ä½“èŽ·å– v4l2_subdev 指针的方法。对于常用的 i2c_client 结构体,i2c_set_clientdata() 函数å¯ç”¨äºŽä¿å˜ä¸€ä¸ª v4l2_subdev æŒ‡é’ˆï¼›å¯¹äºŽå…¶ä»–æ€»çº¿ä½ å¯èƒ½éœ€è¦ä½¿ç”¨å…¶ä»–相关函数。 桥驱动ä¸ä¹Ÿåº”ä¿å˜æ¯ä¸ªå设备的ç§æœ‰æ•°æ®ï¼Œæ¯”如一个指å‘特定桥的å„设备ç§æœ‰ æ•°æ®çš„æŒ‡é’ˆã€‚ä¸ºæ¤ v4l2_subdev 结构体æ供主机ç§æœ‰æ•°æ®åŸŸ(host_priv), 并å¯é€šè¿‡ v4l2_get_subdev_hostdata() å’Œ v4l2_set_subdev_hostdata() 访问。 ä»Žæ€»çº¿æ¡¥é©±åŠ¨çš„è§†è§’ï¼Œé©±åŠ¨åŠ è½½å设备模å—并以æŸç§æ–¹å¼èŽ·å¾— v4l2_subdev 结构体指针。对于 i2c 总线设备相对简å•ï¼šè°ƒç”¨ i2c_get_clientdata()。 对于其他总线也需è¦åšç±»ä¼¼çš„æ“作。针对 I2C 总线上的åè®¾å¤‡è¾…åŠ©å‡½æ•°å¸®ä½ å®Œæˆäº†å¤§éƒ¨åˆ†å¤æ‚的工作。 æ¯ä¸ª v4l2_subdev 都包å«å设备驱动需è¦å®žçŽ°çš„函数指针(如果对æ¤è®¾å¤‡ ä¸é€‚用,å¯ä¸ºNULL)。由于å设备å¯å®Œæˆè®¸å¤šä¸åŒçš„工作,而在一个庞大的 函数指针结构体ä¸é€šå¸¸ä»…有少数有用的函数实现其功能肯定ä¸åˆé€‚。所以, å‡½æ•°æŒ‡é’ˆæ ¹æ®å…¶å®žçŽ°çš„功能被分类,æ¯ä¸€ç±»éƒ½æœ‰è‡ªå·±çš„函数指针结构体。 顶层函数指针结构体包å«äº†æŒ‡å‘å„类函数指针结构体的指针,如果å设备驱动 ä¸æ”¯æŒè¯¥ç±»å‡½æ•°ä¸çš„任何一个功能,则指å‘该类结构体的指针为NULL。 这些结构体定义如下: struct v4l2_subdev_core_ops { int (*log_status)(struct v4l2_subdev *sd); int (*init)(struct v4l2_subdev *sd, u32 val); ... }; struct v4l2_subdev_tuner_ops { ... }; struct v4l2_subdev_audio_ops { ... }; struct v4l2_subdev_video_ops { ... }; struct v4l2_subdev_pad_ops { ... }; struct v4l2_subdev_ops { const struct v4l2_subdev_core_ops *core; const struct v4l2_subdev_tuner_ops *tuner; const struct v4l2_subdev_audio_ops *audio; const struct v4l2_subdev_video_ops *video; const struct v4l2_subdev_pad_ops *video; }; å…¶ä¸ coreï¼ˆæ ¸å¿ƒï¼‰å‡½æ•°é›†é€šå¸¸å¯ç”¨äºŽæ‰€æœ‰å设备,其他类别的实现ä¾èµ–于 å设备。如视频设备å¯èƒ½ä¸æ”¯æŒéŸ³é¢‘æ“作函数,å之亦然。 è¿™æ ·çš„è®¾ç½®åœ¨é™åˆ¶äº†å‡½æ•°æŒ‡é’ˆæ•°é‡çš„åŒæ—¶ï¼Œè¿˜ä½¿å¢žåŠ æ–°çš„æ“作函数和分类 å˜å¾—较为容易。 å设备驱动å¯ä½¿ç”¨å¦‚下函数åˆå§‹åŒ– v4l2_subdev 结构体: v4l2_subdev_init(sd, &ops); 然åŽï¼Œä½ 必须用一个唯一的åå—åˆå§‹åŒ– subdev->name,并åˆå§‹åŒ–模å—çš„ owner 域。若使用 i2c è¾…åŠ©å‡½æ•°ï¼Œè¿™äº›éƒ½ä¼šå¸®ä½ å¤„ç†å¥½ã€‚ 若需åŒåª’体框架整åˆï¼Œä½ 必须调用 media_entity_pads_init() åˆå§‹åŒ– v4l2_subdev 结构体ä¸çš„ media_entity 结构体(entity 域): struct media_pad *pads = &my_sd->pads; int err; err = media_entity_pads_init(&sd->entity, npads, pads); pads 数组必须预先åˆå§‹åŒ–ã€‚æ— é¡»æ‰‹åŠ¨è®¾ç½® media_entity çš„ type å’Œ name 域,但如有必è¦ï¼Œrevision 域必须åˆå§‹åŒ–。 当(任何)å设备节点被打开/å…³é—,对 entity 的引用将被自动获å–/释放。 在å设备被注销之åŽï¼Œä¸è¦å¿˜è®°æ¸…ç† media_entity 结构体: media_entity_cleanup(&sd->entity); 如果å设备驱动趋å‘于处ç†è§†é¢‘并整åˆè¿›äº†åª’体框架,必须使用 v4l2_subdev_pad_ops 替代 v4l2_subdev_video_ops å®žçŽ°æ ¼å¼ç›¸å…³çš„功能。 è¿™ç§æƒ…况下,å设备驱动应该设置 link_validate 域,以æ供它自身的链接 验è¯å‡½æ•°ã€‚链接验è¯å‡½æ•°åº”对管é“(两端链接的都是 V4L2 å设备)ä¸çš„æ¯ä¸ª 链接调用。驱动还è¦è´Ÿè´£éªŒè¯åè®¾å¤‡å’Œè§†é¢‘èŠ‚ç‚¹é—´æ ¼å¼é…置的æ£ç¡®æ€§ã€‚ 如果 link_validate æ“作没有设置,默认的 v4l2_subdev_link_validate_default() 函数将会被调用。这个函数ä¿è¯å®½ã€é«˜å’Œåª’体总线åƒç´ æ ¼å¼åœ¨é“¾æŽ¥çš„收å‘两端 都一致。å设备驱动除了它们自己的检测外,也å¯ä»¥è‡ªç”±ä½¿ç”¨è¿™ä¸ªå‡½æ•°ä»¥æ‰§è¡Œ 上é¢æ到的检查。 设备(桥)驱动程åºå¿…é¡»å‘ v4l2_device 注册 v4l2_subdev: int err = v4l2_device_register_subdev(v4l2_dev, sd); 如果å设备模å—在它注册å‰æ¶ˆå¤±ï¼Œè¿™ä¸ªæ“作å¯èƒ½å¤±è´¥ã€‚在这个函数æˆåŠŸè¿”回åŽï¼Œ subdev->dev 域就指å‘了 v4l2_device。 如果 v4l2_device 父设备的 mdev åŸŸä¸ºéž NULL 值,则å设备实体将被自动 注册为媒体设备。 注销å设备则å¯ç”¨å¦‚下函数: v4l2_device_unregister_subdev(sd); æ¤åŽï¼Œå设备模å—å°±å¯å¸è½½ï¼Œä¸” sd->dev == NULL。 注册之设备åŽï¼Œå¯é€šè¿‡ä»¥ä¸‹æ–¹å¼ç›´æŽ¥è°ƒç”¨å…¶æ“作函数: err = sd->ops->core->g_std(sd, &norm); 但使用如下å®ä¼šæ¯”较容易且åˆé€‚: err = v4l2_subdev_call(sd, core, g_std, &norm); 这个å®å°†ä¼šåš NULL 指针检查,如果 subdev 为 NULL,则返回-ENODEV;如果 subdev->core 或 subdev->core->g_std 为 NULL,则返回 -ENOIOCTLCMDï¼› å¦åˆ™å°†è¿”回 subdev->ops->core->g_std ops 调用的实际结果。 有时也å¯èƒ½åŒæ—¶è°ƒç”¨æ‰€æœ‰æˆ–一系列å设备的æŸä¸ªæ“作函数: v4l2_device_call_all(v4l2_dev, 0, core, g_std, &norm); 任何ä¸æ”¯æŒæ¤æ“作的åè®¾å¤‡éƒ½ä¼šè¢«è·³è¿‡ï¼Œå¹¶å¿½ç•¥é”™è¯¯è¿”å›žå€¼ã€‚ä½†å¦‚æžœä½ éœ€è¦ æ£€æŸ¥å‡ºé”™ç ,则å¯ä½¿ç”¨å¦‚下函数: err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_std, &norm); 除 -ENOIOCTLCMD 外的任何错误都会跳出循环并返回错误值。如果(除 -ENOIOCTLCMD 外)没有错误å‘生,则返回 0。 对于以上两个函数的第二个å‚数为组 ID。如果为 0,则所有å设备都会执行 这个æ“ä½œã€‚å¦‚æžœä¸ºéž 0 值,则åªæœ‰é‚£äº›ç»„ ID 匹é…çš„å设备æ‰ä¼šæ‰§è¡Œæ¤æ“作。 在桥驱动注册一个å设备å‰ï¼Œå¯ä»¥è®¾ç½® sd->grp_id 为任何期望值(默认值为 0)。这个值属于桥驱动,且å设备驱动将ä¸ä¼šä¿®æ”¹å’Œä½¿ç”¨å®ƒã€‚ 组 ID 赋予了桥驱动更多对于如何调用回调的控制。例如,电路æ¿ä¸Šæœ‰å¤šä¸ª 音频芯片,æ¯ä¸ªéƒ½æœ‰æ”¹å˜éŸ³é‡çš„能力。但当用户想è¦æ”¹å˜éŸ³é‡çš„时候,通常 åªæœ‰ä¸€ä¸ªä¼šè¢«å®žé™…ä½¿ç”¨ã€‚ä½ å¯ä»¥å¯¹è¿™æ ·çš„å设备设置组 ID 为(例如 AUDIO_CONTROLLER) 并在调用 v4l2_device_call_all() 时指定它为组 ID 值。这就ä¿è¯äº†åªæœ‰ 需è¦çš„å设备æ‰ä¼šæ‰§è¡Œè¿™ä¸ªå›žè°ƒã€‚ 如果å设备需è¦é€šçŸ¥å®ƒçš„ v4l2_device 父设备一个事件,å¯ä»¥è°ƒç”¨ v4l2_subdev_notify(sd, notification, arg)。这个å®æ£€æŸ¥æ˜¯å¦æœ‰ä¸€ä¸ª notify() 回调被注册,如果没有,返回 -ENODEV。å¦åˆ™è¿”回 notify() 调用 结果。 使用 v4l2_subdev 的好处在于它是一个通用结构体,且ä¸åŒ…å«ä»»ä½•åº•å±‚硬件 ä¿¡æ¯ã€‚所有驱动å¯ä»¥åŒ…å«å¤šä¸ª I2C 总线的å设备,但也有å设备是通过 GPIO 控制。这个区别仅在é…置设备时有关系,一旦å设备注册完æˆï¼Œå¯¹äºŽ v4l2 å系统æ¥è¯´å°±å®Œå…¨é€æ˜Žäº†ã€‚ V4L2 å设备用户空间API -------------------- 除了通过 v4l2_subdev_ops ç»“æž„å¯¼å‡ºçš„å†…æ ¸ API,V4L2 å设备也å¯ä»¥ç›´æŽ¥ 通过用户空间应用程åºæ¥æŽ§åˆ¶ã€‚ å¯ä»¥åœ¨ /dev ä¸åˆ›å»ºå为 v4l-subdevX 设备节点,以通过其直接访问å设备。 如果å设备支æŒç”¨æˆ·ç©ºé—´ç›´æŽ¥é…置,必须在注册å‰è®¾ç½® V4L2_SUBDEV_FL_HAS_DEVNODE æ ‡å¿—ã€‚ 注册å设备之åŽï¼Œ v4l2_device 驱动会通过调用 v4l2_device_register_subdev_nodes() 函数为所有已注册并设置了 V4L2_SUBDEV_FL_HAS_DEVNODE çš„å设备创建 设备节点。这些设备节点会在åè®¾å¤‡æ³¨é”€æ—¶è‡ªåŠ¨åˆ é™¤ã€‚ è¿™äº›è®¾å¤‡èŠ‚ç‚¹å¤„ç† V4L2 API 的一个å集。 VIDIOC_QUERYCTRL VIDIOC_QUERYMENU VIDIOC_G_CTRL VIDIOC_S_CTRL VIDIOC_G_EXT_CTRLS VIDIOC_S_EXT_CTRLS VIDIOC_TRY_EXT_CTRLS 这些 ioctls 控制与 V4L2 ä¸å®šä¹‰çš„一致。他们行为相åŒï¼Œå”¯ä¸€çš„ ä¸åŒæ˜¯ä»–们åªå¤„ç†åè®¾å¤‡çš„æŽ§åˆ¶å®žçŽ°ã€‚æ ¹æ®é©±åŠ¨ç¨‹åºï¼Œè¿™äº›æŽ§åˆ¶ä¹Ÿ å¯ä»¥é€šè¿‡ä¸€ä¸ªï¼ˆæˆ–多个) V4L2 设备节点访问。 VIDIOC_DQEVENT VIDIOC_SUBSCRIBE_EVENT VIDIOC_UNSUBSCRIBE_EVENT 这些 ioctls 事件与 V4L2 ä¸å®šä¹‰çš„一致。他们行为相åŒï¼Œå”¯ä¸€çš„ ä¸åŒæ˜¯ä»–们åªå¤„ç†åè®¾å¤‡äº§ç”Ÿçš„äº‹ä»¶ã€‚æ ¹æ®é©±åŠ¨ç¨‹åºï¼Œè¿™äº›äº‹ä»¶ä¹Ÿ å¯ä»¥é€šè¿‡ä¸€ä¸ªï¼ˆæˆ–多个) V4L2 设备节点上报。 è¦ä½¿ç”¨äº‹ä»¶é€šçŸ¥çš„å设备驱动,在注册å设备å‰å¿…须在 v4l2_subdev::flags ä¸è®¾ç½® V4L2_SUBDEV_USES_EVENTS 并在 v4l2_subdev::nevents ä¸åˆå§‹åŒ–事件队列深度。注册完æˆåŽï¼Œäº‹ä»¶ä¼šåœ¨ v4l2_subdev::devnode 设备节点ä¸åƒé€šå¸¸ä¸€æ ·è¢«æŽ’队。 为æ£ç¡®æ”¯æŒäº‹ä»¶æœºåˆ¶ï¼Œpoll() 文件æ“作也应被实现。 ç§æœ‰ ioctls ä¸åœ¨ä»¥ä¸Šåˆ—表ä¸çš„所有 ioctls 会通过 core::ioctl æ“ä½œç›´æŽ¥ä¼ é€’ ç»™å设备驱动。 I2C å设备驱动 ------------- 由于这些驱动很常è§ï¼Œæ‰€ä»¥å†…特æ供了特定的辅助函数(v4l2-common.h)让这些 è®¾å¤‡çš„ä½¿ç”¨æ›´åŠ å®¹æ˜“ã€‚ æ·»åŠ v4l2_subdev 支æŒçš„推è方法是让 I2C 驱动将 v4l2_subdev 结构体 嵌入到为æ¯ä¸ª I2C 设备实例创建的状æ€ç»“构体ä¸ã€‚而最简å•çš„è®¾å¤‡æ²¡æœ‰çŠ¶æ€ ç»“æž„ä½“ï¼Œæ¤æ—¶å¯ä»¥ç›´æŽ¥åˆ›å»ºä¸€ä¸ª v4l2_subdev 结构体。 一个典型的状æ€ç»“构体如下所示(‘chipname’用芯片å代替): struct chipname_state { struct v4l2_subdev sd; ... /* é™„åŠ çš„çŠ¶æ€åŸŸ*/ }; åˆå§‹åŒ– v4l2_subdev 结构体的方法如下: v4l2_i2c_subdev_init(&state->sd, client, subdev_ops); 这个函数将填充 v4l2_subdev 结构体ä¸çš„所有域,并ä¿è¯ v4l2_subdev å’Œ i2c_client 都指å‘å½¼æ¤ã€‚ åŒæ—¶ï¼Œä½ 也应该为从 v4l2_subdev 指针找到 chipname_state 结构体指针 æ·»åŠ ä¸€ä¸ªè¾…åŠ©å†…è”函数。 static inline struct chipname_state *to_state(struct v4l2_subdev *sd) { return container_of(sd, struct chipname_state, sd); } 使用以下函数å¯ä»¥é€šè¿‡ v4l2_subdev 结构体指针获得 i2c_client 结构体 指针: struct i2c_client *client = v4l2_get_subdevdata(sd); 而以下函数则相å,通过 i2c_client 结构体指针获得 v4l2_subdev 结构体 指针: struct v4l2_subdev *sd = i2c_get_clientdata(client); 当 remove()函数被调用å‰ï¼Œå¿…é¡»ä¿è¯å…ˆè°ƒç”¨ v4l2_device_unregister_subdev(sd)。 æ¤æ“作将会从桥驱动ä¸æ³¨é”€å设备。å³ä½¿å设备没有注册,调用æ¤å‡½æ•°ä¹Ÿæ˜¯ 安全的。 å¿…é¡»è¿™æ ·åšçš„åŽŸå› æ˜¯ï¼šå½“æ¡¥é©±åŠ¨æ³¨é”€ i2c 适é…器时,remove()回调函数 会被那个适é…器上的 i2c 设备调用。æ¤åŽï¼Œç›¸åº”çš„ v4l2_subdev 结构体 å°±ä¸å˜åœ¨äº†ï¼Œæ‰€æœ‰å®ƒä»¬å¿…须先被注销。在 remove()回调函数ä¸è°ƒç”¨ v4l2_device_unregister_subdev(sd),å¯ä»¥ä¿è¯æ‰§è¡Œæ€»æ˜¯æ£ç¡®çš„。 桥驱动也有一些辅组函数å¯ç”¨ï¼š struct v4l2_subdev *sd = v4l2_i2c_new_subdev(v4l2_dev, adapter, "module_foo", "chipid", 0x36, NULL); è¿™ä¸ªå‡½æ•°ä¼šåŠ è½½ç»™å®šçš„æ¨¡å—(如果没有模å—需è¦åŠ 载,å¯ä»¥ä¸º NULL), 并用给定的 i2c 适é…器结构体指针(i2c_adapter)和 器件地å€ï¼ˆchip/address) 作为å‚数调用 i2c_new_device()。如果一切顺利,则就在 v4l2_device ä¸æ³¨å†Œäº†å设备。 ä½ ä¹Ÿå¯ä»¥åˆ©ç”¨ v4l2_i2c_new_subdev()的最åŽä¸€ä¸ªå‚æ•°ï¼Œä¼ é€’ä¸€ä¸ªå¯èƒ½çš„ I2C 地å€æ•°ç»„,让函数自动探测。这些探测地å€åªæœ‰åœ¨å‰ä¸€ä¸ªå‚数为 0 çš„ 情况下使用。éžé›¶å‚æ•°æ„味ç€ä½ 知é“准确的 i2c 地å€ï¼Œæ‰€ä»¥æ¤æ—¶æ— 须进行 探测。 如果出错,两个函数都返回 NULL。 注æ„ï¼šä¼ é€’ç»™ v4l2_i2c_new_subdev()çš„ chipid 通常与模å—å一致。 它å…è®¸ä½ æŒ‡å®šä¸€ä¸ªèŠ¯ç‰‡çš„å˜ä½“,比如“saa7114â€æˆ–“saa7115â€ã€‚一般通过 i2c 驱动自动探测。chipid 的使用是在今åŽéœ€è¦æ·±å…¥äº†è§£çš„事情。这个与 i2c 驱动ä¸åŒï¼Œè¾ƒå®¹æ˜“混淆。è¦çŸ¥é“支æŒå“ªäº›èŠ¯ç‰‡å˜ä½“ï¼Œä½ å¯ä»¥æŸ¥é˜… i2c 驱动代ç çš„ i2c_device_id 表,上é¢åˆ—出了所有å¯èƒ½æ”¯æŒçš„芯片。 还有两个辅助函数: v4l2_i2c_new_subdev_cfgï¼šè¿™ä¸ªå‡½æ•°æ·»åŠ æ–°çš„ irq å’Œ platform_data å‚数,并有‘addr’和‘probed_addrs’å‚数:如果 addr éžé›¶ï¼Œåˆ™è¢«ä½¿ç”¨ (ä¸æŽ¢æµ‹å˜ä½“),å¦åˆ™ probed_addrs ä¸çš„地å€å°†ç”¨äºŽè‡ªåŠ¨æŽ¢æµ‹ã€‚ 例如:以下代ç 将会探测地å€ï¼ˆ0x10): struct v4l2_subdev *sd = v4l2_i2c_new_subdev_cfg(v4l2_dev, adapter, "module_foo", "chipid", 0, NULL, 0, I2C_ADDRS(0x10)); v4l2_i2c_new_subdev_board 使用一个 i2c_board_info 结构体,将其 替代 irqã€platform_data å’Œ add rå‚æ•°ä¼ é€’ç»™ i2c 驱动。 如果åè®¾å¤‡æ”¯æŒ s_config æ ¸å¿ƒæ“作,这个æ“作会在å设备é…置好之åŽä»¥ irq å’Œ platform_data 为å‚数调用。早期的 v4l2_i2c_new_(probed_)subdev 函数 åŒæ ·ä¹Ÿä¼šè°ƒç”¨ s_config,但仅在 irq 为 0 且 platform_data 为 NULL 时。 video_device结构体 ----------------- 在 /dev ç›®å½•ä¸‹çš„å®žé™…è®¾å¤‡èŠ‚ç‚¹æ ¹æ® video_device 结构体(v4l2-dev.h) 创建。æ¤ç»“构体既å¯ä»¥åŠ¨æ€åˆ†é…也å¯ä»¥åµŒå…¥åˆ°ä¸€ä¸ªæ›´å¤§çš„结构体ä¸ã€‚ 动æ€åˆ†é…方法如下: struct video_device *vdev = video_device_alloc(); if (vdev == NULL) return -ENOMEM; vdev->release = video_device_release; 如果将其嵌入到一个大结构体ä¸ï¼Œåˆ™å¿…须自己实现 release()回调。 struct video_device *vdev = &my_vdev->vdev; vdev->release = my_vdev_release; release()回调必须被设置,且在最åŽä¸€ä¸ª video_device ç”¨æˆ·é€€å‡ºä¹‹åŽ è¢«è°ƒç”¨ã€‚ 默认的 video_device_release()回调åªæ˜¯è°ƒç”¨ kfree æ¥é‡Šæ”¾ä¹‹å‰åˆ†é…çš„ 内å˜ã€‚ ä½ åº”è¯¥è®¾ç½®è¿™äº›åŸŸï¼š - v4l2_dev: 设置为 v4l2_device 父设备。 - name: 设置为唯一的æ述性设备å。 - fops: 设置为已有的 v4l2_file_operations 结构体。 - ioctl_ops: å¦‚æžœä½ ä½¿ç”¨v4l2_ioctl_ops æ¥ç®€åŒ– ioctl 的维护 (强烈建议使用,且将æ¥å¯èƒ½å˜ä¸ºå¼ºåˆ¶æ€§çš„!),然åŽè®¾ç½®ä½ 自己的 v4l2_ioctl_ops 结构体. - lock: å¦‚æžœä½ è¦åœ¨é©±åŠ¨ä¸å®žçŽ°æ‰€æœ‰çš„é”æ“作,则设为 NULL 。å¦åˆ™ å°±è¦è®¾ç½®ä¸€ä¸ªæŒ‡å‘ struct mutex_lock 结构体的指针,这个é”å°† 在 unlocked_ioctl 文件æ“作被调用å‰ç”±å†…æ ¸èŽ·å¾—ï¼Œå¹¶åœ¨è°ƒç”¨è¿”å›žåŽ é‡Šæ”¾ã€‚è¯¦è§ä¸‹ä¸€èŠ‚。 - prio: ä¿æŒå¯¹ä¼˜å…ˆçº§çš„跟踪。用于实现 VIDIOC_G/S_PRIORITY。如果 设置为 NULL,则会使用 v4l2_device ä¸çš„ v4l2_prio_state 结构体。 如果è¦å¯¹æ¯ä¸ªè®¾å¤‡èŠ‚点(组)实现独立的优先级,å¯ä»¥å°†å…¶æŒ‡å‘自己 实现的 v4l2_prio_state 结构体。 - parent: 仅在使用 NULL 作为父设备结构体å‚数注册 v4l2_device æ—¶ 设置æ¤å‚数。åªæœ‰åœ¨ä¸€ä¸ªç¡¬ä»¶è®¾å¤‡åŒ…å«å¤šä¸€ä¸ª PCI 设备,共享åŒä¸€ä¸ª v4l2_device æ ¸å¿ƒæ—¶æ‰ä¼šå‘生。 cx88 驱动就是一个例å:一个 v4l2_device ç»“æž„ä½“æ ¸å¿ƒï¼Œè¢«ä¸€ä¸ªè£¸çš„ 视频 PCI 设备(cx8800)和一个 MPEG PCI 设备(cx8802)共用。由于 v4l2_device æ— æ³•ä¸Žç‰¹å®šçš„ PCI 设备关è”,所有没有设置父设备。但当 video_device é…ç½®åŽï¼Œå°±çŸ¥é“使用哪个父 PCI 设备了。 å¦‚æžœä½ ä½¿ç”¨ v4l2_ioctl_ops,则应该在 v4l2_file_operations ç»“æž„ä½“ä¸ è®¾ç½® .unlocked_ioctl æŒ‡å‘ video_ioctl2。 请勿使用 .ioctlï¼å®ƒå·²è¢«åºŸå¼ƒï¼Œä»ŠåŽå°†æ¶ˆå¤±ã€‚ æŸäº›æƒ…å†µä¸‹ä½ è¦å‘Šè¯‰æ ¸å¿ƒï¼šä½ 在 v4l2_ioctl_ops 指定的æŸä¸ªå‡½æ•°åº”被忽略。 ä½ å¯ä»¥åœ¨ video_device_register 被调用å‰é€šè¿‡ä»¥ä¸‹å‡½æ•°æ ‡è®°è¿™ä¸ª ioctls。 void v4l2_disable_ioctl(struct video_device *vdev, unsigned int cmd); åŸºäºŽå¤–éƒ¨å› ç´ ï¼ˆä¾‹å¦‚æŸä¸ªæ¿å¡å·²è¢«ä½¿ç”¨ï¼‰ï¼Œåœ¨ä¸åˆ›å»ºæ–°ç»“æž„ä½“çš„æƒ…å†µä¸‹ï¼Œä½ æƒ³ è¦å…³é— v4l2_ioctl_ops ä¸æŸä¸ªç‰¹æ€§å¾€å¾€éœ€è¦è¿™ä¸ªæœºåˆ¶ã€‚ v4l2_file_operations 结构体是 file_operations 的一个åé›†ã€‚å…¶ä¸»è¦ åŒºåˆ«åœ¨äºŽï¼šå› inode å‚数从未被使用,它将被忽略。 如果需è¦ä¸Žåª’体框架整åˆï¼Œä½ 必须通过调用 media_entity_pads_init() åˆå§‹åŒ– 嵌入在 video_device 结构体ä¸çš„ media_entity(entity 域)结构体: struct media_pad *pad = &my_vdev->pad; int err; err = media_entity_pads_init(&vdev->entity, 1, pad); pads 数组必须预先åˆå§‹åŒ–。没有必è¦æ‰‹åŠ¨è®¾ç½® media_entity çš„ type å’Œ name 域。 当(任何)å设备节点被打开/å…³é—,对 entity 的引用将被自动获å–/释放。 v4l2_file_operations ä¸Žé” -------------------------- ä½ å¯ä»¥åœ¨ video_device 结构体ä¸è®¾ç½®ä¸€ä¸ªæŒ‡å‘ mutex_lock 的指针。通常 这既å¯æ˜¯ä¸€ä¸ªé¡¶å±‚互斥é”也å¯ä¸ºè®¾å¤‡èŠ‚点自身的互斥é”。默认情况下,æ¤é” 用于 unlocked_ioctl,但为了使用 ioctls ä½ é€šè¿‡ä»¥ä¸‹å‡½æ•°å¯ç¦ç”¨é”定: void v4l2_disable_ioctl_locking(struct video_device *vdev, unsigned int cmd); 例如: v4l2_disable_ioctl_locking(vdev, VIDIOC_DQBUF); ä½ å¿…é¡»åœ¨æ³¨å†Œ video_device å‰è°ƒç”¨è¿™ä¸ªå‡½æ•°ã€‚ 特别是对于 USB 驱动程åºï¼ŒæŸäº›å‘½ä»¤ï¼ˆå¦‚设置控制)需è¦å¾ˆé•¿çš„时间,å¯èƒ½ 需è¦è‡ªè¡Œä¸ºç¼“冲区队列的 ioctls 实现é”定。 å¦‚æžœä½ éœ€è¦æ›´ç»†ç²’度的é”ï¼Œä½ å¿…é¡»è®¾ç½® mutex_lock 为 NULL,并完全自己实现 é”机制。 这完全由驱动开å‘者决定使用何ç§æ–¹æ³•ã€‚ç„¶è€Œï¼Œå¦‚æžœä½ çš„é©±åŠ¨å˜åœ¨é•¿å»¶æ—¶æ“作 ï¼ˆä¾‹å¦‚ï¼Œæ”¹å˜ USB æ‘„åƒå¤´çš„æ›å…‰æ—¶é—´å¯èƒ½éœ€è¦è¾ƒé•¿æ—¶é—´ï¼‰ï¼Œè€Œä½ åˆæƒ³è®©ç”¨æˆ· 在ç‰å¾…长延时æ“作完æˆæœŸé—´åšå…¶ä»–çš„äº‹ï¼Œåˆ™ä½ æœ€å¥½è‡ªå·±å®žçŽ°é”机制。 如果指定一个é”,则所有 ioctl æ“作将在这个é”çš„ä½œç”¨ä¸‹ä¸²è¡Œæ‰§è¡Œã€‚å¦‚æžœä½ ä½¿ç”¨ videobuf,则必须将åŒä¸€ä¸ªé”ä¼ é€’ç»™ videobuf 队列åˆå§‹åŒ–函数;如 videobuf å¿…é¡»ç‰å¾…一帧的到达,则å¯ä¸´æ—¶è§£é”并在这之åŽé‡æ–°ä¸Šé”。如果驱动 也在代ç 执行期间ç‰å¾…,则å¯åšåŒæ ·çš„工作(临时解é”,å†ä¸Šé”)让其他进程 å¯ä»¥åœ¨ç¬¬ä¸€ä¸ªè¿›ç¨‹é˜»å¡žæ—¶è®¿é—®è®¾å¤‡èŠ‚点。 在使用 videobuf2 的情况下,必须实现 wait_prepare å’Œ wait_finish 回调 在适当的时候解é”/åŠ é”。进一æ¥æ¥è¯´ï¼Œå¦‚æžœä½ åœ¨ video_device 结构体ä¸ä½¿ç”¨ é”,则必须在 wait_prepare å’Œ wait_finish ä¸å¯¹è¿™ä¸ªäº’æ–¥é”进行解é”/åŠ é”。 çƒæ’拔的æ–开实现也必须在调用 v4l2_device_disconnect å‰èŽ·å¾—é”。 video_device注册 --------------- 接下æ¥ä½ 需è¦æ³¨å†Œè§†é¢‘è®¾å¤‡ï¼šè¿™ä¼šä¸ºä½ åˆ›å»ºä¸€ä¸ªå—符设备。 err = video_register_device(vdev, VFL_TYPE_GRABBER, -1); if (err) { video_device_release(vdev); /* or kfree(my_vdev); */ return err; } 如果 v4l2_device 父设备的 mdev åŸŸä¸ºéž NULL 值,视频设备实体将自动 注册为媒体设备。 注册哪ç§è®¾å¤‡æ˜¯æ ¹æ®ç±»åž‹ï¼ˆtype)å‚数。å˜åœ¨ä»¥ä¸‹ç±»åž‹ï¼š VFL_TYPE_GRABBER: 用于视频输入/输出设备的 videoX VFL_TYPE_VBI: 用于垂直消éšæ•°æ®çš„ vbiX (例如,éšè—å¼å—幕,图文电视) VFL_TYPE_RADIO: 用于广æ’è°ƒè°å™¨çš„ radioX 最åŽä¸€ä¸ªå‚æ•°è®©ä½ ç¡®å®šä¸€ä¸ªæ‰€æŽ§åˆ¶è®¾å¤‡çš„è®¾å¤‡èŠ‚ç‚¹å·æ•°é‡(例如 videoX ä¸çš„ X)。 é€šå¸¸ä½ å¯ä»¥ä¼ å…¥-1,让 v4l2 框架自己选择第一个空闲的编å·ã€‚但是有时用户 需è¦é€‰æ‹©ä¸€ä¸ªç‰¹å®šçš„节点å·ã€‚驱动å…许用户通过驱动模å—å‚数选择一个特定的 设备节点å·æ˜¯å¾ˆæ™®é的。这个编å·å°†ä¼šä¼ 递给这个函数,且 video_register_device 将会试图选择这个设备节点å·ã€‚如果这个编å·è¢«å 用,下一个空闲的设备节点 ç¼–å·å°†è¢«é€‰ä¸ï¼Œå¹¶å‘å†…æ ¸æ—¥å¿—ä¸å‘é€ä¸€ä¸ªè¦å‘Šä¿¡æ¯ã€‚ å¦ä¸€ä¸ªä½¿ç”¨åœºæ™¯æ˜¯å½“驱动创建多个设备时。这ç§æƒ…况下,对ä¸åŒçš„视频设备在 ç¼–å·ä¸Šä½¿ç”¨ä¸åŒçš„范围是很有用的。例如,视频æ•èŽ·è®¾å¤‡ä»Ž 0 开始,视频 输出设备从 16 å¼€å§‹ã€‚æ‰€ä»¥ä½ å¯ä»¥ä½¿ç”¨æœ€åŽä¸€ä¸ªå‚æ•°æ¥æŒ‡å®šè®¾å¤‡èŠ‚点å·æœ€å°å€¼ï¼Œ 而 v4l2 框架会试图选择第一个的空闲编å·ï¼ˆç‰äºŽæˆ–å¤§äºŽä½ æ供的编å·ï¼‰ã€‚ 如果失败,则它会就选择第一个空闲的编å·ã€‚ 由于这ç§æƒ…å†µä¸‹ï¼Œä½ ä¼šå¿½ç•¥æ— æ³•é€‰æ‹©ç‰¹å®šè®¾å¤‡èŠ‚ç‚¹å·çš„è¦å‘Šï¼Œåˆ™å¯è°ƒç”¨ video_register_device_no_warn() 函数é¿å…è¦å‘Šä¿¡æ¯çš„产生。 åªè¦è®¾å¤‡èŠ‚点被创建,一些属性也会åŒæ—¶åˆ›å»ºã€‚在 /sys/class/video4linux 目录ä¸ä½ 会找到这些设备。例如进入其ä¸çš„ video0 ç›®å½•ï¼Œä½ ä¼šçœ‹åˆ°â€˜name’和 ‘index’属性。‘name’属性值就是 video_device 结构体ä¸çš„‘name’域。 ‘index’属性值就是设备节点的索引值:æ¯æ¬¡è°ƒç”¨ video_register_device(), 索引值都递增 1 。第一个视频设备节点总是从索引值 0 开始。 用户å¯ä»¥è®¾ç½® udev 规则,利用索引属性生æˆèŠ±å“¨çš„设备å(例如:用‘mpegX’ 代表 MPEG 视频æ•èŽ·è®¾å¤‡èŠ‚点)。 在设备æˆåŠŸæ³¨å†ŒåŽï¼Œå°±å¯ä»¥ä½¿ç”¨è¿™äº›åŸŸï¼š - vfl_type: ä¼ é€’ç»™ video_register_device 的设备类型。 - minor: 已指派的次设备å·ã€‚ - num: è®¾å¤‡èŠ‚ç‚¹ç¼–å· (例如 videoX ä¸çš„ X)。 - index: 设备索引å·ã€‚ å¦‚æžœæ³¨å†Œå¤±è´¥ï¼Œä½ å¿…é¡»è°ƒç”¨ video_device_release() æ¥é‡Šæ”¾å·²åˆ†é…çš„ video_device 结构体;如果 video_device 是嵌入在自己创建的结构体ä¸ï¼Œ ä½ ä¹Ÿå¿…é¡»é‡Šæ”¾å®ƒã€‚vdev->release() 回调ä¸ä¼šåœ¨æ³¨å†Œå¤±è´¥ä¹‹åŽè¢«è°ƒç”¨ï¼Œ ä½ ä¹Ÿä¸åº”试图在注册失败åŽæ³¨é”€è®¾å¤‡ã€‚ video_device 注销 ---------------- 当视频设备节点已被移除,ä¸è®ºæ˜¯å¸è½½é©±åŠ¨è¿˜æ˜¯USB设备æ–å¼€ï¼Œä½ éƒ½åº”æ³¨é”€ 它们: video_unregister_device(vdev); 这个æ“作将从 sysfs ä¸ç§»é™¤è®¾å¤‡èŠ‚点(导致 udev 将其从 /dev ä¸ç§»é™¤ï¼‰ã€‚ video_unregister_device() 返回之åŽï¼Œå°±æ— 法完æˆæ‰“å¼€æ“作。尽管如æ¤ï¼Œ USB 设备的情况则ä¸åŒï¼ŒæŸäº›åº”用程åºå¯èƒ½ä¾ç„¶æ‰“å¼€ç€å…¶ä¸ä¸€ä¸ªå·²æ³¨é”€è®¾å¤‡ 节点。所以在注销之åŽï¼Œæ‰€æœ‰æ–‡ä»¶æ“作(当然除了 release )也应返回错误值。 当最åŽä¸€ä¸ªè§†é¢‘设备节点的用户退出,则 vdev->release() 回调会被调用, å¹¶ä¸”ä½ å¯ä»¥åšæœ€åŽçš„清ç†æ“作。 ä¸è¦å¿˜è®°æ¸…ç†ä¸Žè§†é¢‘设备相关的媒体入å£ï¼ˆå¦‚果被åˆå§‹åŒ–过): media_entity_cleanup(&vdev->entity); è¿™å¯ä»¥åœ¨ release 回调ä¸å®Œæˆã€‚ video_device 辅助函数 --------------------- 一些有用的辅助函数如下: - file/video_device ç§æœ‰æ•°æ® ä½ å¯ä»¥ç”¨ä»¥ä¸‹å‡½æ•°åœ¨ video_device 结构体ä¸è®¾ç½®/获å–驱动ç§æœ‰æ•°æ®ï¼š void *video_get_drvdata(struct video_device *vdev); void video_set_drvdata(struct video_device *vdev, void *data); 注æ„:在调用 video_register_device() å‰æ‰§è¡Œ video_set_drvdata() 是安全的。 而以下函数: struct video_device *video_devdata(struct file *file); 返回 file 结构体ä¸æ‹¥æœ‰çš„çš„ video_device 指针。 video_drvdata 辅助函数结åˆäº† video_get_drvdata å’Œ video_devdata 的功能: void *video_drvdata(struct file *file); ä½ å¯ä»¥ä½¿ç”¨å¦‚下代ç 从 video_device 结构体ä¸èŽ·å– v4l2_device 结构体 指针: struct v4l2_device *v4l2_dev = vdev->v4l2_dev; - 设备节点å video_device è®¾å¤‡èŠ‚ç‚¹åœ¨å†…æ ¸ä¸çš„å称å¯ä»¥é€šè¿‡ä»¥ä¸‹å‡½æ•°èŽ·å¾— const char *video_device_node_name(struct video_device *vdev); 这个åå—被用户空间工具(例如 udev)作为æ示信æ¯ä½¿ç”¨ã€‚应尽å¯èƒ½ä½¿ç”¨ æ¤åŠŸèƒ½ï¼Œè€Œéžè®¿é—® video_device::num å’Œ video_device::minor 域。 视频缓冲辅助函数 --------------- v4l2 æ ¸å¿ƒ API æ供了一个处ç†è§†é¢‘ç¼“å†²çš„æ ‡å‡†æ–¹æ³•(称为“videobufâ€)。 这些方法使驱动å¯ä»¥é€šè¿‡ç»Ÿä¸€çš„æ–¹å¼å®žçŽ° read()ã€mmap() å’Œ overlay()。 ç›®å‰åœ¨è®¾å¤‡ä¸Šæ”¯æŒè§†é¢‘缓冲的方法有分散/èšé›† DMA(videobuf-dma-sg)〠线性 DMA(videobuf-dma-contig)以åŠå¤§å¤šç”¨äºŽ USB 设备的用 vmalloc 分é…的缓冲(videobuf-vmalloc)。 请å‚阅 Documentation/media/kapi/v4l2-videobuf.rst,以获得更多关于 videobuf 层的使用信æ¯ã€‚ v4l2_fh 结构体 ------------- v4l2_fh 结构体æ供一个ä¿å˜ç”¨äºŽ V4L2 框架的文件å¥æŸ„特定数æ®çš„简å•æ–¹æ³•ã€‚ 如果 video_device æ ‡å¿—ï¼Œæ–°é©±åŠ¨ 必须使用 v4l2_fh ç»“æž„ä½“ï¼Œå› ä¸ºå®ƒä¹Ÿç”¨äºŽå®žçŽ°ä¼˜å…ˆçº§å¤„ç†ï¼ˆVIDIOC_G/S_PRIORITY)。 v4l2_fh 的用户(ä½äºŽ V4l2 框架ä¸ï¼Œå¹¶éžé©±åŠ¨ï¼‰å¯é€šè¿‡æµ‹è¯• video_device->flags ä¸çš„ V4L2_FL_USES_V4L2_FH ä½å¾—知驱动是å¦ä½¿ç”¨ v4l2_fh 作为他的 file->private_data 指针。这个ä½ä¼šåœ¨è°ƒç”¨ v4l2_fh_init() 时被设置。 v4l2_fh 结构体作为驱动自身文件å¥æŸ„结构体的一部分被分é…,且驱动在 其打开函数ä¸å°† file->private_data 指å‘它。 在许多情况下,v4l2_fh 结构体会嵌入到一个更大的结构体ä¸ã€‚这钟情况下, 应该在 open() ä¸è°ƒç”¨ v4l2_fh_init+v4l2_fh_add,并在 release() ä¸ è°ƒç”¨ v4l2_fh_del+v4l2_fh_exit。 驱动å¯ä»¥é€šè¿‡ä½¿ç”¨ container_of å®æå–他们自己的文件å¥æŸ„结构体。例如: struct my_fh { int blah; struct v4l2_fh fh; }; ... int my_open(struct file *file) { struct my_fh *my_fh; struct video_device *vfd; int ret; ... my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL); ... v4l2_fh_init(&my_fh->fh, vfd); ... file->private_data = &my_fh->fh; v4l2_fh_add(&my_fh->fh); return 0; } int my_release(struct file *file) { struct v4l2_fh *fh = file->private_data; struct my_fh *my_fh = container_of(fh, struct my_fh, fh); ... v4l2_fh_del(&my_fh->fh); v4l2_fh_exit(&my_fh->fh); kfree(my_fh); return 0; } 以下是 v4l2_fh 函数使用的简介: void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev) åˆå§‹åŒ–文件å¥æŸ„。这*å¿…é¡»*在驱动的 v4l2_file_operations->open() 函数ä¸æ‰§è¡Œã€‚ void v4l2_fh_add(struct v4l2_fh *fh) æ·»åŠ ä¸€ä¸ª v4l2_fh 到 video_device 文件å¥æŸ„列表。一旦文件å¥æŸ„ åˆå§‹åŒ–完æˆå°±å¿…须调用。 void v4l2_fh_del(struct v4l2_fh *fh) 从 video_device() ä¸è§£é™¤æ–‡ä»¶å¥æŸ„çš„å…³è”。文件å¥æŸ„的退出函数也 将被调用。 void v4l2_fh_exit(struct v4l2_fh *fh) 清ç†æ–‡ä»¶å¥æŸ„。在清ç†å®Œ v4l2_fh åŽï¼Œç›¸å…³å†…å˜ä¼šè¢«é‡Šæ”¾ã€‚ 如果 v4l2_fh ä¸æ˜¯åµŒå…¥åœ¨å…¶ä»–结构体ä¸çš„,则å¯ä»¥ç”¨è¿™äº›è¾…助函数: int v4l2_fh_open(struct file *filp) 分é…一个 v4l2_fh 结构体空间,åˆå§‹åŒ–å¹¶å°†å…¶æ·»åŠ åˆ° file 结构体相关的 video_device 结构体ä¸ã€‚ int v4l2_fh_release(struct file *filp) 从 file 结构体相关的 video_device 结构体ä¸åˆ 除 v4l2_fh ï¼Œæ¸…ç† v4l2_fh 并释放空间。 这两个函数å¯ä»¥æ’入到 v4l2_file_operation çš„ open() å’Œ release() æ“作ä¸ã€‚ æŸäº›é©±åŠ¨éœ€è¦åœ¨ç¬¬ä¸€ä¸ªæ–‡ä»¶å¥æŸ„打开和最åŽä¸€ä¸ªæ–‡ä»¶å¥æŸ„å…³é—的时候åšäº› å·¥ä½œã€‚æ‰€ä»¥åŠ å…¥äº†ä¸¤ä¸ªè¾…åŠ©å‡½æ•°ä»¥æ£€æŸ¥ v4l2_fh 结构体是å¦æ˜¯ç›¸å…³è®¾å¤‡ 节点打开的唯一文件å¥æŸ„。 int v4l2_fh_is_singular(struct v4l2_fh *fh) 如果æ¤æ–‡ä»¶å¥æŸ„是唯一打开的文件å¥æŸ„,则返回 1 ,å¦åˆ™è¿”回 0 。 int v4l2_fh_is_singular_file(struct file *filp) 功能相åŒï¼Œä½†é€šè¿‡ filp->private_data 调用 v4l2_fh_is_singular。 V4L2 事件机制 ----------- V4L2 事件机制æä¾›äº†ä¸€ä¸ªé€šç”¨çš„æ–¹æ³•å°†äº‹ä»¶ä¼ é€’åˆ°ç”¨æˆ·ç©ºé—´ã€‚é©±åŠ¨å¿…é¡»ä½¿ç”¨ v4l2_fh æ‰èƒ½æ”¯æŒ V4L2 事件机制。 事件通过一个类型和选择 ID æ¥å®šä¹‰ã€‚ID 对应一个 V4L2 对象,例如 一个控制 ID。如果未使用,则 ID 为 0。 当用户订阅一个事件,驱动会为æ¤åˆ†é…一些 kevent 结构体。所以æ¯ä¸ª 事件组(类型ã€ID)都会有自己的一套 kevent 结构体。这ä¿è¯äº†å¦‚æžœ 一个驱动çŸæ—¶é—´å†…产生了许多åŒç±»äº‹ä»¶ï¼Œä¸ä¼šè¦†ç›–其他类型的事件。 ä½†å¦‚æžœä½ æ”¶åˆ°çš„äº‹ä»¶æ•°é‡å¤§äºŽåŒç±»äº‹ä»¶ kevent çš„ä¿å˜æ•°é‡ï¼Œåˆ™æœ€æ—©çš„ äº‹ä»¶å°†è¢«ä¸¢å¼ƒï¼Œå¹¶åŠ å…¥æ–°äº‹ä»¶ã€‚ æ¤å¤–,v4l2_subscribed_event 结构体内部有å¯ä¾›é©±åŠ¨è®¾ç½®çš„ merge() å’Œ replace() 回调,这些回调会在新事件产生且没有多余空间的时候被调用。 replace() å›žè°ƒè®©ä½ å¯ä»¥å°†æ—©æœŸäº‹ä»¶çš„净è·æ›¿æ¢ä¸ºæ–°äº‹ä»¶çš„净è·ï¼Œå°†æ—©æœŸ 净è·çš„相关数æ®åˆå¹¶åˆ°æ›¿æ¢è¿›æ¥çš„新净è·ä¸ã€‚当该类型的事件仅分é…了一个 kevent 结构体时,它将被调用。merge() å›žè°ƒè®©ä½ å¯ä»¥åˆå¹¶æœ€æ—©çš„äº‹ä»¶å‡€è· åˆ°åœ¨å®ƒä¹‹åŽçš„那个事件净è·ä¸ã€‚当该类型的事件分é…了两个或更多 kevent 结构体时,它将被调用。 è¿™ç§æ–¹æ³•ä¸ä¼šæœ‰çŠ¶æ€ä¿¡æ¯ä¸¢å¤±ï¼Œåªä¼šå¯¼è‡´ä¸é—´æ¥éª¤ä¿¡æ¯ä¸¢å¤±ã€‚ 关于 replace/merge 回调的一个ä¸é”™çš„例å在 v4l2-event.c ä¸ï¼šç”¨äºŽ 控制事件的 ctrls_replace() å’Œ ctrls_merge() 回调。 注æ„:这些回调å¯ä»¥åœ¨ä¸æ–上下文ä¸è°ƒç”¨ï¼Œæ‰€ä»¥å®ƒä»¬å¿…须尽快完æˆå¹¶é€€å‡ºã€‚ 有用的函数: void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev) å°†äº‹ä»¶åŠ å…¥è§†é¢‘è®¾å¤‡çš„é˜Ÿåˆ—ã€‚é©±åŠ¨ä»…è´Ÿè´£å¡«å…… type å’Œ data 域。 其他域由 V4L2 填充。 int v4l2_event_subscribe(struct v4l2_fh *fh, struct v4l2_event_subscription *sub, unsigned elems, const struct v4l2_subscribed_event_ops *ops) video_device->ioctl_ops->vidioc_subscribe_event 必须检测驱动能 产生特定 id 的事件。然åŽè°ƒç”¨ v4l2_event_subscribe() æ¥è®¢é˜…该事件。 elems å‚数是该事件的队列大å°ã€‚若为 0,V4L2 æ¡†æž¶å°†ä¼šï¼ˆæ ¹æ®äº‹ä»¶ç±»åž‹ï¼‰ 填充默认值。 ops å‚æ•°å…许驱动指定一系列回调: * add: å½“æ·»åŠ ä¸€ä¸ªæ–°ç›‘å¬è€…时调用(é‡å¤è®¢é˜…åŒä¸€ä¸ªäº‹ä»¶ï¼Œæ¤å›žè°ƒ 仅被执行一次)。 * del: 当一个监å¬è€…åœæ¢ç›‘å¬æ—¶è°ƒç”¨ã€‚ * replace: 用‘新’事件替æ¢â€˜æ—©æœŸâ€˜äº‹ä»¶ã€‚ * merge: 将‘早期‘事件åˆå¹¶åˆ°â€˜æ–°â€™äº‹ä»¶ä¸ã€‚ 这四个调用都是å¯é€‰çš„,如果ä¸æƒ³æŒ‡å®šä»»ä½•å›žè°ƒï¼Œåˆ™ ops å¯ä¸º NULL。 int v4l2_event_unsubscribe(struct v4l2_fh *fh, struct v4l2_event_subscription *sub) v4l2_ioctl_ops 结构体ä¸çš„ vidioc_unsubscribe_event 回调函数。 驱动程åºå¯ä»¥ç›´æŽ¥ä½¿ç”¨ v4l2_event_unsubscribe() 实现退订事件过程。 特殊的 V4L2_EVENT_ALL 类型,å¯ç”¨äºŽé€€è®¢æ‰€æœ‰äº‹ä»¶ã€‚驱动å¯èƒ½åœ¨ç‰¹æ®Š 情况下需è¦åšæ¤æ“作。 int v4l2_event_pending(struct v4l2_fh *fh) 返回未决事件的数é‡ã€‚有助于实现轮询(poll)æ“作。 事件通过 poll ç³»ç»Ÿè°ƒç”¨ä¼ é€’åˆ°ç”¨æˆ·ç©ºé—´ã€‚é©±åŠ¨å¯ç”¨ v4l2_fh->wait (wait_queue_head_t 类型)作为å‚数调用 poll_wait()。 äº‹ä»¶åˆ†ä¸ºæ ‡å‡†äº‹ä»¶å’Œç§æœ‰äº‹ä»¶ã€‚æ–°çš„æ ‡å‡†äº‹ä»¶å¿…é¡»ä½¿ç”¨å¯ç”¨çš„最å°äº‹ä»¶ç±»åž‹ ç¼–å·ã€‚驱动必须从他们本类型的编å·èµ·å§‹å¤„分é…事件。类型的编å·èµ·å§‹ä¸º V4L2_EVENT_PRIVATE_START + n * 1000 ï¼Œå…¶ä¸ n 为å¯ç”¨æœ€å°ç¼–å·ã€‚æ¯ä¸ª 类型ä¸çš„第一个事件类型编å·æ˜¯ä¸ºä»¥åŽçš„使用ä¿ç•™çš„,所以第一个å¯ç”¨äº‹ä»¶ 类型编å·æ˜¯â€˜class base + 1’。 V4L2 事件机制的使用实例å¯ä»¥åœ¨ OMAP3 ISP 的驱动 (drivers/media/video/omap3isp)ä¸æ‰¾åˆ°ã€‚