在應用程式獲取視頻數據的流程中,都是通過 ioctl 命令與驅動程式進行交互,常見的 ioctl 命令有: 1 VIDIOC_QUERYCAP /* 獲取設備支持的操作 */ 2 VIDIOC_G_FMT /* 獲取設置支持的視頻格式 */ 3 VIDIOC_S_FMT /* 設置捕獲視頻的格式 * ...
在應用程式獲取視頻數據的流程中,都是通過 ioctl 命令與驅動程式進行交互,常見的 ioctl 命令有:
1 VIDIOC_QUERYCAP /* 獲取設備支持的操作 */ 2 VIDIOC_G_FMT /* 獲取設置支持的視頻格式 */ 3 VIDIOC_S_FMT /* 設置捕獲視頻的格式 */ 4 VIDIOC_REQBUFS /* 向驅動提出申請記憶體的請求 */ 5 VIDIOC_QUERYBUF /* 向驅動查詢申請到的記憶體 */ 6 VIDIOC_QBUF /* 將空閑的記憶體加入可捕獲視頻的隊列 */ 7 VIDIOC_DQBUF /* 將已經捕獲好視頻的記憶體拉出已捕獲視頻的隊列 */ 8 VIDIOC_STREAMON /* 打開視頻流 */ 9 VIDIOC_STREAMOFF /* 關閉視頻流 */ 10 VIDIOC_QUERYCTRL /* 查詢驅動是否支持該命令 */ 11 VIDIOC_G_CTRL /* 獲取當前命令值 */ 12 VIDIOC_S_CTRL /* 設置新的命令值 */ 13 VIDIOC_G_TUNER /* 獲取調諧器信息 */ 14 VIDIOC_S_TUNER /* 設置調諧器信息 */ 15 VIDIOC_G_FREQUENCY /* 獲取調諧器頻率 */ 16 VIDIOC_S_FREQUENCY /* 設置調諧器頻率 */
1、struct v4l2_capability 與 VIDIOC_QUERYCAP
VIDIOC_QUERYCAP 命令通過結構 v4l2_capability 獲取設備支持的操作模式:
1 struct v4l2_capability { 2 __u8 driver[16]; /* i.e. "bttv" */ 3 __u8 card[32]; /* i.e. "Hauppauge WinTV" */ 4 __u8 bus_info[32]; /* "PCI:" + pci_name(pci_dev) */ 5 __u32 version; /* should use KERNEL_VERSION() */ 6 __u32 capabilities; /* Device capabilities */ 7 __u32 reserved[4]; 8 };
其中域 capabilities 代表設備支持的操作模式,常見的值有 V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING 表示是一個視頻捕捉設備並且具有數據流控制模式;另外 driver 域需要和 struct video_device 中的 name 匹配。
2、struct v4l2_format 與 VIDIOC_G_FMT、VIDIOC_S_FMT、VIDIOC_TRY_FMT
通常用 VIDIOC_S_FMT 命令通過結構 v4l2_format 初始化捕獲視頻的格式,如果要改變格式則用 VIDIOC_TRY_FMT 命令:
1 struct v4l2_format { 2 enum v4l2_buf_type type; 3 union { 4 struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */ 5 struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */ 6 struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */ 7 struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */ 8 __u8 raw_data[200]; /* user-defined */ 9 } fmt; 10 }; 11 其中 12 enum v4l2_buf_type { 13 V4L2_BUF_TYPE_VIDEO_CAPTURE = 1, 14 V4L2_BUF_TYPE_VIDEO_OUTPUT = 2, 15 V4L2_BUF_TYPE_VIDEO_OVERLAY = 3, 16 ... 17 V4L2_BUF_TYPE_PRIVATE = 0x80, 18 }; 19 20 struct v4l2_pix_format { 21 __u32 width; 22 __u32 height; 23 __u32 pixelformat; 24 enum v4l2_field field; 25 __u32 bytesperline; /* for padding, zero if unused */ 26 __u32 sizeimage; 27 enum v4l2_colorspace colorspace; 28 __u32 priv; /* private data, depends on pixelformat */ 29 };
常見的捕獲模式為 V4L2_BUF_TYPE_VIDEO_CAPTURE 即視頻捕捉模式,在此模式下 fmt 聯合體採用域 v4l2_pix_format:其中 width 為視頻的寬、height 為視頻的高、pixelformat 為視頻數據格式(常見的值有 V4L2_PIX_FMT_YUV422P | V4L2_PIX_FMT_RGB565)、bytesperline 為一行圖像占用的位元組數、sizeimage 則為圖像占用的總位元組數、colorspace 指定設備的顏色空間。
3、struct v4l2_requestbuffers 與 VIDIOC_REQBUFS
VIDIOC_REQBUFS 命令通過結構 v4l2_requestbuffers 請求驅動申請一片連續的記憶體用於緩存視頻信息:
1 struct v4l2_requestbuffers { 2 __u32 count; 3 enum v4l2_buf_type type; 4 enum v4l2_memory memory; 5 __u32 reserved[2]; 6 }; 7 其中 8 enum v4l2_memory { 9 V4L2_MEMORY_MMAP = 1, 10 V4L2_MEMORY_USERPTR = 2, 11 V4L2_MEMORY_OVERLAY = 3, 12 };
count 指定根據圖像占用空間大小申請的緩存區個數,type 為視頻捕獲模式,memory 為記憶體區的使用方式。
4、struct v4l2_buffer與 VIDIOC_QUERYBUF
VIDIOC_QUERYBUF 命令通過結構 v4l2_buffer 查詢驅動申請的記憶體區信息:
1 struct v4l2_buffer { 2 __u32 index; 3 enum v4l2_buf_type type; 4 __u32 bytesused; 5 __u32 flags; 6 enum v4l2_field field; 7 struct timeval timestamp; 8 struct v4l2_timecode timecode; 9 __u32 sequence; 10 11 /* memory location */ 12 enum v4l2_memory memory; 13 union { 14 __u32 offset; 15 unsigned long userptr; 16 } m; 17 __u32 length; 18 __u32 input; 19 __u32 reserved; 20 };
5、enum v4l2_buf_type 與 VIDIOC_STREAMON、VIDIOC_STREAMOFF
這兩個命令使用的只是一個整形數據,即 v4l2_buf_type,一般只要指定其值為 V4L2_BUF_TYPE_VIDEO_CAPTURE 即可。
6、struct v4l2_queryctrl 與 VIDIOC_QUERYCTRL
VIDIOC_QUERYCTRL 命令通過結構 v4l2_queryctrl 查詢驅動是否支持該 id 代表的命令,並返回該命令的各種參數:
1 struct v4l2_queryctrl { 2 __u32 id; /* 命令編號 */ 3 enum v4l2_ctrl_type type; /* 命令值的類型 */ 4 __u8 name[32]; /* 命令名稱*/ 5 __s32 minimum; /* 最小的命令值 */ 6 __s32 maximum; /* 最大的命令值 */ 7 __s32 step; /* 命令值變化的步長 */ 8 __s32 default_value; /* 預設的命令值 */ 9 __u32 flags; /* 命令的標誌 */ 10 __u32 reserved[2]; /* 命令值的點陣圖表示 */ 11 }; 12 其中 13 enum v4l2_ctrl_type { 14 V4L2_CTRL_TYPE_INTEGER = 1, /* 整形 */ 15 V4L2_CTRL_TYPE_BOOLEAN = 2, /* 真值 */ 16 V4L2_CTRL_TYPE_MENU = 3, /* 菜單 */ 17 V4L2_CTRL_TYPE_BUTTON = 4, /* 無值 */ 18 V4L2_CTRL_TYPE_INTEGER64 = 5, /* 後面三種不常用 */ 19 V4L2_CTRL_TYPE_CTRL_CLASS = 6, 20 V4L2_CTRL_TYPE_STRING = 7, 21 }; 22 命令的標誌取值如下: 23 /* Control flags */ 24 #define V4L2_CTRL_FLAG_DISABLED 0x0001 25 #define V4L2_CTRL_FLAG_GRABBED 0x0002 26 #define V4L2_CTRL_FLAG_READ_ONLY 0x0004 27 #define V4L2_CTRL_FLAG_UPDATE 0x0008 28 #define V4L2_CTRL_FLAG_INACTIVE 0x0010 29 #define V4L2_CTRL_FLAG_SLIDER 0x0020 30 #define V4L2_CTRL_FLAG_WRITE_ONLY 0x0040 31 32 /* Query flag, to be ORed with the control ID */ 33 #define V4L2_CTRL_FLAG_NEXT_CTRL 0x80000000
id 是命令的編號,常見的命令有兩種:一種以 V4L2_CID_BASE 為起始值,是公用命令;一種以 V4L2_CID_PRIVATE_BASE 為起始值,是私有命令。在一般的應用中命令值可見如下:
1 V4L2_CID_CONTRAST (V4L2_CID_BASE+1) /* 對比度調節 */ 2 V4L2_CID_SATURATION (V4L2_CID_BASE+2) /* 飽和度調節 */ 3 V4L2_CID_AUDIO_VOLUME (V4L2_CID_BASE+5) /* 音量調節 */ 4 V4L2_CID_AUDIO_MUTE (V4L2_CID_BASE+9) /* 靜音設置 */ 5 V4L2_CID_DO_WHITE_BALANCE (V4L2_CID_BASE+13) /* 白平衡調節 */ 6 V4L2_CID_GAMMA (V4L2_CID_BASE+16) /* 伽馬值調節 */ 7 V4L2_CID_EXPOSURE (V4L2_CID_BASE+17) /* 曝光度調節 */ 8 9 V4L2_CID_PRIVATE_ATXX_FLASH (V4L2_CID_PRIVATE_BASE + 2) /* 閃光燈控制 */ 10 V4L2_CID_PRIVATE_ATXX_FRAME (V4L2_CID_PRIVATE_BASE + 12) /* 幀率調節 */
type 為命令值的類型(總共有7中類型的值),name 是命令的名稱,reserved 則是命令值的點陣圖表示,驅動會將所有的命令值都以 bit 的形式寫到 64 位的域中,上層應用查詢時可以根據點陣圖判斷命令支持的值。
7、struct v4l2_control 與 VIDIOC_G_CTRL、VIDIOC_S_CTRL
VIDIOC_S_CTRL 或 VIDIOC_G_CTRL 命令通過結構 v4l2_control 設置或者獲取 id 命令的值:
1 struct v4l2_control { 2 __u32 id; 3 __s32 value; 4 };
這個結構只有 2 個域,id 是命令編號,value 則是命令的值。
8、struct v4l2_tuner 與 VIDIOC_G_TUNER、VIDIOC_S_TUNER
VIDIOC_S_TUNER 或 VIDIOC_G_TUNER 命令通過結構 v4l2_tuner 設置調諧器的信息:
1 struct v4l2_tuner {
2 __u32 index; /* 調諧器編號,由應用程式設置 */
3 __u8 name[32]; /* 調諧器名稱 */
4 enum v4l2_tuner_type type; /* 調諧器類型 */
5 __u32 capability; /* 調諧器支持的操作 */
6 __u32 rangelow; /* 最低頻率值,單位為62.5Hz或者62.5KHz */
7 __u32 rangehigh; /* 最高頻率值 */
8 __u32 rxsubchans; /* 接收的音頻信號類型 */
9 __u32 audmode; /* 當前音頻播放形式 */
10 __s32 signal; /* 信號強度 */
11 __s32 afc; /* 自動頻率控制 */
12 __u32 reserved[4]; /* 保留備用 */
13 };
14 其中
15 enum v4l2_tuner_type {
16 V4L2_TUNER_RADIO = 1, /* 調頻收音機 */
17 V4L2_TUNER_ANALOG_TV = 2, /* 模擬電視高頻頭 */
18 V4L2_TUNER_DIGITAL_TV = 3, /* 數字電視高頻頭 */
19 };
其中域 type 有三種類型;capability 域一般為 V4L2_TUNER_CAP_LOW,表明頻率調節的步長是62.5Hz,如果沒有這個標誌位則步長為62.5KHz;rangelow 與 rangehigh 是調諧器可以調頻率的最高值和最低值,但都以步長為單位表示;rxsubchans 表示調諧器接收的音頻信號類型,常見值有 V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO 即單聲道與立體聲;audmode 表示以何種方式播放聲音,常見值有 V4L2_TUNER_MODE_MONO | V4L2_TUNER_MODE_STEREO,即以單聲道還是立體聲的方式播放;signal 為當前信號強度,一般取值範圍為 0 - 65535。
9、struct v4l2_frequency 與 VIDIOC_G_FREQUENCY、VIDIOC_S_FREQUENCY
VIDIOC_S_FREQUENCY 或 VIDIOC_G_FREQUENCY 命令通過結構 v4l2_frequency 設置或獲取當前頻率值:
struct v4l2_frequency { __u32 tuner; /* 調諧器編號 */ enum v4l2_tuner_type type; /* 調諧器類型 */ __u32 frequency; /* 調諧器頻率 */ __u32 reserved[8]; };
註意:frequency 的值是以62.5Hz 或者 62.5KHZ 為單位的。
附:_IO、_IOR、_IOW、_IOWR 巨集的使用說明
驅動程式中 ioctl 函數傳遞的變數 cmd 是應用程式向驅動程式請求處理的命令。cmd 除了用於區別不同命令的數值,還可包含有助於處理的幾種信息。cmd 的大小為 32 bit,共分 4 個域:
bit29 ~ bit31: 3bit 為 “讀寫” 區,作用是區分是讀命令還是寫命令。
bit16 ~ bit28:13bit 為 "數據大小" 區,表示 ioctl 中的 arg 變數傳遞的數據大小;有時候為 14bit 即將 bit29 覆蓋。
bit8 ~ bit15: 8bit 為 “魔數"(也稱為"幻數")區,這個值用以與其它設備驅動程式的 ioctl 命令進行區別。
bit0 ~ bit7: 8bit 為 "序號" 區,是區分命令的命令順序序號。
魔數(magic number)
魔數範圍為 0~255 。通常,用英文字元 'A' ~ 'Z' 或者 'a' ~ 'z' 來表示。設備驅動程式從傳遞進來的命令獲取魔數,然後與自身處理的魔數想比較,如果相同則處理,不同則不處理。魔數是拒絕誤使用的初步輔助參數。設備驅動程式可以通過巨集 _IOC_TYPE (cmd) 來獲取魔數。不同的設備驅動程式最好設置不同的魔數,但並不是要求絕對,也是可以使用其他設備驅動程式已用過的魔數。
基數(序號)
基數用於區別各種命令。通常,從 0開始遞增,相同設備驅動程式上可以重覆使用該值。例如,讀和寫命令中使用了相同的基數,設備驅動程式也能分辨出來,原因在於設備驅動程式區分命令時使用 switch ,且直接使用命令變數 cmd 值。創建命令的巨集生成的值由多個域組合而成,所以即使是相同的基數,也會判斷為不同的命令。設備驅動程式想要從命令中獲取該基數,就使用巨集 _IOC_NR (cmd)。
下麵我們看一下上述巨集在內核中的原型:
下麵我們看一下上述巨集在內核中的原型
:
1 /* 2 * Our DIR and SIZE overlap in order to simulteneously provide 3 * a non-zero _IOC_NONE (for binary compatibility) and 4 * 14 bits of size as on i386. Here's the layout: 5 * 6 * 0xE0000000 DIR 3bit 7 * 0x80000000 DIR = WRITE bit31 8 * 0x40000000 DIR = READ bit30 9 * 0x20000000 DIR = NONE bit29 10 * 0x3FFF0000 SIZE (overlaps NONE bit) 13bit 11 * 0x0000FF00 TYPE 8bit 12 * 0x000000FF NR (CMD) 8bit 13 */ 14 /* 各個域的長度 */ 15 #define _IOC_NRBITS 8 16 #define _IOC_TYPEBITS 8 17 #define _IOC_SIZEBITS 13 /* Actually 14, see below. */ 18 #define _IOC_DIRBITS 3 19 /* 各個域的掩碼 */ 20 #define _IOC_NRMASK ((1 << _IOC_NRBITS)-1) 21 #define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1) 22 #define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1) 23 #define _IOC_XSIZEMASK ((1 << (_IOC_SIZEBITS+1))-1) 24 #define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1) 25 /* 各個域的偏移 */ 26 #define _IOC_NRSHIFT 0 27 #define _IOC_TYPESHIFT (_IOC_NRSHIFT + _IOC_NRBITS) /* 8 */ 28 #define _IOC_SIZESHIFT (_IOC_TYPESHIFT + _IOC_TYPEBITS) /* 16 */ 29 #define _IOC_DIRSHIFT (_IOC_SIZESHIFT + _IOC_SIZEBITS) /* 29 */ 30 /* 讀寫域的值 */ 31 #define _IOC_NONE 1U 32 #define _IOC_READ 2U 33 #define _IOC_WRITE 4U 34 35 #define _IOC(dir,type,nr,size) \ 36 (((dir) << _IOC_DIRSHIFT) | \ /* 讀寫方向左移 29bit */ 37 ((type) << _IOC_TYPESHIFT) | \ /* 幻數左移 8bit */ 38 ((nr) << _IOC_NRSHIFT) | \ /* 命令序號 */ 39 ((size) << _IOC_SIZESHIFT)) /* 參數大小左移 16bit */ 40 /* 巨集原型,這裡將會根據傳遞的數據類型取其長度 */ 41 #define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0) 42 #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size)) 43 #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size)) 44 #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size)) 45 /* 獲取各個域的值 */ 46 #define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK) 47 #define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK) 48 #define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK) 49 #define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
這裡特別說明一下 _IO 巨集,該巨集沒有可傳遞的變數,只用於發送命令。這是因為變數需要可變數據,只作為命令(比如 reset)使用時,沒有必要判斷設備上的數據,因此設備驅動程式沒有必要執行文件相關的處理。在 v4l2 中使用示例如下:
#define VIDIOC_QUERYCAP _IOR('V', 0, struct v4l2_capability) #define VIDIOC_RESERVED _IO('V', 1) #define VIDIOC_S_FMT _IOWR('V', 5, struct v4l2_format) #define VIDIOC_STREAMON _IOW('V', 18, int)
v4l2 中對上述巨集命令的處理在 video_ioctl2 函數中:
1 static unsigned long cmd_input_size(unsigned int cmd) 2 { 3 #define CMDINSIZE(cmd, type, field) \ 4 case VIDIOC_##cmd: \ 5 return offsetof(struct v4l2_##type, field) + \ /* 域的偏移 */ 6 sizeof(((struct v4l2_##type *)0)->field); /* 域的長度 */ 7 8 switch (cmd) { 9 CMDINSIZE(ENUM_FMT, fmtdesc, type); 10 CMDINSIZE(G_FMT, format, type); 11 ... 12 CMDINSIZE(ENUM_FRAMESIZES, frmsizeenum, pixel_format); 13 CMDINSIZE(ENUM_FRAMEINTERVALS, frmivalenum, height); 14 default: 15 return _IOC_SIZE(cmd); /* 剩下的是需要全部拷貝的命令 */ 16 } 17 } 18 19 long video_ioctl2(struct file *file, unsigned int cmd, unsigned long arg) 20 { 21 char sbuf[128]; /* 在棧中分配128個位元組空間用來儲存命令的參數 */ 22 void *mbuf = NULL; 23 void *parg = NULL; /* 參數存放的首地址 */ 24 long err = -EINVAL; 25 int is_ext_ctrl; 26 size_t ctrls_size = 0; 27 void __user *user_ptr = NULL; 28 29 ... 30 /* 判斷是否包含讀寫命令,如果是則將用戶空間的參數值拷貝到內核 */ 31 if (_IOC_DIR(cmd) != _IOC_NONE) { 32 /* 判斷參數大小是否超過128位元組 */ 33 if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { 34 parg = sbuf; 35 } else { 36 /* 如果超過128位元組則從堆中申請 */ 37 mbuf = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL); 38 if (NULL == mbuf) 39 return -ENOMEM; 40 parg = mbuf; 41 } 42 43 err = -EFAULT; 44 /* 如果包含寫命令 */ 45 if (_IOC_DIR(cmd) & _IOC_WRITE) { 46 /* 計算需要拷貝的有效數據長度,有的命令不需要全部拷貝 */ 47 unsigned long n = cmd_input_size(cmd); 48 /* 從用戶空間拷貝參數值 */ 49 if (copy_from_user(parg, (void __user *)arg, n)) 50 goto out; 51 52 /* 將剩下的空間清零 */ 53 if (n < _IOC_SIZE(cmd)) 54 memset((u8 *)parg + n, 0, _IOC_SIZE(cmd) - n); 55 } else { 56 /* 如果是只讀命令則將整個buffer清零 */ 57 memset(parg, 0, _IOC_SIZE(cmd)); 58 } 59 } 60 61 ... 62 /* 調用 v4l2_ioctl_ops 的成員函數處理命令 */ 63 err = __video_do_ioctl(file, cmd, parg); 64 if (err == -ENOIOCTLCMD) 65 err = -EINVAL; 66 ... 67 if (err < 0) 68 goto out; 69 70 out_ext_ctrl: 71 /* 如果包含讀命令則將參數值拷貝到用戶空間 */ 72 switch (_IOC_DIR(cmd)) { 73 case _IOC_READ: 74 case (_IOC_WRITE | _IOC_READ): 75 if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd))) 76 err = -EFAULT; 77 break; 78 } 79 80 out: 81 kfree(mbuf); 82 return err; 83 } 84 EXPORT_SYMBOL(video_ioctl2);
然後我們在 struct v4l2_file_operations 中將 ioctl 成員設置為 video_ioctl2 即可