一、V4L2采集YUYV视频数据a)打开V4L2设备并创建接收yuyv数据的文件open_v4l2_device(const char *const devname)video_obj.v4l2_fd=open(devname,O_RDWR)//打卡v4l2设备fopen(name,"wb+")//创建yuyv数据接收文件b)设置视频格式,分辨率set_v4l2_fmt(unsigned int format,unsigned int width,unsigned int height) video_obj.fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;video_obj.fmt.fmt.pix.pixelformat = format;video_obj.fmt.fmt.pix.width = width;video_obj.fmt.fmt.pix.height = height;ioctl(video_obj.v4l2_fd, VIDIOC_S_FMT, &video_obj.fmt)c)获取当前的格式和分辨率,查看设置是否生效struct v4l2_format fmt;fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ioctl(video_obj.v4l2_fd, VIDIOC_G_FMT, &fmt)d)设置帧率set_v4l2_param(unsigned int num,unsigned int deno)struct v4l2_streamparm param;memset(¶m, 0, sizeof(struct v4l2_streamparm));param.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;param.parm.capture.timeperframe.numerator = num;param.parm.capture.timeperframe.denominator =deno;ioctl(video_obj.v4l2_fd, VIDIOC_S_PARM,¶m)e)获取帧率,查看设置是否生效get_v4l2_param(void)struct v4l2_streamparm param;memset(¶m, 0, sizeof(struct v4l2_streamparm));param.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ioctl(video_obj.v4l2_fd, VIDIOC_G_PARM, ¶m)f)申请V4L2帧缓存request_v4l2_buffer(unsigned int count)video_obj.buffers = calloc(count, sizeof(VideoBuffer));memset(&video_obj.req, 0, sizeof(video_obj.req));video_obj.req.count = count;video_obj.req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;video_obj.req.memory = V4L2_MEMORY_MMAP;ioctl(video_obj.v4l2_fd, VIDIOC_REQBUFS, &video_obj.req)g)内存映射摄像头的缓存for (numBufs = 0; numBufs < video_obj.req.count; numBufs++){memset(&video_obj.buf, 0, sizeof(video_obj.buf));//数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTUREvideo_obj.buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//存储类型:V4L2_MEMORY_MMAP(内存映射)或V4L2_MEMORY_USERPTR(用户指针)video_obj.buf.memory = V4L2_MEMORY_MMAP;video_obj.buf.index = numBufs;//使配置生效ioctl(video_obj.v4l2_fd, VIDIOC_QUERYBUF, &video_obj.buf)video_obj.buffers[numBufs].length = video_obj.buf.length;video_obj.buffers[numBufs].offset = (size_t)video_obj.buf.m.offset;//使用mmap函数将申请的缓存地址转换应用程序的绝对地址video_obj.buffers[numBufs].start =mmap(NULL,video_obj.buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,video_obj.v4l2_fd,video_obj.buf.m.offset);//放入缓存队列ioctl(video_obj.v4l2_fd,VIDIOC_QBUF,&video_obj.buf)}h)开始采集数据i.获取一帧缓存数据start_v4l2_capture(void)type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ioctl(video_obj.v4l2_fd, VIDIOC_STREAMON, &type)pull_v4l2_frame_buffer(unsigned int index,unsigned char **start,unsigned int *len) video_obj.buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;video_obj.buf.memory = V4L2_MEMORY_MMAP;video_obj.buf.index = index;ioctl(video_obj.v4l2_fd,VIDIOC_DQBUF,&video_obj.buf)*start = video_obj.buffers[index].start;*len = video_obj.buffers[index].length;ii.将yuyv数据写入到文件(同时通过SDL2显示当前帧的数据)fwrite(photo,1,len,fd);fflush(fd);iii.将该帧缓存放入到缓存池中push_v4l2_frame_buffer(unsigned int index)video_obj.buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;video_obj.buf.memory = V4L2_MEMORY_MMAP;video_obj.buf.index = index;ioctl(video_obj.v4l2_fd, VIDIOC_QBUF, &video_obj.buf)i)清理动态申请的数据release_v4l2_resource(void)for (numBufs = 0; numBufs < video_obj.req.count; numBufs++)munmap(video_obj.buffers[numBufs].start,video_obj.buf.length);free(video_obj.buffers);close(video_obj.v4l2_fd);二、通过SDL2显示采集到的yuyv原始数据a)初始化sdl2需要用到的功能sdl2_init(int w,int h)SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER)b)创建新的窗口sdl2_init(int w,int h)win = SDL_CreateWindow("Sam",0,0,w,h,SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE);c)创建渲染器sdl2_init(int w,int h)renderer = SDL_CreateRenderer(win,-1,SDL_RENDERER_SOFTWARE);d)设置渲染器的纹理sdl2_init(int w,int h)Texture=SDL_CreateTexture(renderer,SDL_PIXELFORMAT_YUY2,SDL_TEXTUREACCESS_STREAMING,w,h);e)创建SDL2的事件处理线程pthread_create(&pid, NULL,event_loop,NULL)SDL_PollEvent(&event)f)循环显示步骤1:更新渲染器纹理sdl2_refresh(void *pixels,int pitch)SDL_UpdateTexture(texture,NULL,pixels,pitch)g)循环显示步骤2:清空渲染器sdl2_refresh(void *pixels,int pitch)SDL_UpdateTexture(texture,NULL,pixels,pitch)h)循环显示步骤3:将纹理数据拷贝到渲染器sdl2_refresh(void *pixels,int pitch)SDL_RenderCopy(renderer,texture,NULL,NULL)i)循环显示步骤4:显示视频sdl2_refresh(void *pixels,int pitch)SDL_RenderPresent(renderer);三、通过libX264压缩视频数据到H264a)创建相关结构体,打开yuyv文件和将要保存h264数据的文件x264_nal_t* pNals = NULL;x264_t* pHandle = NULL;x264_picture_t* pPic_in = (x264_picture_t*)malloc(sizeof(x264_picture_t));x264_picture_t* pPic_out = (x264_picture_t*)malloc(sizeof(x264_picture_t));x264_param_t* pParam = (x264_param_t*)malloc(sizeof(x264_param_t));FILE* fp_src = fopen("x.yuv", "rb");FILE* fp_dst = fopen("x.h264", "wb");b)给结构体x264_param_t赋予一些默认的参数,修改参数中的宽高和数据格式(因为录制的时候是采用V4L2_PIX_FMT_YUYV格式,宽高为640x480)x264_param_default(pParam);pParam->i_width = width;pParam->i_height = height;pParam->i_csp = csp;c)设置profilex264_param_apply_profile(pParam, x264_profile_names[4]);d)打开编码器pHandle = x264_encoder_open(pParam);e)初始化帧数据的输入输出结构体x264_picture_init(pPic_out);x264_picture_alloc(pPic_in, csp, pParam->i_width, pParam->i_height);f)根据视频数据计算出视频帧数fseek(fp_src,0,SEEK_END);switch(csp){case X264_CSP_I444:frame_num=ftell(fp_src)/(y_size*3);break;case X264_CSP_I420:frame_num=ftell(fp_src)/(y_size*3/2);break;case X264_CSP_I422:frame_num=ftell(fp_src)/(y_size*2);break;}fseek(fp_src,0,SEEK_SET);g)循环编码步骤1:分离yuv分量h)循环编码步骤2:编码一帧i)循环编码步骤3:将编码后的数据写入到h264文件中上面三步骤包含的内容:for( i=0;i<frame_num;i++){switch(csp){case X264_CSP_I444:{fread(pPic_in->img.plane[0],y_size,1,fp_src);//Yfread(pPic_in->img.plane[1],y_size,1,fp_src);//Ufread(pPic_in->img.plane[2],y_size,1,fp_src);//Vbreak;}case X264_CSP_I420:{fread(pPic_in->img.plane[0],y_size,1,fp_src);//Yfread(pPic_in->img.plane[1],y_size/4,1,fp_src);//Ufread(pPic_in->img.plane[2],y_size/4,1,fp_src);//Vbreak;}case X264_CSP_I422:{/*Yuyv格式数据的存放方式为:(4X4像素)Y U Y V Y U Y VY U Y V Y U Y VY U Y V Y U Y VY U Y V Y U Y VY的个数为像素点的个数,实际上像素点的个数为y个数的两倍*/int index = 0;int y_i = 0 , u_i = 0 , v_i = 0;for(index = 0 ; index < y_size*2 ;){fread(&pPic_in->img.plane[0][y_i++],1,1,fp_src);//Yindex++;fread(&pPic_in->img.plane[1][u_i++],1,1,fp_src);//Uindex++;fread(&pPic_in->img.plane[0][y_i++],1,1,fp_src);//Yindex++;fread(&pPic_in->img.plane[2][v_i++],1,1,fp_src);//Vindex++;}break;}}pPic_in->i_pts = i;x264_encoder_encode(pHandle, &pNals, &iNal, pPic_in, pPic_out);for ( j = 0; j < iNal; ++j)fwrite(pNals[j].p_payload, 1, pNals[j].i_payload, fp_dst);}j)编码步骤4:将还残留在编码器中的数据flush out,并写入到文件while(1){ret = x264_encoder_encode(pHandle, &pNals, &iNal, NULL, pPic_out);if(ret == 0)Break;for(j = 0;j < iNal; ++j)fwrite(pNals[j].p_payload, 1, pNals[j].i_payload, fp_dst);}k)清理动态申请的资源x264_picture_clean(pPic_in);x264_encoder_close(pHandle);pHandle = NULL;free(pPic_in);free(pPic_out);free(pParam);fclose(fp_src);fclose(fp_dst);下面是两个项目的源码:说明:1、V4L2采集数据和SDL2显示是在同一个项目中2、YUV数据文件通过libx264压缩为H264格式的文件为一个项目3、实验环境是vmware的ubuntu系统,默认安装了SDL1.2,卸载了原来安装的1.2版本,重新编译安装SDL2.04、需要安装Libx264库5、项目中的源码大部分是参考网上各论坛的博客:/leixiaohua1020/article/details/42078645/yuanhubilie/article/details/37930429文件v4l2lib.c(数据采集和显示项目)//编译命令gcc v4l2lib.c -L/usr/local/SDL/lib/ -I/usr/local/SDL/include/SDL2 -lSDL2 -lpthread -o v4l2//SDL的库安装路径为/usr/local/SDL,根据自己安装路径修改#include <linux/videodev2.h>#include <fcntl.h>#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <string.h>#include <errno.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <sys/mman.h>#include <sys/select.h>#include <sys/time.h>#include <pthread.h>#include "SDL.h"#include <stdio.h>#include <time.h>//--------macro definition------#define MAX_DEV_NAME 32#define MAX_BUF 5#define VIDEO_REC#define WIDTH_PIX 640#define HEIGHT_PIX 480//--------structions defined here-------typedef struct VideoBuffer{unsigned char *start;size_t offset;size_t length;}VideoBuffer;typedef struct v4l2_param{char v4l2_devname[MAX_DEV_NAME];//设备名int v4l2_fd;//描述符号VideoBuffer *buffers;struct v4l2_requestbuffers req;struct v4l2_capability cap;struct v4l2_input input;struct v4l2_format fmt;struct v4l2_buffer buf;}VIDEO_T;//--------variable defined here-------static VIDEO_T video_obj;static pthread_t pid;static unsigned char state = 0;//--------SDL2----------static unsigned char inited = 0;static SDL_Window * win = NULL;static SDL_Renderer * renderer = NULL;static SDL_Texture * texture = NULL;static SDL_CommonEvent comm;static SDL_Event event;static int open_v4l2_device(const char *const devname) {//打开设备if(strlen(devname) >= MAX_DEV_NAME){printf("device name fail:%s\n",devname);return -1;}elsememset(&video_obj,0,sizeof(video_obj));video_obj.v4l2_fd = open(devname,O_RDWR);if(video_obj.v4l2_fd <= 0){perror("open fail");return -1;}elseprintf("%s success\n",__func__);memcpy(video_obj.v4l2_devname,devname,strlen(devname));return 0;}static int set_v4l2_param(unsigned int num,unsigned int deno){struct v4l2_streamparm param;memset(¶m, 0, sizeof(struct v4l2_streamparm));param.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;param.parm.capture.timeperframe.numerator = num;param.parm.capture.timeperframe.denominator =deno;if(ioctl(video_obj.v4l2_fd, VIDIOC_S_PARM,¶m) < 0){printf("%s fail\n",__func__);return -1;}else{printf("%s ok\n",__func__);return 0;}}static int get_v4l2_param(void){struct v4l2_streamparm param;memset(¶m, 0, sizeof(struct v4l2_streamparm));param.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if(ioctl(video_obj.v4l2_fd, VIDIOC_G_PARM, ¶m) < 0){perror("get param failed");return -1;}else{printf("%s:%d/%d\n",__func__,param.parm.capture.timeperframe.numerator,param.parm.capture.timeperframe.denominator);return 0;}}static int set_v4l2_fmt(unsigned int format,unsigned int width,unsigned int height){//设置视频格式memset(&video_obj.fmt,0,sizeof(video_obj.fmt));//视频数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTUREvideo_obj.fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//视频源的格式为JPEG或YUN4:2:2或RGB V4L2_PIX_FMT_RGB565 V4L2_PIX_FMT_YUV565 video_obj.fmt.fmt.pix.pixelformat = format;//video_obj.fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;//设置视频宽度video_obj.fmt.fmt.pix.width = width;//设置视频高度video_obj.fmt.fmt.pix.height = height;if (ioctl(video_obj.v4l2_fd, VIDIOC_S_FMT, &video_obj.fmt) < 0)//使配置生效{perror("set format failed");return -1;}elseprintf("%s success[format:%X w:%d h:%d]\n",__func__,format,width,height);struct v4l2_format fmt;fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (ioctl(video_obj.v4l2_fd, VIDIOC_G_FMT, &fmt) < 0){perror("set format failed");return -1;}elseprintf("%sget success[format:%X w:%d h:%d]\n",__func__,fmt.fmt.pix.pixelformat,fmt.fmt.pix.width,fmt.fmt.pix.height);return 0;}static int request_v4l2_buffer(unsigned int count){//申请帧缓冲video_obj.buffers = calloc(count, sizeof(VideoBuffer));memset(&video_obj.req, 0, sizeof(video_obj.req));//缓存数量,即可保存的图片数量video_obj.req.count = count;//数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTUREvideo_obj.req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//存储类型:V4L2_MEMORY_MMAP或V4L2_MEMORY_USERPTRvideo_obj.req.memory = V4L2_MEMORY_MMAP;//使配置生效if (ioctl(video_obj.v4l2_fd, VIDIOC_REQBUFS, &video_obj.req) == -1){perror("request buffer error \n");return -1;}elseprintf("%s success[request %d buffers]\n",__func__,count);return 0;}static int mmap_v4l2_buffer(void){//将VIDIOC_REQBUFS获取内存转为物理空间int numBufs;for (numBufs = 0; numBufs < video_obj.req.count; numBufs++){memset(&video_obj.buf, 0, sizeof(video_obj.buf));//数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTUREvideo_obj.buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//存储类型:V4L2_MEMORY_MMAP(内存映射)或V4L2_MEMORY_USERPTR(用户指针)video_obj.buf.memory = V4L2_MEMORY_MMAP;video_obj.buf.index = numBufs;//使配置生效if (ioctl(video_obj.v4l2_fd, VIDIOC_QUERYBUF, &video_obj.buf) < 0){perror("VIDIOC_QUERYBUF");return -1;}//printf("request buf %d success\n",numBufs);video_obj.buffers[numBufs].length = video_obj.buf.length;video_obj.buffers[numBufs].offset = (size_t)video_obj.buf.m.offset;//使用mmap函数将申请的缓存地址转换应用程序的绝对地址video_obj.buffers[numBufs].start = mmap(NULL,video_obj.buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,video_obj.v4l2_fd,video_obj.buf.m.offset);if (video_obj.buffers[numBufs].start == MAP_FAILED){perror("buffers error");return -1;}//printf("mmap buf 0x%p lenght:%d success\n",video_obj.buffers[numBufs].start,video_obj.buf.length);//放入缓存队列if (ioctl(video_obj.v4l2_fd,VIDIOC_QBUF,&video_obj.buf) < 0){printf("VIDIOC_QBUF");return -1;}}printf("%s success\n",__func__);return 0;}static int start_v4l2_capture(void){//开始视频显示enum v4l2_buf_type type;//数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTUREtype = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (ioctl(video_obj.v4l2_fd, VIDIOC_STREAMON, &type) < 0){perror("VIDIOC_STREAMON");return -1;}printf("%s stream on success\n",__func__);return 0;}static int pull_v4l2_frame_buffer(unsigned int index , unsigned char **start , unsigned int *len){video_obj.buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //取得原始采集数据video_obj.buf.memory = V4L2_MEMORY_MMAP; //存储类型:V4L2_MEMORY_MMAP(内存映射)或V4L2_MEMORY_USERPTR(用户指针)if(video_obj.req.count <= index)return -1;video_obj.buf.index = index; //读取缓存中的第几帧if (ioctl(video_obj.v4l2_fd,VIDIOC_DQBUF,&video_obj.buf) < 0){perror("VIDIOC_DQBUF");return -1;}*start = video_obj.buffers[index].start;*len = video_obj.buffers[index].length;return 0;}static int push_v4l2_frame_buffer(unsigned int index){video_obj.buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //取得原始采集数据video_obj.buf.memory = V4L2_MEMORY_MMAP; //存储类型:V4L2_MEMORY_MMAP(内存映射)或V4L2_MEMORY_USERPTR(用户指针)if(video_obj.req.count <= index)return -1;video_obj.buf.index = index; //第几帧放入缓存//获取下一帧视频数据if (ioctl(video_obj.v4l2_fd, VIDIOC_QBUF, &video_obj.buf) < 0){perror("VIDIOC_QBUF");return -1;}return 0;}static void release_v4l2_resource(void){int numBufs;for (numBufs = 0; numBufs < video_obj.req.count; numBufs++)munmap(video_obj.buffers[numBufs].start,video_obj.buf.length);free(video_obj.buffers);close(video_obj.v4l2_fd);printf("%s\n",__func__);}static int sdl2_init(int w,int h){if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER) == -1){printf("SDL_Init fail!");return -1;}elseprintf("SDL_Init success\n");/*title :窗口标题x :窗口位置x坐标。