Linux开发板调用摄像头(V4L2编程,含YUYV解码RGB)

news/2025/3/15 4:39:05/

使用Linux的V4L2编程部分参考:

        Linux之V4L2基础编程 - emouse - 博客园

使用YUY2(YUV)与RGB之间相互转化参考:

        (转)RGB、YUY2、YUYV、YVYU、UYVY、AYUV格式详解 - 神一样的魔鬼 - 博客园

        本文是基于Linux开发板的V4L2摄像头调用程序,包括YUYV解码为RGB,以及将摄像头数据显示在开发板屏幕上。代码未封装,可直接在linux下编译使用。

        工作流程:打开设备 —> 检查和设置设备属性 —> 设置帧格式 —> 设置一种输入输出方法(缓冲区管理) —> 循环获取数据 —> 数据解码 —> 显示在lcd上 —> 关闭设备。

#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 <errno.h>
#include <pthread.h>
#include <linux/fb.h>
#include <stdbool.h>unsigned int *lcdptr = NULL;  //lcd映射内存首地址
int lcd_width;  //lcd屏幕宽
int lcd_height;   //lcd屏幕高int camera_width = 640;  //camere屏幕宽
int camera_height = 480;   //camera屏幕高void lcd_show_rgb(unsigned char *rgbdata,int width,int height);   //在lcd上显示
int YUV2RGB(void* pYUV, void* pRGB, int width, int height, bool alphaYUV, bool alphaRGB);  //YUYV格式转换为RGB格式int main(void)
{int lcdfd = open("/dev/fb0", O_RDWR); //打开 LCD屏幕if(lcdfd < 0){perror("/dev/fb0");exit(-1);}//获取屏幕宽高struct fb_var_screeninfo lcdvar;ioctl(lcdfd, FBIOGET_VSCREENINFO , &lcdvar);lcd_width = lcdvar.xres;  lcd_height = lcdvar.yres; //4 = 透明度 + RGBlcdptr = (unsigned int *)mmap( NULL, lcd_width*lcd_height*4 ,    	 	//映射fb内存空间长度 	 PROT_READ | PROT_WRITE,	//可读可写MAP_SHARED,     		//进程间共享机制lcdfd, 				//lcd的文件描述符0);//1.打开摄像头设备int fd = open("/dev/video1",O_RDWR);  //video0 或 video1if(fd < 0){perror("打开设备失败");return -1;}//2.获取摄像头支持的格式 ioctl(文件描述符,命令,与命令对应的结构体)//查询并显示所有支持的格式:VIDIOC_ENUM_FMT ,获取对应结构体/*struct v4l2_fmtdesc{u32 index; // 要查询的格式序号,应用程序设置enum v4l2_buf_type type; // 帧类型,应用程序设置u32 flags; // 是否为压缩格式u8 description[32]; // 格式名称u32 pixelformat; // 格式u32 reserved[4]; // 保留};*///3.配置摄像头采集格式//查看或设置当前格式: VIDIOC_G_FMT, VIDIOC_S_FMT/* struct v4l2_format{enum v4l2_buf_type type; // 帧类型,应用程序设置union fmt{struct v4l2_pix_format pix; // 视频设备使用struct v4l2_window win;struct v4l2_vbi_format vbi;struct v4l2_sliced_vbi_format sliced;u8 raw_data[200];}; }; struct v4l2_pix_format{u32 width; // 帧宽,单位像素u32 height; // 帧高,单位像素u32 pixelformat; // 帧格式enum v4l2_field field;u32 bytesperline;u32 sizeimage;enum v4l2_colorspace colorspace;u32 priv;};*/struct v4l2_format vfmt; vfmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; //设置类型摄像头采集vfmt.fmt.pix.width = camera_width;  //设置宽vfmt.fmt.pix.height = camera_height;   //设置高vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;  //根据摄像头设置格式int ret;ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);if(ret < 0){perror("设置格式失败");}	printf("Current data format information:\n\twidth:%d\n\theight:%d\n",vfmt.fmt.pix.width,vfmt.fmt.pix.height);//4.申请内核空间//应用程序和设备有三种交换数据的方法,直接 read/write、内存映射(memory mapping) 和用户指针。//向设备申请缓冲区 VIDIOC_REQBUFS/*相关函数:int ioctl(int fd, int request, struct v4l2_requestbuffers *argp);相关结构体:struct v4l2_requestbuffers{u32 count; // 缓冲区内缓冲帧的数目enum v4l2_buf_type type; // 缓冲帧数据格式enum v4l2_memory memory; // 区别是内存映射还是用户指针方式u32 reserved[2];};enum v4l2_memory{V4L2_MEMORY_MMAP, V4L2_MEMORY_USERPTR};//count,type,memory 都要应用程序设置*/struct v4l2_requestbuffers reqbuffer; reqbuffer.count = 4;   //申请4个缓冲区  reqbuffer.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 缓冲帧数据格式reqbuffer.memory=V4L2_MEMORY_MMAP;   //内存映射ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);if(ret < 0){perror("申请队列空间失败");}//5.把内核的缓冲区队列映射到用户空间//获取缓冲帧的地址,长度:VIDIOC_QUERYBUF//相关函数:int ioctl(int fd, int request, struct v4l2_buffer *argp);/*相关结构体:struct v4l2_buffer{u32 index; //buffer 序号enum v4l2_buf_type type; //buffer 类型u32 byteused; //buffer 中已使用的字节数u32 flags; // 区分是MMAP 还是USERPTRenum v4l2_field field;struct timeval timestamp; // 获取第一个字节时的系统时间struct v4l2_timecode timecode;u32 sequence; // 队列中的序号enum v4l2_memory memory; //IO 方式,被应用程序设置union m{u32 offset; // 缓冲帧地址,只对MMAP 有效unsigned long userptr;};	u32 length; // 缓冲帧长度u32 input;u32 reserved;};*/unsigned char *mptr[4];  //保护映射后用户空间的首地址unsigned int size[4];struct v4l2_buffer mapbuffer;for (unsigned int n_buffers = 0; n_buffers < reqbuffer.count; n_buffers++)  //count=4个缓冲区{memset(&mapbuffer,0,sizeof(mapbuffer));   //清空mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;    //设置类型摄像头采集mapbuffer.memory = V4L2_MEMORY_MMAP;    //内存映射  IO 方式,被应用程序设置mapbuffer.index = n_buffers;     //buffer 序号// 查询序号为n_buffers 的缓冲区,得到其起始物理地址和大小ret = ioctl (fd, VIDIOC_QUERYBUF, &mapbuffer);    //从内核空间中查询一个空间做映射if(ret < 0){perror("查询内核空间队列失败");}mptr[n_buffers] = (unsigned char *)mmap( NULL, mapbuffer.length ,    //被映射内存块的长度 PROT_READ | PROT_WRITE,	//可读可写MAP_SHARED,     		//进程间共享机制fd, 				//摄像头的文件描述符mapbuffer.m.offset);  // 缓冲帧地址,只对MMAP 有效size[n_buffers]	= mapbuffer.length;  //保存长度,释放用//通知使用完毕--‘放回去’//VIDIOC_QBUF// 把帧放入队列//VIDIOC_DQBUF// 从队列中取出帧ret = ioctl (fd, VIDIOC_QBUF, &mapbuffer);if(ret < 0){perror("帧放入队列失败");}}//6.开始采集//冲区处理好之后,就可以开始获取数据了//启动 或 停止数据流 VIDIOC_STREAMON, VIDIOC_STREAMOFFint type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl (fd, VIDIOC_STREAMON, &type); if(ret < 0){perror("开启失败");}//定义一个空间存储解码后的RGB数据unsigned char rgbdata[camera_width*camera_height*3];while(1){//7.采集数据//从队列中提取一帧数据struct v4l2_buffer readbuffer;readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl (fd, VIDIOC_DQBUF, &readbuffer); // 从缓冲区取出一个缓冲帧if(ret < 0){perror("提取数据失败");}/*  //把一帧数据写入文件FILE *file = fopen("my.yuyv","w+");//mptr[readbuffer.index]fwrite(mptr[readbuffer.index],readbuffer.length,file);fclose(file); */YUV2RGB(mptr[readbuffer.index], rgbdata,camera_width,camera_height,0,0);  //把YUYV数据解码为RGB数据lcd_show_rgb(rgbdata,camera_width,camera_height);  //在开发板lcd上显示//通知内核已经使用完毕ret = ioctl (fd,VIDIOC_QBUF,&readbuffer);if(ret < 0){perror("放回队列失败");}}//8.停止采集ret = ioctl (fd,VIDIOC_STREAMOFF,&type);//9.释放映射for(int i=0; i<4; i++)munmap(mptr[i], size[i]);// 断开映射//10.关闭设备close(fd);return 0;	
}//将解码后的摄像头数据显示在开发板屏幕上
void lcd_show_rgb(unsigned char *rgbdata,int width,int height) //width、height为摄像头图片的宽高
{unsigned int *ptr = lcdptr;//以字节对齐的方式,将RGB颜色数据转换成ARGB的LCD数据unsigned int lcd_buf[width*height];//对齐像素for(int i=0;i<width*height;i++){lcd_buf[i] = rgbdata[3*i+0] | rgbdata[3*i+1]<<8 | rgbdata[3*i+2]<<16 | 0x00<<24; //蓝色				//绿色			//红色              //透明度}	//数据不能超过lcd屏幕尺寸以及摄像头图片尺寸for(int x=0; x<lcd_width && x<width ;x++){for(int y=0; y<lcd_height && y<height ; y++){		// 获取lcd屏幕中的偏移量int lcd_offset = x +  lcd_width*y ; // 获取camera图片中的偏移量int camera_offset = x + width*(height - y -1);*(ptr+lcd_offset) = lcd_buf[camera_offset];}}return ;
}// YUV2RGB 
// pYUV         point to the YUV data 
// pRGB         point to the RGB data 
// width        width of the picture 
// height       height of the picture 
// alphaYUV     is there an alpha channel in YUV 
// alphaRGB     is there an alpha channel in RGB 
//YUYV格式转换为RGB格式
int YUV2RGB(void* pYUV, void* pRGB, int width, int height, bool alphaYUV, bool alphaRGB)
{ if (NULL == pYUV) { return -1; } unsigned char* pYUVData = (unsigned char *)pYUV; unsigned char* pRGBData = (unsigned char *)pRGB; /*if (NULL == pRGBData) { if (alphaRGB) { pRGBData = new unsigned char[width*height*4];} else pRGBData = new unsigned char[width*height*3]; }*/int Y1, U1, V1, Y2, alpha1, alpha2, R1, G1, B1, R2, G2, B2; int C1, D1, E1, C2; if (alphaRGB) { if (alphaYUV) { for (int i=0; i<height; ++i) { for (int j=0; j<width/2; ++j) { Y1 = *(pYUVData+i*width*3+j*6);    //i*width*3 = i*(width/2)*6 U1 = *(pYUVData+i*width*3+j*6+1); Y2 = *(pYUVData+i*width*3+j*6+2); V1 = *(pYUVData+i*width*3+j*6+3); alpha1 = *(pYUVData+i*width*3+j*6+4); alpha2 = *(pYUVData+i*width*3+j*6+5); C1 = Y1-16; C2 = Y2-16; D1 = U1-128; E1 = V1-128; R1 = ((298*C1 + 409*E1 + 128)>>8>255 ? 255 : (298*C1 + 409*E1 + 128)>>8); G1 = ((298*C1 - 100*D1 - 208*E1 + 128)>>8>255 ? 255 : (298*C1 - 100*D1 - 208*E1 + 128)>>8);   B1 = ((298*C1+516*D1 +128)>>8>255 ? 255 : (298*C1+516*D1 +128)>>8);   R2 = ((298*C2 + 409*E1 + 128)>>8>255 ? 255 : (298*C2 + 409*E1 + 128)>>8); G2 = ((298*C2 - 100*D1 - 208*E1 + 128)>>8>255 ? 255 : (298*C2 - 100*D1 - 208*E1 + 128)>>8); B2 = ((298*C2 + 516*D1 +128)>>8>255 ? 255 : (298*C2 + 516*D1 +128)>>8);   *(pRGBData+(height-i-1)*width*4+j*8+2) = R1<0 ? 0 : R1; *(pRGBData+(height-i-1)*width*4+j*8+1) = G1<0 ? 0 : G1; *(pRGBData+(height-i-1)*width*4+j*8) = B1<0 ? 0 : B1; *(pRGBData+(height-i-1)*width*4+j*8+3) = alpha1;     *(pRGBData+(height-i-1)*width*4+j*8+6) = R2<0 ? 0 : R2; *(pRGBData+(height-i-1)*width*4+j*8+5) = G2<0 ? 0 : G2; *(pRGBData+(height-i-1)*width*4+j*8+4) = B2<0 ? 0 : B2; *(pRGBData+(height-i-1)*width*4+j*8+7) = alpha2;     } }    } else { int alpha = 255; for (int i=0; i<height; ++i) { for (int j=0; j<width/2; ++j) { Y1 = *(pYUVData+i*width*2+j*4); U1 = *(pYUVData+i*width*2+j*4+1); Y2 = *(pYUVData+i*width*2+j*4+2); V1 = *(pYUVData+i*width*2+j*4+3); C1 = Y1-16; C2 = Y2-16; D1 = U1-128; E1 = V1-128; R1 = ((298*C1 + 409*E1 + 128)>>8>255 ? 255 : (298*C1 + 409*E1 + 128)>>8); G1 = ((298*C1 - 100*D1 - 208*E1 + 128)>>8>255 ? 255 : (298*C1 - 100*D1 - 208*E1 + 128)>>8);   B1 = ((298*C1+516*D1 +128)>>8>255 ? 255 : (298*C1+516*D1 +128)>>8);   R2 = ((298*C2 + 409*E1 + 128)>>8>255 ? 255 : (298*C2 + 409*E1 + 128)>>8); G2 = ((298*C2 - 100*D1 - 208*E1 + 128)>>8>255 ? 255 : (298*C2 - 100*D1 - 208*E1 + 128)>>8); B2 = ((298*C2 + 516*D1 +128)>>8>255 ? 255 : (298*C2 + 516*D1 +128)>>8);   *(pRGBData+(height-i-1)*width*4+j*8+2) = R1<0 ? 0 : R1; *(pRGBData+(height-i-1)*width*4+j*8+1) = G1<0 ? 0 : G1; *(pRGBData+(height-i-1)*width*4+j*8) = B1<0 ? 0 : B1; *(pRGBData+(height-i-1)*width*4+j*8+3) = alpha;  *(pRGBData+(height-i-1)*width*4+j*8+6) = R2<0 ? 0 : R2; *(pRGBData+(height-i-1)*width*4+j*8+5) = G2<0 ? 0 : G2; *(pRGBData+(height-i-1)*width*4+j*8+4) = B2<0 ? 0 : B2; *(pRGBData+(height-i-1)*width*4+j*8+7) = alpha;  } }    } } else { if (alphaYUV) { for (int i=0; i<height; ++i) { for (int j=0; j<width/2; ++j) { Y1 = *(pYUVData+i*width*3+j*4); U1 = *(pYUVData+i*width*3+j*4+1); Y2 = *(pYUVData+i*width*3+j*4+2); V1 = *(pYUVData+i*width*3+j*4+3); C1 = Y1-16; C2 = Y2-16; D1 = U1-128; E1 = V1-128; R1 = ((298*C1 + 409*E1 + 128)>>8>255 ? 255 : (298*C1 + 409*E1 + 128)>>8); G1 = ((298*C1 - 100*D1 - 208*E1 + 128)>>8>255 ? 255 : (298*C1 - 100*D1 - 208*E1 + 128)>>8);   B1 = ((298*C1+516*D1 +128)>>8>255 ? 255 : (298*C1+516*D1 +128)>>8);   R2 = ((298*C2 + 409*E1 + 128)>>8>255 ? 255 : (298*C2 + 409*E1 + 128)>>8); G2 = ((298*C2 - 100*D1 - 208*E1 + 128)>>8>255 ? 255 : (298*C2 - 100*D1 - 208*E1 + 128)>>8); B2 = ((298*C2 + 516*D1 +128)>>8>255 ? 255 : (298*C2 + 516*D1 +128)>>8);   *(pRGBData+(height-i-1)*width*3+j*6+2) = R1<0 ? 0 : R1; *(pRGBData+(height-i-1)*width*3+j*6+1) = G1<0 ? 0 : G1; *(pRGBData+(height-i-1)*width*3+j*6) = B1<0 ? 0 : B1; *(pRGBData+(height-i-1)*width*3+j*6+5) = R2<0 ? 0 : R2; *(pRGBData+(height-i-1)*width*3+j*6+4) = G2<0 ? 0 : G2; *(pRGBData+(height-i-1)*width*3+j*6+3) = B2<0 ? 0 : B2; } } } else { for (int i=0; i<height; ++i) { for (int j=0; j<width/2; ++j) { Y1 = *(pYUVData+i*width*2+j*4); U1 = *(pYUVData+i*width*2+j*4+1); Y2 = *(pYUVData+i*width*2+j*4+2); V1 = *(pYUVData+i*width*2+j*4+3); C1 = Y1-16; C2 = Y2-16; D1 = U1-128; E1 = V1-128; R1 = ((298*C1 + 409*E1 + 128)>>8>255 ? 255 : (298*C1 + 409*E1 + 128)>>8); G1 = ((298*C1 - 100*D1 - 208*E1 + 128)>>8>255 ? 255 : (298*C1 - 100*D1 - 208*E1 + 128)>>8);   B1 = ((298*C1+516*D1 +128)>>8>255 ? 255 : (298*C1+516*D1 +128)>>8);   R2 = ((298*C2 + 409*E1 + 128)>>8>255 ? 255 : (298*C2 + 409*E1 + 128)>>8); G2 = ((298*C2 - 100*D1 - 208*E1 + 128)>>8>255 ? 255 : (298*C2 - 100*D1 - 208*E1 + 128)>>8); B2 = ((298*C2 + 516*D1 +128)>>8>255 ? 255 : (298*C2 + 516*D1 +128)>>8);   *(pRGBData+(height-i-1)*width*3+j*6+2) = R1<0 ? 0 : R1; *(pRGBData+(height-i-1)*width*3+j*6+1) = G1<0 ? 0 : G1; *(pRGBData+(height-i-1)*width*3+j*6) = B1<0 ? 0 : B1; *(pRGBData+(height-i-1)*width*3+j*6+5) = R2<0 ? 0 : R2; *(pRGBData+(height-i-1)*width*3+j*6+4) = G2<0 ? 0 : G2; *(pRGBData+(height-i-1)*width*3+j*6+3) = B2<0 ? 0 : B2; } }    } } return 0; 
} 

以上是我对linux开发板V4L2调用摄像头代码的一些整理,希望能对大家提供一些帮助!


http://www.ppmy.cn/news/490060.html

相关文章

全志V3s学习记录(13)OV2640的使用

文章目录 硬件分析一、修改设备树二、增加Linux驱动配置三、构建Buildroot文件系统使用I2c工具调试意外收获RAW看图软件 7yuv 测试 参考&#xff1a;https://blog.51cto.com/u_15294654/3111978 参考&#xff1a;https://blog.csdn.net/xiangkezhi167810/article/details/11225…

SpringBoot整合模板引擎Thymeleaf(4)

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 概述 在之前的教程中&#xff0c;我们介绍了Thymeleaf的基础知识。在此&#xff0c;以案例形式详细介绍Thymeleaf的基本使用。 项目结构 要点概述&#xff1a; 1、在st…

V4L2不支持一个数据输入端接多个输入线路

内核版本&#xff1a;4.4.162 &vi0 { status "okay"; port { #address-cells <1>; #size-cells <0>; vi_0_0: endpoint0 { reg <0>; remote-endpoint <&…

全志V3S 驱动OV2640 OV7725把图像显示到ST7789V LCD屏上

全志V3S 驱动OV2640 OV7725把图像显示到LCD屏上 文章目录 全志V3S 驱动OV2640 OV7725把图像显示到LCD屏上一、设备树添加摄像头驱动二、摄像头编译到内核三、编译重启查看启动log是否正常1.OV7725启动2.OV2640启动 四、C实现摄像头捕获图像显示到fb五、OV2640注意 一、设备树添…

Linux环境下使用V4L2+opencv以MJPEG格式读取USB摄像头并实时显示

转眼间&#xff0c;V4L2已经搞了很长时间&#xff0c;从最开始的一窍不通&#xff0c;到后来的渐渐熟悉&#xff0c;从最开始照猫画虎的使用YUYV格式之间转换&#xff0c;到后来使用MJPEG格式读取&#xff0c;中间颇有周折。趁任务完成间隙&#xff0c;来简单总结下V4L2的使用。…

【正点原子Linux连载】第二十章 V4L2摄像头应用编程-摘自【正点原子】I.MX6U嵌入式Linux C应用编程指南V1.1

1&#xff09;实验平台&#xff1a;正点原子阿尔法Linux开发板 2&#xff09;平台购买地址&#xff1a;https://item.taobao.com/item.htm?id603672744434 2&#xff09;全套实验源码手册视频下载地址&#xff1a;http://www.openedv.com/thread-300792-1-1.html 3&#xff09…

Linux的V4L2架构分析

V4L2全名为VideoFor Linux 2&#xff0c;它是针对Linux系统的视频设备处理架构。视频设备主要包括输入设备&#xff08;摄像头&#xff09;及输出设备&#xff08;显示设备&#xff09;。 一、Linux的V4L2架构介绍 V4L2的初衷是想为linux系统建立统一的视频类设备处理模型&…

Android APP CC2640R2F OTA 流程

Android APP CC2640R2F OTA 流程 1.镜像&#xff08;bin文件&#xff09;验证 a. ffc1写入&#xff08;10:00&#xff09;打开通知 ffc2写入&#xff08;10:00&#xff09;打开通知 ffc5写入&#xff08;10:00&#xff09;打开通知 发送CMD 01&#xff08;1byte&#xff…