摄像头像素格式:yuv
分辨率:640*480(可以调节)
所用技术关键:交叉编译、v4l2,yuv转RGB,RGB压缩为jpeg格式
common.h
#ifndef COMMON_H
#define COMMON_H
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <linux/fb.h>
#include <linux/videodev2.h> // for video
#include <linux/input.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <setjmp.h>
#include <semaphore.h>
#include "jpeglib.h"
extern sem_t s;
extern int R[256][256];
extern int G[256][256][256];
extern int B[256][256];
//函数声明
int Open();
void getCamInfo(int camfd);
void getCamFmt(int camfd);
void setCamFmt(int camfd);
void getCamCap(int camfd);
int reqCamBuf(int camfd);
void display(unsigned char *yuv);
unsigned char *yuv2rgb(unsigned char *yuv);
void rgb2jpg(unsigned char *rgbdata);
#endif // COMMON_H
main.c
#include <iostream>
#include "common.h"
using namespace std;
int R[256][256];
int G[256][256][256];
int B[256][256];
int i = 0;
int nbuf;
//无名信号量,用来协调视频监控线程和捉拍线程
sem_t s;
// 将RGB的所有可能的取值,都提前算出来,因为每次都要算的需要花费非常多的时间,会变得非常卡
void *convert(void *arg)
{
pthread_detach(pthread_self());
for(int i=0; i<256; i++)
{
for(int j=0; j<256; j++)
{
R[i][j] = i + 1.042*(j-128);
R[i][j] = R[i][j]>255 ? 255 : R[i][j];
R[i][j] = R[i][j]<0 ? 0 : R[i][j];
B[i][j] = i + 1.772*(j-128);
B[i][j] = B[i][j]>255 ? 255 : B[i][j];
B[i][j] = B[i][j]<0 ? 0 : B[i][j];
for(int k=0; k<256; k++)
{
G[i][j][k] = i + 0.344*(j-128)-0.714*(k-128);
G[i][j][k] = G[i][j][k]>255 ? 255 : G[i][j][k];
G[i][j][k] = G[i][j][k]<0 ? 0 : G[i][j][k];
}
}
}
}
void *takePhoto(void *arg)
{
unsigned char **start = (unsigned char **)arg;
while(1)
{
//等待回车捉拍
getchar();
cout << "捉拍";
//进行P操作:-1
sem_wait(&s);
//将yuv转换为RGB格式
unsigned char *rgbdata = yuv2rgb(start[i%nbuf]);
cout << "======";
//将RGB编码成为jpg格式图片
rgb2jpg(rgbdata);
cout << "........";
}
}
int main()
{
//1、打开摄像头
int camfd = Open();
getCamCap(camfd);
//2、获取摄支持格式信息
getCamInfo(camfd);
//3、获取摄像头分辨率、以及像素格式
getCamFmt(camfd);
//4、设置摄像头分辨率、及像素格式
setCamFmt(camfd);
//5、申请摄像头缓存
nbuf = reqCamBuf(camfd);
//6、映射用户空间
//创建线程
pthread_t tid;
pthread_create(&tid,NULL,convert,NULL);
//管理摄像头缓存
struct v4l2_buffer buf[nbuf];
//用来存放摄像头缓存映射的空间地址
unsigned char *start[nbuf];
for(int i = 0 ;i < nbuf;i++)
{
bzero(&buf[i],sizeof(buf[i]));
//指定摄像头类型
buf[i].type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf[i].memory = V4L2_MEMORY_MMAP;
buf[i].index = i;
//获取指定缓存的具体信息(比如偏移量、尺寸)
ioctl(camfd,VIDIOC_QUERYBUF,&buf[i]);
// 将驱动中的缓存一一映射到用户空间
// NULL: 代表映射得到用户空间地址由系统自动分配
// buffer[i].length: 代表所要映射的内存的尺寸
// PROT_READ|PROT_WRITE: 代表映射后的内存可读可写
// MAP_SHARED: 其他进程也能访问这块映射内存
// cam_fd: 摄像头设备文件的描述符
// buffer[i].m.offset: 本次映射的缓存在内核中的总缓存的偏移量
//映射用户空间
start[i] =(unsigned char*) mmap(NULL,buf[i].length,PROT_READ | PROT_WRITE,
MAP_SHARED,camfd,buf[i].m.offset);
//将指定的缓存入队
ioctl(camfd,VIDIOC_QBUF,&buf[i]);
}
//启动摄像头,开始捕获数据
enum v4l2_buf_type vtype = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(camfd,VIDIOC_STREAMON,&vtype);
// 为取出图像数据所需要的参数准备好结构体,并清零
bzero(buf,sizeof(buf));
for(int i = 0 ;i<nbuf;i++)
{
buf[i].type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf[i].memory = V4L2_MEMORY_MMAP;
}
//信号量初始化
sem_init(&s,0/*线程间信号量*/,0/*初始值*/);
//创建线程进行捉拍
pthread_create(&tid,NULL,takePhoto,(void*)start);
while(1)
{
//将数据从缓存中取出
if( -1 == ioctl(camfd,VIDIOC_DQBUF,&buf[i%nbuf]))
{
perror("从缓存获取数据失败");
exit(0);
}
//printf("获得一帧图片 大小:%d",buf[i%nbuf].length);
//将一帧图片显示、并使用信号量控制
sem_post(&s);
display(start[i%nbuf]);
// 如果在当前瞬间发生抓拍,那么此处会卡住,直到抓拍完毕
sem_wait(&s);
//重新入队
ioctl(camfd,VIDIOC_QBUF,&buf[i%nbuf]);
i++;
}
return 0;
}
caminfo.cpp
#include "common.h"
//打开摄像头
v4l2_format format;
int Open()
{
int fd = open("/dev/video7",O_RDWR);
if(-1 == fd)
{
perror("打开摄像头失败\n");
exit(0);
}
return fd;
}
//获取摄像头支持格式
void getCamInfo(int camfd)
{
char formats[5][16];
//定义结构提
v4l2_fmtdesc fmtdesc;
//指定设备类型 视频输入设备
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtdesc.index = 0;
//将摄像头支持的格式打印出来
printf("像素格式\n");
while( ioctl(camfd,VIDIOC_ENUM_FMT,&fmtdesc) == 0)
{
sprintf(formats[fmtdesc.index]+0,"%c",(fmtdesc.pixelformat >> 8*0) & 0xFF);
sprintf(formats[fmtdesc.index]+1,"%c",(fmtdesc.pixelformat >> 8*1) & 0xFF);
sprintf(formats[fmtdesc.index]+2,"%c",(fmtdesc.pixelformat >> 8*2) & 0xFF);
sprintf(formats[fmtdesc.index]+3,"%c",(fmtdesc.pixelformat >> 8*3) & 0xFF);
printf("%s\n",fmtdesc.description);
printf("详细描述:%s\n",formats[fmtdesc.index]);
fmtdesc.index++;
}
}
//获取摄像头的分辨率,及像素格式
void getCamFmt(int camfd)
{
bzero(&format,sizeof(format));
//指定设备类型
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(-1 == ioctl(camfd,VIDIOC_G_FMT,&format))
{
perror("获取摄像头像素格式失败\n");
exit(0);
}
printf("分辨率:%d*%d\n",format.fmt.pix.width,format.fmt.pix.height);
switch(format.fmt.pix.pixelformat)
{
case V4L2_PIX_FMT_MJPEG:
printf("V4L2_PIX_FMT_MJPEG\n");
break;
case V4L2_PIX_FMT_JPEG:
printf("V4L2_PIX_FMT_JPEG\n");
break;
case V4L2_PIX_FMT_MPEG:
printf("V4L2_PIX_FMT_MPEG\n");
break;
case V4L2_PIX_FMT_MPEG1:
printf("V4L2_PIX_FMT_MPEG1\n");
break;
case V4L2_PIX_FMT_MPEG2:
printf("V4L2_PIX_FMT_MPEG2\n");
break;
case V4L2_PIX_FMT_MPEG4:
printf("V4L2_PIX_FMT_MPEG4\n");
break;
case V4L2_PIX_FMT_H264:
printf("V4L2_PIX_FMT_H264\n");
break;
case V4L2_PIX_FMT_XVID:
printf("V4L2_PIX_FMT_XVID\n");
break;
case V4L2_PIX_FMT_RGB24:
printf("V4L2_PIX_FMT_RGB24\n");
break;
case V4L2_PIX_FMT_BGR24:
printf("V4L2_PIX_FMT_BGR24\n");
break;
case V4L2_PIX_FMT_YUYV:
printf("V4L2_PIX_FMT_YUYV\n");
break;
case V4L2_PIX_FMT_YYUV:
printf("V4L2_PIX_FMT_YYUV\n");
break;
case V4L2_PIX_FMT_YVYU:
printf("V4L2_PIX_FMT_YVYU\n");
break;
case V4L2_PIX_FMT_YUV444:
printf("V4L2_PIX_FMT_YUV444\n");
break;
case V4L2_PIX_FMT_YUV410:
printf("V4L2_PIX_FMT_YUV410\n");
break;
case V4L2_PIX_FMT_YUV420:
printf("V4L2_PIX_FMT_YUV420\n");
break;
case V4L2_PIX_FMT_YVU420:
printf("V4L2_PIX_FMT_YVU420\n");
break;
case V4L2_PIX_FMT_YUV422P:
printf("V4L2_PIX_FMT_YUV422P\n");
break;
default:
printf("未知\n");
}
}
//获取摄像头的基本属性
void getCamCap(int camfd)
{
v4l2_capability cap;
if(-1 == ioctl(camfd,VIDIOC_QUERYCAP,&cap))
{
printf("获取摄像头信息失败%s\n",strerror(errno));
exit(0);
}
printf("驱动:%s\n",cap.driver);
printf("显卡:%s",cap.card);
if(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
printf("视频采集设备\n");
if(cap.capabilities & V4L2_CAP_RDS_OUTPUT)
printf("视频输出设备\n");
}
//设置摄像头像素格式
void setCamFmt(int camfd)
{
v4l2_format format;
bzero(&format,sizeof(format));
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
format.fmt.pix.height = 480;
format.fmt.pix.width = 640;
// 设置摄像头的图像生成方式: 比如从上到下扫描、从下到上扫描、混合扫描方式
// a. 选择什么方式,是摄像头内部的工作细节,跟最终生成的画面无关
// b. 只要确保生成图像时所采用的方式跟播放时所采用的方式一致即可
format.fmt.pix.field = V4L2_FIELD_INTERLACED; // 混合扫描方式
if(-1 == ioctl(camfd,VIDIOC_S_FMT,&format))
{
printf("设置像素格式失败 %s\n",strerror(errno));
}
}
//申请摄像头缓存
int reqCamBuf(int camfd)
{
int nbuf = 3;
v4l2_requestbuffers rebuf;
bzero(&rebuf,sizeof(rebuf));
//指定设备类型
rebuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
//申请缓存个数
rebuf.count = nbuf;
//分配方式,系统自动分配
rebuf.memory = V4L2_MEMORY_MMAP;
//申请缓存
if(-1 == ioctl(camfd,VIDIOC_REQBUFS,&rebuf))
{
printf("申请摄像头缓存失败%s\n",strerror(errno));
exit(0);
}
return rebuf.count;
}
unsigned char *yuv2rgb(unsigned char *yuv)
{
//摄像头的分辨率
int camW = format.fmt.pix.width;
int camH = format.fmt.pix.height;
int yuv_offset = 0;
int rgb_offset = 0;
u_int8_t Y0,U;
u_int8_t Y1,V;
//rgb每个像素点有三个字节
unsigned char *rgbdata =(unsigned char*)calloc(1,camW*camH*3);
//套用yuv转rgb的公式的得带RGB数据
for(int i = 0;i < camH ;i++)
{
for(int j = 0;j < camW;j += 2)
{
yuv_offset = ( camW*i + j ) * 2;
rgb_offset = ( camW*i + j ) * 3;
Y0 = *(yuv + yuv_offset + 0);
U = *(yuv + yuv_offset + 1);
Y1 = *(yuv + yuv_offset + 2);
V = *(yuv + yuv_offset + 3);
*(rgbdata + rgb_offset + 0) = R[Y0][V];
*(rgbdata + rgb_offset + 1) = G[Y0][U][V];
*(rgbdata + rgb_offset + 2) = B[Y0][U];
*(rgbdata + rgb_offset + 3) = R[Y1][V];
*(rgbdata + rgb_offset + 4) = G[Y1][U][V];
*(rgbdata + rgb_offset + 5) = B[Y1][U];
}
}
return rgbdata;
}
void rgb2jpg(unsigned char *rgbdata)
{
// 2,利用libjpeg.so将 RGB 编码成 jpg 格式的图片(纯粹的固定套路代码)
// 以下代码你需要关注的地方是:
// a. 图片的名称
// b. 图片的尺寸
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
// 准备好保存jpg照片的文件
FILE *fp = fopen("1.jpg", "w");
// 指定编码之后数据最终存储的文件
jpeg_stdio_dest(&cinfo, fp);
cinfo.image_width = format.fmt.pix.width; // 图片的宽(即RGB数据的宽)
cinfo.image_height = format.fmt.pix.height; // 图片的高(即RGB数据的高)
cinfo.input_components = 3; // 每个像素所包含的字节数
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, 80, TRUE); // 调节图像质量: 0-100
jpeg_start_compress(&cinfo, TRUE);
// 定义行指针数组
// [1]代表每次编码1行
JSAMPROW row_pointer[1];
// 开始编码,并将jpg数据存入指定的图片文件中
// 编码按行进行,每次编码n行,此处n=1
while(cinfo.next_scanline < cinfo.image_height)
{
row_pointer[0] = rgbdata + cinfo.next_scanline * format.fmt.pix.width * 3;
jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
//释放资源
free(rgbdata);
fclose(fp);
// V操作: +1
sem_post(&s);
// 将文件发回电脑端
system("tftp 192.168.0.102 -pr 1.jpg");
}
//显示图片
void display(unsigned char *yuv)
{
//static使得变量在函数体内调用值不变
static int lcdfd;
static bool firstime = true;
//显存指针
static char *fbmem;
if(firstime)
{
//打开OLCD
lcdfd = open("/dev/fb0",O_RDWR);
if(-1 == lcdfd)
{
perror("打开摄像头失败\n");
exit(0);
}
//映射OLCD内存、开发板olcd的每个像素点有4个字节ARGB
fbmem = (char *)mmap(NULL,800*480*4,PROT_READ | PROT_WRITE,
MAP_SHARED,lcdfd,0);
firstime = false;
}
//将yuv转换位RGB
int R0,G0,B0;
int R1,G1,B1;
int yuv_offset = 0;
int lcd_offset = 0;
u_int8_t Y0,U;
u_int8_t Y1,V;
for(unsigned int y = 0;y<format.fmt.pix.height;y++)
{
for(unsigned int x = 0 ;x<format.fmt.pix.width;x+=2)
{
yuv_offset = (format.fmt.pix.width*y+x)*2;
// 取四个字节
Y0 = *(yuv + yuv_offset + 0);
U = *(yuv + yuv_offset + 1);
Y1 = *(yuv + yuv_offset + 2);
V = *(yuv + yuv_offset + 3);
//printf("%d,%d,%d,%d\n",Y0,U,Y1,V);
// 作弊得到六个字节
R0 = R[Y0][V];
G0 = G[Y0][U][V];
B0 = B[Y0][U];
R1 = R[Y1][V];
G1 = G[Y1][U][V];
B1 = B[Y1][U];
lcd_offset = ( 800*y + x ) * 4;
memcpy(fbmem+lcd_offset+0, &B0, 1);
memcpy(fbmem+lcd_offset+1, &G0, 1);
memcpy(fbmem+lcd_offset+2, &R0, 1);
memcpy(fbmem+lcd_offset+4, &B1, 1);
memcpy(fbmem+lcd_offset+5, &G1, 1);
memcpy(fbmem+lcd_offset+6, &R1, 1);
}
}
代码之中有很多不足的地方,欢迎各位指正!大家共同进步相互学习。