2010-09-10 16:26:04|??分類: 默認(rèn)分類 |??標(biāo)簽: |字號大中小?訂閱
?轉(zhuǎn)載:
以前做的智能家居的項目用的是Linux2.6.13的核,使用的中星微的攝像頭,移植了spcaview進(jìn)行圖像的獲取,后來用了2.6.29的核,發(fā)現(xiàn)以前移植的spcaview不能用了,后來查了一下,發(fā)現(xiàn)2.6.29核采用了UVC的驅(qū)動(萬能驅(qū)動),采用了V4L2框架,而spcaview是基于V4L1的框架,API接口存在差異。所以需要自己寫圖片獲取的應(yīng)用程序。
?
下面主要是一些資料的總結(jié),并給出一個可以結(jié)果測試的代碼:
?
一.什么是video4linux
Video4linux2(簡稱V4L2),是linux中關(guān)于視頻設(shè)備的內(nèi)核驅(qū)動。在Linux中,視頻設(shè)備是設(shè)備文件,可以像訪問普通文件一樣對其進(jìn)行讀寫,攝像頭在/dev/video0下。
?
二.一般操作流程(視頻設(shè)備):
1.打開設(shè)備文件。 int fd=open(”/dev/video0″,O_RDWR);
2.取得設(shè)備的capability,看看設(shè)備具有什么功能,比如是否具有視頻輸入,或者音頻輸入輸出等。VIDIOC_QUERYCAP,struct v4l2_capability
3.設(shè)置視頻的制式和幀格式,制式包括PAL,NTSC,幀的格式個包括寬度和高度等。
VIDIOC_S_STD,VIDIOC_S_FMT,struct v4l2_std_id,struct v4l2_format
4.向驅(qū)動申請幀緩沖,一般不超過5個。struct v4l2_requestbuffers
5.將申請到的幀緩沖映射到用戶空間,這樣就可以直接操作采集到的幀了,而不必去復(fù)制。mmap
6.將申請到的幀緩沖全部入隊列,以便存放采集到的數(shù)據(jù).VIDIOC_QBUF,struct v4l2_buffer
7.開始視頻的采集。VIDIOC_STREAMON
8.出隊列以取得已采集數(shù)據(jù)的幀緩沖,取得原始采集數(shù)據(jù)。VIDIOC_DQBUF
9.將緩沖重新入隊列尾,這樣可以循環(huán)采集。VIDIOC_QBUF
10.停止視頻的采集。VIDIOC_STREAMOFF
11.關(guān)閉視頻設(shè)備。close(fd);
三、常用的結(jié)構(gòu)體(參見/usr/include/linux/videodev2.h):
struct v4l2_requestbuffers reqbufs;//向驅(qū)動申請幀緩沖的請求,里面包含申請的個數(shù)
struct v4l2_capability cap;//這個設(shè)備的功能,比如是否是視頻輸入設(shè)備
struct v4l2_standard std;//視頻的制式,比如PAL,NTSC
struct v4l2_format fmt;//幀的格式,比如寬度,高度等
struct v4l2_buffer buf;//代表驅(qū)動中的一幀
v4l2_std_id stdid;//視頻制式,例如:V4L2_STD_PAL_B
struct v4l2_queryctrl query;//查詢的控制
struct v4l2_control control;//具體控制的值
?
下面具體說明開發(fā)流程(網(wǎng)上找的)
打開視頻設(shè)備
在V4L2中,視頻設(shè)備被看做一個文件。使用open函數(shù)打開這個設(shè)備:
//用非阻塞模式打開攝像頭設(shè)備
intcameraFd;
cameraFd= open(“/dev/video0″, O_RDWR| O_NONBLOCK, 0);
//如果用阻塞模式打開攝像頭設(shè)備,上述代碼變?yōu)椋?/p>
//cameraFd = open(”/dev/video0″, O_RDWR, 0);
關(guān)于阻塞模式和非阻塞模式
應(yīng)用程序能夠使用阻塞模式或非阻塞模式打開視頻設(shè)備,如果使用非阻塞模式調(diào)用視頻設(shè)備,即使尚未捕獲到信息,驅(qū)動依舊會把緩存(DQBUFF)里的東西返回給應(yīng)用程序。
設(shè)定屬性及采集方式
打開視頻設(shè)備后,可以設(shè)置該視頻設(shè)備的屬性,例如裁剪、縮放等。這一步是可選的。在Linux編程中,一般使用ioctl函數(shù)來對設(shè)備的I/O通道進(jìn)行管理:
extern intioctl(int__fd, unsigned long int__request, …) __THROW;
__fd:設(shè)備的ID,例如剛才用open函數(shù)打開視頻通道后返回的cameraFd;
__request:具體的命令標(biāo)志符。
在進(jìn)行V4L2開發(fā)中,一般會用到以下的命令標(biāo)志符:
1 VIDIOC_REQBUFS:分配內(nèi)存
2 VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的數(shù)據(jù)緩存轉(zhuǎn)換成物理地址
3 VIDIOC_QUERYCAP:查詢驅(qū)動功能
4 VIDIOC_ENUM_FMT:獲取當(dāng)前驅(qū)動支持的視頻格式
5 VIDIOC_S_FMT:設(shè)置當(dāng)前驅(qū)動的頻捕獲格式
6 VIDIOC_G_FMT:讀取當(dāng)前驅(qū)動的頻捕獲格式
7 VIDIOC_TRY_FMT:驗證當(dāng)前驅(qū)動的顯示格式
8 VIDIOC_CROPCAP:查詢驅(qū)動的修剪能力
9 VIDIOC_S_CROP:設(shè)置視頻信號的邊框
10 VIDIOC_G_CROP:讀取視頻信號的邊框
11 VIDIOC_QBUF:把數(shù)據(jù)從緩存中讀取出來
12 VIDIOC_DQBUF:把數(shù)據(jù)放回緩存隊列
13 VIDIOC_STREAMON:開始視頻顯示函數(shù)
14 VIDIOC_STREAMOFF:結(jié)束視頻顯示函數(shù)
15 VIDIOC_QUERYSTD:檢查當(dāng)前視頻設(shè)備支持的標(biāo)準(zhǔn),例如PAL或NTSC。
這些IO調(diào)用,有些是必須的,有些是可選擇的。
檢查當(dāng)前視頻設(shè)備支持的標(biāo)準(zhǔn)
在亞洲,一般使用PAL(720X576)制式的攝像頭,而歐洲一般使用NTSC(720X480),使用VIDIOC_QUERYSTD來檢測:
v4l2_std_id std;
do{
ret= ioctl(fd, VIDIOC_QUERYSTD, &std);
} while(ret== -1 && errno== EAGAIN);
switch(std) {
caseV4L2_STD_NTSC:
//……
caseV4L2_STD_PAL:
//……
}
設(shè)置視頻捕獲格式
當(dāng)檢測完視頻設(shè)備支持的標(biāo)準(zhǔn)后,還需要設(shè)定視頻捕獲格式:
structv4l2_format??? fmt;
memset( &fmt, 0, sizeof(fmt) );
fmt.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width= 720;
fmt.fmt.pix.height= 576;
fmt.fmt.pix.pixelformat= V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field= V4L2_FIELD_INTERLACED;
if(ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
return-1;
}
v4l2_format結(jié)構(gòu)體定義如下:
structv4l2_format
{
enumv4l2_buf_type type;??? //數(shù)據(jù)流類型,必須永遠(yuǎn)是//V4L2_BUF_TYPE_VIDEO_CAPTURE
union
{
structv4l2_pix_format??? pix;
structv4l2_window??????? win;
structv4l2_vbi_format??? vbi;
__u8??? raw_data[200];
} fmt;
};
structv4l2_pix_format
{
__u32?????????????????? width;???????? //寬,必須是16的倍數(shù)
__u32?????????????????? height;??????? //高,必須是16的倍數(shù)
__u32?????????????????? pixelformat;?? //視頻數(shù)據(jù)存儲類型,例如是//YUV4:2:2還是RGB
enumv4l2_field???????? field;
__u32?????????????????? bytesperline;
__u32?????????????????? sizeimage;
enumv4l2_colorspace??? colorspace;
__u32?????????????????? priv;
};
分配內(nèi)存
接下來可以為視頻捕獲分配內(nèi)存:
structv4l2_requestbuffers? req;
if(ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
return-1;
}
v4l2_requestbuffers定義如下:
structv4l2_requestbuffers
{
__u32?????????????? count;? //緩存數(shù)量,也就是說在緩存隊列里保持多少張照片
enumv4l2_buf_type? type;?? //數(shù)據(jù)流類型,必須永遠(yuǎn)是V4L2_BUF_TYPE_VIDEO_CAPTURE
enumv4l2_memory??? memory; // V4L2_MEMORY_MMAP或 V4L2_MEMORY_USERPTR
__u32????????????? ?reserved[2];
};
獲取并記錄緩存的物理空間
使用VIDIOC_REQBUFS,我們獲取了req.count個緩存,下一步通過調(diào)用VIDIOC_QUERYBUF命令來獲取這些緩存的地址,然后使用mmap函數(shù)轉(zhuǎn)換成應(yīng)用程序中的絕對地址,最后把這段緩存放入緩存隊列:
typedef structVideoBuffer{
void*start;
size_t? length;
} VideoBuffer;
?
VideoBuffer*????????? buffers= calloc( req.count, sizeof(*buffers) );
structv4l2_buffer??? buf;
?
for(numBufs= 0; numBufs< req.count; numBufs++) {
memset( &buf, 0, sizeof(buf) );
buf.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory= V4L2_MEMORY_MMAP;
buf.index= numBufs;
//讀取緩存
if(ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
return-1;
}
buffers[numBufs].length= buf.length;
//轉(zhuǎn)換成相對地址
buffers[numBufs].start= mmap(NULL, buf.length,
PROT_READ| PROT_WRITE,
MAP_SHARED,
fd, buf.m.offset);
?
if(buffers[numBufs].start== MAP_FAILED) {
return-1;
}
?
//放入緩存隊列
if(ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
return-1;
}
}
關(guān)于視頻采集方式
操作系統(tǒng)一般把系統(tǒng)使用的內(nèi)存劃分成用戶空間和內(nèi)核空間,分別由應(yīng)用程序管理和操作系統(tǒng)管理。應(yīng)用程序可以直接訪問內(nèi)存的地址,而內(nèi)核空間存放的是供內(nèi)核訪問的代碼和數(shù)據(jù),用戶不能直接訪問。v4l2捕獲的數(shù)據(jù),最初是存放在內(nèi)核空間的,這意味著用戶不能直接訪問該段內(nèi)存,必須通過某些手段來轉(zhuǎn)換地址。
一共有三種視頻采集方式:使用read、write方式;內(nèi)存映射方式和用戶指針模式。
read、write方式:在用戶空間和內(nèi)核空間不斷拷貝數(shù)據(jù),占用了大量用戶內(nèi)存空間,效率不高。
內(nèi)存映射方式:把設(shè)備里的內(nèi)存映射到應(yīng)用程序中的內(nèi)存控件,直接處理設(shè)備內(nèi)存,這是一種有效的方式。上面的mmap函數(shù)就是使用這種方式。
用戶指針模式:內(nèi)存片段由應(yīng)用程序自己分配。這點(diǎn)需要在v4l2_requestbuffers里將memory字段設(shè)置成V4L2_MEMORY_USERPTR。
處理采集數(shù)據(jù)
V4L2有一個數(shù)據(jù)緩存,存放req.count數(shù)量的緩存數(shù)據(jù)。數(shù)據(jù)緩存采用FIFO的方式,當(dāng)應(yīng)用程序調(diào)用緩存數(shù)據(jù)時,緩存隊列將最先采集到的 視頻數(shù)據(jù)緩存送出,并重新采集一張視頻數(shù)據(jù)。這個過程需要用到兩個ioctl命令,VIDIOC_DQBUF和VIDIOC_QBUF:
structv4l2_buffer buf;
memset(&buf,0,sizeof(buf));
buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory=V4L2_MEMORY_MMAP;
buf.index=0;
//讀取緩存
if(ioctl(cameraFd, VIDIOC_DQBUF, &buf) == -1)
{
return-1;
}
//…………視頻處理算法
//重新放入緩存隊列
if(ioctl(cameraFd, VIDIOC_QBUF, &buf) == -1) {
return-1;
}
關(guān)閉視頻設(shè)備
使用close函數(shù)關(guān)閉一個視頻設(shè)備
close(cameraFd)
還需要使用munmap方法。
?
下面是代碼,加了一部分注釋:
//#加了點(diǎn)注釋
?
//#Rockie Cheng
?
?
#include
#include
#include
#include
?
#include
?
#include
#include
#include
#include
#include
#include
#include
#include
#include
?
#include
#include
?
#define CLEAR(x) memset (&(x), 0, sizeof (x))
?
struct buffer {
??????? void *????????????????? start;
??????? size_t????????????????? length;
};
?
static char *?????????? dev_name??????? = "/dev/video0";//攝像頭設(shè)備名
static int????????????? fd????????????? = -1;
struct buffer *???????? buffers???????? = NULL;
static unsigned int???? n_buffers?????? = 0;
?
FILE *file_fd;
static unsigned long file_length;
static unsigned char *file_name;
//////////////////////////////////////////////////////
//獲取一幀數(shù)據(jù)
//////////////////////////////////////////////////////
static int read_frame (void)
{
struct v4l2_buffer buf;
unsigned int i;
?
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
int ff = ioctl (fd, VIDIOC_DQBUF, &buf);
if(ff<0)
printf("failturen"); //出列采集的幀緩沖
?
assert (buf.index < n_buffers);
?? printf ("buf.index dq is %d,n",buf.index);
?
fwrite(buffers[buf.index].start, buffers[buf.index].length, 1, file_fd); //將其寫入文件中
?
ff=ioctl (fd, VIDIOC_QBUF, &buf); //再將其入列
if(ff<0)
printf("failture VIDIOC_QBUFn");
return 1;
}
?
int main (int argc,char ** argv)
{
struct v4l2_capability cap;
struct v4l2_format fmt;
unsigned int i;
enum v4l2_buf_type type;
?
file_fd = fopen("test-mmap.jpg", "w");//圖片文件名
?
fd = open (dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);//打開設(shè)備
?
int ff=ioctl (fd, VIDIOC_QUERYCAP, &cap);//獲取攝像頭參數(shù)
if(ff<0)
printf("failture VIDIOC_QUERYCAPn");
?
?????? struct v4l2_fmtdesc fmt1;
??????? int ret;
?????? memset(&fmt1, 0, sizeof(fmt1));
?????? fmt1.index = 0;
?????? fmt1.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
?????? while ((ret = ioctl(fd, VIDIOC_ENUM_FMT, &fmt1)) == 0)
?????? {
????????????? fmt1.index++;
????????????? printf("{ pixelformat = '%c%c%c%c', description = '%s' }n",
??????????????????????????? fmt1.pixelformat & 0xFF, (fmt1.pixelformat >> 8) & 0xFF,
??????????????????????????? (fmt1.pixelformat >> 16) & 0xFF, (fmt1.pixelformat >> 24) & 0xFF,
??????????????????????????? fmt1.description);
?????????????
?????? }
CLEAR (fmt);
fmt.type??????????????? = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width?????? = 640;
fmt.fmt.pix.height????? = 480;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;//V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_YVU420;//V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field?????? = V4L2_FIELD_INTERLACED;
ff = ioctl (fd, VIDIOC_S_FMT, &fmt); //設(shè)置圖像格式
if(ff<0)
printf("failture VIDIOC_S_FMTn");
file_length = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; //計算圖片大小
?
struct v4l2_requestbuffers req;
CLEAR (req);
req.count?????????????? = 1;
req.type??????????????? = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory????????????? = V4L2_MEMORY_MMAP;
?
ioctl (fd, VIDIOC_REQBUFS, &req); //申請緩沖,count是申請的數(shù)量
if(ff<0)
printf("failture VIDIOC_REQBUFSn");
if (req.count < 1)
?? printf("Insufficient buffer memoryn");
?
buffers = calloc (req.count, sizeof (*buffers));//內(nèi)存中建立對應(yīng)空間
?
for (n_buffers = 0; n_buffers < req.count; ++n_buffers)
{
?? struct v4l2_buffer buf;?? //驅(qū)動中的一幀
?? CLEAR (buf);
?? buf.type??????? = V4L2_BUF_TYPE_VIDEO_CAPTURE;
?? buf.memory????? = V4L2_MEMORY_MMAP;
?? buf.index?????? = n_buffers;
?
?? if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buf)) //映射用戶空間
??? printf ("VIDIOC_QUERYBUF errorn");
?
?? buffers[n_buffers].length = buf.length;
?? buffers[n_buffers].start =
?? mmap (NULL /* start anywhere */,??? //通過mmap建立映射關(guān)系
??? buf.length,
??? PROT_READ | PROT_WRITE /* required */,
??? MAP_SHARED /* recommended */,
??? fd, buf.m.offset);
?
?? if (MAP_FAILED == buffers[n_buffers].start)
??? printf ("mmap failedn");
??????? }
?
for (i = 0; i < n_buffers; ++i)
{
?? struct v4l2_buffer buf;
?? CLEAR (buf);
?
?? buf.type??????? = V4L2_BUF_TYPE_VIDEO_CAPTURE;
?? buf.memory????? = V4L2_MEMORY_MMAP;
?? buf.index?????? = i;
?
?? if (-1 == ioctl (fd, VIDIOC_QBUF, &buf))//申請到的緩沖進(jìn)入列隊
??? printf ("VIDIOC_QBUF failedn");
}
???????????????
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
?
if (-1 == ioctl (fd, VIDIOC_STREAMON, &type)) //開始捕捉圖像數(shù)據(jù)
?? printf ("VIDIOC_STREAMON failedn");
?
for (;;) //這一段涉及到異步IO
{
?? fd_set fds;
?? struct timeval tv;
?? int r;
?
?? FD_ZERO (&fds);//將指定的文件描述符集清空
?? FD_SET (fd, &fds);//在文件描述符集合中增加一個新的文件描述符
?
?? /* Timeout. */
?? tv.tv_sec = 2;
?? tv.tv_usec = 0;
?
?? r = select (fd + 1, &fds, NULL, NULL, &tv);//判斷是否可讀(即攝像頭是否準(zhǔn)備好),tv是定時
?
?? if (-1 == r) {
??? if (EINTR == errno)
???? continue;
??? printf ("select errn");
??????????????????????? }
?? if (0 == r) {
??? fprintf (stderr, "select timeoutn");
??? exit (EXIT_FAILURE);
???? ???????????????????}
?
?? if (read_frame ())//如果可讀,執(zhí)行read_frame ()函數(shù),并跳出循環(huán)
?? break;
}
?
unmap:
for (i = 0; i < n_buffers; ++i)
?? if (-1 == munmap (buffers[i].start, buffers[i].length))
??? printf ("munmap error");
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;??
?? ?????if (-1 == ioctl(fd, VIDIOC_STREAMOFF, &type))??
??????????? printf("VIDIOC_STREAMOFF");
close (fd);
fclose (file_fd);
exit (EXIT_SUCCESS);
return 0;
}