linux之V4L2摄像头应用流程
?對于v4l2,上次是在調試收音機驅動的時候用過,其他也就只是用i2c配置一些寄存器就可以了。那時只是粗粗的了解了,把收音機當作v4l2的設備后會在/dev目錄下生成一個radio的節點。然后就可以操作了。后來就沒怎么接觸了。這周,需要調試下usb的攝像頭。因為有問題,所以就要跟進,于是也就要開始學習下linux的v4l2了。看到一篇很不錯的文章,下面參考這篇文章,加上自己的一些見解,做一些總結把。
? ? ? ?Video for Linuxtwo(Video4Linux2)簡稱V4L2,是V4L的改進版。V4L2是linux操作系統下用于采集圖片、視頻和音頻數據的API接口,配合適當的視頻采集設備和相應的驅動程序,可以實現圖片、視頻、音頻等的采集。在遠程會議、可視電話、視頻監控系統和嵌入式多媒體終端中都有廣泛的應用。
在Linux下,所有外設都被看成一種特殊的文件,成為“設備文件”,可以象訪問普通文件一樣對其進行讀寫。一般來說,采用V4L2驅動的攝像頭設備文件是/dev/video0。V4L2支持兩種方式來采集圖像:內存映射方式(mmap)和直接讀取方式(read)。V4L2在include/linux/videodev.h文件中定義了一些重要的數據結構,在采集圖像的過程中,就是通過對這些數據的操作來獲得最終的圖像數據。Linux系統V4L2的能力可在Linux內核編譯階段配置,默認情況下都有此開發接口。
? ? ? ?而攝像頭所用的主要是capature了,視頻的捕捉,具體linux的調用可以參考下圖。
應用程序通過V4L2進行視頻采集的原理
V4L2支持內存映射方式(mmap)和直接讀取方式(read)來采集數據,前者一般用于連續視頻數據的采集,后者常用于靜態圖片數據的采集,本文重點討論內存映射方式的視頻采集。
應用程序通過V4L2接口采集視頻數據分為五個步驟:
首先,打開視頻設備文件,進行視頻采集的參數初始化,通過V4L2接口設置視頻圖像的采集窗口、采集的點陣大小和格式;
其次,申請若干視頻采集的幀緩沖區,并將這些幀緩沖區從內核空間映射到用戶空間,便于應用程序讀取/處理視頻數據;
第三,將申請到的幀緩沖區在視頻采集輸入隊列排隊,并啟動視頻采集;
第四,驅動開始視頻數據的采集,應用程序從視頻采集輸出隊列取出幀緩沖區,處理完后,將幀緩沖區重新放入視頻采集輸入隊列,循環往復采集連續的視頻數據;
第五,停止視頻采集。
具體的程序實現流程可以參考下面的流程圖:
應用程序通過V4L2進行視頻采集的原理
V4L2支持內存映射方式(mmap)和直接讀取方式(read)來采集數據,前者一般用于連續視頻數據的采集,后者常用于靜態圖片數據的采集,本文重點討論內存映射方式的視頻采集。
應用程序通過V4L2接口采集視頻數據分為五個步驟:
首先,打開視頻設備文件,進行視頻采集的參數初始化,通過V4L2接口設置視頻圖像的采集窗口、采集的點陣大小和格式;
其次,申請若干視頻采集的幀緩沖區,并將這些幀緩沖區從內核空間映射到用戶空間,便于應用程序讀取/處理視頻數據;
第三,將申請到的幀緩沖區在視頻采集輸入隊列排隊,并啟動視頻采集;
第四,驅動開始視頻數據的采集,應用程序從視頻采集輸出隊列取出幀緩沖區,處理完后,將幀緩沖區重新放入視頻采集輸入隊列,循環往復采集連續的視頻數據;
第五,停止視頻采集。
具體的程序實現流程可以參考下面的流程圖:
?
每一個幀緩沖區都有一個對應的狀態標志變量,其中每一個比特代表一個狀態
V4L2_BUF_FLAG_UNMAPPED 0B0000
V4L2_BUF_FLAG_MAPPED 0B0001
V4L2_BUF_FLAG_ENQUEUED 0B0010
V4L2_BUF_FLAG_DONE 0B0100
緩沖區的狀態轉化如圖所示。
?
下面的程序注釋的很好,就拿來參考下:
?
?
V4L2 編程
1. 定義
V4L2(Video ForLinux Two) 是內核提供給應用程序訪問音、視頻驅動的統一接口。
?
2. 工作流程:
打開設備-> 檢查和設置設備屬性->設置幀格式-> 設置一種輸入輸出方法(緩沖區管理)-> 循環獲取數據-> 關閉設備。
?
3. 設備的打開和關閉:
?
#include<fcntl.h>
int open(constchar *device_name, int flags);
?
#include <unistd.h>
int close(intfd);
例:
int fd=open(“/dev/video0”,O_RDWR);// 打開設備 close(fd);// 關閉設備注意:V4L2 的相關定義包含在頭文件<linux/videodev2.h>中.
?
4. 查詢設備屬性: VIDIOC_QUERYCAP
相關函數:
?
int ioctl(intfd, int request, struct v4l2_capability *argp);相關結構體
structv4l2_capability { __u8 driver[16]; // 驅動名字 __u8 card[32]; // 設備名字 __u8bus_info[32]; // 設備在系統中的位置 __u32 version; // 驅動版本號 __u32capabilities; // 設備支持的操作 __u32reserved[4]; // 保留字段 }; capabilities 常用值: V4L2_CAP_VIDEO_CAPTURE // 是否支持圖像獲取?例:顯示設備信息
structv4l2_capability cap; ioctl(fd,VIDIOC_QUERYCAP,&cap); printf(“DriverName:%s/nCard Name:%s/nBus info:%s/nDriverVersion:%u.%u.%u/n”,cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0XFF,(cap.version>>8)&0XFF,cap.version&OXFF);5. 幀格式:
VIDIOC_ENUM_FMT// 顯示所有支持的格式 int ioctl(intfd, int request, struct v4l2_fmtdesc *argp); structv4l2_fmtdesc { __u32 index; // 要查詢的格式序號,應用程序設置 enumv4l2_buf_type type; // 幀類型,應用程序設置 __u32 flags; // 是否為壓縮格式 __u8 description[32]; // 格式名稱 __u32pixelformat; // 格式 __u32reserved[4]; // 保留 };例:顯示所有支持的格式
structv4l2_fmtdesc fmtdesc; fmtdesc.index=0; fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; printf("Supportformat:/n"); while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1) { printf("/t%d.%s/n",fmtdesc.index+1,fmtdesc.description); fmtdesc.index++; }?
// 查看或設置當前格式
VIDIOC_G_FMT,VIDIOC_S_FMT
// 檢查是否支持某種格式
VIDIOC_TRY_FMT int ioctl(intfd, int request, struct v4l2_format *argp); structv4l2_format { enumv4l2_buf_type type;// 幀類型,應用程序設置 union fmt { structv4l2_pix_format pix;// 視頻設備使用 structv4l2_window win; structv4l2_vbi_format vbi; structv4l2_sliced_vbi_format sliced; __u8raw_data[200]; }; }; structv4l2_pix_format { __u32 width; // 幀寬,單位像素 __u32 height; // 幀高,單位像素 __u32pixelformat; // 幀格式 enum v4l2_fieldfield; __u32bytesperline; __u32 sizeimage; enumv4l2_colorspace colorspace; __u32 priv; };例:顯示當前幀的相關信息
structv4l2_format fmt; fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(fd,VIDIOC_G_FMT,&fmt); printf(“Currentdata format information: /n/twidth:%d/n/theight:%d/n”,fmt.fmt.width,fmt.fmt.height); structv4l2_fmtdesc fmtdesc; fmtdesc.index=0; fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1) { if(fmtdesc.pixelformat& fmt.fmt.pixelformat) { printf(“/tformat:%s/n”,fmtdesc.description); break; } fmtdesc.index++; }?例:檢查是否支持某種幀格式
structv4l2_format fmt; fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_RGB32; if(ioctl(fd,VIDIOC_TRY_FMT,&fmt)==-1) if(errno==EINVAL) printf(“notsupport format RGB32!/n”);6. 圖像的縮放
VIDIOC_CROPCAP int ioctl(int fd,int request, struct v4l2_cropcap *argp); structv4l2_cropcap { enumv4l2_buf_type type;// 應用程序設置 struct v4l2_rectbounds;// 最大邊界 struct v4l2_rectdefrect;// 默認值 structv4l2_fract pixelaspect; };// 設置縮放
VIDIOC_G_CROP,VIDIOC_S_CROP int ioctl(intfd, int request, struct v4l2_crop *argp); int ioctl(intfd, int request, const struct v4l2_crop *argp); struct v4l2_crop { enumv4l2_buf_type type;// 應用程序設置 struct v4l2_rectc; }7. 申請和管理緩沖區,應用程序和設備有三種交換數據的方法,直接read/write ,內存映射(memorymapping) ,用戶指針。這里只討論 memorymapping.
// 向設備申請緩沖區
VIDIOC_REQBUFS
int ioctl(intfd, int request, struct v4l2_requestbuffers *argp);
structv4l2_requestbuffers
{
__u32 count; ?// 緩沖區內緩沖幀的數目
enumv4l2_buf_type type; ? ? // 緩沖幀數據格式
enum v4l2_memorymemory; ? ? ? // 區別是內存映射還是用戶指針方式
__u32 reserved[2];
};
?
enum v4l2_memoy{V4L2_MEMORY_MMAP,V4L2_MEMORY_USERPTR};
//count,type,memory都要應用程序設置
例:申請一個擁有四個緩沖幀的緩沖區
structv4l2_requestbuffers req; req.count=4; req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory=V4L2_MEMORY_MMAP; ioctl(fd,VIDIOC_REQBUFS,&req);獲取緩沖幀的地址,長度:
VIDIOC_QUERYBUF
int ioctl(intfd, int request, struct v4l2_buffer *argp);
structv4l2_buffer { __u32 index; //buffer 序號 enumv4l2_buf_type type; //buffer 類型 __u32 byteused; //buffer 中已使用的字節數 __u32 flags; // 區分是MMAP 還是USERPTR enum v4l2_fieldfield; struct timevaltimestamp;// 獲取第一個字節時的系統時間 structv4l2_timecode timecode; __u32 sequence;// 隊列中的序號 enum v4l2_memorymemory;//IO 方式,被應用程序設置 union m { __u32 offset;// 緩沖幀地址,只對MMAP 有效 unsigned longuserptr; }; __u32 length;// 緩沖幀長度 __u32 input; __u32 reserved; };?MMAP ,定義一個結構體來映射每個緩沖幀。
Struct buffer { void* start; unsigned intlength; }*buffers;#include<sys/mman.h>
void *mmap(void*addr, size_t length, int prot, int flags, int fd, off_t offset);
//addr 映射起始地址,一般為NULL ,讓內核自動選擇
//length 被映射內存塊的長度
//prot 標志映射后能否被讀寫,其值為PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE
//flags 確定此內存映射能否被其他進程共享,MAP_SHARED,MAP_PRIVATE
//fd,offset, 確定被映射的內存地址
返回成功映射后的地址,不成功返回MAP_FAILED ((void*)-1);
?
int munmap(void*addr, size_t length);// 斷開映射
//addr 為映射后的地址,length 為映射后的內存長度
?
例:將四個已申請到的緩沖幀映射到應用程序,用buffers 指針記錄。
buffers =(buffer*)calloc (req.count, sizeof (*buffers)); if (!buffers) { fprintf (stderr,"Out of memory/n"); exit(EXIT_FAILURE); }// 映射
for (unsignedint n_buffers = 0; n_buffers < req.count; ++n_buffers) { struct v4l2_bufferbuf; memset(&buf,0,sizeof(buf)); buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory =V4L2_MEMORY_MMAP; buf.index =n_buffers; // 查詢序號為n_buffers 的緩沖區,得到其起始物理地址和大小 if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf)) exit(-1); buffers[n_buffers].length= buf.length; // 映射內存 buffers[n_buffers].start=mmap (NULL,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED,fd, buf.m.offset); if (MAP_FAILED== buffers[n_buffers].start) exit(-1); }?8. 緩沖區處理好之后,就可以開始獲取數據了
// 啟動/ 停止數據流 VIDIOC_STREAMON,VIDIOC_STREAMOFF int ioctl(intfd, int request, const int *argp); //argp 為流類型指針,如V4L2_BUF_TYPE_VIDEO_CAPTURE. 在開始之前,還應當把緩沖幀放入緩沖隊列: VIDIOC_QBUF// 把幀放入隊列 VIDIOC_DQBUF// 從隊列中取出幀 int ioctl(intfd, int request, struct v4l2_buffer *argp);?例:把四個緩沖幀放入隊列,并啟動數據流
unsigned int i; enum v4l2_buf_typetype; // 將緩沖幀放入隊列 for (i = 0; i< 4; ++i) { structv4l2_buffer buf; buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory =V4L2_MEMORY_MMAP; buf.index = i; ioctl (fd,VIDIOC_QBUF, &buf); } type =V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl (fd,VIDIOC_STREAMON, &type);例:獲取一幀并處理
structv4l2_buffer buf; CLEAR (buf); buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory =V4L2_MEMORY_MMAP; // 從緩沖區取出一個緩沖幀 ioctl (fd,VIDIOC_DQBUF, &buf); // 圖像處理 process_image(buffers[buf.index].start); // 將取出的緩沖幀放回緩沖區 ioctl (fd, VIDIOC_QBUF,&buf);至于驅動的實現,可以參考內核中,我是用usb攝像頭的,所以,其實現都是好的。主要就是應用程序的實現了。驅動都哦在uvc目錄下面,這個待理解。?
?本文轉自:https://blog.csdn.net/eastmoon502136/article/details/8190262
?
?
?
?
?
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的linux之V4L2摄像头应用流程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 爆炸般的伤害 dnf神圣符咒属性详细解析
- 下一篇: 《暗黑3》彩虹关物品合成方法详细介绍