最近使用csi摄像头做网络视频的项目,需要用到关键帧强制刷新功能,经测试bcm2835-v4l2模块不支持V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME的调用,于是重新编译内核源码,添加相应的功能。
内核编译详见树莓派官方网站的Kernel building,网址:https://www.raspberrypi.org/documentation/linux/kernel/building.md。我的是树莓派4。
1.修改kernel-src/linux/drivers/staging/vc04_services/bcm2835-camera/controls.c
在static int bm2835_mmal_s_ctrl(struct v4l2_ctrl *ctrl)的结构体

V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, MMAL_CONTROL_TYPE_STD的下面添加:
    //by aphero 2020年2月22日 添加关键帧刷新	
    	{
    		V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME, MMAL_CONTROL_TYPE_STD,
    		1, 1, 1, 1, NULL,//未使用
    		MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME,
    		&ctrl_set_forcekeyframe,
    		false
    	},
    //by aphero end

2.添加调用函数:参考ctrl_set_bitrate()函数修改:

//by aphero 
static int ctrl_set_forcekeyframe(struct bm2835_mmal_dev *dev,
			    struct v4l2_ctrl *ctrl,
			    const struct bm2835_mmal_v4l2_ctrl *mmal_ctrl)
{
	int ret;
	struct vchiq_mmal_port *encoder_out;
	u32 mmal_bool = 1;

	encoder_out = &dev->component[COMP_VIDEO_ENCODE]->output[0];

	ret = vchiq_mmal_port_parameter_set(dev->instance, encoder_out,
					    mmal_ctrl->mmal_id,
					    &mmal_bool, sizeof(mmal_bool));
	ret = 0;
	return ret;
}//by aphero end

3.修改bcm2835-camera.h文件

#define V4L2_CTRL_COUNT 30 /* number of v4l controls  by aphero 添加帧刷新*/

4.安装新模块–手动安装

make modules
sudo cp ./drivers/staging/vc04_services/bcm2835-camera/bcm2835-v4l2.ko /lib/modules/$(uname -r)/kernel/drivers/staging/vc04_services/bcm2835-camera/bcm2835-v4l2.ko
sudo depmod -a   #重新建立模块的依赖关系
sudo reboot

5.测试程序:通过v4l2方式从csi摄像头直接读取h264格式的视频,在采集的时候临时调用新添加的key_frame强制刷新功能:

/*=============================================================================
#     FileName: v4l2.c
#     Desc: this program aim to get image from rpi csi camera, used the V4L2 interface.
#     Author: aphero  修改于capture   详见https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/capture.c.html
#     LastChange: 2020-2-10 
#
# 编译:gcc ./v4l2.c -o v4l2
=============================================================================*/
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <linux/types.h>
#include <linux/videodev2.h>
#include <malloc.h>
#include <math.h>
#include <string.h>
#include <sys/mman.h>
#include <errno.h>
#include <assert.h>
#include <sys/time.h>
#include <linux/uvcvideo.h> //for c920
#include "uvch264.h" for c920

#define FILE_VIDEO  "/dev/video0"
#define WIDTH 1920;
#define HEIGHT 1080;
#define FPS  30
#define BIT_RATE 1024000*2;
#define KEYFRAME_PERIOD 200
#define LOOP 30*60*60;
 
typedef struct{
    void *start;
	int length;
	int bytesused;
}BUFTYPE;
 
BUFTYPE *usr_buf;
static unsigned int n_buffer = 0;
struct timeval first_time, second_time;
int j=0;
FILE *f;


/*set video capture ways(mmap)*/
int init_mmap(int fd)
{
	/*to request frame cache, contain requested counts*/
	struct v4l2_requestbuffers reqbufs;
 
	memset(&reqbufs, 0, sizeof(reqbufs));
	reqbufs.count = 4; 	 							/*the number of buffer*/
	reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;    
	reqbufs.memory = V4L2_MEMORY_MMAP;				
 
	if(-1 == ioctl(fd,VIDIOC_REQBUFS,&reqbufs))
	{
		perror("Fail to ioctl 'VIDIOC_REQBUFS'");
		exit(EXIT_FAILURE);
	}
	
	n_buffer = reqbufs.count;
	printf("n_buffer = %d\n", n_buffer);
	//usr_buf = calloc(reqbufs.count, sizeof(usr_buf));
	usr_buf = calloc(reqbufs.count, sizeof(BUFTYPE));
	if(usr_buf == NULL)
	{
		printf("Out of memory\n");
		exit(-1);
	}
 
	/*map kernel cache to user process*/
	for(n_buffer = 0; n_buffer < reqbufs.count; ++n_buffer)
	{
		//stand for a frame
		struct v4l2_buffer buf;
		memset(&buf, 0, sizeof(buf));
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.index = n_buffer;
		
		/*check the information of the kernel cache requested*/
		if(-1 == ioctl(fd,VIDIOC_QUERYBUF,&buf))
		{
			perror("Fail to ioctl : VIDIOC_QUERYBUF");
			exit(EXIT_FAILURE);
		}
 
		usr_buf[n_buffer].length = buf.length;//注意:h264的时候使用buf.bytesused,420p等使用buf.length
		usr_buf[n_buffer].bytesused = buf.bytesused;//for h264
		usr_buf[n_buffer].start = (char *)mmap(NULL,buf.length,PROT_READ | PROT_WRITE,MAP_SHARED, fd,buf.m.offset);
		//由原来的MAP_PRIVATE 模式改为MAP_SHARED方式
		if(MAP_FAILED == usr_buf[n_buffer].start)
		{
			perror("Fail to mmap");
			exit(EXIT_FAILURE);
		}
 
	}

}
 
int open_camera(void)
{
	int fd;
	struct v4l2_input inp;
 
	fd = open(FILE_VIDEO, O_RDWR | O_NONBLOCK,0);//camera 打开由阻塞打开改为了非阻塞方式打开
	if(fd < 0)
	{	
		fprintf(stderr, "%s open err \n", FILE_VIDEO);
		exit(EXIT_FAILURE);
	};
 
	inp.index = 0;
	if (-1 == ioctl (fd, VIDIOC_S_INPUT, &inp))
	{
		fprintf(stderr, "VIDIOC_S_INPUT \n");
	}
 
	return fd;
}
//下面是uvc的摄像头(罗技c920)的关键帧刷新方式,不能适用于树莓派的csi摄像头
int get_key_frame(int fd,int type) {//0=I-Frame, 1=IDR, 2=IDR+SPS+PPS  
	u_int16_t len;
	struct uvc_xu_control_query ctrl;
	uvcx_picture_type_control_t conf;
	ctrl.unit = 12;//H264_ctrl_unit_id;
	ctrl.size = sizeof(len);
	ctrl.selector = UVCX_PICTURE_TYPE_CONTROL;
	ctrl.query = UVC_GET_LEN;
	ctrl.data = (unsigned char*)&len;

        if (-1 == ioctl(fd, UVCIOC_CTRL_QUERY, &ctrl)){
                printf("v4l2_codec: get key frame fail \n");
       }
       //printf("----------------------get key frame len=%d----\n",len);
	
	conf.wLayerID = 0;
	conf.wPicType = type;
	ctrl.query = UVC_SET_CUR;
	if(len == sizeof(conf)) {
		ctrl.size = sizeof(conf);
		ctrl.data = (unsigned char*)&conf;
	} else if(len == 2) {
		len = type;
	}
        if (-1 == ioctl(fd, UVCIOC_CTRL_QUERY, &ctrl)){
                printf("v4l2_codec: get key frame fail \n");
       
	}
    return 0;
}

int init_camera(int fd)
{
	struct v4l2_capability 	cap;	/* decive fuction, such as video input */
	struct v4l2_format 		tv_fmt; /* frame format */  
	struct v4l2_fmtdesc 	fmtdesc;  	/* detail control value */
	struct v4l2_control 	ctrl;
	int ret;
	
	/*show all the support format*/
	memset(&fmtdesc, 0, sizeof(fmtdesc));
	fmtdesc.index = 0 ;                 /* the number to check */
	fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
 
	/* check video decive driver capability */
	if(ret=ioctl(fd, VIDIOC_QUERYCAP, &cap)<0)
	{
		fprintf(stderr, "fail to ioctl VIDEO_QUERYCAP \n");
		exit(EXIT_FAILURE);
	}
	
	/*judge wherher or not to be a video-get device*/
	if(!(cap.capabilities & V4L2_BUF_TYPE_VIDEO_CAPTURE))
	{
		fprintf(stderr, "The Current device is not a video capture device \n");
		exit(EXIT_FAILURE);
	}
 
	/*judge whether or not to supply the form of video stream*/
	if(!(cap.capabilities & V4L2_CAP_STREAMING))
	{
		printf("The Current device does not support streaming i/o\n");
		exit(EXIT_FAILURE);
	}
	
	printf("\ncamera driver name is : %s\n",cap.driver);
	printf("camera device name is : %s\n",cap.card);
	printf("camera bus information: %s\n",cap.bus_info);
 
	/*display the format device support*/
	printf("\n");
	while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
	{	
		printf("support device %d.%s\n",fmtdesc.index+1,fmtdesc.description);
		fmtdesc.index++;
	}
	printf("\n");
 
	/*set the form of camera capture data*/
	tv_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;      /*v4l2_buf_typea,camera must use V4L2_BUF_TYPE_VIDEO_CAPTURE*/
	tv_fmt.fmt.pix.width = WIDTH;
	tv_fmt.fmt.pix.height = HEIGHT;
	tv_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_H264;//V4L2_PIX_FMT_H264;//V4L2_PIX_FMT_YUV420;	/*V4L2_PIX_FMT_YYUV*/YUYV
	tv_fmt.fmt.pix.field = V4L2_FIELD_NONE;   		/*V4L2_FIELD_NONE*/
	if (ioctl(fd, VIDIOC_S_FMT, &tv_fmt)< 0) 
	{
		fprintf(stderr,"VIDIOC_S_FMT set err\n");
		exit(-1);
		close(fd);
	}
	
	//----------------------fps---------------------------------
	printf("set fps\n");
 	struct v4l2_streamparm* setfps;
	setfps=(struct v4l2_streamparm *) calloc(1, sizeof(struct v4l2_streamparm));
	memset(setfps, 0, sizeof(struct v4l2_streamparm));
	setfps->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (-1 == ioctl(fd, VIDIOC_G_PARM, setfps)) {
			perror("v4l2-1: get fps fail\n");
	}
	printf("get-1 fps:%d/%d\n",setfps->parm.output.timeperframe.denominator,setfps->parm.output.timeperframe.numerator);
	setfps->parm.output.timeperframe.numerator = 1;
	setfps->parm.output.timeperframe.denominator =FPS;
	if (-1 == ioctl(fd, VIDIOC_S_PARM, setfps)) {
			perror("v4l2-2: set fps fail\n");
	}
 	if (-1 == ioctl(fd, VIDIOC_G_PARM, setfps)) {
			perror("v4l2-3: get fps fail\n");
	}
	printf("get-2 fps:%d/%d\n",setfps->parm.output.timeperframe.denominator,setfps->parm.output.timeperframe.numerator);
	
	
	//----------------------bitrate------hdmi输入板也可以用---------------------------
	printf("set bitrate\n");
	struct v4l2_control bitrate_ctrl;
	bitrate_ctrl.id = V4L2_CID_MPEG_VIDEO_BITRATE;
	if (-1 == ioctl(fd, VIDIOC_G_CTRL, &bitrate_ctrl)) {
			fprintf(stderr,"v4l2: get bitrate fail\n");
	}
	printf("get-1 bitrate:%d\n",bitrate_ctrl.value);
	bitrate_ctrl.value = BIT_RATE;
	if (ioctl(fd, VIDIOC_S_CTRL, &bitrate_ctrl) == -1) {
		printf("ERR(%s):v4l2_s_bitrate failed\n", __func__);
	}
	if (-1 == ioctl(fd, VIDIOC_G_CTRL, &bitrate_ctrl)) {
			fprintf(stderr,"v4l2: get bitrate fail\n");
	}
	printf("get-2 bitrate:%d\n",bitrate_ctrl.value);
	
	//----------------------I帧间隔---------hdmi输入板也可以用------------------------
	printf("set period\n");
	struct v4l2_control period_ctrl;
	period_ctrl.id = V4L2_CID_MPEG_VIDEO_H264_I_PERIOD;
	if (-1 == ioctl(fd, VIDIOC_G_CTRL, &period_ctrl)) {
			fprintf(stderr,"v4l2: get period fail\n");
	}
	printf("get-1 period:%d\n",period_ctrl.value);
	period_ctrl.value = KEYFRAME_PERIOD;
	if (ioctl(fd, VIDIOC_S_CTRL, &period_ctrl) == -1) {
		printf("ERR(%s):v4l2_s_period failed\n", __func__);
		return -1;
	}
	if (-1 == ioctl(fd, VIDIOC_G_CTRL, &period_ctrl)) {
			fprintf(stderr,"v4l2: get period fail\n");
	}
	printf("get-2 period:%d\n",period_ctrl.value);
	
	init_mmap(fd);
}
 
int start_capture(int fd)
{
	unsigned int i;
	enum v4l2_buf_type type;
	

		struct v4l2_buffer buf;
		memset(&buf, 0, sizeof(buf));
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.index = 0;
		if(-1 == ioctl(fd, VIDIOC_QBUF, &buf))
		{
			perror("Fail to ioctl 'VIDIOC_QBUF'");
			exit(EXIT_FAILURE);
		}
	
	if(-1 == ioctl(fd, VIDIOC_STREAMON, &buf.type))
	{
		perror("VIDIOC_STREAMON");
		close(fd);
		exit(EXIT_FAILURE);
	}
 
	return 0;
}
 

int process_image(void *addr, int length,int fd)
{
	//fwrite(addr, 1, length, f);//写入文件
	char *data = (char *)malloc(length);
	memcpy(data,addr,length);
	fwrite(data, 1, length, f);
/*	
	for(int i=0;i<10;i++)		printf("[%02x] ",data[i]);	
	printf("\n");
*/	
	if((data[4]&0x7)==7) printf("sps\n");
	if((data[4]&0x7)==8) printf("pps\n");
	if((data[4]&0x7)==5) printf("IDR\n");
	
	if(j==0) gettimeofday(&first_time, NULL);//开始计时
	j++;
	if(!(j % 30)){//打印编码信息
		gettimeofday(&second_time, NULL);
		double time_val = (second_time.tv_sec - first_time.tv_sec) * 1000000 + second_time.tv_usec - first_time.tv_usec;
		printf("capture frame %3d (size=%5d) time(ms) = %lf fps=%lf\n", j, length,time_val / 1000.0, j*1000.0/(time_val/1000.0));
	}

	if(!(j % 60)){//用于模拟关键帧强制刷新
		//get_key_frame(fd,2);//强制I帧,for c920
		struct v4l2_control key_ctrl;
		key_ctrl.id = V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME;//修改了内核中bcm2835-v4l2模块,添加了相应功能
		if (-1 == ioctl(fd, VIDIOC_S_CTRL, &key_ctrl)) {
				fprintf(stderr,"v4l2: Set key_ctrl fail\n");
		}
	}
	return 0;
}
 
int read_frame(int fd)
{
	struct v4l2_buffer buf;
	unsigned int i;
	memset(&buf, 0, sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;
	//put cache from queue
	if(-1 == ioctl(fd, VIDIOC_DQBUF,&buf))
	{
		perror("Fail to ioctl 'VIDIOC_DQBUF'");
		exit(EXIT_FAILURE);
	}
	assert(buf.index < n_buffer);
	//read process space's data to a file
	//process_image(usr_buf[buf.index].start, usr_buf[buf.index].length);
	process_image(usr_buf[buf.index].start, buf.bytesused,fd);

	if(-1 == ioctl(fd, VIDIOC_QBUF,&buf))
	{
		perror("Fail to ioctl 'VIDIOC_QBUF'");
		exit(EXIT_FAILURE);
	}
	return 1;
}
 
 
int mainloop(int fd)
{
	int count = 1000;
	while(count-- > 0)
	{
		for(;;)
		{
			fd_set fds;
			struct timeval tv;
			int r;
 
			FD_ZERO(&fds);
			FD_SET(fd,&fds);
 
			/*Timeout*/
			tv.tv_sec = 1;
			tv.tv_usec = 0;
			r = select(fd + 1,&fds,NULL,NULL,&tv);
			
			if(-1 == r)
			{
				 if(EINTR == errno)
					continue;
				perror("Fail to select");
				exit(EXIT_FAILURE);
			}
			if(0 == r)
			{
				fprintf(stderr,"select Timeout\n");
				exit(-1);
			}
 
			if(read_frame(fd))
			break;
		}
	}
	return 0;
}
 
void stop_capture(int fd)
{
	enum v4l2_buf_type type;
	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if(-1 == ioctl(fd,VIDIOC_STREAMOFF,&type))
	{
		perror("Fail to ioctl 'VIDIOC_STREAMOFF'");
		exit(EXIT_FAILURE);
	}
}
 
void close_camera_device(int fd)
{
	unsigned int i;
	for(i = 0;i < n_buffer; i++)
	{
		if(-1 == munmap(usr_buf[i].start,usr_buf[i].length))
		{
			exit(-1);
		}
	}
 
	free(usr_buf);
 
	if(-1 == close(fd))
	{
		perror("Fail to close fd");
		exit(EXIT_FAILURE);
	}
}
 
 
void main(void)
{
	int fd;
	f= fopen("csi.264", "wb");
	fd = open_camera();
	init_camera(fd);
	start_capture(fd);
	mainloop(fd);
	stop_capture(fd);
	close_camera_device(fd);
	fclose(f);
}
 

运行测试:

pi@raspberrypi:~/v4l2-encode $ ./v4l2

camera driver name is : bm2835 mmal
camera device name is : mmal service 16.1
camera bus information: platform:bcm2835-v4l2

support device 1.Planar YUV 4:2:0
support device 2.YUYV 4:2:2
support device 3.24-bit RGB 8-8-8
support device 4.JFIF JPEG
support device 5.H.264
support device 6.Motion-JPEG
support device 7.YVYU 4:2:2
support device 8.VYUY 4:2:2
support device 9.UYVY 4:2:2
support device 10.Y/CbCr 4:2:0
support device 11.24-bit BGR 8-8-8
support device 12.Planar YVU 4:2:0
support device 13.Y/CrCb 4:2:0
support device 14.32-bit BGRA/X 8-8-8-8

set fps
get-1 fps:30/1
get-2 fps:30/1
set bitrate
get-1 bitrate:1000000
get-2 bitrate:2050000
set period
get-1 period:100
get-2 period:200
n_buffer = 4
sps
IDR
capture frame  30 (size=10336) time(ms) = 980.206000 fps=30.605811
capture frame  60 (size= 9957) time(ms) = 1979.443000 fps=30.311557
IDR
capture frame  90 (size= 9741) time(ms) = 2978.976000 fps=30.211724
capture frame 120 (size= 9867) time(ms) = 3978.102000 fps=30.165139
IDR
capture frame 150 (size=10135) time(ms) = 4977.559000 fps=30.135253
capture frame 180 (size= 9733) time(ms) = 5976.824000 fps=30.116329
IDR
capture frame 210 (size= 6193) time(ms) = 6975.564000 fps=30.105093
capture frame 240 (size= 5737) time(ms) = 7974.643000 fps=30.095391
IDR
capture frame 270 (size= 9753) time(ms) = 8974.832000 fps=30.084129
capture frame 300 (size= 9292) time(ms) = 9974.089000 fps=30.077935
IDR
capture frame 330 (size= 9468) time(ms) = 10973.688000 fps=30.071932
capture frame 360 (size= 9360) time(ms) = 11972.855000 fps=30.068016
IDR
capture frame 390 (size= 9690) time(ms) = 12972.313000 fps=30.064029
capture frame 420 (size= 5921) time(ms) = 13970.772000 fps=30.062762
IDR
capture frame 450 (size= 9463) time(ms) = 14971.081000 fps=30.057950
capture frame 480 (size= 9314) time(ms) = 15970.215000 fps=30.055951
IDR
capture frame 510 (size= 9385) time(ms) = 16969.614000 fps=30.053718
capture frame 540 (size= 9094) time(ms) = 17968.907000 fps=30.051911
IDR
capture frame 570 (size= 9250) time(ms) = 18967.688000 fps=30.051106
capture frame 600 (size= 5978) time(ms) = 19966.885000 fps=30.049755
IDR
capture frame 630 (size= 5893) time(ms) = 20966.259000 fps=30.048279
capture frame 660 (size= 9158) time(ms) = 21966.289000 fps=30.046040
IDR
capture frame 690 (size= 9416) time(ms) = 22965.743000 fps=30.044750
capture frame 720 (size= 6625) time(ms) = 23964.501000 fps=30.044439
IDR
capture frame 750 (size= 9188) time(ms) = 24965.954000 fps=30.040911
capture frame 780 (size= 9079) time(ms) = 25963.696000 fps=30.041948
IDR
capture frame 810 (size= 9017) time(ms) = 26962.996000 fps=30.041172
capture frame 840 (size= 5899) time(ms) = 27965.858000 fps=30.036625
IDR
capture frame 870 (size= 6049) time(ms) = 28961.101000 fps=30.040294
capture frame 900 (size= 6105) time(ms) = 29960.513000 fps=30.039539
IDR
capture frame 930 (size= 9271) time(ms) = 30960.524000 fps=30.038251
capture frame 960 (size= 8929) time(ms) = 31959.759000 fps=30.037773
IDR
capture frame 990 (size= 9255) time(ms) = 32959.217000 fps=30.037121
Logo

更多推荐