基本思想:因为手中有一块RK3399 PRO的开发板,外接了AHD的摄像头,为了实现实时获取视频帧,所以需要学习一下v4l2编程。。
第一步:先拷贝一张图,整个v4l2的取帧流程
具体讲解参考这个大佬的bilibili和有道笔记 https://note.youdao.com/ynoteshare/index.html?id=7c4c0e28888d03ec70d339118c374edb&type=note&_time=1652171642765
第一步
查看支持的显示 分辨率
ubutnu@ubuntu~$: v4l2-ctl --list-formats-ext -d /dev/video0
代码,完成摄像头的帧获取和写入本地显示
#include#include #include #include #include #include #include #include #include int main() { //打开设备 int fd=open("/dev/video0",O_RDWR); if(fd<0){ perror("open device failn"); return -1; } // 获取摄像头支持的格式 //获取摄像头支持的格式 struct v4l2_fmtdesc v4fmt; v4fmt.index=0; v4fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; int ret=ioctl(fd,VIDIOC_ENUM_FMT,&v4fmt ); if(ret<0){ perror("acvquire failn"); } printf("%sn",v4fmt.description); unsigned char *p=(unsigned char*)&v4fmt.pixelformat; printf("%c %c %c %cn",p[0],p[1],p[2],p[3]); // 设置摄像头支持的格式 //设置摄像头支持的格式 struct v4l2_format vFormat; vFormat.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; vFormat.fmt.pix.width=640; vFormat.fmt.pix.height=480; vFormat.fmt.pix.pixelformat=V4L2_PIX_FMT_MJPEG; ret=ioctl(fd,VIDIOC_S_FMT,&vFormat); if(ret<0){ perror("set failn"); } //申请内核空间 struct v4l2_requestbuffers vqbuff; vqbuff.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; vqbuff.count=4; vqbuff.memory=V4L2_MEMORY_MMAP; ret =ioctl(fd,VIDIOC_REQBUFS,&vqbuff); if(ret<0){ perror("buff failn"); } //申请内存空间 struct v4l2_buffer vbuff; vbuff.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; unsigned char * mptr[4]; for(int i=0;i<4;i++){ vbuff.index=i; ret=ioctl(fd,VIDIOC_QUERYBUF,&vbuff); if(ret<0) { perror("requeire buff failn"); } mptr[i]= (unsigned char *s)mmap(NULL,vbuff.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,vbuff.m.offset); //通知完毕 ret=ioctl(fd,VIDIOC_QBUF,&vbuff); if(ret<0){ perror("put fail"); } } // 开始采集 int type=V4L2_BUF_TYPE_VIDEO_CAPTURE; ret=ioctl(fd,VIDIOC_STREAMON,&type); if(ret<0){ perror("open fail"); } //从队列中取数据 struct v4l2_buffer readbuff; readbuff.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; ret=ioctl(fd,VIDIOC_DQBUF,&readbuff); if(ret<0){ perror("read fail"); } FILE* file=fopen("example.jpg","w+"); fwrite(mptr[readbuff.index],readbuff.length,1,file); fclose(file); //通知内核 已经使用完 ret=ioctl(fd,VIDIOC_QBUF,&readbuff); if(ret<0){ perror("put quee failn"); } //停止采集 ret=ioctl(fd,VIDIOC_STREAMOFF,&type); if(ret<0){ perror("stop eque@e failn"); } //关闭设备 close(fd); printf("close device successfullyn"); return 0; }
写到本地的图片
cmakelist.txt
cmake_minimum_required(VERSION 3.16)
project(untitled3)
set(CMAKE_CXX_STANDARD 14)
#sudo apt install libjpeg8-dev
add_executable(untitled3 main.cpp)
target_link_libraries(
untitled3
-ljpeg
)
源代码,编译ctrl+alt+f1在命令行执行
#include#include #include #include #include #include #include #include #include #include #include #include int read_JPEG_file(unsigned char *jpegData, unsigned char *rgbdata, int size) { struct jpeg_error_mgr jerr; struct jpeg_decompress_struct cinfo; cinfo.err = jpeg_std_error(&jerr); //1创建解码对象并且初始化 jpeg_create_decompress(&cinfo); //2.装备解码的数据 //jpeg_stdio_src(&cinfo, infile); jpeg_mem_src(&cinfo, (unsigned char *) jpegData, size); //3.获取jpeg图片文件的参数 (void) jpeg_read_header(&cinfo, TRUE); //5.开始解码 (void) jpeg_start_decompress(&cinfo); //6.申请存储一行数据的内存空间 int row_stride = cinfo.output_width * cinfo.output_components; unsigned char *buffer = (unsigned char *) (malloc(row_stride)); int i = 0; while (cinfo.output_scanline < cinfo.output_height) { //printf("****%dn",i); (void) jpeg_read_scanlines(&cinfo, &buffer, 1); memcpy(rgbdata + i * 640 * 3, buffer, row_stride); i++; } //7.解码完成 (void) jpeg_finish_decompress(&cinfo); //8.释放解码对象 jpeg_destroy_decompress(&cinfo); return 1; } void lcd_show_rgb(unsigned int *lcdptr, int lcd_w, int lcd_h, unsigned char *rgb_data, int width, int height) { unsigned int *ptr = lcdptr; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { memcpy(ptr + j, rgb_data + j * 3, 3); } ptr += lcd_w; rgb_data += width * 3; } } int main() { //打开lcd int lcdfd = open("/dev/fb0", O_RDWR); if (lcdfd < 0) { perror("open lcdfd fail"); return -1; } struct fb_var_screeninfo info; ioctl(lcdfd, FBIOGET_VSCREENINFO, &info); printf("xres = %u, yres = %u.n", info.xres, info.yres); printf("xres_virtual = %u, yres_virtual = %u.n", info.xres_virtual, info.yres_virtual); printf("bpp = %u.n", info.bits_per_pixel); //xu ni ji ubuntu // int lcd_w = info.xres_virtual; //int lcd_h = info.yres_virtual; // arm int lcd_w = info.xres; int lcd_h = info.yres; unsigned int *lcdptr = (unsigned int *) mmap(NULL, lcd_w * lcd_h * 4, PROT_READ | PROT_WRITE, MAP_SHARED, lcdfd, 0); if (lcdptr == NULL) { perror("create lcdptr fail"); return -1; } //打开设备 int fd = open("/dev/video0", O_RDWR); if (fd < 0) { perror("open device failn"); return -1; } // 获取摄像头支持的格式 //获取摄像头支持的格式 struct v4l2_fmtdesc v4fmt; v4fmt.index = 0; v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt); if (ret < 0) { perror("acquire failn"); return -1; } printf("%sn", v4fmt.description); unsigned char *p = (unsigned char *) &v4fmt.pixelformat; printf("%c %c %c %cn", p[0], p[1], p[2], p[3]); // 设置摄像头支持的格式 //设置摄像头支持的格式 struct v4l2_format vFormat; vFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vFormat.fmt.pix.width = 640; vFormat.fmt.pix.height = 480; vFormat.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; ret = ioctl(fd, VIDIOC_S_FMT, &vFormat); if (ret < 0) { perror("set failn"); return -1; } //申请内核空间 struct v4l2_requestbuffers vqbuff; vqbuff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vqbuff.count = 4; vqbuff.memory = V4L2_MEMORY_MMAP; ret = ioctl(fd, VIDIOC_REQBUFS, &vqbuff); if (ret < 0) { perror("buff failn"); return -1; } //申请内存空间 struct v4l2_buffer mptrbuff; mptrbuff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; unsigned char *mptr[4]; unsigned int mptr_len[4]; for (int i = 0; i < 4; i++) { mptrbuff.index = i; ret = ioctl(fd, VIDIOC_QUERYBUF, &mptrbuff); if (ret < 0) { perror("require buff failn"); return -1; } mptr[i] = (unsigned char *) mmap(NULL, mptrbuff.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mptrbuff.m.offset); mptr_len[i] = mptrbuff.length; //通知完毕 ret = ioctl(fd, VIDIOC_QBUF, &mptrbuff); if (ret < 0) { perror("put fail"); return -1; } } // 开始采集 int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = ioctl(fd, VIDIOC_STREAMON, &type); if (ret < 0) { perror("open fail"); return -1; } unsigned char rgb_data[640 * 480 * 3]; while (true) { //从队列中取数据 struct v4l2_buffer readbuff; readbuff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = ioctl(fd, VIDIOC_DQBUF, &readbuff); if (ret < 0) { perror("read fail"); } read_JPEG_file(mptr[readbuff.index], rgb_data, readbuff.length); lcd_show_rgb(lcdptr,lcd_w,lcd_h, rgb_data, 640, 480); // FILE* file=fopen("example.jpg","w+"); // fwrite(mptr[readbuff.index],readbuff.length,1,file); //fclose(file); //通知内核 已经使用完 ret = ioctl(fd, VIDIOC_QBUF, &readbuff); if (ret < 0) { perror("put equee failn"); } } //停止采集 ret = ioctl(fd, VIDIOC_STREAMOFF, &type); if (ret < 0) { perror("stop eque@e failn"); } //释放资源、 for (int i = 0; i < 4; i++) { munmap(mptr[i], mptr_len[i]); } //关闭设备 close(fd); printf("close device successfullyn"); return 0; }
测是的MJPEG的格式的图片显示
yuyv转bgr显示 640*480
#include#include #include #include #include #include #include #include #include #include #include #include int read_JPEG_file(unsigned char *jpegData, unsigned char *rgbdata, int size) { struct jpeg_error_mgr jerr; struct jpeg_decompress_struct cinfo; cinfo.err = jpeg_std_error(&jerr); //1创建解码对象并且初始化 jpeg_create_decompress(&cinfo); //2.装备解码的数据 //jpeg_stdio_src(&cinfo, infile); jpeg_mem_src(&cinfo, (unsigned char *) jpegData, size); //3.获取jpeg图片文件的参数 (void) jpeg_read_header(&cinfo, TRUE); //5.开始解码 (void) jpeg_start_decompress(&cinfo); //6.申请存储一行数据的内存空间 int row_stride = cinfo.output_width * cinfo.output_components; unsigned char *buffer = (unsigned char *) (malloc(row_stride)); int i = 0; while (cinfo.output_scanline < cinfo.output_height) { //printf("****%dn",i); (void) jpeg_read_scanlines(&cinfo, &buffer, 1); memcpy(rgbdata + i * 640 * 3, buffer, row_stride); i++; } //7.解码完成 (void) jpeg_finish_decompress(&cinfo); //8.释放解码对象 jpeg_destroy_decompress(&cinfo); return 1; } void yuyv_to_rgb(unsigned char *yuyvdata, unsigned char *rgbdata, int w, int h) { //码流Y0 U0 Y1 V1 Y2 U2 Y3 V3 --》YUYV像素[Y0 U0 V1] [Y1 U0 V1] [Y2 U2 V3] [Y3 U2 V3]--》RGB像素 int r1, g1, b1; int r2, g2, b2; for(int i=0; i [Y0 U0 V1] [Y1 U0 V1] r1 = Y0+1.4075*(V1-128); if(r1>255)r1=255; if(r1<0)r1=0; g1 =Y0- 0.3455 * (U0-128) - 0.7169*(V1-128); if(g1>255)g1=255; if(g1<0)g1=0; b1 = Y0 + 1.779 * (U0-128); if(b1>255)b1=255; if(b1<0)b1=0; r2 = Y1+1.4075*(V1-128);if(r2>255)r2=255; if(r2<0)r2=0; g2 = Y1- 0.3455 * (U0-128) - 0.7169*(V1-128); if(g2>255)g2=255; if(g2<0)g2=0; b2 = Y1 + 1.779 * (U0-128); if(b2>255)b2=255; if(b2<0)b2=0; // rgb的数据 // rgbdata[i*6+0]=r1; // rgbdata[i*6+1]=g1; // rgbdata[i*6+2]=b1; // rgbdata[i*6+3]=r2; // rgbdata[i*6+4]=g2; // rgbdata[i*6+5]=b2; //bgr的数据 rgbdata[i*6+0]=b1; rgbdata[i*6+1]=g1; rgbdata[i*6+2]=r1; rgbdata[i*6+3]=b2; rgbdata[i*6+4]=g2; rgbdata[i*6+5]=r2; } } void lcd_show_rgb(unsigned int *lcdptr, int lcd_w, int lcd_h, unsigned char *rgb_data, int width, int height) { unsigned int *ptr = lcdptr; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { memcpy(ptr + j, rgb_data + j * 3, 3); } ptr += lcd_w; rgb_data += width * 3; } } int main() { //打开lcd int lcdfd = open("/dev/fb0", O_RDWR); if (lcdfd < 0) { perror("open lcdfd fail"); return -1; } struct fb_var_screeninfo info; ioctl(lcdfd, FBIOGET_VSCREENINFO, &info); printf("xres = %u, yres = %u.n", info.xres, info.yres); printf("xres_virtual = %u, yres_virtual = %u.n", info.xres_virtual, info.yres_virtual); printf("bpp = %u.n", info.bits_per_pixel); //xu ni ji ubuntu // int lcd_w = info.xres_virtual; //int lcd_h = info.yres_virtual; // arm int lcd_w = info.xres; int lcd_h = info.yres; unsigned int *lcdptr = (unsigned int *) mmap(NULL, lcd_w * lcd_h * 4, PROT_READ | PROT_WRITE, MAP_SHARED, lcdfd, 0); if (lcdptr == NULL) { perror("create lcdptr fail"); return -1; } //打开设备 int fd = open("/dev/video0", O_RDWR); if (fd < 0) { perror("open device failn"); return -1; } // 获取摄像头支持的格式 //获取摄像头支持的格式 struct v4l2_fmtdesc v4fmt; v4fmt.index = 0; v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt); if (ret < 0) { perror("acquire failn"); return -1; } printf("%sn", v4fmt.description); unsigned char *p = (unsigned char *) &v4fmt.pixelformat; printf("%c %c %c %cn", p[0], p[1], p[2], p[3]); // 设置摄像头支持的格式 //设置摄像头支持的格式 struct v4l2_format vFormat; vFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vFormat.fmt.pix.width = 640; vFormat.fmt.pix.height = 480; vFormat.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; ret = ioctl(fd, VIDIOC_S_FMT, &vFormat); if (ret < 0) { perror("set failn"); return -1; } //申请内核空间 struct v4l2_requestbuffers vqbuff; vqbuff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vqbuff.count = 4; vqbuff.memory = V4L2_MEMORY_MMAP; ret = ioctl(fd, VIDIOC_REQBUFS, &vqbuff); if (ret < 0) { perror("buff failn"); return -1; } //申请内存空间 struct v4l2_buffer mptrbuff; mptrbuff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; unsigned char *mptr[4]; unsigned int mptr_len[4]; for (int i = 0; i < 4; i++) { mptrbuff.index = i; ret = ioctl(fd, VIDIOC_QUERYBUF, &mptrbuff); if (ret < 0) { perror("require buff failn"); return -1; } mptr[i] = (unsigned char *) mmap(NULL, mptrbuff.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mptrbuff.m.offset); mptr_len[i] = mptrbuff.length; //通知完毕 ret = ioctl(fd, VIDIOC_QBUF, &mptrbuff); if (ret < 0) { perror("put fail"); return -1; } } // 开始采集 int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = ioctl(fd, VIDIOC_STREAMON, &type); if (ret < 0) { perror("open fail"); return -1; } unsigned char rgb_data[640 * 480 * 3]; while (true) { //从队列中取数据 struct v4l2_buffer readbuff; readbuff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = ioctl(fd, VIDIOC_DQBUF, &readbuff); if (ret < 0) { perror("read fail"); } yuyv_to_rgb(mptr[readbuff.index], rgb_data, 640, 480); //read_JPEG_file(mptr[readbuff.index], rgb_data, readbuff.length); lcd_show_rgb(lcdptr,lcd_w,lcd_h, rgb_data, 640, 480); // FILE* file=fopen("example.jpg","w+"); // fwrite(mptr[readbuff.index],readbuff.length,1,file); //fclose(file); //通知内核 已经使用完 ret = ioctl(fd, VIDIOC_QBUF, &readbuff); if (ret < 0) { perror("put equee failn"); } } //停止采集 ret = ioctl(fd, VIDIOC_STREAMOFF, &type); if (ret < 0) { perror("stop eque@e failn"); } //释放资源、 for (int i = 0; i < 4; i++) { munmap(mptr[i], mptr_len[i]); } //关闭设备 close(fd); printf("close device successfullyn"); return 0; }
测试图片
参考
6、读取YUV数据并理解YUV数据_sxj731533730的博客-CSDN博客_解析yuv数据
有道云笔记
Linux应用开发【第七章】摄像头V4L2编程应用开发 - 知乎



