Linux-视频监控系统(10)-对USB摄像头的YUV图片压缩成视频

2024-08-31 06:38

本文主要是介绍Linux-视频监控系统(10)-对USB摄像头的YUV图片压缩成视频,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

需要把图片压缩成摄像头,需要一个工具,就是大名鼎鼎的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函数来等待数据完成。

 

测试好并注释的代码

有些地方自己也不是很懂,注释是网上查来的,如果有不对的地方希望大家能够指出。

#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 1struct 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; 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/14521869pCodecCtxEnc->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.0pCodecCtxEnc->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;
}

更多Linux资料及视频教程点击这里

 

这篇关于Linux-视频监控系统(10)-对USB摄像头的YUV图片压缩成视频的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1123168

相关文章

Zabbix在MySQL性能监控方面的运用及最佳实践记录

《Zabbix在MySQL性能监控方面的运用及最佳实践记录》Zabbix通过自定义脚本和内置模板监控MySQL核心指标(连接、查询、资源、复制),支持自动发现多实例及告警通知,结合可视化仪表盘,可有效... 目录一、核心监控指标及配置1. 关键监控指标示例2. 配置方法二、自动发现与多实例管理1. 实践步骤

使用Python构建一个高效的日志处理系统

《使用Python构建一个高效的日志处理系统》这篇文章主要为大家详细讲解了如何使用Python开发一个专业的日志分析工具,能够自动化处理、分析和可视化各类日志文件,大幅提升运维效率,需要的可以了解下... 目录环境准备工具功能概述完整代码实现代码深度解析1. 类设计与初始化2. 日志解析核心逻辑3. 文件处

Linux进程CPU绑定优化与实践过程

《Linux进程CPU绑定优化与实践过程》Linux支持进程绑定至特定CPU核心,通过sched_setaffinity系统调用和taskset工具实现,优化缓存效率与上下文切换,提升多核计算性能,适... 目录1. 多核处理器及并行计算概念1.1 多核处理器架构概述1.2 并行计算的含义及重要性1.3 并

Linux线程之线程的创建、属性、回收、退出、取消方式

《Linux线程之线程的创建、属性、回收、退出、取消方式》文章总结了线程管理核心知识:线程号唯一、创建方式、属性设置(如分离状态与栈大小)、回收机制(join/detach)、退出方法(返回/pthr... 目录1. 线程号2. 线程的创建3. 线程属性4. 线程的回收5. 线程的退出6. 线程的取消7.

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

golang程序打包成脚本部署到Linux系统方式

《golang程序打包成脚本部署到Linux系统方式》Golang程序通过本地编译(设置GOOS为linux生成无后缀二进制文件),上传至Linux服务器后赋权执行,使用nohup命令实现后台运行,完... 目录本地编译golang程序上传Golang二进制文件到linux服务器总结本地编译Golang程序

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

prometheus如何使用pushgateway监控网路丢包

《prometheus如何使用pushgateway监控网路丢包》:本文主要介绍prometheus如何使用pushgateway监控网路丢包问题,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录监控网路丢包脚本数据图表总结监控网路丢包脚本[root@gtcq-gt-monitor-prome

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u