天天看點

v4l2 程式設計接口 之 ioctl(有中文注釋很詳細)

在應用程式擷取視訊資料的流程中,都是通過 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 即可。