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

news/2024/7/10 18:37:51 标签: ffmpeg, USB摄像头, 视频压缩

需要把图片压缩成摄像头,需要一个工具,就是大名鼎鼎的ffmpegffmpeg的功能实在在太强大了,源代码也比较复杂,同时需要掌握很多音视频压缩的相关知识,我也是初次接触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 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; 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;
}

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

 


http://www.niftyadmin.cn/n/890780.html

相关文章

树莓派3在无屏幕无路由器无串口情况下的使用

在做一个项目&#xff0c;打算移植到树莓派3中&#xff0c;连接并控制树莓派一般都会使用Putty这个软件&#xff0c;但是需要提前知道树莓派的IP地址。网上的方法大都是连接路由器后通过路由器查看&#xff0c;但是我的路由器是无线的&#xff0c;没有接口了。同时又没有屏幕&a…

GDB调试时候的问题

写了一个工程&#xff0c;有好几个文件&#xff0c;出了一点问题&#xff0c;找不到原因&#xff0c;只能调试&#xff0c;但是在调试的时候也碰到了各种问题&#xff0c;现在记录在这篇博客里面&#xff0c;也为后面的人带来更多的帮助。 首先是这个问题&#xff1a;Missing …

在linux下使用socket通信,accept调用产生accept error:Invalid argument

accept error :Invalid argument顾名思义&#xff0c;就是accept错误&#xff0c;非法变量。这是accept的函数&#xff1a; int accept(int sockfd, void *addr, int *addrlen);accept()函数的参数意义如下: sockfd 是正在 listen() 的一个套接字描述符。 addr 一般是一…

Linux线程同步之读写锁(rwlock)

读写锁和互斥量&#xff08;互斥锁&#xff09;很类似&#xff0c;是另一种线程同步机制&#xff0c;但不属于POSIX标准&#xff0c;可以用来同步同一进程中的各个线程。当然如果一个读写锁存放在多个进程共享的某个内存区中&#xff0c;那么还可以用来进行进程间的同步&#x…

Linux-视频监控系统(11)-监控系统初步完善

代码写了这么久&#xff0c;期间也碰到了各种各样的问题&#xff0c;解决方法都记录在了我的博客里面。这里面记录一下自己开发的思路和方法。 现在项目在原来的基础上又增加了以下功能&#xff1a; 1、创建了线程池&#xff0c;支持多个客户机的访问 2、可以保存捕捉到的视…

ffmpeg的安装(for X86 and ARM)

本以为ffmpeg在ARM下的安装挺简单的&#xff0c;想不到还是有点麻烦和复杂。 首先下载ffmpeg的安装包&#xff0c;官网上都有。 然后解压&#xff1a; #tar -xvzf ffmpeg***** 然后进入到解压目录中。 对于X86系统&#xff0c;比较简单&#xff0c;先在/usr/local/目录下创…

Linux-视频监控系统(12)-移植到树莓派中

由于之前的代码编写及测试工作在Linux PC上都已经完成&#xff0c;现在只需要移植到树莓派中即可。 在这里我们有2项任务要做 首先需要修改源代码的编译工具&#xff0c;把gcc修改成arm-linux-gcc(具体的工具要参考你的机器) 然后需要在树莓派中安装ffmpeg包&#xff0c;具体可…

树莓派 Raspberry Pi SD卡系统备份与还原

近半年时间用来监控陆龟的树莓派 Raspberry Pi 还算稳定&#xff0c;可到了冬天龟箱里的加热灯长时间加热导致树莓派温度很高&#xff0c;时不时的自动重启。如果每次都能正常起来也就罢了&#xff0c;可偶尔会挂不上SD卡的文件系统直接宕了&#xff0c;还得用备份还原SD卡(如…