Linux下视频开发之实时监控、捉拍

摄像头像素格式: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);
         }
     }
代码之中有很多不足的地方,欢迎各位指正!大家共同进步相互学习。
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值