-
在應用程式擷取視訊資料的流程中,都是通過 ioctl 指令與驅動程式進行互動,常見的 ioctl 指令有:
[cpp]
VIDIOC_QUERYCAP
VIDIOC_G_FMT
VIDIOC_S_FMT
VIDIOC_REQBUFS
VIDIOC_QUERYBUF
VIDIOC_QBUF
VIDIOC_DQBUF
VIDIOC_STREAMON
VIDIOC_STREAMOFF
VIDIOC_QUERYCTRL
VIDIOC_G_CTRL
VIDIOC_S_CTRL
VIDIOC_G_TUNER
VIDIOC_S_TUNER
VIDIOC_G_FREQUENCY
VIDIOC_S_FREQUENCY
1、struct v4l2_capability 與 VIDIOC_QUERYCAP
VIDIOC_QUERYCAP 指令通過結構 v4l2_capability 擷取裝置支援的操作模式:
[cpp]
struct v4l2_capability {
__u8 driver[16];
__u8 card[32];
__u8 bus_info[32];
__u32 version;
__u32 capabilities;
__u32 reserved[4];
};
其中域 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 指令:
[cpp]
struct v4l2_format {
enum v4l2_buf_type type;
union {
struct v4l2_pix_format pix;
struct v4l2_window win;
struct v4l2_vbi_format vbi;
struct v4l2_sliced_vbi_format sliced;
__u8 raw_data[200];
} fmt;
};
其中
enum v4l2_buf_type {
V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,
V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,
V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,
...
V4L2_BUF_TYPE_PRIVATE = 0x80,
};
struct v4l2_pix_format {
__u32 width;
__u32 height;
__u32 pixelformat;
enum v4l2_field field;
__u32 bytesperline;
__u32 sizeimage;
enum v4l2_colorspace colorspace;
__u32 priv;
};
常見的捕獲模式為 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 請求驅動申請一片連續的記憶體用于緩存視訊資訊:
[cpp]
struct v4l2_requestbuffers {
__u32 count;
enum v4l2_buf_type type;
enum v4l2_memory memory;
__u32 reserved[2];
};
其中
enum v4l2_memory {
V4L2_MEMORY_MMAP = 1,
V4L2_MEMORY_USERPTR = 2,
V4L2_MEMORY_OVERLAY = 3,
};
count 指定根據圖像占用空間大小申請的緩存區個數,type 為視訊捕獲模式,memory 為記憶體區的使用方式。
4、struct v4l2_buffer與 VIDIOC_QUERYBUF
VIDIOC_QUERYBUF 指令通過結構 v4l2_buffer 查詢驅動申請的記憶體區資訊:
[cpp]
struct v4l2_buffer {
__u32 index;
enum v4l2_buf_type type;
__u32 bytesused;
__u32 flags;
enum v4l2_field field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
enum v4l2_memory memory;
union {
__u32 offset;
unsigned long userptr;
} m;
__u32 length;
__u32 input;
__u32 reserved;
};
index 為緩存編号,type 為視訊捕獲模式,bytesused 為緩存已使用空間大小,flags 為緩存目前狀态(常見值有 V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE,分别代表目前緩存已經映射、緩存可以采集資料、緩存可以提取資料),timestamp 為時間戳,sequence為緩存序号,memory 為緩存使用方式,offset 為目前緩存與記憶體區起始位址的偏移,length 為緩存大小,reserved 一般用于傳遞實體位址值。
另外 VIDIOC_QBUF 和 VIDIOC_DQBUF 指令都采用結構 v4l2_buffer 與驅動通信:VIDIOC_QBUF 指令向驅動傳遞應用程式已經處理完的緩存,即将緩存加入空閑可捕獲視訊的隊列,傳遞的主要參數為 index;VIDIOC_DQBUF 指令向驅動擷取已經存放有視訊資料的緩存,v4l2_buffer 的各個域幾乎都會被更新,但主要的參數也是 index,應用程式會根據 index 确定可用資料的起始位址和範圍。
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 代表的指令,并傳回該指令的各種參數:
[cpp]
struct v4l2_queryctrl {
__u32 id;
enum v4l2_ctrl_type type;
__u8 name[32];
__s32 minimum;
__s32 maximum;
__s32 step;
__s32 default_value;
__u32 flags;
__u32 reserved[2];
};
其中
enum v4l2_ctrl_type {
V4L2_CTRL_TYPE_INTEGER = 1,
V4L2_CTRL_TYPE_BOOLEAN = 2,
V4L2_CTRL_TYPE_MENU = 3,
V4L2_CTRL_TYPE_BUTTON = 4,
V4L2_CTRL_TYPE_INTEGER64 = 5,
V4L2_CTRL_TYPE_CTRL_CLASS = 6,
V4L2_CTRL_TYPE_STRING = 7,
};
指令的标志取值如下:
#define V4L2_CTRL_FLAG_DISABLED 0x0001
#define V4L2_CTRL_FLAG_GRABBED 0x0002
#define V4L2_CTRL_FLAG_READ_ONLY 0x0004
#define V4L2_CTRL_FLAG_UPDATE 0x0008
#define V4L2_CTRL_FLAG_INACTIVE 0x0010
#define V4L2_CTRL_FLAG_SLIDER 0x0020
#define V4L2_CTRL_FLAG_WRITE_ONLY 0x0040
#define V4L2_CTRL_FLAG_NEXT_CTRL 0x80000000
id 是指令的編号,常見的指令有兩種:一種以 V4L2_CID_BASE 為起始值,是公用指令;一種以 V4L2_CID_PRIVATE_BASE 為起始值,是私有指令。在一般的應用中指令值可見如下:
[cpp]
V4L2_CID_CONTRAST (V4L2_CID_BASE+1)
V4L2_CID_SATURATION (V4L2_CID_BASE+2)
V4L2_CID_AUDIO_VOLUME (V4L2_CID_BASE+5)
V4L2_CID_AUDIO_MUTE (V4L2_CID_BASE+9)
V4L2_CID_DO_WHITE_BALANCE (V4L2_CID_BASE+13)
V4L2_CID_GAMMA (V4L2_CID_BASE+16)
V4L2_CID_EXPOSURE (V4L2_CID_BASE+17)
V4L2_CID_PRIVATE_ATXX_FLASH (V4L2_CID_PRIVATE_BASE + 2)
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 指令的值:
[cpp]
struct v4l2_control {
__u32 id;
__s32 value;
};
這個結構隻有 2 個域,id 是指令編号,value 則是指令的值。
8、struct v4l2_tuner 與 VIDIOC_G_TUNER、VIDIOC_S_TUNER
VIDIOC_S_TUNER 或 VIDIOC_G_TUNER 指令通過結構 v4l2_tuner 設定調諧器的資訊:
[cpp]
struct v4l2_tuner {
__u32 index;
__u8 name[32];
enum v4l2_tuner_type type;
__u32 capability;
__u32 rangelow;
__u32 rangehigh;
__u32 rxsubchans;
__u32 audmode;
__s32 signal;
__s32 afc;
__u32 reserved[4];
};
其中
enum v4l2_tuner_type {
V4L2_TUNER_RADIO = 1,
V4L2_TUNER_ANALOG_TV = 2,
V4L2_TUNER_DIGITAL_TV = 3,
};
其中域 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 設定或擷取目前頻率值:
[cpp] view plaincopy
struct v4l2_frequency {
__u32 tuner;
enum v4l2_tuner_type type;
__u32 frequency;
__u32 reserved[8];
};
注意:frequency 的值是以62.5Hz 或者 62.5KHZ 為機關的。
附1、_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)。
下面我們看一下上述宏在核心中的原型:
[cpp]
#define _IOC_NRBITS 8
#define _IOC_TYPEBITS 8
#define _IOC_SIZEBITS 13
#define _IOC_DIRBITS 3
#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1)
#define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1)
#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1)
#define _IOC_XSIZEMASK ((1 << (_IOC_SIZEBITS+1))-1)
#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1)
#define _IOC_NRSHIFT 0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT + _IOC_NRBITS)
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT + _IOC_TYPEBITS)
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT + _IOC_SIZEBITS)
#define _IOC_NONE 1U
#define _IOC_READ 2U
#define _IOC_WRITE 4U
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
這裡特别說明一下 _IO 宏,該宏沒有可傳遞的變量,隻用于發送指令。這是因為變量需要可變資料,隻作為指令(比如 reset)使用時,沒有必要判斷裝置上的資料,是以裝置驅動程式沒有必要執行檔案相關的處理。在 v4l2 中使用示例如下:
[cpp]
#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 函數中:
[cpp] view plaincopy
static unsigned long cmd_input_size(unsigned int cmd)
{
#define CMDINSIZE(cmd, type, field) \
case VIDIOC_##cmd: \
return offsetof(struct v4l2_##type, field) + \
sizeof(((struct v4l2_##type *)0)->field);
switch (cmd) {
CMDINSIZE(ENUM_FMT, fmtdesc, type);
CMDINSIZE(G_FMT, format, type);
...
CMDINSIZE(ENUM_FRAMESIZES, frmsizeenum, pixel_format);
CMDINSIZE(ENUM_FRAMEINTERVALS, frmivalenum, height);
default:
return _IOC_SIZE(cmd);
}
}
long video_ioctl2(struct file *file, unsigned int cmd, unsigned long arg)
{
char sbuf[128];
void *mbuf = NULL;
void *parg = NULL;
long err = -EINVAL;
int is_ext_ctrl;
size_t ctrls_size = 0;
void __user *user_ptr = NULL;
...
if (_IOC_DIR(cmd) != _IOC_NONE) {
if (_IOC_SIZE(cmd) <= sizeof(sbuf)) {
parg = sbuf;
} else {
mbuf = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL);
if (NULL == mbuf)
return -ENOMEM;
parg = mbuf;
}
err = -EFAULT;
if (_IOC_DIR(cmd) & _IOC_WRITE) {
unsigned long n = cmd_input_size(cmd);
if (copy_from_user(parg, (void __user *)arg, n))
goto out;
if (n < _IOC_SIZE(cmd))
memset((u8 *)parg + n, 0, _IOC_SIZE(cmd) - n);
} else {
memset(parg, 0, _IOC_SIZE(cmd));
}
}
...
err = __video_do_ioctl(file, cmd, parg);
if (err == -ENOIOCTLCMD)
err = -EINVAL;
...
if (err < 0)
goto out;
out_ext_ctrl:
switch (_IOC_DIR(cmd)) {
case _IOC_READ:
case (_IOC_WRITE | _IOC_READ):
if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
err = -EFAULT;
break;
}
out:
kfree(mbuf);
return err;
}
EXPORT_SYMBOL(video_ioctl2);
然後我們在 struct v4l2_file_operations 中将 ioctl 成員設定為 video_ioctl2 即可。