歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

OK210-uvc攝像頭采集並顯示在屏幕上(V4L2編程)

手頭有一個UVC(usb video class)攝像頭(也稱為免驅攝像頭),就順便學習了一下V4L2編程 ,寫代碼的過程中參考了前輩的博客,覺得寫的非常的好,特將鏈接貼在這裡

http://www.linuxidc.com/Linux/2016-11/137067.htm


關於V4L2講解的可以學習前輩的博客,這裡只是寫了一個實例代碼供看了知識點還無從下手寫代碼的新手作為參考。

平台描述:
    OK210開發板。
    屏幕是開發板自帶的800*480的RGB32格式屏幕。
    攝像頭輸出格式為 640*480 的 YUYV422格式

關於YUYV格式請看這篇博客
http://www.linuxidc.com/Linux/2016-11/137068.htm

關於RGB32的
rgb32 : low memory address —-> high memory address
| pixel | pixel | pixel | pixel | pixel | pixel |…
|——-|——-|——-|——-|——-|——-|…
|B|G|R|A|B|G|R|A|B|G|R|A|B|G|R|A|B|G|R|A|B|G|R|A|..
A表示透明度,0表示不透明,255表示透明度最高

全部代碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/fb.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>


int status = 1;   //停止標志位,1開始,0停止

/* 監聽線程 當啟動攝像頭後從鍵盤輸入q,結束程序 */
void * listen(void * arg) 
{
    while(1) {
        char buf[20];
        scanf("%s", buf);
        if(strcmp("q", buf) == 0) {
            status = 0;
            break;
        }
        usleep(10);
    }
}

/* 屏幕初始化和銷毀函數 */
char * fb_init(char * devname, int * fd, int * len);
void fb_destory(int fd, char * screen_bbf, int screenlen); 

/* 
    函數功能:進行圖像轉換,將uvc輸出的yuyv格式圖像轉換成RGB32格式的圖像
    返回值:無
    參數: 
        yuv yuyv格式圖像存儲地址
        buf RGB32格式圖像存儲地址
        length 圖像的大小(單位:字節)
*/
void process_image(unsigned char * yuv, unsigned char *buf, int length);
/*
    函數功能:向屏幕輸出圖像
    返回值:無
    參數 : screen_bbf  內存映射後屏幕在程序中的地址
                    buf                 RGB32格式的數據地址
                    width               圖像的寬度
                    height          圖像的高度
*/
void show_image(char * screen_bbf, char *buf, int width, int height);


/* 保存攝像頭內存映射後的內存地址和數據長度 */
struct buffer {
    char * start;
    unsigned int length;
};

int width,height;

int main(int argc, char ** argv)
{
    /* 攝像頭采集的是YUYV格式的圖像, 
        屏幕顯示需要RGB32格式的圖像,
        而且屏幕大小和攝像頭的視野大小也不一樣
        這裡申請一塊內存作為緩沖區,存儲格式轉換後的數據
     */
    unsigned char * bbf = (unsigned char *)malloc(640*480*4);  
    printf(" bbf address : %p\n", bbf);
    /**************** 設置屏幕 ****************/
    int fb_fd, screenlen;
    char * screen_bbf = fb_init("/dev/fb0", &fb_fd, &screenlen);

    /*************** 開始設置攝像頭 ********************/
    /* 打開攝像頭設備 */
    int cam_fd = open("/dev/video3", O_RDWR);
    if (cam_fd == -1) {
        printf("error : %s\n", strerror(errno));
        return -1;
    }
    /* 得到描述攝像頭信息的結構體 */
    struct v4l2_capability cap;
    int rel = ioctl(cam_fd, VIDIOC_QUERYCAP, &cap);
    if ( rel == -1) {
        printf("error : %s\n", strerror(errno));
        goto ERROR;
    }
    /* 判斷改設備支不支持捕獲圖像和流輸出功能 */
    if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE) 
        printf("it's camer!\n");
    else {
        printf("it's not a camer!\n");
        goto ERROR;
    }
    if ((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING)
        printf("it's stream device!\n");
    else {
        printf("it's not a stream device!\n");
        goto ERROR;
    }

    printf("Driver Name : %s\n\
    Card Name : %s\nBus info : %s\n\
    Driver Version : %u.%u.%u\n ",\
    cap.driver, cap.card, cap.bus_info,\
     (cap.version>>16)&0xff, (cap.version>>8)&0xff, (cap.version)&0xff);

    /* 得到攝像頭采集圖像的格式信息 */
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    rel = ioctl(cam_fd, VIDIOC_G_FMT, &fmt);
    if (rel == -1) {
        printf("get fmt failed!\n");
        goto ERROR;
    }

    width = fmt.fmt.pix.width;
    height = fmt.fmt.pix.height;

    printf("width : %d  height : %d\n\
    pixelformat : %d\n\
    field : %d\n\
    bytesperline : %d\n\
    sizeimage : %d\n\
    colorspace : %d\n\
    priv : %d\n",\
    fmt.fmt.pix.width,\
     fmt.fmt.pix.height,\
    fmt.fmt.pix.pixelformat,\
     fmt.fmt.pix.field, \
     fmt.fmt.pix.bytesperline, \
     fmt.fmt.pix.sizeimage, \
     fmt.fmt.pix.colorspace, \
     fmt.fmt.pix.priv);
    /* 得到攝像頭所支持的所有格式 */
    struct v4l2_fmtdesc fmtdesc;
    fmtdesc.index = 0;
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    printf(" Support format : \n");
    while (ioctl(cam_fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1) {
        printf("\t%d.%s\n", fmtdesc.index+1, fmtdesc.description);
        fmtdesc.index++;
    }


    /* 向攝像頭申請一個數據幀隊列 */
    struct v4l2_requestbuffers req;
    req.count = 4;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    rel = ioctl(cam_fd, VIDIOC_REQBUFS, &req);
    if(rel < 0) {
        printf("request buffers error : %s\n", strerror(errno));
        goto ERROR;
    } else 
        printf("request buffers successed!\n");

    /* 申請存儲圖像緩沖區地址和長度的數組內存 */
    struct buffer * buffers = (struct buffer *)malloc(4*sizeof(struct buffer *));
    if (buffers == NULL ) {
        printf("malloc buffers err : %s\n", strerror(errno));
        goto ERROR;
    }
    /* 將緩沖區的內存映射到用戶空間 */
    int n_buffers = 0;
    for (; n_buffers < req.count; n_buffers++) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = n_buffers;
        if( -1 == ioctl(cam_fd, VIDIOC_QUERYBUF, &buf)) {  //獲取緩沖幀的地址
            printf("set buf error : %s\n", strerror(errno));
            goto ERROR;
        } else {
            printf("set buf success!\n");
        }
        buffers[n_buffers].length = buf.length;
        /* 映射內存空間 */
        buffers[n_buffers].start = mmap(NULL, buf.length, PROT_READ|PROT_WRITE, \
        MAP_SHARED, cam_fd, buf.m.offset);
        if (NULL == buffers[n_buffers].start) {
            printf("mmap error : %s\n", strerror(errno));
            goto MAP_ERROR;
        } else 
            printf("mmap success! address = %p\n",buffers[n_buffers].start);
            ioctl(cam_fd, VIDIOC_QBUF, &buf);
    }

    /* 開啟視頻流 */
        enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (-1 == ioctl(cam_fd, VIDIOC_STREAMON, &type))
            goto MAP_ERROR;
        else 
            printf("start stream!\n");

    /* 創建一個監聽線程,用於停止程序 */
    pthread_t pd;
    pthread_create(&pd, NULL, listen, NULL);

    /* 開始捕獲攝像頭數據,並顯示在屏幕上 */
    while(status) {
        /* 初始化select監聽  */
        fd_set fds;
        FD_ZERO(&fds);
        FD_SET(cam_fd, &fds);
        struct timeval tv;
        tv.tv_sec = 0;
        tv.tv_usec = 125000;
        int ret = select(cam_fd+1, &fds, NULL, NULL, &tv);
        if (ret == -1) {
            printf(" error : listen failes\n");
        } else if (ret == 0) {
            printf(" time out !\n");
        } else {
            struct v4l2_buffer buf;
            memset(&buf, 0, sizeof(buf));
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            /* 得到一幀數據 */
            if (ioctl(cam_fd, VIDIOC_DQBUF, &buf) != -1) {
                /* 進行格式轉換 */
                process_image(buffers[buf.index].start, bbf, buffers[buf.index].length);    
                /* 顯示在屏幕上 */
                show_image(screen_bbf, bbf, width, height);
                /* 將幀放回隊列 */
                if (-1 != (ioctl(cam_fd, VIDIOC_QBUF, &buf)))
                    printf(" put in success!\n");
                else 
                    printf(" put in failed!\n");
            } else 
                printf(" get frame failed!\n");
        }
    }

    /* 關閉視頻流 */
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(cam_fd, VIDIOC_STREAMOFF, &type);

    /* 解除內存映射,關閉文件描述符,程序結束 */
    int i;
    for (i=0; i<4; i++) {
        if (buffers[i].start != NULL)
            munmap(buffers[i].start, buffers[i].length);
    }
    close(cam_fd);
    fb_destory(fb_fd, screen_bbf, screenlen);
    free(bbf);
    return 0;


MAP_ERROR:  
    for (n_buffers=0; n_buffers<4; n_buffers++) {
        if (buffers[n_buffers].start != NULL)
            munmap(buffers[n_buffers].start, buffers[n_buffers].length);
    }
ERROR:
    fb_destory(fb_fd, screen_bbf, screenlen);
    close(cam_fd);
    free(bbf);
    return -1;
}
/* 
    不知道為什麼以YUVY格式轉換的RGB32在屏幕上的效果比YUYV格式展開的還要好
    下邊程序中的是以YUVY格式展開的程序,YUYV的轉換程序應該是
            v0 = yuv[count*4+0];
        y0 = yuv[count*4+1];
        u0 = yuv[count*4+2];
        y1 = yuv[count*4+3];
*/
void process_image(unsigned char * yuv, unsigned char * buf, int length)
{
    int count;
    int y0,u0,y1,v0;
    for (count=0; count<length/4; count++) {
        y0 = yuv[count*4+0];
        u0 = yuv[count*4+1];
        y1 = yuv[count*4+2];
        v0 = yuv[count*4+3];
        buf[count*8+0] = 1.164*(y0-16) + 2.018*(u0-128); //b
        buf[count*8+1] = 1.164*(y0-16) - 0.380*(u0-128) + 0.813*(v0-128); //g
        buf[count*8+2] = 1.164*(y0-16) + 1.159*(v0-128); //r
        buf[count*8+3] = 0; //透明度

        buf[count*8+4] = 1.164*(y1-16) + 2.018*(u0-128); //b
        buf[count*8+5] = 1.164*(y1-16) - 0.380*(u0-128) + 0.813*(v0-128); //g
        buf[count*8+6] = 1.164*(y1-16) + 1.159*(v0-128); //r
        buf[count*8+7] = 0; //透明度
    }
}

void show_image(char * screen_bbf, char * buf, int width, int height)
{
    int i,j;
    for (i=0; i<height; i++) {
            memcpy(screen_bbf+80*4+i*800*4 ,buf+i*width*4,width*4);
    }
}

char * fb_init(char * devname, int * fd, int * screenlen)
{
    *fd = open(devname, O_RDWR);
    if (*fd == -1) {
        printf("error : %s\n", strerror(errno));
        return -1;
    }
    struct fb_var_screeninfo fbvs;
    int ret = ioctl(*fd, FBIOGET_VSCREENINFO, &fbvs);
    if (ret == -1) {
        printf("get screen info failed!\n");
        close(*fd);
        return NULL;
    } else {
        printf("screen info:\n\twidth : %d\thwight : %d\tbits_per_pixel : %d\n", \
        fbvs.xres, fbvs.yres,fbvs.bits_per_pixel);
    }

    *screenlen =  fbvs.xres*fbvs.yres*fbvs.bits_per_pixel/8;
    char *screen_bbf = (char *)mmap(NULL, *screenlen, PROT_WRITE | PROT_READ, MAP_SHARED, *fd,0);
    if (screen_bbf == NULL) {
        printf("screen mmap failed!\n");
        close(*fd);
        return NULL;
    } else {
        return screen_bbf;
    }
}

void fb_destory(int fd, char * screen_bbf, int screenlen) 
{
    munmap(screen_bbf, screenlen);
    close(fd);
}

效果圖如下:

Copyright © Linux教程網 All Rights Reserved