linux v4l2攝像頭應用層編程介紹

一.前言

最近項目需要做一個工業條形讀碼器,在底層應該會適配linux v4l2框架,就自己研究了一下在應用層怎麼使用v4l2進行編程,查閱了相關資料,主要是網上的博客資料,有:
https://www.cnblogs.com/emouse/archive/2013/03/04/2943243.html
https://www.jianshu.com/p/fd5730e939e7
大家可以進行查閱,比較學習,敝人也是參考,在一些地方進行了補充,希望可以共同學習,話不多說,喝杯咖啡,開動。

二.簡介

百度這麼說v4l2


百度說,v4l2是Video4linux2的簡稱,是linux中關於視頻設備的內核驅動,在Linux中,視頻設備是設備文件,可以像訪問普通文件一樣對其進行讀寫,攝像頭設備文件位置是/dev/video0。V4L2在設計時,是要支持很多廣泛的設備的,它們之中只有一部分在本質上是真正的視頻設備。


因爲我們這篇文章不涉及內核部分攝像頭驅動的實現,大致可以簡單說下,內核部分的實現分兩部分:1.我們要根據攝像頭的種類,實現具體的攝像頭傳感器的驅動,這裏可能有一些數據和控制的通信總線的協議。2.然後這個具體的驅動需要適配這個v4l2這個框架,然後向用戶層映射成一個字符設備文件。而我們的主題就是對這個設備文件進行操作,這就是應用層該做的事,你可能會說,這能學到什麼,而我要說,學習是漸進的,先理解應用層的需求,對內核驅動的實現也會有更深的理解,你說呢?

三.使用V4L2的一般步驟

使用V4L2進行視頻採集,一般是五個步驟:

  • 1.打開設備,進行初始化參數設置
  • 2.申請圖像幀緩衝,並進行內存映射
  • 3.把幀緩衝進行入隊操作,開始視頻流進行採集
  • 4.進行出隊,然後對數據進行處理,然後入隊,如此往復
  • 5.釋放資源,停止採集工作

流程如下:

 

視頻採集流程圖

四.V4L2編程例子

我這裏是使用vmware+ubuntu14.04的環境,筆記本自帶一個前置攝像頭。
首先先保證你的攝像頭可以在虛擬機下使用。
1.windows + r運行services.msc命令,打開服務列表,找到如下所示服務,手動開啓

 

image.png

2.開啓虛擬機,在 虛擬機->可移動設備 可以看到你的usb攝像頭,在虛擬機設置裏,usb控制器一欄注意兼容性,可能要設置成兼容3.0,不然有問題。

3.ubuntu下使用cheese命令,查看你的攝像頭有沒有問題

 

image.png

前期工作完成,下面是源碼+Makefile


/*v4l2_example.c*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <linux/videodev2.h>
#include <sys/mman.h>
#include <sys/time.h>



#define TRUE            (1)
#define FALSE           (0)

#define FILE_VIDEO      "/dev/video0"
#define IMAGE           "./img/demo"

#define IMAGEWIDTH      640
#define IMAGEHEIGHT     480

#define FRAME_NUM       4

int fd;
struct v4l2_buffer buf;

struct buffer
{
    void * start;
    unsigned int length;
    long long int timestamp;
} *buffers;


int v4l2_init()
{
    struct v4l2_capability cap;
    struct v4l2_fmtdesc fmtdesc;
    struct v4l2_format fmt;
    struct v4l2_streamparm stream_para;

    //打開攝像頭設備
    if ((fd = open(FILE_VIDEO, O_RDWR)) == -1) 
    {
        printf("Error opening V4L interface\n");
        return FALSE;
    }

    //查詢設備屬性
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) 
    {
        printf("Error opening device %s: unable to query device.\n",FILE_VIDEO);
        return FALSE;
    }
    else
    {
        printf("driver:\t\t%s\n",cap.driver);
        printf("card:\t\t%s\n",cap.card);
        printf("bus_info:\t%s\n",cap.bus_info);
        printf("version:\t%d\n",cap.version);
        printf("capabilities:\t%x\n",cap.capabilities);
        
        if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE) 
        {
            printf("Device %s: supports capture.\n",FILE_VIDEO);
        }

        if ((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING) 
        {
            printf("Device %s: supports streaming.\n",FILE_VIDEO);
        }
    }


    //顯示所有支持幀格式
    fmtdesc.index=0;
    fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    printf("Support format:\n");
    while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
    {
        printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
        fmtdesc.index++;
    }

    //檢查是否支持某幀格式
    struct v4l2_format fmt_test;
    fmt_test.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt_test.fmt.pix.pixelformat=V4L2_PIX_FMT_RGB32;
    if(ioctl(fd,VIDIOC_TRY_FMT,&fmt_test)==-1)
    {
        printf("not support format RGB32!\n");      
    }
    else
    {
        printf("support format RGB32\n");
    }


    //查看及設置當前格式
    printf("set fmt...\n");
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; //jpg格式
    //fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//yuv格式

    fmt.fmt.pix.height = IMAGEHEIGHT;
    fmt.fmt.pix.width = IMAGEWIDTH;
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
    printf("fmt.type:\t\t%d\n",fmt.type);
    printf("pix.pixelformat:\t%c%c%c%c\n",fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,(fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
    printf("pix.height:\t\t%d\n",fmt.fmt.pix.height);
    printf("pix.width:\t\t%d\n",fmt.fmt.pix.width);
    printf("pix.field:\t\t%d\n",fmt.fmt.pix.field);
    if(ioctl(fd, VIDIOC_S_FMT, &fmt) == -1)
    {
        printf("Unable to set format\n");
        return FALSE;
    }

    printf("get fmt...\n"); 
    if(ioctl(fd, VIDIOC_G_FMT, &fmt) == -1)
    {
        printf("Unable to get format\n");
        return FALSE;
    }
    {
        printf("fmt.type:\t\t%d\n",fmt.type);
        printf("pix.pixelformat:\t%c%c%c%c\n",fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,(fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
        printf("pix.height:\t\t%d\n",fmt.fmt.pix.height);
        printf("pix.width:\t\t%d\n",fmt.fmt.pix.width);
        printf("pix.field:\t\t%d\n",fmt.fmt.pix.field);
    }

    //設置及查看幀速率,這裏只能是30幀,就是1秒採集30張圖
    memset(&stream_para, 0, sizeof(struct v4l2_streamparm));
    stream_para.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
    stream_para.parm.capture.timeperframe.denominator = 30;
    stream_para.parm.capture.timeperframe.numerator = 1;

    if(ioctl(fd, VIDIOC_S_PARM, &stream_para) == -1)
    {
        printf("Unable to set frame rate\n");
        return FALSE;
    }
    if(ioctl(fd, VIDIOC_G_PARM, &stream_para) == -1)
    {
        printf("Unable to get frame rate\n");
        return FALSE;       
    }
    {
        printf("numerator:%d\ndenominator:%d\n",stream_para.parm.capture.timeperframe.numerator,stream_para.parm.capture.timeperframe.denominator);
    }
    return TRUE;
}



int v4l2_mem_ops()
{
    unsigned int n_buffers;
    struct v4l2_requestbuffers req;
    
    //申請幀緩衝
    req.count=FRAME_NUM;
    req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory=V4L2_MEMORY_MMAP;
    if(ioctl(fd,VIDIOC_REQBUFS,&req)==-1)
    {
        printf("request for buffers error\n");
        return FALSE;
    }

    // 申請用戶空間的地址列
    buffers = malloc(req.count*sizeof (*buffers));
    if (!buffers) 
    {
        printf ("out of memory!\n");
        return FALSE;
    }
    
    // 進行內存映射
    for (n_buffers = 0; n_buffers < FRAME_NUM; n_buffers++) 
    {
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = n_buffers;
        //查詢
        if (ioctl (fd, VIDIOC_QUERYBUF, &buf) == -1)
        {
            printf("query buffer error\n");
            return FALSE;
        }

        //映射
        buffers[n_buffers].length = buf.length;
        buffers[n_buffers].start = mmap(NULL,buf.length,PROT_READ|PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
        if (buffers[n_buffers].start == MAP_FAILED)
        {
            printf("buffer map error\n");
            return FALSE;
        }
    }
    return TRUE;    
}



int v4l2_frame_process()
{
    unsigned int n_buffers;
    enum v4l2_buf_type type;
    char file_name[100];
    char index_str[10];
    long long int extra_time = 0;
    long long int cur_time = 0;
    long long int last_time = 0;

    //入隊和開啓採集
    for (n_buffers = 0; n_buffers < FRAME_NUM; n_buffers++)
    {
        buf.index = n_buffers;
        ioctl(fd, VIDIOC_QBUF, &buf);
    }
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(fd, VIDIOC_STREAMON, &type);
    


    //出隊,處理,寫入yuv文件,入隊,循環進行
    int loop = 0;
    while(loop < 15)
    {
        for(n_buffers = 0; n_buffers < FRAME_NUM; n_buffers++)
        {
            //出隊
            buf.index = n_buffers;
            ioctl(fd, VIDIOC_DQBUF, &buf);

            //查看採集數據的時間戳之差,單位爲微妙
            buffers[n_buffers].timestamp = buf.timestamp.tv_sec*1000000+buf.timestamp.tv_usec;
            cur_time = buffers[n_buffers].timestamp;
            extra_time = cur_time - last_time;
            last_time = cur_time;
            printf("time_deta:%lld\n\n",extra_time);
            printf("buf_len:%d\n",buffers[n_buffers].length);

            //處理數據只是簡單寫入文件,名字以loop的次數和幀緩衝數目有關
            printf("grab image data OK\n");
            memset(file_name,0,sizeof(file_name));
            memset(index_str,0,sizeof(index_str));
            sprintf(index_str,"%d",loop*4+n_buffers);
            strcpy(file_name,IMAGE);
            strcat(file_name,index_str);
            strcat(file_name,".jpg");
            //strcat(file_name,".yuv");
            FILE *fp2 = fopen(file_name, "wb");
            if(!fp2)
            {
                printf("open %s error\n",file_name);
                return(FALSE);
            }
            fwrite(buffers[n_buffers].start, IMAGEHEIGHT*IMAGEWIDTH*2,1,fp2);
            fclose(fp2);
            printf("save %s OK\n",file_name);

            //入隊循環
            ioctl(fd, VIDIOC_QBUF, &buf);       
        }

        loop++;
    }
    return TRUE;    
}




int v4l2_release()
{
    unsigned int n_buffers;
    enum v4l2_buf_type type;

    //關閉流
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(fd, VIDIOC_STREAMON, &type);
    
    //關閉內存映射
    for(n_buffers=0;n_buffers<FRAME_NUM;n_buffers++)
    {
        munmap(buffers[n_buffers].start,buffers[n_buffers].length);
    }
    
    //釋放自己申請的內存
    free(buffers);
    
    //關閉設備
    close(fd);
    return TRUE;
}




/*int v4l2_video_input_output()
{
    struct v4l2_input input;
    struct v4l2_standard standard;

    //首先獲得當前輸入的index,注意只是index,要獲得具體的信息,就的調用列舉操作
    memset (&input,0,sizeof(input));
    if (-1 == ioctl (fd, VIDIOC_G_INPUT, &input.index)) {
        printf("VIDIOC_G_INPUT\n");
        return FALSE;
    }
    //調用列舉操作,獲得 input.index 對應的輸入的具體信息
    if (-1 == ioctl (fd, VIDIOC_ENUMINPUT, &input)) {
        printf("VIDIOC_ENUM_INPUT \n");
        return FALSE;
    }
    printf("Current input %s supports:\n", input.name);


    //列舉所有的所支持的 standard,如果 standard.id 與當前 input 的 input.std 有共同的
    //bit flag,意味着當前的輸入支持這個 standard,這樣將所有驅動所支持的 standard 列舉一個
    //遍,就可以找到該輸入所支持的所有 standard 了。

    memset(&standard,0,sizeof (standard));
    standard.index = 0;
    while(0 == ioctl(fd, VIDIOC_ENUMSTD, &standard)) {
        if (standard.id & input.std){
            printf ("%s\n", standard.name);
        }
        standard.index++;
    }
    // EINVAL indicates the end of the enumeration, which cannot be empty unless this device falls under the USB exception. 

    if (errno != EINVAL || standard.index == 0) {
        printf("VIDIOC_ENUMSTD\n");
        return FALSE;
    }

}*/






int main(int argc, char const *argv[])
{
    printf("begin....\n");
    sleep(10);

    v4l2_init();
    printf("init....\n");
    sleep(10);

    v4l2_mem_ops();
    printf("malloc....\n");
    sleep(10);

    v4l2_frame_process();
    printf("process....\n");
    sleep(10);

    v4l2_release();
    printf("release\n");
    sleep(20);
    
    return TRUE;
}

#Makefile
CC      = gcc
CFLAGS  = -g -Wall -O2
TARGET  = v4l2_example.bin
SRCS    = v4l2_example.c
C_OBJS  = v4l2_example.o

all:$(TARGET)

$(TARGET):$(C_OBJS)
    $(CC) $(CFLAGS) -o $@ $^
%.o:%.c
    $(CC) $(CFLAGS) -c -o $@ $<
.PHONY:clean
clean:
    rm -rf *.o $(TARGET) $(CXX_OBJS) $(C_OBJS)
    rm ./img/*

v4l2_example.c,Makefile所在目錄新建img目錄用於存放圖像文件,可以編譯運行,如下:


begin....
driver:     uvcvideo
card:       HP Wide Vision HD Camera
bus_info:   usb-0000:03:00.0-2
version:    201480
capabilities:   84200001
Device /dev/video0: supports capture.
Device /dev/video0: supports streaming.
Support format:
    1.MJPEG
    2.YUV 4:2:2 (YUYV)
support format RGB32
set fmt...
fmt.type:       1
pix.pixelformat:    RGB4
pix.height:     480
pix.width:      640
pix.field:      4
get fmt...
fmt.type:       1
pix.pixelformat:    MJPG
pix.height:     480
pix.width:      640
pix.field:      1
numerator:1
denominator:30
init....
malloc....
time_deta:17741459080

buf_len:614400
grab image data OK
save ./img/demo0.jpg OK
time_deta:33239

buf_len:614400
grab image data OK
save ./img/demo1.jpg OK
time_deta:33466

buf_len:614400
grab image data OK
save ./img/demo2.jpg OK
time_deta:33139

buf_len:614400
grab image data OK
save ./img/demo3.jpg OK
time_deta:33239

buf_len:614400
grab image data OK
save ./img/demo4.jpg OK
time_deta:33382

buf_len:614400
grab image data OK
save ./img/demo5.jpg OK
time_deta:33044

buf_len:614400
grab image data OK
save ./img/demo6.jpg OK
time_deta:33225

buf_len:614400
grab image data OK
save ./img/demo7.jpg OK
time_deta:33369

buf_len:614400
grab image data OK
save ./img/demo8.jpg OK
time_deta:33091

buf_len:614400
grab image data OK
save ./img/demo9.jpg OK
time_deta:32418

buf_len:614400
grab image data OK
save ./img/demo10.jpg OK
time_deta:34051

buf_len:614400
grab image data OK
save ./img/demo11.jpg OK
time_deta:33189

buf_len:614400
grab image data OK
save ./img/demo12.jpg OK
time_deta:32841

buf_len:614400
grab image data OK
save ./img/demo13.jpg OK
time_deta:33599

buf_len:614400
grab image data OK
save ./img/demo14.jpg OK
time_deta:32628

buf_len:614400
grab image data OK
save ./img/demo15.jpg OK
time_deta:33783

buf_len:614400
grab image data OK
save ./img/demo16.jpg OK
time_deta:32628

buf_len:614400
grab image data OK
save ./img/demo17.jpg OK
time_deta:33105

buf_len:614400
grab image data OK
save ./img/demo18.jpg OK
time_deta:34140

buf_len:614400
grab image data OK
save ./img/demo19.jpg OK
time_deta:33219

buf_len:614400
grab image data OK
save ./img/demo20.jpg OK
time_deta:33334

buf_len:614400
grab image data OK
save ./img/demo21.jpg OK
time_deta:33245

buf_len:614400
grab image data OK
save ./img/demo22.jpg OK
time_deta:33276

buf_len:614400
grab image data OK
save ./img/demo23.jpg OK
time_deta:33133

buf_len:614400
grab image data OK
save ./img/demo24.jpg OK
time_deta:32533

buf_len:614400
grab image data OK
save ./img/demo25.jpg OK
time_deta:34036

buf_len:614400
grab image data OK
save ./img/demo26.jpg OK
time_deta:33093

buf_len:614400
grab image data OK
save ./img/demo27.jpg OK
time_deta:33374

buf_len:614400
grab image data OK
save ./img/demo28.jpg OK
time_deta:33432

buf_len:614400
grab image data OK
save ./img/demo29.jpg OK
time_deta:32969

buf_len:614400
grab image data OK
save ./img/demo30.jpg OK
time_deta:32412

buf_len:614400
grab image data OK
save ./img/demo31.jpg OK
time_deta:34113

buf_len:614400
grab image data OK
save ./img/demo32.jpg OK
time_deta:33193

buf_len:614400
grab image data OK
save ./img/demo33.jpg OK
time_deta:33654

buf_len:614400
grab image data OK
save ./img/demo34.jpg OK
time_deta:33330

buf_len:614400
grab image data OK
save ./img/demo35.jpg OK
time_deta:32732

buf_len:614400
grab image data OK
save ./img/demo36.jpg OK
time_deta:33140

buf_len:614400
grab image data OK
save ./img/demo37.jpg OK
time_deta:33969

buf_len:614400
grab image data OK
save ./img/demo38.jpg OK
time_deta:33044

buf_len:614400
grab image data OK
save ./img/demo39.jpg OK
time_deta:32326

buf_len:614400
grab image data OK
save ./img/demo40.jpg OK
time_deta:333137

buf_len:614400
grab image data OK
save ./img/demo41.jpg OK
time_deta:31840

buf_len:614400
grab image data OK
save ./img/demo42.jpg OK
time_deta:33712

buf_len:614400
grab image data OK
save ./img/demo43.jpg OK
time_deta:32597

buf_len:614400
grab image data OK
save ./img/demo44.jpg OK
time_deta:67859

buf_len:614400
grab image data OK
save ./img/demo45.jpg OK
time_deta:33813

buf_len:614400
grab image data OK
save ./img/demo46.jpg OK
time_deta:33024

buf_len:614400
grab image data OK
save ./img/demo47.jpg OK
time_deta:33146

buf_len:614400
grab image data OK
save ./img/demo48.jpg OK
time_deta:33227

buf_len:614400
grab image data OK
save ./img/demo49.jpg OK
time_deta:33007

buf_len:614400
grab image data OK
save ./img/demo50.jpg OK
time_deta:33680

buf_len:614400
grab image data OK
save ./img/demo51.jpg OK
time_deta:32909

buf_len:614400
grab image data OK
save ./img/demo52.jpg OK
time_deta:32553

buf_len:614400
grab image data OK
save ./img/demo53.jpg OK
time_deta:33578

buf_len:614400
grab image data OK
save ./img/demo54.jpg OK
time_deta:33308

buf_len:614400
grab image data OK
save ./img/demo55.jpg OK
time_deta:33421

buf_len:614400
grab image data OK
save ./img/demo56.jpg OK
time_deta:32596

buf_len:614400
grab image data OK
save ./img/demo57.jpg OK
time_deta:33463

buf_len:614400
grab image data OK
save ./img/demo58.jpg OK
time_deta:33100

buf_len:614400
grab image data OK
save ./img/demo59.jpg OK
process....
release

相關的說明註釋已經很明顯了,關於被註釋的函數int v4l2_video_input_output()目前有些問題,你也可以研究一下,其他應該沒問題。

通過結果,可以看出,圖片大小640*480,格式是MJPG,幀率爲30,採集了60張圖片,每次緩衝區的時間戳之差爲33463us左右,1s/33463us = 29.88,約爲30。主函數中設置一些sleep 爲了在運行中新開終端去測試此程序所佔內存,結果爲
begin ->4204Kbyte
init->4204Kbyte
malloc ->6736Kbyte
process->6736Kbyte
release->4336Kbyte
申請的一個緩衝區爲614400byte,4個一共是2457.6Kbyte,而6736-4204=2532,還要考慮程序自己申請的堆空間及一些其他情況,可以接受
這邊是使用ps -aux | grep 程序名,進行分析的,可能會有更好的工具,更細緻的分析,希望你可以評論。

另外你可以把格式改成yuv的,需要專門yuv的查看器,去看採集的圖片
,然後還可以把文件打開的方式改成追加方式,把數據寫成一個yuv文件,可以進行圖像幀的播放,感覺就像是動畫。



作者:爲瞬間停留
鏈接:https://www.jianshu.com/p/0ac427d267d4
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章