Linux-视频监控系统(10)-对USB摄像头的YUV图片压缩成视频
2017-08-16 22:08
676 查看
需要把图片压缩成摄像头,需要一个工具,就是大名鼎鼎的ffmpeg。ffmpeg的功能实在在太强大了,源代码也比较复杂,同时需要掌握很多音视频压缩的相关知识,我也是初次接触ffmpeg,了解的东西还不是很多,如果需要进一步了解的同学可以参考[总结]FFMPEG视音频编解码零基础学习方法
我在这里呢,总结一下我在开发过程中碰到的问题,以及贴出经过自己理解后的代码。
GA/gabin/lib/libavformat.a(allformats.o): In function `av_register_all':
这类原因有2种,一个是没有头文件,可以把全部头文件都加上,具体头文件参考我接下来贴出的代码
二是在编译的时候没有解决好库的依赖关系,编译的时候对各个库的顺序要合适:
#gcc test.c -o test -lavformat -lavdevice -lavcodec -lavutil
解决之后又碰到了这个问题:
Picture size 0x0 is invalid in FFMPEG log
以及:
Specified sample format %s is invalid or not supported\n
这2个问题其实都是库不匹配的问题引起的,我下面的demo代码适用于ffmpeg-1.1.16和ffmpeg-1.1.2版本,如果是更高版本会出现这个错误。
再之后又碰到了这个问题:
在摄像头帧数据出队列的时候失败了,VIDIOC_DQBUF failed 。
这是由于摄像头摄像头文件在是以非阻塞的方式打开的,在读取的时候没有停留就出队列可能会出现这个情况。=,因此最好以阻塞方式打开,默认情况下就是阻塞方式打开的。然后在帧数据出队列的之前最好使用select函数来等待数据完成。
我在这里呢,总结一下我在开发过程中碰到的问题,以及贴出经过自己理解后的代码。
问题总结
首先是这类问题:GA/gabin/lib/libavformat.a(allformats.o): In function `av_register_all':
这类原因有2种,一个是没有头文件,可以把全部头文件都加上,具体头文件参考我接下来贴出的代码
二是在编译的时候没有解决好库的依赖关系,编译的时候对各个库的顺序要合适:
#gcc test.c -o test -lavformat -lavdevice -lavcodec -lavutil
解决之后又碰到了这个问题:
Picture size 0x0 is invalid in FFMPEG log
以及:
Specified sample format %s is invalid or not supported\n
这2个问题其实都是库不匹配的问题引起的,我下面的demo代码适用于ffmpeg-1.1.16和ffmpeg-1.1.2版本,如果是更高版本会出现这个错误。
再之后又碰到了这个问题:
在摄像头帧数据出队列的时候失败了,VIDIOC_DQBUF failed 。
这是由于摄像头摄像头文件在是以非阻塞的方式打开的,在读取的时候没有停留就出队列可能会出现这个情况。=,因此最好以阻塞方式打开,默认情况下就是阻塞方式打开的。然后在帧数据出队列的之前最好使用select函数来等待数据完成。
测试好并注释的代码
有些地方自己也不是很懂,注释是网上查来的,如果有不对的地方希望大家能够指出。#include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <linux/videodev2.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavformat/avio.h> #include <libavutil/opt.h> #include <libswscale/swscale.h> #include <libavutil/mathematics.h> #define VIDEO_WIDTH 640 #define VIDEO_HEIGHT 480 #define VIDEO_FORMAT V4L2_PIX_FMT_YUYV #define BUFFER_COUNT 4 #define URL_WRONLY 1 struct fimc_buffer { int length; void *start; } framebuf[BUFFER_COUNT]; int fd; unsigned char yuv4200[10000000] = { 0 }; unsigned char yuv4220[10000000] = { 0 }; //这个结构贯穿整个视频压缩 AVFormatContext* pFormatCtxEnc; AVCodecContext* pCodecCtxEnc; //存放帧原始数据 AVFrame* pFrameEnc; void register_init(); int open_device(); int capability(); int set_v4l2_format(); int request_buffers(); int get_camera_data(); void unregister_all(); void video_encode_init(); int yuv422_2_yuv420(unsigned char* yuv420, unsigned char* yuv422, int width, int height); void register_init() { //注册编解码器 avcodec_register_all(); //初始化所有组件 av_register_all(); } /*打开摄像头文件*/ int open_device() { char camera_device[20]; struct stat buf; int i; //浏览当前可以用的设备文件 for (i = 0; i < 10 4000 ; i++) { sprintf(camera_device, "/dev/video%i", i); if (stat(camera_device, &buf) == 0) { break; } } //设备以阻塞方式打开 fd = open(camera_device, O_RDWR, 0); if (fd < 0) { printf("Cannot open camera_device\n"); return -1; } } int set_v4l2_format() { int ret; struct v4l2_format fmt; //设置视频制式和帧格式 memset(&fmt, 0, sizeof(fmt)); //指定buf的类型为capture,用于视频捕获设备 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //设置宽度、高度、格式及采样方式 fmt.fmt.pix.width = VIDEO_WIDTH; fmt.fmt.pix.height = VIDEO_HEIGHT; fmt.fmt.pix.pixelformat = VIDEO_FORMAT; fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; ret = ioctl(fd, VIDIOC_S_FMT, &fmt); if (ret < 0) { printf("VIDIOC_S_FMT failed\n"); return ret; } //获取视频制式和帧格式的实际值,看是否设置正确 ret = ioctl(fd, VIDIOC_G_FMT, &fmt); if (ret < 0 || (fmt.fmt.pix.pixelformat != VIDEO_FORMAT)) { printf("VIDIOC_G_FMT failed (%d)/n", ret); return ret; } } int request_buffers() { int ret; int i; struct v4l2_requestbuffers reqbuf; //向驱动申请帧缓冲 reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.memory = V4L2_MEMORY_MMAP; reqbuf.count = BUFFER_COUNT; ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuf); if (ret < 0) { printf("VIDIOC_REQBUFS failed \n"); return ret; } struct v4l2_buffer buf; //获取帧缓冲地址 for (i = 0; i < BUFFER_COUNT; i++) { buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; //获取帧缓冲地址和长度信息 ret = ioctl(fd, VIDIOC_QUERYBUF, &buf); if (ret < 0) { printf("VIDIOC_QUERYBUF failed\n"); return ret; } //将申请到的帧缓冲映射到用户空间,就能直接操作采集的帧 framebuf[i].length = buf.length; framebuf[i].start = (char *) mmap(0, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (framebuf[i].start == MAP_FAILED) { printf("mmap (%d) failed: %s/n", i, strerror(errno)); return -1; } //将申请到的帧缓冲全部入队列,以便存放数据 ret = ioctl(fd, VIDIOC_QBUF, &buf); if (ret < 0) { printf("VIDIOC_QBUF (%d) failed (%d)/n", i, ret); return -1; } } } int get_camera_data() { int ret; int i=0, k=0; struct v4l2_buffer buf; //获取帧缓冲地址 fd_set fds; enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //开始视频采集 ret = ioctl(fd, VIDIOC_STREAMON, &type); if (ret < 0) { printf("VIDIOC_STREAMON failed (%d)\n", ret); return ret; } video_encode_init(); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; //把需要监视的文件描述符加入到fds集合中 FD_ZERO (&fds); FD_SET (fd, &fds); while (1) { static int delayFrame = 0; int got_packet = 0; printf("-----------seconds = %d----------\n", ++i); for (k = 0; k < 25; k++) { //等待摄像头准备一帧图像 select(fd + 1, &fds, NULL, NULL, NULL); ret = ioctl(fd, VIDIOC_DQBUF, &buf); //出队列以取得已采集数据的帧缓冲,取得原始数据 if (ret < 0) { printf("VIDIOC_DQBUF failed (%d)/n", ret); return ret; } //把帧缓冲拿到的数据保存在yuv4220里面 strncpy(yuv4220, framebuf[buf.index].start,framebuf[buf.index].length); //摄像头采集的是YUV422的图片,而H.264标准的编码需要YUV420的格式,因此要做一个转换 //这里会耗费很多时间,下次要优化这里 yuv422_2_yuv420(yuv4200, yuv4220, VIDEO_WIDTH, VIDEO_HEIGHT); av_image_alloc(pFrameEnc->data, pFrameEnc->linesize, pCodecCtxEnc->width, pCodecCtxEnc->height, pCodecCtxEnc->pix_fmt, 1); pFrameEnc->data[0] = yuv4200; pFrameEnc->data[1] = pFrameEnc->data[0] + pCodecCtxEnc->width * pCodecCtxEnc->height; pFrameEnc->data[2] = pFrameEnc->data[1] + pCodecCtxEnc->width * pCodecCtxEnc->height / 4; pFrameEnc->linesize[0] = pCodecCtxEnc->width; pFrameEnc->linesize[1] = pCodecCtxEnc->width / 2; pFrameEnc->linesize[2] = pCodecCtxEnc->width / 2; pFrameEnc->pts = (k + (i - 1) * 25) * 40; pFrameEnc->width = VIDEO_WIDTH; pFrameEnc->height = VIDEO_HEIGHT; if (!pFormatCtxEnc->nb_streams) { printf("output file does not contain any stream\n"); exit(0); } //存储压缩编码数据的结构体 AVPacket pkt; av_init_packet(&pkt); pkt.data = NULL; pkt.size = 0; printf("encoding frame %d-------", k); //编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。 ret = avcodec_encode_video2(pCodecCtxEnc, &pkt, pFrameEnc, &got_packet); if (ret < 0) { av_log(NULL, AV_LOG_FATAL, "Video encoding failed\n"); } if (got_packet) { printf("output frame %d size = %d\n", k - delayFrame, pkt.size); ret = av_interleaved_write_frame(pFormatCtxEnc, &pkt); if (ret != 0) { fprintf(stderr, "write frame into file is failed\n"); } else { printf("encode and write one frame success\n"); } } else { delayFrame++; printf("no frame output\n"); } av_free_packet(&pkt); //将缓冲重新入对尾,可以循环采集 ret = ioctl(fd, VIDIOC_QBUF, &buf); if (ret < 0) { printf("VIDIOC_QBUF failed (%d)\n", ret); return ret; } } /* 获取并编码延时的帧 *一般来说在分布式编码中会出现这个问题,这里应该不会 *但是没有必要删除这部分代码。 */ for (got_packet = 1; got_packet; k++) { fflush(stdout); AVPacket pkt; av_init_packet(&pkt); pkt.data = NULL; pkt.size = 0; ret = avcodec_encode_video2(pCodecCtxEnc, &pkt, NULL, &got_packet); if (ret < 0) { fprintf(stderr, "error encoding frame\n"); exit(1); } if (got_packet) { printf("output delayed frame %3d (size=%5d)\n", k - delayFrame, pkt.size); av_interleaved_write_frame(pFormatCtxEnc, &pkt); av_free_packet(&pkt); } } //这里加入一个break是希望编码25帧之后自己退出,否则会一直编码 break; } //写文件尾 av_write_trailer(pFormatCtxEnc); if (!(pFormatCtxEnc->flags & AVFMT_NOFILE)) avio_close(pFormatCtxEnc->pb); for (i = 0; i < BUFFER_COUNT; i++) { munmap(framebuf[i].start, framebuf[i].length); //取消映射,释放内存 } close(fd); return 0; } /*查询摄像头驱动属性*/ int capability() { int ret; struct v4l2_capability cap; ret = ioctl(fd, VIDIOC_QUERYCAP, &cap); //查询摄像头驱动属性 if (ret < 0) { printf("VIDIOC_QUERYCAP failed \n"); return ret; } } /*这里先不看了,以后希望少去这一步 *参考资料http://blog.csdn.net/u012785877/article/details/48997883 */ int yuv422_2_yuv420(unsigned char* yuv420, unsigned char* yuv422, int width,int height) { int imgSize = width * height * 2; int widthStep422 = width * 2; unsigned char* p422 = yuv422; unsigned char* p420y = yuv420; unsigned char* p420u = yuv420 + imgSize / 2; unsigned char* p420v = p420u + imgSize / 8; int i, j; for (i = 0; i < height; i += 2) { p422 = yuv422 + i * widthStep422; for (j = 0; j < widthStep422; j += 4) { *(p420y++) = p422[j]; *(p420u++) = p422[j + 1]; *(p420y++) = p422[j + 2]; } p422 += widthStep422; for (j = 0; j < widthStep422; j += 4) { *(p420y++) = p422[j]; *(p420v++) = p422[j + 3]; *(p420y++) = p422[j + 2]; } } return 0; } void unregister_all() { int i; for (i = 0; i < BUFFER_COUNT; i++) { munmap(framebuf[i].start, framebuf[i].length); //取消映射,释放内存 } //关闭设备文件 close(fd); printf("Camera test Done.\n"); } /*初始化编码器*/ void video_encode_init() { char* filename = "./264.flv"; //存储编解码信息的结构体 AVCodec* pCodecEnc; AVOutputFormat* pOutputFormat; //存储每一个音视频流的结构体 AVStream* video_st; int i; int ret; //查询和文件名相关的容器 pOutputFormat = av_guess_format(NULL, filename, NULL); if (pOutputFormat == NULL) { fprintf(stderr, "Could not guess the format from file\n"); exit(0); } else { printf("guess the format from file success\n"); } //为pFormatCtxEnc分配内存 pFormatCtxEnc = avformat_alloc_context(); if (pFormatCtxEnc == NULL) { fprintf(stderr, "could not allocate AVFormatContex\n"); exit(0); } else { printf("allocate AVFormatContext success\n"); } pFormatCtxEnc->oformat = pOutputFormat; sprintf(pFormatCtxEnc->filename, "%s", filename); printf("filename is %s\n", pFormatCtxEnc->filename); //创建一个流通道 video_st = avformat_new_stream(pFormatCtxEnc, 0); if (!video_st) { fprintf(stderr, "could not allocate AVstream\n"); exit(0); } else { printf("allocate AVstream success\n"); } pCodecCtxEnc = video_st->codec; pCodecCtxEnc->codec_id = pOutputFormat->video_codec; //采用视频编解码器 pCodecCtxEnc->codec_type = AVMEDIA_TYPE_VIDEO; pCodecCtxEnc->bit_rate = 1000000; //表示有多少bit的视频流可以偏移出目前的设定.这里的"设定"是指的cbr或者vbr. pCodecCtxEnc->bit_rate_tolerance = 300000000; pCodecCtxEnc->width = VIDEO_WIDTH; pCodecCtxEnc->height = VIDEO_HEIGHT; //介绍见http://blog.csdn.net/supermanwg/article/details/14521869 pCodecCtxEnc->time_base = (AVRational) {1,25}; //pCodecCtxEnc->time_base.num = 1; //pCodecCtxEnc->time_base.den = 25; pCodecCtxEnc->pix_fmt = PIX_FMT_YUV420P; //?? pCodecCtxEnc->gop_size = 10; pCodecCtxEnc->max_b_frames = 0; //为什么设置2次不一样的? av_opt_set(pCodecCtxEnc->priv_data, "preset", "superfast", 0); av_opt_set(pCodecCtxEnc->priv_data, "tune", "zerolatency", 0); //以下的参数完全不知道什么意思 //运动估计 pCodecCtxEnc->pre_me = 2; //设置最小和最大拉格朗日乘数 //拉格朗日常数是统计学用来检测瞬间平均值的一种方法 pCodecCtxEnc->lmin = 10; pCodecCtxEnc->lmax = 50; //最小和最大量化系数 pCodecCtxEnc->qmin = 20; pCodecCtxEnc->qmax = 80; //因为我们的量化系数q是在qmin和qmax之间浮动的, //qblur表示这种浮动变化的变化程度,取值范围0.0~1.0,取0表示不削减 pCodecCtxEnc->qblur = 0.0; //空间复杂度masking力度,取值范围0.0~1.0 pCodecCtxEnc->spatial_cplx_masking = 0.3; //运动场景预判功能的力度,数值越大编码时间越长 pCodecCtxEnc->me_pre_cmp = 2; //采用(qmin/qmax的比值来控制码率,1表示局部采用此方法,) pCodecCtxEnc->rc_qsquish = 1; //设置 i帧、p帧与B帧之间的量化系数q比例因子,这个值越大,B帧越不清楚 //B帧量化系数 = 前一个P帧的量化系数q * b_quant_factor + b_quant_offset pCodecCtxEnc->b_quant_factor = 4.9; //i帧、p帧与B帧的量化系数便宜量,便宜越大,B帧越不清楚 pCodecCtxEnc->b_quant_offset = 2; //p和i的量化系数比例因子,越接近1,P帧越清楚 //p的量化系数 = I帧的量化系数 * i_quant_factor + i_quant_offset pCodecCtxEnc->i_quant_factor = 0.1; pCodecCtxEnc->i_quant_offset = 0.0; //码率控制测率,宏定义,查API pCodecCtxEnc->rc_strategy = 2; //b帧的生成策略 pCodecCtxEnc->b_frame_strategy = 0; //DCT变换算法的设置,有7种设置,这个算法的设置是根据不同的CPU指令集来优化的取值范围在0-7之间 pCodecCtxEnc->dct_algo = 0; ////这两个参数表示对过亮或过暗的场景作masking的力度,0表示不作 pCodecCtxEnc->lumi_masking = 0.0; pCodecCtxEnc->dark_masking = 0.0; if (!strcmp(pFormatCtxEnc->oformat->name, "flv")) { pCodecCtxEnc->flags |= CODEC_FLAG_GLOBAL_HEADER; } else { printf("output format is %s\n", pFormatCtxEnc->oformat->name); } //查找编码器 pCodecEnc = avcodec_find_encoder(pCodecCtxEnc->codec_id); if (!pCodecEnc) { fprintf(stderr, "could not find suitable video encoder\n"); exit(0); } else { printf("find the encoder success\n"); } //打开编码器 if (avcodec_open2(pCodecCtxEnc, pCodecEnc, NULL) < 0) { fprintf(stderr, "could not open video codec\n"); exit(0); } else { printf("open the video codec success\n"); } //为pFrameEnc申请内存 pFrameEnc = avcodec_alloc_frame(); if (pFrameEnc == NULL) { fprintf(stderr, "could not allocate pFrameEnc\n"); exit(0); } else { printf("allocate pFrameEnc success\n"); } //打开输出文件 ret = avio_open(&pFormatCtxEnc->pb, filename, AVIO_FLAG_WRITE); if (ret < 0) { fprintf(stderr, "could not open '%s': %s\n", filename, av_err2str(ret)); exit(0); } else { printf("open filename = %s success\n", filename); } //写文件头 ret = avformat_write_header(pFormatCtxEnc, NULL); if (ret < 0) { fprintf(stderr, "error occurred when opening outputfile: %s\n", av_err2str(ret)); exit(0); } else { printf("write the header success\n"); } } int main() { register_init(); open_device(); capability(); set_v4l2_format(); request_buffers(); get_camera_data(); //由于上面的函数已经释放内存,并关闭文件,这里的可以取消 //unregister_all(); return 0; }
相关文章推荐
- 基于视频压缩的实时监控系统-A2:linux中最优秀的多路复用机制Epoll
- Linux-视频监控系统(4)-摄像头子系统实现
- 基于itop4412在Linux最小系统下的USB摄像头采集视频的H264编码
- USB视频采集系统 视频测试软件将正式发布(方便调试测试各自摄像头,RAW,RGB,YUV)
- linux系统V4L2架构OV3640摄像头视频捕获保存图片jpg格式
- oks3c6410开发板 linux-3.0.1内核 ZC301P摄像头 构成视频监控系统时内核oops解决办法
- Linux-视频监控系统(2)-Epoll的介绍及使用
- Linux-视频监控系统(13)-BUG统计及修复
- 项目-基于视频压缩的实时监控系统--tiny6410
- 寒冰linux视频教程笔记8 系统监控
- 基于Linux的视频监控系统构建方法
- 基于S3C2440的Linux-3.6.6移植——基于UVC的USB摄像头移植及视频显示
- Ehome:智能家居之基于USB摄像头的实时视频监控功能
- Linux系统下USB摄像头驱动开发
- linux下usb摄像头采集的YUYV格式转换成JPEG格式的图片
- 虚拟机上Linux读取播放USB摄像头视频卡住的问题
- 基于视频压缩的实时监控系统-A5:net.c代码解析
- Linux-视频监控系统(7)-播放器子系统2
- ubuntu-Linux系统读取USB摄像头数据(uvc)
- 基于视频压缩的实时监控系统-A4:main.c代码解析