序
做了两年的DVR NVR IPC开发,一直在上层做些修修改改,近些时候对视频码流,h264, mp4,jpeg都不能完全整清楚关系,于是有个想法,自己买个摄像头,用2440板,获取码流,存储为MP4,rtsp发送码流,什么onvif、p2p都整进来,还可以整个微信小程序,呵呵!甚至移植个opencv到板上,移动检测人脸识别啊啥的都可以玩了。^_^。
那么第一步,先来个摄像头,把数据整出来先:
拿到摄像头,先检测下硬件
usb链接到Ubuntu。注意要买uvc免驱的,就是Linux2.6之后内核里面的驱动已经对这个厂家的这个设备提供支持了的。所以,就不用我们自己去实现这个usb摄像头的驱动了,重点,就放在怎么使用驱动去获取码流。
要看自己的设备的ID:
这么成熟的一个玩意,当然有现成的软件,可以直接预览视频的了,网上也很多介绍,有使用
不过个人用了之后,图像却有,但是没过几秒就超时卡主没有码流了。。
那换一个,用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是压缩过的。