轉自:http://blog.csdn.net/morixinguan/article/details/51001713
版權聲明:本文為博主原創文章,如有需要,請注明轉載地址:http://blog.csdn.net/morixinguan。若是侵權用於商業用途,請聯系博主,否則將追究責任 目錄(?)[-] 采集方式 V4L2操作流程點擊這個網址說得很詳細了這里不多說 httpbaikebaiducomview5494174htm 我們都知道,想要驅動Linux下的攝像頭,其實很簡單,照着V4L2的手冊一步步來寫,很快就可以寫出來,但是在寫之前我們要注意改變系統的一些配置,使系統支持framebuffer,在dev下產生fb0這樣的節點,這樣我們才能在linux系統上操作Camera攝像頭,framebuffer在之前的博文已經有說過了,這里就不再提了。 有需要了解framebuffer的那么請點擊:http://baike.baidu.com/view/3351639.htm 最重要的,我們需要改一個腳本,在/dev/grub.conf,我們來看看怎么改: [cpp] view plain copy print? # grub.conf generated by anaconda # # Note that you do not have to rerun grub after making changes to this file # NOTICE: You have a /boot partition. This means that # all kernel and initrd paths are relative to /boot/, eg. # root (hd0,0) # kernel /vmlinuz-version ro root=/dev/sdb2 # initrd /initrd-[generic-]version.img #boot=/dev/sdb default=0 timeout=5 splashimage=(hd0,0)/grub/splash.xpm.gz hiddenmenu title CentOS (2.6.32-431.el6.i686) root (hd0,0) kernel /vmlinuz-2.6.32-431.el6.i686 ro root=UUID=2bc12537-d6c1-4e67-b4e5-e9c466205554 nomodeset rd_NO_LUKS KEYBOARDTYPE=pc KEYTABLE=us rd_NO_MD crashkernel=auto LANG=zh_CN.UTF-8 rd_NO_LVM rd_NO_DM rhgb quiet vga=0x318 initrd /initramfs-2.6.32-431.el6.i686.img 通常情況下,要讓framebuffer生效,要加一句vga=???(這里是參數),簡單介紹一下: 我寫vga=0x318就是默認就設置為1024x768x24bpp模式。當然還有其它的模式:如下圖,根據自己的系統來配置。 色彩 640x400 640x480 800x600 1024x768 1280x1024 1600x1200 4bits ? ? 0x302 ? ? ? 8bits 0x300 0x301 0x303 0x305 0x307 0x31C 15bits ? 0x310 0x313 0x316 0x319 0x31D 16bits ? 0x311 0x314 0x317 0x31A 0x31E 24bits ? 0x312 0x315 0x318 0x31B 0x31F 32bits ? ? ? ? ? ? 配置完成以后,我們先來了解一下V4L2的主要功能。 V4L2就使程序有發現設備和操作設備的能力.它主要是用一系列的回調函數來實現這些功能。像設置攝像頭的頻率、幀頻、視頻壓縮格式和圖像參數等等。當然也可以用於其他多媒體的開發,如音頻等。 但是此框架只能運行在Linux操作系統之上。v4L2是針對uvc免驅usb設備的編程框架 ,主要用於采集usb攝像頭等,編程模式如下: 采集方式 打開視頻設備后,可以設置該視頻設備的屬性,例如裁剪、縮放等。這一步是可選的。在Linux編程中,一般使用ioctl函數來對設備的I/O通道進行管理: [cpp] view plain copy print? extern int ioctl (int __fd, unsigned long int __request, …) __THROW; __fd:設備的ID,例如剛才用open函數打開視頻通道后返回的cameraFd; __request:具體的命令標志符。 在進行V4L2開發中,一般會用到以下的命令標志符: VIDIOC_REQBUFS:分配內存 VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的數據緩存轉換成物理地址 VIDIOC_QUERYCAP:查詢驅動功能 VIDIOC_ENUM_FMT:獲取當前驅動支持的視頻格式 VIDIOC_S_FMT:設置當前驅動的頻捕獲格式 VIDIOC_G_FMT:讀取當前驅動的頻捕獲格式 VIDIOC_TRY_FMT:驗證當前驅動的顯示格式 VIDIOC_CROPCAP:查詢驅動的修剪能力 VIDIOC_S_CROP:設置視頻信號的邊框 VIDIOC_G_CROP:讀取視頻信號的邊框 VIDIOC_QBUF:把數據放回緩存隊列 VIDIOC_DQBUF:把數據從緩存中讀取出來 VIDIOC_STREAMON:開始視頻顯示函數 VIDIOC_STREAMOFF:結束視頻顯示函數 VIDIOC_QUERYSTD:檢查當前視頻設備支持的標准,例如PAL或NTSC。 這些IO調用,有些是必須的,有些是可選擇的。 V4L2操作流程:點擊這個網址,說得很詳細了,這里不多說。 http://baike.baidu.com/view/5494174.htm 接下來我們來看看實戰部分,下面是我自己寫的程序接口,可以實現視頻采集:生氣 1、project.c [cpp] view plain copy print? #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include "j-yuv.h" #include "CameralOpt.h" #include "FrameBufferOpt.h" #define WIDTH 640 #define HIGHT 480 int main(void) { char yuyv[WIDTH*HIGHT*2]; char bmp[WIDTH*HIGHT*3]; // set_bmp_header((struct bmp_header_t *)bmp, WIDTH, HIGHT); //初始化攝像頭 Init_Cameral(WIDTH , HIGHT ); //初始化framebuffer Init_FrameBuffer(WIDTH , HIGHT ); //開啟攝像頭 Start_Cameral(); //采集一張圖片 int count = 0 ; while(1) { Get_Picture(yuyv); yuyv2rgb24(yuyv, bmp, WIDTH, HIGHT); Write_FrameBuffer(bmp); // printf("count:%d \n" , count++); } //關閉攝像頭 Stop_Cameral(); //關閉Framebuffer Exit_Framebuffer(); //退出 Exit_Cameral(); return 0; } 2、juv.h [cpp] view plain copy print? #ifndef __JYUV_H #define __JYUV_H typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; #pragma pack(1) //定義bmp頭 struct bmp_header_t{ u16 magic; u32 file_size; u32 RESERVED1; u32 offset; //54 bytes 表示54個偏移量 u32 head_num; //40 u32 width; u32 height; u16 color_planes; //1 u16 bit_count; u32 bit_compression; //0 u32 image_size; //except the size of header u32 h_resolution; u32 v_resolution; u32 color_num; u32 important_colors; }; #pragma pack() void set_bmp_header(struct bmp_header_t * header, u32 width, u32 height); int yuyv2rgb24(u8 *yuyv, u8 *rgb, u32 width, u32 height); #endif /* __JYUV_H */ 3、juv.c [cpp] view plain copy print? #include "j-yuv.h" #define BIT_COUNT 24 void set_bmp_header(struct bmp_header_t *header, u32 width, u32 height) { header->magic = 0x4d42; header->image_size = width * height * BIT_COUNT/8; header->file_size = header->image_size + 54; header->RESERVED1 = 0; header->offset = 54; header->head_num = 40; header->width = width; header->height = height; header->color_planes = 1; header->bit_count = BIT_COUNT; header->bit_compression = 0; header->h_resolution = 0; header->v_resolution = 0; header->color_num = 0; header->important_colors = 0; } //yuyv轉rgb24的算法實現 int yuyv2rgb24(u8 *yuyv, u8 *rgb, u32 width, u32 height) { u32 i, in, rgb_index = 0; u8 y0, u0, y1, v1; int r, g, b; u32 out = 0, x, y; for(in = 0; in < width * height * 2; in += 4) { y0 = yuyv[in+0]; u0 = yuyv[in+1]; y1 = yuyv[in+2]; v1 = yuyv[in+3]; for (i = 0; i < 2; i++) { if (i) y = y1; else y = y0; r = y + (140 * (v1-128))/100; //r g = y - (34 * (u0-128))/100 - (71 * (v1-128))/100; //g b = y + (177 * (u0-128))/100; //b if(r > 255) r = 255; if(g > 255) g = 255; if(b > 255) b = 255; if(r < 0) r = 0; if(g < 0) g = 0; if(b < 0) b = 0; y = height - rgb_index/width -1; x = rgb_index%width; rgb[(y*width+x)*3+0] = b; rgb[(y*width+x)*3+1] = g; rgb[(y*width+x)*3+2] = r; rgb_index++; } } return 0; } 4、FrameBufferOpt.c [cpp] view plain copy print? #include "FrameBufferOpt.h" static int Frame_fd ; static int *FrameBuffer = NULL ; static int W , H ; //初始化framebuffer int Init_FrameBuffer(int Width , int Higth) { W = Width ; H = Higth ; Frame_fd = open("/dev/fb" , O_RDWR); if(-1 == Frame_fd) { perror("open frame buffer fail"); return -1 ; } //根本就不用CPU搬運 用DMA做為搬運工 FrameBuffer = mmap(0, 1280*1024*4 , PROT_READ | PROT_WRITE , MAP_SHARED , Frame_fd ,0 ); if(FrameBuffer == (void *)-1) { perror("memory map fail"); return -2 ; } return 0 ; } //寫入framebuffer int Write_FrameBuffer(const char *buffer) { int row , col ; char *p = NULL ; for(row = 0 ; row <1024 ; row++) { for(col = 0 ; col < 1280 ; col++) { if((row < H) && (col < W)) { p = (char *)(buffer + (row * W+ col ) * 3); FrameBuffer[row*1280+col] = RGB((unsigned char)(*(p+2)),(unsigned char)(*(p+1)),(unsigned char )(*p)); } } } return 0 ; } //退出framebuffer int Exit_Framebuffer(void) { munmap(FrameBuffer , W*H*4); close(Frame_fd); return 0 ; } 5、FrameBufferOpt.h [cpp] view plain copy print? #ifndef _FRAMEBUFFEROPT_H #define _FRAMEBUFFEROPT_H #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #define RGB(r,g,b) ((r<<16)|(g<<8)|b) //初始化ramebuffer int Init_FrameBuffer(int Width , int Higth); //寫數據到framebuffer int Write_FrameBuffer(const char *buffer); //退出framebuffer int Exit_Framebuffer(void); #endif //_FRAMEBUFFEROPT_H 6、CameralOpt.h [cpp] view plain copy print? #ifndef _CAMERALOPT_H #define _CAMERALOPT_H #include <stdio.h> #include <linux/videodev2.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <sys/mman.h> #define COUNT 3 //初始化攝像頭 int Init_Cameral(int Width , int Hight); int Exit_Cameral(void); //退出攝像頭 //攝像頭開始采集 int Start_Cameral(void); int Stop_Cameral(void);//停止攝像頭 //獲取攝像頭的數據 int Get_Picture(char *buffer); #endif //_CAMERALOPT_H 7、CameralOpt.c [cpp] view plain copy print? #include "CameralOpt.h" int video_fd ; int length ; char *yuv[COUNT] ; struct v4l2_buffer enqueue , dequeue ; //定義出入隊的操作結構體成員 int Init_Cameral(int Width , int Hight) { //參數檢查 char *videodevname = NULL ; videodevname = "/dev/video0" ; //打開設備 video_fd = open(videodevname , O_RDWR); if(-1 == video_fd ) { perror("open video device fail"); return -1 ; } int i ; int ret ; struct v4l2_format format ; format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ; format.fmt.pix.width = Width; format.fmt.pix.height = Hight; format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV ; //我支持的格式是這個 ret = ioctl(video_fd , VIDIOC_S_FMT , &format); if(ret != 0) { perror("set video format fail"); return -2 ; } //申請buffer,切割成幾個部分 //3 struct v4l2_requestbuffers requestbuffer ; requestbuffer.count = COUNT ; requestbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ; requestbuffer.memory = V4L2_MEMORY_MMAP ; ret = ioctl(video_fd , VIDIOC_REQBUFS , &requestbuffer); if(ret != 0) { perror("request buffer fail "); return -3 ; } //querybuffer struct v4l2_buffer querybuffer ; querybuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ; querybuffer.memory = V4L2_MEMORY_MMAP ; for(i = 0 ; i < COUNT ; i++) { querybuffer.index = i ; ret = ioctl(video_fd , VIDIOC_QUERYBUF , &querybuffer); if(ret != 0) { perror("query buffer fail"); return -4 ; } // printf("index:%d length:%d offset:%d \n" , // querybuffer.index , querybuffer.length , querybuffer.m.offset); length = querybuffer.length ; //將攝像頭內存印射到進程的內存地址 yuv[i] = mmap(0,querybuffer.length , PROT_READ | PROT_WRITE , MAP_SHARED , video_fd , querybuffer.m.offset ); //列隊 struct v4l2_buffer queuebuffer ; queuebuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ; queuebuffer.memory = V4L2_MEMORY_MMAP ; queuebuffer.index = i ; ret = ioctl(video_fd , VIDIOC_QBUF , &queuebuffer); if(ret != 0) { perror("queuebuffer fail"); return -5 ; } } //初始化入隊出隊 enqueue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ; dequeue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ; enqueue.memory = V4L2_MEMORY_MMAP ; dequeue.memory = V4L2_MEMORY_MMAP ; return 0 ; } int Exit_Cameral(void) { int i ; for(i = 0 ; i < COUNT ; i++) munmap(yuv+i , length); close(video_fd); return 0 ; } int Start_Cameral(void) { //開啟攝像頭 int ret ; int on = 1 ; ret = ioctl(video_fd , VIDIOC_STREAMON , &on); if(ret != 0) { perror("start Cameral fail"); return -1 ; } return 0 ; } int Stop_Cameral(void) { //停止攝像頭 int ret ; int off= 1 ; ret = ioctl(video_fd , VIDIOC_STREAMOFF, &off); if(ret != 0) { perror("stop Cameral fail"); return -1 ; } return 0 ; } int Get_Picture(char *buffer) { int ret ; //出隊 ret = ioctl(video_fd , VIDIOC_DQBUF , &dequeue); if(ret != 0) { perror("dequeue fail"); return -1 ; } //獲取圖片數據 YUV yuv[dequeue.index] memcpy(buffer , yuv[dequeue.index] , dequeue.length); // write(yuyv_fd , yuv[dequeue.index] , dequeue.length); enqueue.index = dequeue.index ; ret = ioctl(video_fd , VIDIOC_QBUF , &enqueue); if(ret != 0) { perror("enqueue fail"); return -2 ; } return 0 ; }
