參考資料:韋東山第三期
v4l2應用程式開發分為兩個部分,資料採集流程和控制流程兩個部分
資料採集流程:
分為空閒連結串列和完成連結串列
驅動程式週而復始地做如下事情:
- 從硬體採集到資料
- 把"空閒連結串列"取出buffer,把資料存入buffer
- 把含有資料的buffer放入"完成連結串列"
APP也會週而復始地做如下事情:
- 監測"完成連結串列",等待它含有buffer
- 從"完成連結串列"中取出buffer
- 處理資料
- 把buffer放入"空閒連結串列"
連結串列操作示意圖:
攝像頭的應用操作流程,如下:
- open:開啟裝置節點/dev/videoX
- ioctl VIDIOC_QUERYCAP:Query Capbility,查詢能力,比如
- 確認它是否是"捕獲裝置",因為有些節點是輸出裝置
- 確認它是否支援mmap操作,還是僅支援read/write操作
- ioctl VIDIOC_ENUM_FMT:列舉它支援的格式
- ioctl VIDIOC_S_FMT:在上面列舉出來的格式裡,選擇一個來設定格式
- ioctl VIDIOC_REQBUFS:申請buffer,APP可以申請很多個buffer,但是驅動程式不一定能申請到,requery申請
- ioctl VIDIOC_QUERYBUF和mmap:查詢buffer資訊、對映
- 如果申請到了N個buffer,這個ioctl就應該執行N次
- 執行mmap後,APP就可以直接讀寫這些buffer
- ioctl VIDIOC_QBUF:把buffer放入"空閒連結串列"
- 如果申請到了N個buffer,這個ioctl就應該執行N次
- ioctl VIDIOC_STREAMON:啟動攝像頭
- 這裡是一個迴圈:使用poll/select監測buffer,然後從"完成連結串列"中取出buffer,處理後再放入"空閒連結串列"
- poll/select
- ioctl VIDIOC_DQBUF:從"完成連結串列"中取出buffer
- 處理:前面使用mmap對映了每個buffer的地址,處理時就可以直接使用地址來訪問buffer
- ioclt VIDIOC_QBUF:把buffer放入"空閒連結串列"
- ioctl VIDIOC_STREAMOFF:停止攝像頭
控制流程:
應用程式介面
資料格式的列舉、獲得和設定:
1、資料格式
列舉格式的時候,只是返回支援的pixelformat和描述
struct v4l2_fmtdesc fmtdesc; fmtdesc.index = 0; // 比如從0開始 fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 指定type為"捕獲" ioctl(vd->fd, VIDIOC_ENUM_FMT, &fmtdesc); // uapi/linux/videodev2.h struct v4l2_fmtdesc { __u32 index; /* Format number */ __u32 type; /* enum v4l2_buf_type */ __u32 flags; __u8 description[32]; /* Description string */ __u32 pixelformat; /* Format fourcc */ __u32 reserved[4]; }; 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_VBI_CAPTURE = 4, V4L2_BUF_TYPE_VBI_OUTPUT = 5, V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6, V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7, #if 1 /* Experimental */ V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8, #endif V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE = 10, V4L2_BUF_TYPE_SDR_CAPTURE = 11, /* Deprecated, do not use */ V4L2_BUF_TYPE_PRIVATE = 0x80, };
2、獲取當前攝像頭使用的格式
獲取當前格式更加詳細的資訊,就需要使用VIDIOC_G_FRT
struct v4l2_format currentFormat; memset(¤tFormat, 0, sizeof(struct v4l2_format)); currentFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(vd->fd, VIDIOC_G_FMT, ¤tFormat); #if 0 struct v4l2_format { __u32 type; // 表示捕獲裝置 union { struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */ struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */ struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */ struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */ struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */ struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */ __u8 raw_data[200]; /* user-defined */ } fmt; }; /* * V I D E O I M A G E F O R M A T */ struct v4l2_pix_format { __u32 width; __u32 height; __u32 pixelformat; __u32 field; /* enum v4l2_field */ __u32 bytesperline; /* for padding, zero if unused */ __u32 sizeimage; __u32 colorspace; /* enum v4l2_colorspace */ __u32 priv; /* private data, depends on pixelformat */ __u32 flags; /* format flags (V4L2_PIX_FMT_FLAG_*) */ __u32 ycbcr_enc; /* enum v4l2_ycbcr_encoding */ __u32 quantization; /* enum v4l2_quantization */ __u32 xfer_func; /* enum v4l2_xfer_func */ }; #endif
3、設定當前格式
使用VIDIOC_S_FMT
struct v4l2_format fmt; memset(&fmt, 0, sizeof(struct v4l2_format)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = 1024; fmt.fmt.pix.height = 768; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; fmt.fmt.pix.field = V4L2_FIELD_ANY; int ret = ioctl(vd->fd, VIDIOC_S_FMT, &fmt);
選擇輸入源:
int value; ioctl(h->fd,VIDIOC_G_INPUT,&value); // 讀到的value從0開始, 0表示第1個input源 int value = 0; // 0表示第1個input源 ioctl(h->fd,VIDIOC_S_INPUT,&value)
其他引數:
如果每一引數都提供一系列的ioctl cmd,那使用起來很不方便。
對於這些引數,APP使用對應ID來選中它,然後使用VIDIOC_QUERYCTRL、VIDIOC_G_CTRL、VIDIOC_S_CTRL來操作它。
不同引數的ID值不同。以亮度Brightness為例,有如下呼叫方法:
1、查詢
struct v4l2_queryctrl qctrl; memset(&qctrl, 0, sizeof(qctrl)); qctrl.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0; ioctl(fd, VIDIOC_QUERYCTRL, &qctrl); /* Used in the VIDIOC_QUERYCTRL ioctl for querying controls */ struct v4l2_queryctrl { __u32 id; __u32 type; /* enum v4l2_ctrl_type */ __u8 name[32]; /* Whatever */ __s32 minimum; /* Note signedness */ __s32 maximum; __s32 step; __s32 default_value; __u32 flags; __u32 reserved[2]; };
2、獲得當前值
struct v4l2_control c; c.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0; ioctl(h->fd, VIDIOC_G_CTRL, &c); /* * C O N T R O L S */ struct v4l2_control { __u32 id; __s32 value; };
3、設定
struct v4l2_control c; c.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0; c.value = 99; ioctl(h->fd, VIDIOC_S_CTRL, &c);
理解介面:
SU:select unit,選擇輸入源
PU:processiong unit,用於調整亮度、對比度、色度等
EU:encoding unit,對採集到的資料進行個性化處理的功能
ID會傳遞給PU
app->驅動->(id, val)->硬體
操作方法:
使用ioctl操作裝置節點/dev/video0時,不同的ioctl操作可能是video control介面,或者video streaming介面
跟影片流相關的操作,比如VIDIOC_ENUM_FMT,VIDIOC_G_FMT,VIDIOC_S_FMT,VIDIOC_STREAMON,VIDIOC_STREAMOFF,是操作video streaming的介面
其他的介面,大多是video control介面
從驅動和硬體角度來看,要操作video control介面,需要指明:
1、entity:要操作的是那個terminal或者unit,比如PU
2、control selector:要操作entity裡面的那個控制項,比如亮度
3、控制項裡面的哪些位:比如camera terminal裡的CT_PANTILT_RELATIVE_CONTROL控制項對應的32位資料,其中前16位對應PAN控制(左右轉動),後16位對應TILE控制(上下轉動)
但是應用程式不關心這些,使用一個ID來指定entity、control selector、哪些位:
struct v4l2_control c; c.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0; c.value = 99; ioctl(h->fd, VIDIOC_S_CTRL, &c); /* * C O N T R O L S */ struct v4l2_control { __u32 id; __s32 value; };
支援V4L2_CAP_STREAMING就可以用mmap函式,不支援的話就只能用V4L2_CAP_READWRITE了