1. 程式人生 > >基於v4l2通過Map方式讀取攝像頭的理解

基於v4l2通過Map方式讀取攝像頭的理解

在Linux下,基於v4l2通過Map對映方式使用USB攝像頭,流程有點複雜,剛開始每次看都彷彿看懂了,過一段時間就會蒙圈,直到我發現一個很好的比喻……這裡簡要介紹一下思路。

關鍵字:Linux,v4l2,Map,USB攝像頭,比喻理解

0. 你有一臺生產機器

我們把USB攝像頭比作生產機器,攝像頭產生的資料就是產品
我們的目的就是把攝像頭產生的資料讀到記憶體,甚至讀到檔案中,就好像我們要把機器產生的產品通過物流中心送到客戶手中。

1. 為這臺機器申請庫房(VIDIOC_REQBUFS)

首先,我們需要為機器生產的產品申請庫房,而且還是多個庫房,以保證機器不間斷工作的產品可以有效輸出。一旦庫房不夠,機器產生的產品擺放不下,機器必須停止工作或者必須捨棄一些產品——這和我們對視訊連續採集的要求不符!

基於V4l2,首先為攝像頭申請物理快取:

//從網上下載“庫房申請表模板(v4l2_requestbuffers結構體)”,並列印申請表(rb)
struct v4l2_requestbuffers rb;  //庫房的申請表,我們需要在申請表中填好資訊
int ret;

memset(&rb, 0, sizeof rb);
rb.count = nbufs;   //庫房數量
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  //庫房要儲存的產品型別
rb.memory = V4L2_MEMORY_MMAP;   //庫房輸出產品的方式

ret = ioctl(dev, VIDIOC_REQBUFS, &rb);  //開始申請(VIDIOC_REQBUFS)
if (ret < 0) { printf("Unable to allocate buffers: %d.\n", errno); return ret; }

2. 給庫房起名字(VIDIOC_QUERYBUF)

庫房代表實際的物理快取,我們稱呼的時候,不能只用手指著說這個庫房,辣個庫房。沒有名稱,不管是系統還是使用者,根本無法對申請到的物理快取進行操作!比如後面有個“快取入隊”的操作,通俗點講就是告訴系統該庫房可以使用,依據就是庫房標號index!

我們根據面向物件的思想,這一次我們借用另一個表格(v4l2_buffer 結構體),把庫房資訊統計完整,不僅給庫房起名,還統計了庫房儲存產品型別、庫房輸出產品方式等。表格(結構體)裡面需要的資訊我們都填上了!

基於v4l2,對物理快取資訊進行封裝:

for(int i=0;i<nbuf;i++){
    struct v4l2_buffer v4l2_buf;    //這次的表格借用的是v4l2_buffer結構體
    memset(&v4l2_buf, 0, sizeof v4l2_buf);
    v4l2_buf.index = i;     //庫房名稱index,1號房,2號房……
    v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;    //庫房要儲存的產品型別
    v4l2_buf.memory = V4L2_MEMORY_MMAP;     //庫房輸出產品的方式

    /*VIDIOC_QUERYBUF指令表示把物理快取和v4l2_buffer 結構體對應起來,
    可以理解為我們把填好的表格,貼到庫房門上*/
    if (ioctl(dev, VIDIOC_QUERYBUF, &v4l2_buf) < 0) {
        printf("Unable to query buffer %u (%d).\n", i, errno);
        close(dev);         
        return -1;
    }
}

注意:要給nbuf個庫房起名字,所以這裡使用for迴圈

3. 建立對映(Map)關係(設定庫房輸出產品的方式)

攝像頭錄影的資料傳輸過程:
攝像頭物理快取→ 記憶體(malloc申請到堆記憶體)→ h264_save.h264;
這相當於: 庫房 → 物流中心 → 客戶手中;

剛才我們一直提到的“輸出產品的方式”,其實就是如何將資料從攝像頭申請的私人空間,轉移到我們可以使用的使用者快取空間?一般有“對映方式”、“使用者手動讀取方式”等。

這相當於我們庫房的產品,是如何輸送到物流中心。
使用者手動讀取方式,就是通過我們開車(read)把產品從庫房運送到物流中心;
對映方式則非常巧妙,不需要讀取,直接把物理快取對映到使用者快取,通俗點講,就是直接把庫房當做物流中心,快遞公司直接到庫房而不是物流中心取貨。
所以,對映方式省去了一個環節,更為高效。

程式碼分析:

//mem0[nbuf]是指標陣列,儲存nbufs塊使用者記憶體(通過malloc申請)的首地址
for(int i=0;i<nbuf;i++){
    //nbuf個庫房(物理快取)和nbuf個物流中心(使用者記憶體)建立對映關係
    mem0[i] = mmap(0, v4l2_buf.length, PROT_READ, MAP_SHARED, dev, v4l2_buf.m.offset);
if (mem0[i] == MAP_FAILED) {
    printf("Unable to map buffer %u (%d)\n", i, errno);
    close(dev);         
    return -1;
    }
}

注意:要使nbuf個庫房對應nbuf個物流中心,所以這裡使用for迴圈

4. 庫房準備就緒(物理快取入隊)(VIDIOC_QBUF)

VIDIOC_QBUF是個很重要的函式。
在錄影之前,需要使物理快取入隊,告訴系統這一塊物理快取是空的,是可以使用的。系統在獲取攝像頭資料時,自動把資料往這一塊記憶體上寫入;
在迴圈錄影過程中,需要把資料從物理快取對映到使用者記憶體空間,之後,同樣需要告訴系統,物理快取是可以使用的,系統就會把這塊快取“入隊”,輪到這塊快取時就寫入資料。

struct v4l2_buffer v4l2_buf;    //庫房表格資訊
for (i = 0; i < NBUFS; ++i) {
    memset(&v4l2_buf, 0, sizeof v4l2_buf);
    v4l2_buf.index = i;   //庫房標識號碼
    v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2_buf.memory = V4L2_MEMORY_MMAP;

    //VIDIOC_QBUF指令,很可能就是一個宣告,告訴系統v4l2_buf(物理快取)是可以使用的
    if (ioctl(dev, VIDIOC_QBUF, &v4l2_buf) < 0) {
        printf("Unable to queue v4l2_buf.\n");
        close(dev);         
        return -1;
    }
}   

注意1:要讓nbuf個物理快取入隊,所以這裡使用for迴圈;

注意2:VIDIOC_QBUF在整個錄視訊期間都會用到,後面將會有更詳細的解釋。

5. 開始錄製視訊(VIDIOC_DQBUF + fwrite + VIDIOC_QBUF)

錄製視訊這件事包括以下三個步驟:
1. 把資料從物理快取讀到使用者記憶體(VIDIOC_DQBUF
——把產品從庫房轉移到物流中心;
由於是對映關係已經建立,通過對映幾乎什麼都不用做就實現了該步;

在程式中我們執行:ioctl(dev, VIDIOC_DQBUF, &v4l2_buf)實現該步,我認為該步驟的功能是通知系統:貨物已經(從庫房)轉移到物流中心!
雖然這裡產品根本沒有移動,但對於系統來說,還有除了對映之外的其他方式,來實現產品從庫房轉移到物流中心;系統只關心貨物是否達到物流中心,而不關係怎麼到的!

所以這一步並不是可有可無的!
.
2. 把資料從使用者記憶體讀到檔案
——把產品從物流中心送達到使用者手中
物流中心數量有限,產品不能總是放在這裡,送到使用者手中才是根本目的。
對應起來就是通過fwrite函式,把資料從使用者記憶體(堆)寫到檔案中。
.
3. 物理快取入隊
——庫房準備就緒;
詳情參考:4. 庫房準備就緒(物理快取入隊)(VIDIOC_QBUF)

以上步驟迴圈……

程式實現:

struct v4l2_buffer v4l2_buf;    //庫房表格資訊
while(1) {
    /* 1.進行對映 Dequeue a buffer. 資料轉移到使用者記憶體mem0[i]*/
    if (ioctl(dev, VIDIOC_DQBUF, &v4l2_buf) < 0) {
        printf("Unable to dequeue v4l2_buf.\n");
        close(dev);
        return -1;
    }

    /* Record the H264 video file 資料讀到檔案中*/
    fwrite(mem0[v4l2_buf.index], v4l2_buf.bytesused, 1, file);

    //告知系統,物理快取中的資料已經讀取完畢,可以用來存放新的資料資訊
    if (ioctl(dev, VIDIOC_QBUF, &v4l2_buf) < 0) {
        printf("Unable to requeue v4l2_buf.\n");
        close(dev); 
        return -1;
    }

    fflush(stdout);
}   

注意:
錄視訊過程中,我們沒有通過index判斷當前是哪一塊物理快取(或使用者記憶體),因為這是系統決定的;
但是我們可以通過v4l2_buf.index獲得有效資訊,並寫入檔案中:
fwrite(mem0[v4l2_buf.index], v4l2_buf.bytesused, 1, file);

6. 歡迎討論

這麼理解可以解釋的通,也很好理解,可能有別人介紹的有出入。

誰有更好的解釋歡迎留言。

相關推薦

基於v4l2通過Map方式讀取攝像頭理解

在Linux下,基於v4l2通過Map對映方式使用USB攝像頭,流程有點複雜,剛開始每次看都彷彿看懂了,過一段時間就會蒙圈,直到我發現一個很好的比喻……這裡簡要介紹一下思路。 關鍵字:Linux,v4l2,Map,USB攝像頭,比喻理解 0.

spring boot框架學習學前掌握之重要註解(4)-通過註解方式讀取外部資源配置文件2

spring boot kaigejava 凱哥java本節主要內容:1:思考問題:怎麽讀取多個配置文件,如果文件不存在怎麽辦2:配置數據庫連接池聲明:本文是《凱哥陪你學系列-框架學習之spring boot框架學習》中spring boot框架學習學前掌握之重要註解(4)-通過註解方式讀取外部資源配置文件2

最簡單的基於FFmpeg的AVDevice例子 讀取攝像頭

                =====================================================最簡單的基於FFmpeg的AVDevice例子文章列表:最簡單的基於FFmpeg的AVDevice例子(螢幕錄製)=============================

簡述基於V4L2驅動框架的UVC攝像頭驅動(只用於獲取資料,不具備控制功能)

分析的是韋東山第三期視訊中的從零編寫USB攝像頭驅動裡的程式碼 1)入口函式: 註冊一個usb_driver結構體:usb_register裡面有什麼內容?根據id_table進行匹配 :表示它能支援哪些裝置當接上能夠支援的裝置的時候,會呼叫probe函式2)在probe函

最簡單的基於FFmpeg的AVDevice樣例(讀取攝像頭

malloc projects == 格式 mac 跨平臺 buffer 版本 span =====================================================最簡單的基於FFmpeg的AVDevice樣例文章列表:最簡單的基於FFmp

通過DeviceIoControl讀磁盤的方式讀取獨占文件內容

核心 oca 空間 .com 但是 我們 form nbu filesize 前言   windows操作系統中常見的一個文件存儲系統是NTFS。在這個文件系統中MFT是它的核心。                           圖一   MFT

SparkStreaming與kafka通過直連方式讀取資料

1、Spark-Streaming的receive的方式和直連方式有什麼區別: Receive接收固定時間間隔的資料(放在記憶體中),達到固定的時間才進行處理,效率低並且容易丟失資料(Kafka高階API),自動維護偏移量 Direct直連方式,相當於直接連線到Kafka的分割槽上,相當於K

springboot的5種讀取配置方式(5):通過applicationContext.xml讀取

2.通過config讀取指定檔案:可以把同一類的bean進行統一管理,然後通過config指定讀取配置檔案/** * 學生實體類 * Created by ASUS on 2018/5/4 */ public class

最簡單的基於FFmpeg的AVDevice例子(讀取攝像頭)解讀

本文轉載自最簡單的基於FFmpeg的AVDevice例子(讀取攝像頭) 在此基礎上對程式的流程進行解讀,閱讀前請先閱讀原文。 ============================= /** * 最簡單的基於FFmpeg的AVDevice例子(讀取攝像頭) * Simplest F

springboot 讀取配置檔案中的變數(通過註解方式

springboot的application.properties檔案中可以定義一些可配置的常量。在程式中我們不需要再重新的讀取檔案,我們可以直接使用@Value註解讀取配置檔案中的值。首先看一下配置檔案application.properties中的內容是:spring.p

基於OpenCV的讀取攝像頭實現單個人臉驗證MFC程式

與上一篇部落格類似,這篇部落格介紹使用OpenCV實現的MFC程式,可以實現單個人臉的驗 證,並在影象和介面給出識別結果。效果圖如下: 置信度一欄可以填寫判定的閾值,預設為70。開啟攝像頭才能進行驗證或拍照,拍照之前可以清除之前拍攝的訓練圖片,可以拍攝多張用於識別。其中m

在jsp中通過I/O流方式讀取圖片並展示到頁面

之前在做一個專案時用到了圖片上傳並立即展示到頁面瀏覽,而且圖片存放在硬碟上的一個資料夾中而非在工程與資料庫中,這就會出現一個問題,如果不是在開發程式環境中訪問圖片頁面,則會出現圖片不能展示情況,原因很明顯,就是外部無法訪問到伺服器硬碟上的圖片。所以這時就需要用到i/o流讀取

VLC播放攝像頭或者網路攝像頭通過rtsp流讀取視訊

#include <stdio.h>   #include <stdint.h>   #include <math.h>   #include <stdlib.h>   #include <assert.h>  

springboot的5種讀取配置方式(3):通過application.properties讀取

3.通過application.properties讀取:/** * 學生實體類 * Created by ASUS on 2018/5/4 */ @Component("Student") pu

通過JVM記憶體模型深入理解值傳遞和引用傳遞兩種方式

值傳遞和引用傳遞分析Java中資料型別分為兩大類:基本型別和引用型別(也就是物件型別)。基本型別:boolean、char、byte、short、int、long、float、double引用型別:類、介面、陣列因此,變數型別也可分為兩大類:基本型別和引用型別。在分析值傳遞

JAVA--通過DOM4J的方式讀取xml檔案簡單例項

程式碼如下: package com.xml.dom4j; import java.io.File; import java.util.Iterator; import java.util.List

STM32—基於模擬IIC方式讀取EEPROM

前言: 最近在除錯STM32L152晶片利用IIC介面讀取EEPROM的程式,總結下STM32的 IIC介面 讀取EEPROM的使用方法。 PS:由於STM32的硬體IIC存在一些問題,本文暫時使用模擬IIC進行EEPROM的讀取。STM32的硬體IIC使用方法見另外

Python 編寫通過post方式提交的接口測試代碼

urlencode nco 出現 第一次用 python 方式 pen 沒有 iterable 第一次用python編寫程序 是照著視頻裏編寫的 但是期間也出現了很多錯誤 視頻內用了是Python2 版本的 而我的是python 3版本的 寫這兩程序時就發現有很多不同

MySQL,Oracle,PostgreSQL通過web方式管理維護, 提高開發及運維效率

數據庫管理系統 遠程服務 .com gre ava window 開發 功能 mss 在開發及項目運維中,對數據庫的操作大家目前都是使用客戶端工具進行操作,例如MySQL的客戶端工具navicat,Oracle的客戶端工具 PL/SQL Developer

用VLC讀取攝像頭產生RTSP流,DSS主動取流轉發(一)

sdp nco con alt 分享 pad 流轉 publish enc 用VLC讀取攝像頭產生RTSP流,DSS主動取流轉發(一) 攝像機地址是192.1.101.51,VLC運行在192.1.101.77上,DSS服務器架設在192.1.101.