利用mmap 内存共享,用另一个进程来控制录像循环的退出。甚至添加延时停止录像等功能。
时间控制是指:生成了n张图片帧用了多少时间m。帧率等于n/m。对应于头文件,m等于scale, n等于rate.为了精确,采用微秒计时。
I此程序生成的视频远好于ffmpeg,可能是此程序没有压缩数据原因吧。
我的摄像头支持 YUYV422 和MJPG 两种格式,YUYV是不压缩的,MJPG是压缩的,查v4l2.h
头文件得知:YUV422有4种,可任选一种,看到视频采样宽度为16 字节。也就是说每个像素点用16个字节的数据表示图像的亮度,色彩。mjpg 没有说用多少个字节表示一个像素点。我用16 ,24试过都可以用。如果实际观看时帧率不对,则大方向是此值没置错误。
#define V4L2_PIX_FMT_YUYV v4l2_fourcc('Y', 'U', 'Y', 'V') /* 16 YUV 4:2:2 */
#define V4L2_PIX_FMT_YYUV v4l2_fourcc('Y', 'Y', 'U', 'V') /* 16 YUV 4:2:2 */
#define V4L2_PIX_FMT_YVYU v4l2_fourcc('Y', 'V', 'Y', 'U') /* 16 YVU 4:2:2 */
#define V4L2_PIX_FMT_UYVY v4l2_fourcc('U', 'Y', 'V', 'Y') /* 16 YUV 4:2:2 */
#define V4L2_PIX_FMT_VYUY v4l2_fourcc('V', 'Y', 'U', 'Y') /* 16 YUV 4:2:2 */
/* compressed formats (压缩格式) */
#define V4L2_PIX_FMT_MJPEG v4l2_fourcc('M', 'J', 'P', 'G') /* Motion-JPEG */
MJPG 格式为压缩格式
这两种支持格式可任选一种,摄像头与avi文件的视频格式必须一致
也就是程序中 vfmt.fmt.pix.pixelformat 与 strh .codec strf.compression ,这三者要一致。
avi 头文件
-
- #ifndef AVI_H
- #define AVI_H
- #include <stdio.h>
-
-
- //FILE * avi_ks(void);
- //int avi_add(FILE*fp,char *data,int size);
- //int avi_end(FILE *f_file);
-
- struct avi{
- struct riff{
- unsigned char id[4];
- unsigned int size;
- unsigned char type[4];
- }ri1;
-
-
- struct hdrl{
- unsigned char id[4]; //块ID,固定为LIST
- unsigned int size; //块大小,等于struct avi_hdrl_list去掉id和size的大小
- unsigned char type[4]; //块类型,固定为hdrl
-
- struct avih{
- unsigned char id[4]; //块ID,固定为avih
- unsigned int size; //块大小,等于struct avi_avih_chunk去掉id和size的大小
- unsigned int us_per_frame; //视频帧间隔时间(以微秒为单位)
- unsigned int max_bytes_per_sec; //AVI文件的最大数据率
- unsigned int padding; //设为0即可
- unsigned int flags; //AVI文件全局属性,如是否含有索引块、音视频数据是否交叉存储等
- unsigned int total_frames; //总帧数
- unsigned int init_frames; //为交互格式指定初始帧数(非交互格式应该指定为0)
- unsigned int streams; //文件包含的流的个数,仅有视频流时为1
- unsigned int suggest_buff_size; //指定读取本文件建议使用的缓冲区大小,通常为存储一桢图像 //以及同步声音所需的数据之和,不指定时设为0
- unsigned int width; //视频主窗口宽度(单位:像素)
- unsigned int height; //视频主窗口高度(单位:像素)
- unsigned int reserved[4]; //保留段,设为0即可
- }ah1;
-
- struct strl{
- unsigned char id[4]; //块ID,固定为LIST
- unsigned int size; //块大小,等于struct avi_strl_list去掉id和size的大小
- unsigned char type[4]; //块类型,固定为strl
-
- struct strh{
- unsigned char id[4]; //块ID,固定为strh
- unsigned int size; //块大小,等于struct avi_strh_chunk去掉id和size的大小
- unsigned char stream_type[4]; //流的类型,vids表示视频流,auds表示音频流
- unsigned char codec[4]; //指定处理这个流需要的解码器,如JPEG
- unsigned int flags; //标记,如是否允许这个流输出、调色板是否变化等,一般设为0即可
- unsigned short priority; //流的优先级,视频流设为0即可
- unsigned short language; //音频语言代号,视频流设为0即可
- unsigned int init_frames; //为交互格式指定初始帧数(非交互格式应该指定为0)
- unsigned int scale; //
- unsigned int rate; //对于视频流,rate / scale = 帧率fps
- unsigned int start; //对于视频流,设为0即可
- unsigned int length; //对于视频流,length即总帧数
- unsigned int suggest_buff_size; //读取这个流数据建议使用的缓冲区大小
- unsigned int quality; //流数据的质量指标
- unsigned int sample_size; //音频采样大小,视频流设为0即可
- struct rcFrame{ //这个流在视频主窗口中的显示位置,设为{0,0,width,height}即可
-
- short left;
- short top;
- short right;
- short bottom;
- } AVI_RECT_FRAME;
- }sh1;
-
- struct strf{
- unsigned char id[4]; //块ID,固定为strf
- unsigned int size; //块大小,等于struct avi_strf_chunk去掉id和size的大小
- unsigned int size1; //size1含义和值同size一样
- unsigned int width; //视频主窗口宽度(单位:像素)
- unsigned int height; //视频主窗口高度(单位:像素)
- unsigned short planes; //始终为1
- unsigned short bitcount; //每个像素占的位数,只能是1、4、8、16、24和32中的一个
- unsigned char compression[4]; //视频流编码格式,如JPEG、MJPG等
- unsigned int image_size; //视频图像大小,等于width * height * bitcount / 8
- unsigned int x_pixels_per_meter; //显示设备的水平分辨率,设为0即可
- unsigned int y_pixels_per_meter; //显示设备的垂直分辨率,设为0即可
- unsigned int num_colors; //含义不清楚,设为0即可
- unsigned int imp_colors; //含义不清楚,设为0即可
- }sf1;
-
- }sl1;
-
- }hd1;
-
- struct movi{
- unsigned char id[4];
- unsigned int size;
- unsigned char type[4];
- }movi1;
-
- }HEAD;
-
- #endif
主程序
-
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/ioctl.h>
- #include <linux/videodev2.h>
- #include <string.h>
- #include <sys/mman.h>
- #include "Avi.h"
- #include <sys/time.h>
-
- #define wid 1920 //摄像头图像宽度
- #define hei 1080 //图像高度
- #define bitlen 16 //图像采样宽度 如不正确,会影响帧率,必须设置正确,一般是8,16,24,32 之一,可试
- #define perframe 50 //先预估一帧率,可设为摄像头最大帧率,实际使用时的帧率小于次值,主要用于程序设定数据缓冲区大小
- #define jhframe 2000 //准备要录像的图片帧数,控制录像的时间长度
-
- #define HCQ 10 //v4l2 缓冲区个数
-
- #define vcodes {'M','J','P','G'}
- //#define vcodes {'Y','U','Y','V'}
-
- static int nframes = 0; //总帧数
- static int totalsize = 0; //总字节数
-
-
- FILE * avi_ks(void) {
- chdir("/home/wzpc/Videos/");
- FILE *fp = fopen("sample.avi", "w+b");
-
- fseek(fp, sizeof(HEAD), SEEK_SET);
-
- return fp;
- }
-
- int avi_add(FILE*fp, char *data, int size) {
-
- unsigned char tmp[4] = {'0', '0', 'd', 'c'}; //00dc = 压缩的视频数据00db:未压缩
- fwrite(tmp, 4, 1, fp); //写入是否是压缩的视频数据信息
- fwrite(&size, 4, 1, fp); //写入4字节对齐后的JPEG图像大小
- fwrite(data, size, 1, fp); //写入真正的JPEG数据
- return 0;
- }
-
- //----------------------------------------------------------------------------------
- int avi_end(FILE *f_file) {
-
- int width = wid;
- int height = hei;
-
- typedef struct hdrl AVI_HDRL_LIST;
- typedef struct movi AVI_LIST_HEAD;
- typedef struct avih AVI_AVIH_CHUNK;
- typedef struct strl AVI_STRL_LIST;
- typedef struct strh AVI_STRH_CHUNK;
- typedef struct strf AVI_STRF_CHUNK;
- typedef struct avi AVI_HEAD;
-
- AVI_HEAD avi_head = {
-
- {
- {'R', 'I', 'F', 'F'},
- 4 + sizeof(AVI_HDRL_LIST) + sizeof(AVI_LIST_HEAD) + nframes * 8 + totalsize,
- {'A', 'V', 'I', ' '}
- },
-
- {
- {'L', 'I', 'S', 'T'},
- sizeof(AVI_HDRL_LIST) - 8,
- {'h', 'd', 'r', 'l'},
- {
- {'a', 'v', 'i', 'h'},
- sizeof(AVI_AVIH_CHUNK) - 8,
- 1000000 / perframe, width*height*bitlen*perframe / 8, 0, 0, nframes,
- 0, 1, width*height*bitlen / 8, width, height,
- {0, 0, 0, 0}
- },
- {
- {'L', 'I', 'S', 'T'},
- sizeof(AVI_STRL_LIST) - 8,
- {'s', 't', 'r', 'l'},
- {
- {'s', 't', 'r', 'h'},
- sizeof(AVI_STRH_CHUNK) - 8,
- {'v', 'i', 'd', 's'},
- vcodes,
- // {'J','P','E','G'},
- // {'Y', 'U', 'Y', 'V'},
- 0, 0, 0, 0,
- 1, //4750
- perframe, //20000
- 0, nframes, width*height*bitlen*perframe / 8, 10000, 0,
- {0, 0, width, height}
- },
- {
- {'s', 't', 'r', 'f'},
- sizeof(AVI_STRF_CHUNK) - 8,
- sizeof(AVI_STRF_CHUNK) - 8,
- width, height, 1,
- bitlen,
- vcodes,
- // {'J', 'P', 'E', 'G'},
- // {'Y','U','Y','V'},
- width * height *bitlen / 8, 0, 0, 0, 0
- }
- }
-
- },
-
- {
- {'L', 'I', 'S', 'T'},
- 4 + nframes * 8 + totalsize,
- {'m', 'o', 'v', 'i'}
- }
- };
-
-
- fseek(f_file, 0, SEEK_SET);
-
- fwrite(&avi_head, sizeof(HEAD), 1, f_file);
-
-
- return 0;
- }
-
-
- int main(void) {
-
- int fd1=open("/home/wzpc/1.txt",O_RDWR); //进程mmap共享,用于控制录像的停止
- char *p=mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd1,0);
-
-
- // memcpy(p,&bz,4);
-
- int fd = open("/dev/video0", O_RDWR);
- if (fd < 0) {
- perror("打开设备失败");
- return -1;
- }
- struct v4l2_format vfmt;
- vfmt.type = 1;
- vfmt.fmt.pix.width = wid;
- vfmt.fmt.pix.height = hei;
- vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
- // vfmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; //重要 MJPG YUYV422
- // vfmt.fmt.pix.pixelformat=v4l2_fourcc('Y','U','Y','V');
-
- int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);
- if (ret < 0) {
- perror("设置格式失败");
- }
-
- struct v4l2_requestbuffers reqbuffer;
- reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-
- reqbuffer.count = HCQ;
- reqbuffer.memory = V4L2_MEMORY_MMAP ;
- ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
- if (ret < 0) {
- perror("申请队列空间失败");
- }
- //我理解以下部分
- struct v4l2_buffer mapbuffer; //相当于在为mapbuffer 设置读写规则,还没有开始数据传输,怎样从摄像头buf传输数据保存在多少个mmap中
- mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- unsigned char *mptr[HCQ];
- unsigned size[HCQ];
-
- for (int t = 0; t < HCQ; t++) {
-
- mapbuffer.index = t;
- ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);//读摄像头缓冲区数据,为映射mmap准备
- if(ret < 0)
- {
- perror("查询内核空间队列失败");
- }
-
- mptr[t] = mmap(NULL, mapbuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset); //摄像头缓冲区映射到内存
- size[t] = mapbuffer.length;
-
- ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer); //释放一个buf缓冲区空间,摄像头可以为此buf写入数据
- if (ret < 0) {
- perror("放回失败");
- }
- }
- //--------------------------我的理解:以上都是设置---------------------------------------------
- //============================================================================================
-
- int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- ret = ioctl(fd, VIDIOC_STREAMON, &type); //启动流,数据开始传输
- if (ret < 0) {
- perror("开启失败");
- }
-
-
- //---------------------------------------------------------------------------
- FILE *file = avi_ks();
-
- struct v4l2_buffer readbuffer;
- readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //安装mapbuffer设置的规矩来读,数据从mmap中提取
-
-
- struct timeval start, end;
- gettimeofday(&start, NULL ); //微秒记时开始
-
- int bz=1;
- while ((nframes < jhframe)&&(bz)) { //开另一进程控制录像停止
- puts("录像中.....");
- ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer); //读当前队列缓冲区的数据 index循环+1,VIDIOC_DQBUF执行一次,index加1
- if (ret < 0) {
- perror("提取数据失败");
- }
- int i=readbuffer.index;
- // printf("%d\n",mapbuffer.index); //验证index的循环变化
-
- int len = readbuffer.length; //每一个mapbuff等于一帧字节,每一帧的字节数都是相同的
- // int len=wid*hei*bitlen/8;
-
- unsigned char tmp[4] = {'0', '0', 'd', 'c'}; //00dc = 压缩的视频数据
- fwrite(tmp, 4, 1, file);
- fwrite(&len, 4, 1, file);
- fwrite(mptr[i],len, 1, file); //写入真正的JPEG数据
-
- ret = ioctl(fd, VIDIOC_QBUF, &readbuffer); //把缓冲区数据放入读队列中
- if (ret < 0) {
- perror("放回失败");
- }
- nframes++;
- totalsize++;
- memcpy(&bz,p,4);
- }
-
- static float timeuse1 = 0;
- gettimeofday(&end, NULL ); //记时结束
- timeuse1 = 1000 * (1000000 * ( end.tv_sec - start.tv_sec ) + end.tv_usec - start.tv_usec);
- int timeuse = (int)(timeuse1 / 1000000); //转换为豪秒
-
- //---------------------------------------------------------
- ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
- avi_end(file);
-
- int scale = timeuse;
-
- int rate = nframes * 1000; //重写实际帧率=rate/scale ,乘1000是timeuse 为毫秒,转换为秒
- printf("帧率:%d\n", rate / timeuse);
-
- fseek(file, 128, SEEK_SET); //128 为scale 在文件中的字节位数(偏移)
- fwrite(&scale, 4, 1, file);
- fwrite(&rate, 4, 1, file); //rate 为128+4 位
-
- fclose(file);
-
- for (int n = 0; n < HCQ; n++) {
- munmap(mptr, size[n]);
- }
- int kz1=1; //控制位复位1
- memcpy(p,&kz1,4);
- munmap(p,4);
-
-
- close(fd);
- puts("end");
- return 0;
- }
3.停止录像进程
- #include <sys/mman.h>
-
- #include <stdio.h>
-
- #include <string.h>
-
- #include <fcntl.h>
-
- #include <unistd.h>
-
-
-
- int main(void){
-
- int fd=open("/home/wzpc/1.txt",O_RDWR);
-
- char *p=mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
-
- puts("start:t=0\n");
-
- int t=0;
-
- memcpy(p,&t,4);
-
-
-
- return 0;
-
- }