diff --git a/Documentation/zh_CN/video4linux/v4l2-framework.txt b/Documentation/zh_CN/video4linux/v4l2-framework.txt new file mode 100644 index 0000000000000000000000000000000000000000..3e74f13af4266d8d7aab3b3910b93a73bc1b5b71 --- /dev/null +++ b/Documentation/zh_CN/video4linux/v4l2-framework.txt @@ -0,0 +1,983 @@ +Chinese translated version of Documentation/video4linux/v4l2-framework.txt + +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@infradead.org> +Chinese maintainer: Fu Wei <tekkamanninja@gmail.com> +--------------------------------------------------------------------- +Documentation/video4linux/v4l2-framework.txt çš„ä¸æ–‡ç¿»è¯‘ + +如果想评论或更新本文的内容,请直接è”ç³»åŽŸæ–‡æ¡£çš„ç»´æŠ¤è€…ã€‚å¦‚æžœä½ ä½¿ç”¨è‹±æ–‡ +äº¤æµæœ‰å›°éš¾çš„è¯ï¼Œä¹Ÿå¯ä»¥å‘䏿–‡ç‰ˆç»´æŠ¤è€…求助。如果本翻译更新ä¸åŠæ—¶æˆ–者翻 +译å˜åœ¨é—®é¢˜ï¼Œè¯·è”ç³»ä¸æ–‡ç‰ˆç»´æŠ¤è€…。 +英文版维护者: Mauro Carvalho Chehab <mchehab@infradead.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 __devinit 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 (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip); + 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_init() åˆå§‹åŒ– v4l2_subdev +结构体ä¸çš„ media_entity 结构体(entity 域): + + struct media_pad *pads = &my_sd->pads; + int err; + + err = media_entity_init(&sd->entity, npads, pads, 0); + +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_chip_ident(sd, &chip); + +但使用如下å®ä¼šæ¯”较容易且åˆé€‚: + + err = v4l2_subdev_call(sd, core, g_chip_ident, &chip); + +这个å®å°†ä¼šåš NULL 指针检查,如果 subdev 为 NULL,则返回-ENODEV;如果 +subdev->core 或 subdev->core->g_chip_ident 为 NULL,则返回 -ENOIOCTLCMDï¼› +å¦åˆ™å°†è¿”回 subdev->ops->core->g_chip_ident ops 调用的实际结果。 + +有时也å¯èƒ½åŒæ—¶è°ƒç”¨æ‰€æœ‰æˆ–一系列å设备的æŸä¸ªæ“作函数: + + v4l2_device_call_all(v4l2_dev, 0, core, g_chip_ident, &chip); + +任何䏿”¯æŒæ¤æ“作的åè®¾å¤‡éƒ½ä¼šè¢«è·³è¿‡ï¼Œå¹¶å¿½ç•¥é”™è¯¯è¿”å›žå€¼ã€‚ä½†å¦‚æžœä½ éœ€è¦ +检查出错ç ,则å¯ä½¿ç”¨å¦‚下函数: + + err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_chip_ident, &chip); + +除 -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 设备了。 + +- flags:å¯é€‰ã€‚å¦‚æžœä½ è¦è®©æ¡†æž¶å¤„ç†è®¾ç½® VIDIOC_G/S_PRIORITY ioctls, + 请设置 V4L2_FL_USE_FH_PRIOã€‚è¿™è¦æ±‚ä½ ä½¿ç”¨ v4l2_fh 结构体。 + ä¸€æ—¦æ‰€æœ‰é©±åŠ¨ä½¿ç”¨äº†æ ¸å¿ƒçš„ä¼˜å…ˆçº§å¤„ç†ï¼Œæœ€ç»ˆè¿™ä¸ªæ ‡å¿—将消失。但现在它 + 必须被显å¼è®¾ç½®ã€‚ + +å¦‚æžœä½ ä½¿ç”¨ 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_init() åˆå§‹åŒ– +嵌入在 video_device 结构体ä¸çš„ media_entity(entity 域)结构体: + + struct media_pad *pad = &my_vdev->pad; + int err; + + err = media_entity_init(&vdev->entity, 1, pad, 0); + +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/video4linux/videobuf,以获得更多关于 videobuf +层的使用信æ¯ã€‚ + +v4l2_fh 结构体 +------------- + +v4l2_fh 结构体æä¾›ä¸€ä¸ªä¿å˜ç”¨äºŽ V4L2 æ¡†æž¶çš„æ–‡ä»¶å¥æŸ„特定数æ®çš„ç®€å•æ–¹æ³•。 +如果 video_device çš„ flag 设置了 V4L2_FL_USE_FH_PRIO æ ‡å¿—ï¼Œæ–°é©±åŠ¨ +必须使用 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)䏿‰¾åˆ°ã€‚