v4l2: 使用中星微 zc301 USB摄像头+Ubuntu12.04,从摄像头采集视频数据,存储一帧为jpg图片。

做了两年的DVR NVR IPC开发,一直在上层做些修修改改,近些时候对视频码流,h264, mp4,jpeg都不能完全整清楚关系,于是有个想法,自己买个摄像头,用2440板,获取码流,存储为MP4,rtsp发送码流,什么onvif、p2p都整进来,还可以整个微信小程序,呵呵!甚至移植个opencv到板上,移动检测人脸识别啊啥的都可以玩了。^_^。

那么第一步,先来个摄像头,把数据整出来先:

拿到摄像头,先检测下硬件

usb链接到Ubuntu。注意要买uvc免驱的,就是Linux2.6之后内核里面的驱动已经对这个厂家的这个设备提供支持了的。所以,就不用我们自己去实现这个usb摄像头的驱动了,重点,就放在怎么使用驱动去获取码流。

在网站 http://www.ideasonboard.org/uvc/ 上可以看到,uvcvideo这个系统自带的驱动到底已经对哪些厂商设备进行了支持

要看自己的设备的ID:

这么成熟的一个玩意,当然有现成的软件,可以直接预览视频的了,网上也很多介绍,有使用

xawtv软件的:

1. 安装 xawtv 测试软件
#sudo apt-get install xawtv
2. 执行 xawtv 后面带 usb 摄像头的设备节点
#xawtv /dev/videoX

不过个人用了之后,图像却有,但是没过几秒就超时卡主没有码流了。。

那换一个,用cheese

#apt-get install cheese

#cheese

这个不错,预览稳稳的,还可以抓图。


找demo,跑起来

在度娘找了份demo,编译,运行,结果,当然是跑不起来。

那就一步一步分析,找原因了。

下面附上改好之后的代码:

/*
 * capturing from UVC cam
 */

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <asm/types.h>
#include <linux/videodev2.h>

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#define TRUE 1
#define FALSE 0
void quit(const char * msg)
{
  fprintf(stderr, "[%s] %d: %s\n", msg, errno, strerror(errno));
  exit(EXIT_FAILURE);
}

int xioctl(int fd, int request, void* arg)
{
	int i =0;
  for (i = 0; i < 100; i++) {
    int r = ioctl(fd, request, arg);
    if (r != -1 || errno != EINTR) return r;
  }
  return -1;
}

typedef struct {
  uint8_t* start;
  size_t length;
} buffer_t;

typedef struct {
  int fd;
  uint32_t width;
  uint32_t height;
  size_t buffer_count;
  buffer_t* buffers;
  buffer_t head;
} camera_t;


camera_t* camera_open(const char * device, uint32_t width, uint32_t height)
{
  int fd = open(device, O_RDWR | O_NONBLOCK, 0);
  //int fd = open(device, O_RDWR, 0);
  if (fd == -1) quit("open");
  camera_t* camera = malloc(sizeof (camera_t));
  camera->fd = fd;
  camera->width = width;
  camera->height = height;
  camera->buffer_count = 0;
  camera->buffers = NULL;
  camera->head.length = 0;
  camera->head.start = NULL;
  
  return camera;
}
#if 0//from linux
structv4l2_capability  
{  
__u8 driver[16];     // 驱动名字  
__u8 card[32];       // 设备名字  
__u8bus_info[32]; // 设备在系统中的位置  
__u32 version;       // 驱动版本号  
__u32capabilities;  // 设备支持的操作  
__u32reserved[4]; // 保留字段  
};  
capabilities 常用值:  
V4L2_CAP_VIDEO_CAPTURE    // 是否支持图像获取  
#endif

void camera_init(camera_t* camera) {
  struct v4l2_capability cap;
  if (xioctl(camera->fd, VIDIOC_QUERYCAP, &cap) == -1) quit("VIDIOC_QUERYCAP");
  if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) quit("no capture");
  if (!(cap.capabilities & V4L2_CAP_STREAMING)) quit("no streaming");

  printf("driver:%s, card:%s,  bus_info:%s, version :%#x, cap :%#x, \n",cap.driver,cap.card,cap.bus_info, cap.version ,cap.capabilities);

  struct v4l2_cropcap cropcap;
  memset(&cropcap, 0, sizeof cropcap);
  cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  if (xioctl(camera->fd, VIDIOC_CROPCAP, &cropcap) == 0) {
    struct v4l2_crop crop;
    crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    crop.c = cropcap.defrect;
    if (xioctl(camera->fd, VIDIOC_S_CROP, &crop) == -1) {
      // cropping not supported
    }
  }

  //设置帧格式 YUYV
  struct v4l2_format format;
  memset(&format, 0, sizeof format);
  format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  format.fmt.pix.width = camera->width;
  format.fmt.pix.height = camera->height;
  format.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;//V4L2_PIX_FMT_YUYV;
  format.fmt.pix.field = V4L2_FIELD_NONE;
  if (xioctl(camera->fd, VIDIOC_S_FMT, &format) == -1) quit("VIDIOC_S_FMT");

//重新获取,确认。测试发现,
//本设备不支持yuyv,使用的jpeg编码,
//获取的一帧数据是已经用jpeg压缩过的数据
//直接写到文件不用经过转换
struct v4l2_format format2;
memset(&format2, 0, sizeof(struct v4l2_format));
format2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(xioctl(camera->fd,VIDIOC_G_FMT,&format2) == -1)
{// 必须把format2.type 填上,否则获取失败
	printf("erro to getformat \n");
}
printf("VIDIOC_G_FMT width %d height %d format %#x V4L2_PIX_FMT_YUYV %#x\n",format2.fmt.pix.width,format2.fmt.pix.height,format2.fmt.pix.pixelformat,V4L2_PIX_FMT_YUYV);

  printf("pix.pixelformat:\t  %#x %c%c%c%c\n", \
  		  format2.fmt.pix.pixelformat,\
		  format2.fmt.pix.pixelformat & 0xFF,\
		  (format2.fmt.pix.pixelformat >> 8) & 0xFF, \
		  (format2.fmt.pix.pixelformat >> 16) & 0xFF,\
		  (format2.fmt.pix.pixelformat >> 24) & 0xFF);	

  struct v4l2_requestbuffers req;
  memset(&req, 0, sizeof req);
  req.count = 4;
  req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  req.memory = V4L2_MEMORY_MMAP;
  //申请管理缓冲区
  if (xioctl(camera->fd, VIDIOC_REQBUFS, &req) == -1) quit("VIDIOC_REQBUFS");
  camera->buffer_count = req.count;
  camera->buffers = calloc(req.count, sizeof (buffer_t));

  size_t buf_max = 0;
  size_t i = 0;
  for (i = 0; i < camera->buffer_count; i++) {
    struct v4l2_buffer buf;
    memset(&buf, 0, sizeof buf);
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = i;
	//获取缓冲帧的地址,长度
    if (xioctl(camera->fd, VIDIOC_QUERYBUF, &buf) == -1)
    {
      quit("VIDIOC_QUERYBUF");
    }
	
    if (buf.length > buf_max)
	{	
		buf_max = buf.length;
    }
	
    camera->buffers[i].length = buf.length;
    camera->buffers[i].start =
      mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
           camera->fd, buf.m.offset);//映射到用户空间
    if (camera->buffers[i].start == MAP_FAILED) quit("mmap");
  }

  //用户空间用来存储数据的缓冲区
  printf("wang %d %s buf_max %d\n",__LINE__,__FUNCTION__,buf_max);
  camera->head.start = malloc(buf_max);
}


void camera_start(camera_t* camera)
{
	size_t i =0;
  for (i = 0; i < camera->buffer_count; i++) {
    struct v4l2_buffer buf;
    memset(&buf, 0, sizeof buf);
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = i;
	//把帧缓冲放入队列(这个操作在获取帧缓冲后也有此操作)
    if (xioctl(camera->fd, VIDIOC_QBUF, &buf) == -1) quit("VIDIOC_QBUF");
  }

  enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  //开启码流
  if (xioctl(camera->fd, VIDIOC_STREAMON, &type) == -1)
    quit("VIDIOC_STREAMON");
}

void camera_stop(camera_t* camera)
{
  enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  if (xioctl(camera->fd, VIDIOC_STREAMOFF, &type) == -1)
    quit("VIDIOC_STREAMOFF");
}

void camera_finish(camera_t* camera)
{
	size_t i =0;
  for (i = 0; i < camera->buffer_count; i++) {
    munmap(camera->buffers[i].start, camera->buffers[i].length);
  }
  free(camera->buffers);
  camera->buffer_count = 0;
  camera->buffers = NULL;
  free(camera->head.start);
  camera->head.length = 0;
  camera->head.start = NULL;
}

void camera_close(camera_t* camera)
{
  if (close(camera->fd) == -1) quit("close");
  free(camera);
}


int camera_capture(camera_t* camera)
{
  struct v4l2_buffer buf;
  memset(&buf, 0, sizeof buf);
  buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  buf.memory = V4L2_MEMORY_MMAP;
  //从队列中取出帧
  if (xioctl(camera->fd, VIDIOC_DQBUF, &buf) == -1) return FALSE;
  memcpy(camera->head.start, camera->buffers[buf.index].start, buf.bytesused);
  camera->head.length = buf.bytesused;

  //把帧放入队列
  if (xioctl(camera->fd, VIDIOC_QBUF, &buf) == -1) return FALSE;
  return TRUE;
}

int camera_frame(camera_t* camera, struct timeval timeout) {
  fd_set fds;
  FD_ZERO(&fds);
  FD_SET(camera->fd, &fds);
  int r = select(camera->fd + 1, &fds, 0, 0, &timeout);
  if (r == -1) quit("select");
  if (r == 0) return FALSE;
  return camera_capture(camera);
}



int main()
{
  char outfile[]="result.jpg";
  camera_t* camera = camera_open("/dev/video0", 640, 480);
  //camera_t* camera = camera_open("/dev/video0", 320, 240);
  camera_init(camera);
  
  camera_start(camera);

  struct timeval timeout;
  timeout.tv_sec = 2;
  timeout.tv_usec = 0;
  /* skip 5 frames for booting a cam */
  int i =0;
  for (i = 0; i < 5; i++) {
    camera_frame(camera, timeout);
  }
  camera_frame(camera, timeout);
  printf("wang %d %s camera->head.length %d\n",__LINE__,__FUNCTION__,camera->head.length);

  
  FILE* out = fopen(outfile, "w+");
  if(NULL == out)
  {
  	quit("file open failed!\n");
  }
  
  fwrite(camera->head.start,camera->head.length,1,out);
  fclose(out);
  printf("outfile:%s\n",outfile);
  

  camera_stop(camera);
  camera_finish(camera);
  camera_close(camera);
  return 0;
}

说说几个坑: 视频的帧,宽和高到底设置多少?

源demo设置的,ioctr设置成功了,没有报错,设置的码流格式 是YUYV,也没有报错。

既然使用的YUYV ,那,获取一帧数据这帧数据格式是YUYV的,真如各大博客文章所讲,将YUYV转换成rgb,然后使用jpg库,将rgb压缩存储为jpg. 源码确实时这么做的,确报了端错误。为这个问题一直追溯到了yuyv2rgb函数,其实是发现原来是越界访问了的,添加打印信息,也会发现,从驱动中申请来的帧大小,更本不足以存储一张图片。并且获取到的帧数据大小也不对。YUYV 平均每个像素需要2个字节。

废话不多说:原因,ioctl(camera->fd, VIDIOC_S_FMT, &format) 设置帧属性时候,函数调用成功但是并不一定设置成了你认为的属性,受限于硬件,有些属性设置是不成功的。注意设置完再获取一边检查下。

看代码注释,获取的时候也需要注意,要填一个类型V4L2_BUF_TYPE_VIDEO_CAPTURE才能成功获取。

发现,实际的帧属性其实是V4L2_PIX_FMT_JPEG,从里面获取出来的一帧数据,就已经是jpeg数据了,不需要进行数据转换,直接写到文件中就行了,也难怪一帧数据大小不正确,jpeg是压缩过的。

猜你喜欢

转载自blog.csdn.net/u012459903/article/details/80581957