修改树莓派内核bcm2835-v4l2模块,增加h264关键帧强制刷新功能
最近使用csi摄像头做网络视频的项目,需要用到关键帧强制刷新功能,经测试bcm2835-v4l2模块不支持V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME的调用,于是重新编译内核源码,添加相应的功能。内核编译详见树莓派官方网站的Kernel building,网址:https://www.raspberrypi.org/documentation/linux/kernel/.
·
最近使用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
更多推荐
已为社区贡献1条内容
所有评论(0)