linux系统V4L2架构mmap模式OV3640摄像头视频捕获保存图片jpg格式

最近在做一个linux摄像头的应用程序,主要功能是:arm板子跑linux系统,进行摄像头视频采集,捕获一帧视频然后保存成图片。功能很简单,但是我确搞了很久,过程中遇到了很多问题,在此写下点滴记录以备忘,还有很多问题待决解……

硬件平台:arm cotex-A8实验箱 + ov3640 CMOS摄像头

软件平台: (1)开发平台:xp系统上装的virtualbox-2.6.8虚拟机 + ubuntu10.10

           (2)arm板子系统:linux 2.6.35 内核 + qtopia文件系统 

一、摄像头程序:

       1、源代码:camera3640.c

#include "classroom.h"

/*******************************************************************************************************************************************************************/

extern char * chpt_lcd_mmap_addr;           //lcd的缓存指针
extern unsigned int int_lcd_width;          //lcd的宽度
extern unsigned int int_lcd_height;         //lcd的高度
extern unsigned int int_lcd_pixel;          //lcd的像素

extern unsigned int cameratimes;            //Camera采集的次数控制变量
extern const unsigned int camMaxtime;       //Camera最多采集的次数

struct buffer                               // 每个缓冲帧的数据结构
{
	void * start;
	size_t length;
}*buffers;

const char * CameraName = "/dev/video0";   //摄像头设备名
int cam_fd = -1;                           //摄像头打开文件
 
static int n_buffers = 0;                

unsigned int times = 0;
unsigned int bufferLenth = 0;

/*******************************************************************************************************************************************************************/

void cameraOpen(void)
{	
	cam_fd = open( CameraName, O_RDWR | O_NONBLOCK, 0 );         //阻塞方式打开摄像头
	if(cam_fd < 0)
	{
		printf("Open fimc0 error.\n");
		exit(1);
	}
}

void cameraInit(void)
{
    struct v4l2_capability cap;
	int ret = 0;
	unsigned int min;
	struct v4l2_input input; 

	ret = ioctl( cam_fd, VIDIOC_QUERYCAP, &cap );
	if( ret < 0 )
	{
		printf("set VIDIOC_QUERYCAP error.\n");
		exit(1);
	}

	if( !(cap.capabilities & V4L2_CAP_STREAMING) )
	{
		printf("%s can not streaming.\n");
		exit(1);
	}

	input.index = 0;
	
	if ((ioctl(cam_fd, VIDIOC_S_INPUT, &input)) < 0) //单输入模式
	{
		printf("set s_input error.\n");
		exit(1);
	}
   
	//设置视频的格式
	struct v4l2_format fmt;

	CLEAR(fmt);

	fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE; //数据流类型,永远是:V4L2_BUF_TYPE_VIDEO_CAPTURE
	fmt.fmt.pix.width       = int_lcd_width; 		       //800 宽,必须是16的倍数
	fmt.fmt.pix.height      = int_lcd_height;			   //400 高,必须是16的倍数
	fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32;          //视频数据存储类型,例如是YUV4:2:2还是RGB
	fmt.fmt.pix.field       = V4L2_FIELD_ANY;

	if ( ioctl(cam_fd, VIDIOC_S_FMT, &fmt) == -1 )
	{
	   printf("set format error\n");
	}
    //如果该视频设备驱动不支持你所设定的图像格式,视频驱动会重新修改struct v4l2_format结构体变量的值为该视频设备所支持的图像格式,
	//所以在程序设计中,设定完所有的视频格式后,要获取实际的视频格式,要重新读取struct v4l2_format结构体变量。
	if(ioctl(cam_fd, VIDIOC_G_FMT, &fmt) == -1)
    {
        printf("Unable to get format\n");
         exit(1);
     } 
     {
          printf("fmt.type:\t\t%d\n",fmt.type);
          printf("pix.pixelformat:\t%c%c%c%c\n",fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,
					(fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
          printf("pix.height:\t\t%d\n",fmt.fmt.pix.height);
          printf("pix.width:\t\t%d\n",fmt.fmt.pix.width);
          printf("pix.field:\t\t%d\n",fmt.fmt.pix.field);
     }
	
	 //printf("real format is %d X %d,pixel is %d!\n",fmt.fmt.pix.width,fmt.fmt.pix.height,fmt.fmt.pix.pixelformat);
	
	if( int_lcd_width != fmt.fmt.pix.width )
	{
		int_lcd_width = fmt.fmt.pix.width;
		fprintf(stderr,"Image width set to %i by device %s.\n", int_lcd_width, CameraName );
	}
	if( int_lcd_height != fmt.fmt.pix.height )
	{
		int_lcd_height = fmt.fmt.pix.height;
		fprintf(stderr, "Image height set to %i by device %s.\n", int_lcd_height, CameraName );
	}

	min = fmt.fmt.pix.width * 2;
	if( fmt.fmt.pix.bytesperline < min )
	{
		fmt.fmt.pix.bytesperline = min;
	}

	min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
	if( fmt.fmt.pix.sizeimage < min )
	{
		fmt.fmt.pix.sizeimage = min;
	}

	mmapInit();

}

void mmapInit(void) //内存映射
{
	struct v4l2_requestbuffers req;

	CLEAR (req);

	req.count               = 4;
	req.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	req.memory              = V4L2_MEMORY_MMAP;

	if (-1 == xioctl(cam_fd, VIDIOC_REQBUFS, &req))//申请缓存,count是申请的数量
	{

		if (EINVAL == errno) 
		{
			fprintf(stderr, "%s does not support memory mapping\n", CameraName);
			exit(EXIT_FAILURE);
		} 
		else 
		{
			errno_exit("VIDIOC_REQBUFS");
		}
	}

	if (req.count < 2) 
	{
		fprintf(stderr, "Insufficient buffer memory on %s\n", CameraName);
		exit(EXIT_FAILURE);
	}

	buffers = (struct buffer*)calloc(req.count, sizeof(*buffers));//内存中建立对应的空间

	if (!buffers) 
	{
		fprintf(stderr, "Out of memory\n");
		exit(EXIT_FAILURE);
	}

	for (n_buffers = 0; n_buffers < req.count; ++n_buffers)//申请4帧缓存 
	{
		struct v4l2_buffer buf;   //驱动中的一帧

		CLEAR (buf);

		buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory      = V4L2_MEMORY_MMAP;
		buf.index       = n_buffers;

		if (-1 == xioctl(cam_fd, VIDIOC_QUERYBUF, &buf))//映射用户空间
		{
			errno_exit("VIDIOC_QUERYBUF");
		}

		buffers[n_buffers].length = buf.length;
		
		//通过mmap()建立映射关系 
		buffers[n_buffers].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, cam_fd, buf.m.offset);

		if (MAP_FAILED == buffers[n_buffers].start)
		{
			errno_exit("mmap");
		}
	}
}

void captureStart(void)
{
	unsigned int i;
	enum v4l2_buf_type type;

	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 == xioctl(cam_fd, VIDIOC_QBUF, &buf))//申请到的缓存进入队列
		{
			errno_exit("VIDIOC_QBUF");
		}
	}

	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	if ( -1 == xioctl(cam_fd, VIDIOC_STREAMON, &type) )//开始捕获图像数据
	{
		errno_exit("VIDIOC_STREAMON");
	}
	
}

void mainLoop(void)
{
	unsigned int count;

	count = 1;

	while (count-- > 0) 
	{
		for ( ; ; ) 
		{
			fd_set fds;
			struct timeval tv;
			int r;

			FD_ZERO(&fds);          //将指定的文件描述符集清空
			FD_SET(cam_fd, &fds);   //在文件描述符集中增加一个新的文件描述符

			tv.tv_sec = 2;
			tv.tv_usec = 0;

			r = select(cam_fd + 1, &fds, NULL, NULL, &tv); //判断是否可读(即摄像头是否准备好),tv是等待的时间

			if (-1 == r) 
			{
				if (EINTR == errno)
					continue;
				errno_exit("select");
			}

			if (0 == r) 
			{
				fprintf (stderr, "select timeout\n");
				exit(EXIT_FAILURE);
			}
            
			if ( frameRead() )//如果可读则执行frameRead()并跳出循环
			{
				break;
			}
		}
	}
}

unsigned char* frameRead(void)
{

	struct v4l2_buffer buf;

	CLEAR (buf);

	buf.type 	= V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory	= V4L2_MEMORY_MMAP;

	if (-1 == xioctl(cam_fd, VIDIOC_DQBUF, &buf)) //列出采集的帧缓存
	{
		switch (errno) 
		{
		case EAGAIN:
			return 0;	

		case EIO:
		default:
			errno_exit("VIDIOC_DQBUF");
		}
	}

	assert (buf.index < n_buffers);

	imageProcess(buffers[buf.index].start);	//拷贝视频到lcd
    
	bufferLenth = buffers[buf.index].length;

	if (-1 == xioctl(cam_fd, VIDIOC_QBUF, &buf))//再将其入列
	{
		errno_exit("VIDIOC_QBUF");
	}

	return (unsigned char*)buffers[buf.index].start;
}

void imageProcess(const void* p)
{
	unsigned char* src = (unsigned char*)p;//摄像头采集的图像数据 

	cameratimes++;
	printf("\n--------- this is %d times.\n",cameratimes);
	printf("========= bufferLenth = %d.\n",bufferLenth);
	printf("+++++++++ string length = %d.\n",strlen(src));
	if(cameratimes >= camMaxtime)
	{
		//productBmp(src);                                                  //生成bmp图片
		jpgImageProduct(src);                                              //生成jpg图片
		memcpy( chpt_lcd_mmap_addr, src, int_lcd_width*int_lcd_height*4 ); //在LCD液晶屏显示
	}
	
}

void errno_exit(const char * s)
{
	fprintf( stderr, "%s error %d, %s\n", s, errno, strerror(errno) );
	exit(EXIT_FAILURE);
}

int xioctl( int ffd, int request, void * argp)
{
	int r;

	do 
	{
		r = ioctl( ffd, request, argp );
	}
	while( r == -1 && EINTR == errno );

	return r;
}

       2、执行流程:

             (1)打开设备:cameraOpen()

             (2)设备初始化:cameraInit()

             (3)建立内存映射:mmapInit()

             (4)开始视频采集并捕获图像数据:captureStart()

             (5)循环采集:mainLoop()

             (6)读取数据:frameRead()

             (7)数据处理:imageProcess()

3、常见错误及解决方法:

             (1) No capture device info

                   VIDIOC_REQBUFS error 19, No such device

                   

这个错误纠结了很久很久,在网上搜了很久,在群里问了很多人,都没解决,最后求助出售此试验箱设备公司的工程师,他也没给出原因,只是给了我一个可以运行的例程,我就自己对照了下,然后调试,查出来,原因在与,没有设置

二、保存jpg图片程序:

1、源代码:jpg.c

/*
 *     jpg.c
 *
 */

#include "classroom.h"

//

#define jpgImageName "test.jpg"

extern char * chpt_lcd_mmap_addr;
extern unsigned int int_lcd_height;
extern unsigned int int_lcd_width;
extern unsigned int int_lcd_pixel;

static unsigned int width;
static unsigned int height;
static unsigned int channel;

static unsigned char jpegQuality = 100;

//

static void jpegWrite(unsigned char* img)
{
  struct jpeg_compress_struct cinfo;
  struct jpeg_error_mgr jerr;
	
  JSAMPROW row_pointer[1];
  FILE *outfile = fopen( jpgImageName, "w");

  // try to open file for saving
  if (!outfile) {
    errno_exit("jpeg");
  }

  // create jpeg data
  cinfo.err = jpeg_std_error( &jerr );
  jpeg_create_compress(&cinfo);
  jpeg_stdio_dest(&cinfo, outfile);

  // set image parameters
  cinfo.image_width = width;	
  cinfo.image_height = height;
  cinfo.input_components = 3;
  cinfo.in_color_space = JCS_RGB;

  // set jpeg compression parameters to default
  jpeg_set_defaults(&cinfo);
  // and then adjust quality setting
  jpeg_set_quality(&cinfo, jpegQuality, TRUE);

  // start compress 
  jpeg_start_compress(&cinfo, TRUE);

  // feed data
  while (cinfo.next_scanline < cinfo.image_height) 
  {
	row_pointer[0] = &img[(cinfo.image_height - cinfo.next_scanline - 1) * cinfo.image_width*3];
    jpeg_write_scanlines(&cinfo, row_pointer, 1);
  }

  // finish compression
  jpeg_finish_compress(&cinfo);

  // destroy jpeg data
  jpeg_destroy_compress(&cinfo);

  // close output file
  fclose(outfile);
}


void jpgImageProduct(const void* p)
{
	usleep(500000);

	unsigned char* dst;
	unsigned char* src = (unsigned char*)p;
	unsigned int j;
	unsigned int i;

	width = int_lcd_width;
    height = int_lcd_height;

	dst = (unsigned char*)malloc (width*height*3+66);

	for (i=0; i< height; i++)
	{
		for(j=0;j<width;j++)
		{
			memcpy(dst+(i*width+j)*3, src+(i*width+j)*4+2,1);
			memcpy(dst+(i*width+j)*3+1, src+(i*width+j)*4+1,1);
			memcpy(dst+(i*width+j)*3+2, src+(i*width+j)*4,1);
		}
	}
	jpegWrite(dst);
	free(dst);
}

2、分析:

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值