天天看點

【QT學習之路】使用V4L2驅動USB攝像頭一、V4L2簡介二、QT通過V4L2接口采集視訊的步驟三、具體實作過程

一、V4L2簡介

V4L2(video for linux 2的縮寫)是Linux下關于采集圖檔、視訊和音頻資料相關裝置的驅動架構,為驅動和應用程式提供了一套統一的接口規範。使應用層跟硬體層分離,硬體層的驅動操作都交給V4L2,應用層隻需要調用V4L2的接口即可,如下圖

【QT學習之路】使用V4L2驅動USB攝像頭一、V4L2簡介二、QT通過V4L2接口采集視訊的步驟三、具體實作過程

二、QT通過V4L2接口采集視訊的步驟

      1、首先打開攝像頭

      2、配置裝置(采集的頻率,圖像寬高, 圖像格式(jpeg, yuv--422,420)),擷取裝置資訊,確定配置成功

      3、在核心空間申請緩沖區隊列(2-4個),把申請好的緩沖區對象一一映射到使用者空間

      4、開始 / 結束采集   

三、具體實作過程

1、打開攝像頭,可通過open函數來打開攝像頭

this->vfd = ::open(deviceName.c_str(), O_RDWR);
    if(this->vfd < 0)
    {
        perror("open fail");
        VideoException vexp("open fail");//建立異常對象
        //抛異常
        throw vexp;
    }
           

2、配置裝置 和 擷取裝置資訊

在配置時,用到了linux下videodev2.h(路徑為/usr/include/linux/videodev2.h)頭檔案中的V4l2_format 和 v4l2_pix_format 結構體。

V4l2_format

【QT學習之路】使用V4L2驅動USB攝像頭一、V4L2簡介二、QT通過V4L2接口采集視訊的步驟三、具體實作過程

v4l2_pix_format

【QT學習之路】使用V4L2驅動USB攝像頭一、V4L2簡介二、QT通過V4L2接口采集視訊的步驟三、具體實作過程

将上面的結構體資訊寫入裝置即可,通過ioctl函數将屬性寫入裝置,該函數的參數用到了一些宏定義 (videodev2.h中)

     VIDIOC_S_FMT ---設定格式屬性

     VIDIOC_G_FMT ---擷取格式屬性

【QT學習之路】使用V4L2驅動USB攝像頭一、V4L2簡介二、QT通過V4L2接口采集視訊的步驟三、具體實作過程

函數參數說明

int  ioctl(檔案描述符,  指令--上的宏,  參數--上宏定義裡面的結構體對象)

配置屬性和擷取屬性的實作的代碼

truct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //使用第一個結構體
fmt.fmt.pix.width = 640;
vfmt.fmt.pix.height = 480;
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;//(設定視訊輸出格式,但是要攝像頭支援)
//通過ioctl把屬性寫入裝置
int ret  = ioctl(this->vfd, VIDIOC_S_FMT, &vfmt);
//通過ioctl從裝置擷取屬性
memset(&vfmt, 0, sizeof(vfmt));   //清空
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(this->vfd, VIDIOC_G_FMT, &vfmt)
           

3、申請緩沖區隊列, 映射到使用者空間

1、使用的結構體 (同樣也在videodev2.h中)

申請緩沖區隊列

【QT學習之路】使用V4L2驅動USB攝像頭一、V4L2簡介二、QT通過V4L2接口采集視訊的步驟三、具體實作過程

映射 ---從隊列中拿出一個空間做映射

【QT學習之路】使用V4L2驅動USB攝像頭一、V4L2簡介二、QT通過V4L2接口采集視訊的步驟三、具體實作過程

2、使用到的宏定義

【QT學習之路】使用V4L2驅動USB攝像頭一、V4L2簡介二、QT通過V4L2接口采集視訊的步驟三、具體實作過程

3、通過ioctl函數進行操作

申請隊列長度

              ioctl(檔案描述符,  VIDIOC_REQBUFS(宏定義),  struct v4l2_requestbuffer 對象位址)

從隊列拿出一個空間做映射

              ioctl(檔案描述符, VIDIOC_QUERYBUF(宏定義),  struct v4l2_buffer 對象位址)

放回隊列

              ioctl(檔案描述符, VIDIOC_QBUF(宏定義), struct v4l2_buffer 對象位址)

4、具體代碼實作

一、申請緩沖區隊列

//1申請緩沖區隊列
struct v4l2_requestbuffers reqbuffer;
reqbuffer.count = this->count;//申請緩沖區隊列長度
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.memory = V4L2_MEMORY_MMAP;
int ret = ioctl(this->vfd, VIDIOC_REQBUFS, &reqbuffer);
if(ret < 0)
{
    VideoException vexp("req buffer fail");//建立異常對象
    throw vexp;
}
           

二、映射緩沖區隊列到使用者空間

for(int i=0; i<this->count; i++)
{
    struct VideoFrame frame;

    struct v4l2_buffer mapbuffer;
    mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    mapbuffer.index = i;
    mapbuffer.memory = V4L2_MEMORY_MMAP;
    //從隊列中拿到核心空間
    ret  = ioctl(this->vfd, VIDIOC_QUERYBUF, &mapbuffer);
    if(ret < 0)
    {
        perror("query fail");
    }
    //映射
    frame.length = mapbuffer.length;
    frame.start = (char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, this->vfd, mapbuffer.m.offset);

    //空間放回隊列中(核心空間)
    ret = ioctl(this->vfd, VIDIOC_QBUF, &mapbuffer);

    //把frame添加到容器framebuffers
    framebuffers.push_back(frame);
}
           

4、開始/結束采集資料

1、使用到的宏定義

【QT學習之路】使用V4L2驅動USB攝像頭一、V4L2簡介二、QT通過V4L2接口采集視訊的步驟三、具體實作過程

2、通過ioctl函數進行操作

                       enum v4l2_buf_type  type;  //指派為:V4L2_BUF_TYPE_VIDEO_CAPTURE

開始采集        ioctl(檔案描述符,  VIDIOC_STREAMON(宏定義),  &type)

結束采集        ioctl(檔案描述符,  VIDIOC_STREAMOFF(宏定義),  &type)

3、采集資料的步驟

      1、從隊列中拿出一個緩沖區   

      2、從映射使用者空間中把緩沖區資料取走   

      3、把緩沖區放回隊列

3、具體代碼實作

一、開始采集

//開始采集
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret  =  ioctl(this->vfd, VIDIOC_STREAMON, &type);
if(ret < 0)
{
    perror("start fail");
}
           

二、結束采集

//結束采集
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret  =  ioctl(this->vfd, VIDIOC_STREAMOFF, &type);
if(ret < 0)
{
    perror("stop fail");
}
//釋放映射
for(int i=0; i<this->framebuffers.size(); i++)
{
    munmap(framebuffers.at(i).start, framebuffers.at(i).length);
}
           

三、采集資料

struct v4l2_buffer readbuf;
readbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
readbuf.memory = V4L2_MEMORY_MMAP;
if(ioctl(this->vfd, VIDIOC_DQBUF, &readbuf)<0)//取一針資料
{
    perror("read image fail");
    exit(1);
}
printf("%ld\n", readbuf.length);
*length = readbuf.length;
memcpy(imageBuffer,framebuffers[readbuf.index].start, framebuffers[readbuf.index].length);  //拷貝資料

//把用完的隊列空間放回隊列中重複使用
if(ioctl(vfd, VIDIOC_QBUF, &readbuf)<0)
{
     perror("destroy fail");
     exit(1);
}
           

5、測試采集資料是否成功

int main()
{

    V4l2Api mV4l2("/dev/video0");

    mV4l2.open();

    char image[1000000];
    int length=0;
    while(1)
    {

        mV4l2.grapImage(image, &length);

        FILE * file = fopen("./my.jpg", "w+");
        
        fwrite(image,length, 1, file);
        fclose(file);
        break;
    }

    mV4l2.close();

    return 0;
}
           

注意:我這裡采集的視訊輸出格式為MJPEG,是以才能直接存儲為.jpg格式的圖檔。

問題咨詢及項目源碼請加群:

QQ群

名稱:IT項目交流群

群号:245022761

繼續閱讀