加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 1. 什么是v4l2
    • 2. 截取圖象的3種方法
    • 3. v4l2 設(shè)備操作說明
    • 4. ioctl命令說明
    • 5. v4l2設(shè)備抓幀程序編寫
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

Camera | 4.瑞芯微平臺MIPI攝像頭應(yīng)用程序編寫

2023/03/12
3328
閱讀需 51 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

前面我們講解了camera的基礎(chǔ)概念,MIPI協(xié)議,CSI2,常用命令等,本文帶領(lǐng)大家入門,如何用c語言編寫應(yīng)用程序來操作攝像頭

Linux下攝像頭驅(qū)動都是基于v4l2架構(gòu),要基于該架構(gòu)編寫攝像頭的應(yīng)用程序,必須先要搞清楚什么是v4l2。

1. 什么是v4l2

vl42是video for Linux 2的縮寫,是一套Linux內(nèi)核視頻設(shè)備的驅(qū)動框架,該驅(qū)動框架為應(yīng)用層提供一套統(tǒng)一的操作接口(一系列的ioctl)

https://linuxtv.org/downloads/legacy/video4linux/API/V4L2_API/

官網(wǎng)有一個簡單的用于抓圖的程序capture.c。

本文后面基于該實(shí)例編寫一個最簡單的抓圖程序。

v4l2接口

V4L2 :video for linux 2 ,是 linux ??套標(biāo)準(zhǔn)的視頻驅(qū)動,讓應(yīng)?層可以像訪問普通?件?樣對**/dev/videoX** 節(jié)點(diǎn)進(jìn)? open 、 read 、 ioctl 等操作。

V4L2在設(shè)計時,是要支持很多廣泛的設(shè)備的,它們之中只有一部分在本質(zhì)上是真正的視頻設(shè)備,可以支持多種設(shè)備,它可以有以下幾種接口:

1. video capture interface(捕獲):視頻采集接口,這種接口應(yīng)用于攝像頭,v4l2在最初設(shè)計的時候就是應(yīng)用于這種功能

2. video output interface(輸出):視頻輸出接口,將靜止圖像或圖像序列編碼為模擬視頻信號,通過此接口,應(yīng)用程序可以控制編碼過程并將圖像從用戶空間移動到驅(qū)動程序

3. video overlay interface(預(yù)覽):視頻直接傳輸接口,可以將采集到的視頻數(shù)據(jù)直接傳輸?shù)斤@示設(shè)備,不需要cpu參與,這種方式的顯示圖像的效率比其他方式高得多

本文主要講解如何使用capture功能。

2. 截取圖象的3種方法

1)用mmap(內(nèi)存映射)方式截取視頻

mmap( )系統(tǒng)調(diào)用使得進(jìn)程之間通過映射同一個普通文件實(shí)現(xiàn)共享內(nèi)存。普通文件被映射到進(jìn)程地址空間后,進(jìn)程可以向訪問普通內(nèi)存一樣對文件進(jìn)行訪問,不必再調(diào)用read(),write()等操作。

兩個不同進(jìn)程A、B共享內(nèi)存的意思是,同一塊物理內(nèi)存被映射到進(jìn)程A、B各自的進(jìn)程地址空間。進(jìn)程A可以即時看到進(jìn)程B對共享內(nèi)存中數(shù)據(jù)的更新,反之亦然

采用共享內(nèi)存通信的一個顯而易見的好處是效率高,因?yàn)檫M(jìn)程可以直接讀寫內(nèi)存,而不需要任何數(shù)據(jù)的拷貝

*(1)設(shè)置picture的屬性
*(2) 初始化video_mbuf,以得到所映射的buffer的信息

ioctl(vd->fd,?VIDIOCGMBUF,?&(vd->mbuf))

*(3)可以修改video_mmap和幀狀態(tài)的當(dāng)前設(shè)置

?Eg.?vd->mmap.format?=?VIDEO_PALETTE_RGB24
?vd->framestat[0]?=?vd->framestat[1]?=?0;?vd->frame?=?0;

*(4)將mmap與video_mbuf綁定

void*?mmap?(?void?*?addr?,?size_t?len?,?int?prot?,?int?flags?,?int?fd?,?off_t?offset?)
len?//映射到調(diào)用進(jìn)程地址空間的字節(jié)數(shù),它從被映射文件開頭offset個字節(jié)開始算起
Prot?//指定共享內(nèi)存的訪問權(quán)限?PROT_READ(可讀),?PROT_WRITE?(可寫),?PROT_EXEC?(可執(zhí)行)
flags?//?MAP_SHARED?MAP_PRIVATE中必選一個?//?MAP_?FIXED不推薦使用addr?//共內(nèi)存享的起始地址,一般設(shè)0,表示由系統(tǒng)分配
Mmap(?)?返回值是系統(tǒng)實(shí)際分配的起始地址
if((vd->map?=?(unsigned?char*)mmap(0,?vd->mbuf.size,?PROT_READ|PROT_WRITE,?MAP_SHARED,?vd->fd,?0))?<?0)
{
?perror("v4l_mmap?mmap:");
?return?-1;
}

*(5)Mmap方式下真正做視頻截取的 VIDIOCMCAPTURE

ioctl(vd->fd,?VIDIOCMCAPTURE,?&(vd->mmap))?;
若調(diào)用成功,開始一幀的截取,是非阻塞的,
是否截取完畢留給VIDIOCSYNC來判斷

*(6)調(diào)用VIDIOCSYNC等待一幀截取結(jié)束

if(ioctl(vd->fd,?VIDIOCSYNC,?&frame)?<?0)
{
?perror("v4l_sync:VIDIOCSYNC");
?return?-1;
}

若成功,表明一幀截取已完成。可以開始做下一次 VIDIOCMCAPTURE

frame是當(dāng)前截取的幀的序號。

關(guān)于雙緩沖:

video_bmuf?bmuf.frames?=?2;
一幀被處理時可以采集另一幀
int?frame;?//當(dāng)前采集的是哪一幀
int?framestat[2];?//幀的狀態(tài)?沒開始采集|等待采集結(jié)束
幀的地址由vd->map?+?vd->mbuf.offsets[vd->frame]得到
采集工作結(jié)束后調(diào)用munmap取消綁定
munmap(vd->map,?vd->mbuf.size)

2)視頻截取的第二種方法:直接讀設(shè)備

關(guān)于緩沖大小,圖象等的屬性須由使用者事先設(shè)置

int?read?(要訪問的文件描述符;指向要讀寫的信息的指針;應(yīng)該讀寫的字符數(shù));
?返回值為實(shí)際讀寫的字符數(shù)

實(shí)例:

int?len?;
unsigned?char?*vd->map=?(unsigned?char?*)?malloc(vd->capability.maxwidth*vd->capability.maxheight?);
len?=?read(vd->fd,vd->map,?vd->capability.maxwidth*vd->capability.maxheight*3?);

3)用戶指針

3. v4l2 設(shè)備操作說明

對設(shè)備的大多數(shù)操作都是應(yīng)用層通過調(diào)用ioctl實(shí)現(xiàn)的,
不同的命令需要操作不同的文件設(shè)備節(jié)點(diǎn),
具體的需要根據(jù)拓?fù)浣Y(jié)構(gòu)來決定操作那個字符設(shè)備。

以下是瑞芯微rk3568平臺的攝像頭拓?fù)鋱D,移植了ov13850攝像頭。

在這里插入圖片描述

    1. 其中攝像頭對應(yīng)的此設(shè)備為:

/dev/v4l-subdev3

    1. 應(yīng)用層要配置通用配置、或者獲取圖像,需要操作設(shè)備

/dev/video0

    1. 有一些攝像頭專用的命令,我們可以操作

/dev/v4l-subdev3

ov13850攝像頭驅(qū)動中注冊了一些命令對應(yīng)的回調(diào)函數(shù):

這些回調(diào)函數(shù)都注冊到了V4L2架構(gòu)中,我們可以通過字符設(shè)備 /dev/videox,、/dev/v4l-subdevx 直接或者間接訪問到這些回調(diào)函數(shù)。

V4L2定義了一些通用的命令,操作字符設(shè)備 /dev/videox即可調(diào)用,命令具體定義如下:

kerneldriversmediav4l2-corev4l2-ioctl.c

可以通過數(shù)組名+命令對應(yīng)的數(shù)值方式訪問對應(yīng)的回調(diào)函數(shù)。
該數(shù)組定義如下:

struct?v4l2_ioctl_info?{
?unsigned?int?ioctl;
?u32?flags;
?const?char?*?const?name;
?int?(*func)(const?struct?v4l2_ioctl_ops?*ops,?struct?file?*file,
??????void?*fh,?void?*p);
?void?(*debug)(const?void?*arg,?bool?write_only);
};

字符設(shè)備**/dev/v4l-subdevx**支持的命令如下:

@kerneldriversmediav4l2-corev4l2-subdev.c

static?long?subdev_do_ioctl(struct?file?*file,?unsigned?int?cmd,?void?*arg)
{
??……
?switch?(cmd)?{
?case?VIDIOC_QUERYCTRL:
??……
?case?VIDIOC_QUERY_EXT_CTRL:
??……
?case?VIDIOC_QUERYMENU:
??……
?case?VIDIOC_G_CTRL:
??……
?case?VIDIOC_S_CTRL:
??……
?case?VIDIOC_G_EXT_CTRLS:
??……
?case?VIDIOC_S_EXT_CTRLS:
??……
?case?VIDIOC_TRY_EXT_CTRLS:
??……
?case?VIDIOC_DQEVENT:
??……
?case?VIDIOC_SUBSCRIBE_EVENT:
??return?v4l2_subdev_call(sd,?core,?subscribe_event,?vfh,?arg);
?case?VIDIOC_UNSUBSCRIBE_EVENT:
??return?v4l2_subdev_call(sd,?core,?unsubscribe_event,?vfh,?arg);
#ifdef?CONFIG_VIDEO_ADV_DEBUG
?case?VIDIOC_DBG_G_REGISTER:
??……
?case?VIDIOC_DBG_S_REGISTER:
??……
?case?VIDIOC_DBG_G_CHIP_INFO:
??……
#endif
?case?VIDIOC_LOG_STATUS:?{
??……
#if?defined(CONFIG_VIDEO_V4L2_SUBDEV_API)
?case?VIDIOC_SUBDEV_G_FMT:?{
??……
?case?VIDIOC_SUBDEV_S_FMT:?{
??……
?case?VIDIOC_SUBDEV_G_CROP:?{
??……
?}
?case?VIDIOC_SUBDEV_S_CROP:?{
??……
?}
?case?VIDIOC_SUBDEV_ENUM_MBUS_CODE:?{
??……
?}
?case?VIDIOC_SUBDEV_ENUM_FRAME_SIZE:?{
??……
?}
?case?VIDIOC_SUBDEV_G_FRAME_INTERVAL:?{
??……
?}
?case?VIDIOC_SUBDEV_S_FRAME_INTERVAL:?{
??……
?}
?case?VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL:?{
??……
?}
?case?VIDIOC_SUBDEV_G_SELECTION:?{
??……
?}
?case?VIDIOC_SUBDEV_S_SELECTION:?{
??……
?}
?case?VIDIOC_G_EDID:?{
??……
?}
?case?VIDIOC_S_EDID:?{
??……
?}
?case?VIDIOC_SUBDEV_DV_TIMINGS_CAP:?{
??……
?}
?case?VIDIOC_SUBDEV_ENUM_DV_TIMINGS:?{
??……
?}
?case?VIDIOC_SUBDEV_QUERY_DV_TIMINGS:
??return?v4l2_subdev_call(sd,?video,?query_dv_timings,?arg);
?case?VIDIOC_SUBDEV_G_DV_TIMINGS:
??return?v4l2_subdev_call(sd,?video,?g_dv_timings,?arg);
?case?VIDIOC_SUBDEV_S_DV_TIMINGS:
??return?v4l2_subdev_call(sd,?video,?s_dv_timings,?arg);
?case?VIDIOC_G_INPUT:
??return?v4l2_subdev_call(sd,?video,?g_input_status,?arg);
?case?VIDIOC_SUBDEV_G_STD:
??return?v4l2_subdev_call(sd,?video,?g_std,?arg);
?case?VIDIOC_SUBDEV_S_STD:?{
??v4l2_std_id?*std?=?arg;
??return?v4l2_subdev_call(sd,?video,?s_std,?*std);
?}
?case?VIDIOC_SUBDEV_ENUMSTD:?{
??……
?}
?case?VIDIOC_SUBDEV_QUERYSTD:
??……
?}
?return?0;
}

這其中有一些命令是和字符設(shè)備 /dev/videox 的命令重復(fù)的,

比如:VIDIOC_S_CTRL,

VIDIOC_SUBDEV_ 開頭的則是subdev私有的。

關(guān)于這些命令和回調(diào)函數(shù),后續(xù)會再深入講解,對于應(yīng)用程序開發(fā),

我們首先搞清楚設(shè)備的拓?fù)浣Y(jié)構(gòu),然后需要知道我們要執(zhí)行的命令功能以及對應(yīng)的是哪一個設(shè)備節(jié)點(diǎn)即可。

4. ioctl命令說明

參見結(jié)構(gòu)體

?/usr/include/linux/videodev2.h

1)Querying Capabilities

查詢設(shè)備的功能

由于V4L2涵蓋了各種各樣的設(shè)備,因此并非API的所有方面都適用于所有類型的設(shè)備,在使用v4l2設(shè)備時,必須調(diào)用此API,獲得設(shè)備支持的功能(capture、output、overlay…)

ID 描述
VIDIOC_QUERYCAP 查詢設(shè)備功能
struct?v4l2_capability
{
?u8?driver[16];?//?驅(qū)動名字
?u8?card[32];?//?設(shè)備名字
?u8?bus_info[32];?//?設(shè)備在系統(tǒng)中的位置
?u32?version;?//?驅(qū)動版本號
?u32?capabilities;?//?設(shè)備支持的操作
?u32?reserved[4];?//?保留字段
};

capabilities 常用值:

V4L2_CAP_VIDEO_CAPTURE?//?是否支持圖像獲取

2)Application Priority

應(yīng)用優(yōu)先級

當(dāng)多個應(yīng)用程序共享設(shè)備時,可能需要為它們分配不同的優(yōu)先級。視頻錄制應(yīng)用程序可以例如阻止其他應(yīng)用程序改變視頻控制或切換當(dāng)前的電視頻道。

另一個目標(biāo)是允許在后臺工作的低優(yōu)先級應(yīng)用程序,這些應(yīng)用程序可以被用戶控制的應(yīng)用程序搶占,并在以后自動重新獲得對設(shè)備的控制

ID 描述
VIDIOC_G_PRIORITY 獲取優(yōu)先級
VIDIOC_S_PRIORITY 設(shè)置優(yōu)先級

3)Device Inputs and Outputs

輸入和輸出設(shè)備

ID 描述
VIDIOC_ENUMINPUT 枚舉視頻輸入設(shè)備
VIDIOC_G_INPUT 獲取當(dāng)前的視頻輸入設(shè)備
VIDIOC_S_INPUT 設(shè)置視頻輸入設(shè)備
VIDIOC_ENUMOUTPUT 枚舉視頻輸出設(shè)備
VIDIOC_G_OUTPUT 獲取當(dāng)前視頻輸出設(shè)備
VIDIOC_S_OUTPUT 設(shè)置視頻輸出設(shè)備
VIDIOC_ENUMAUDIO 枚舉音頻輸入設(shè)備
VIDIOC_G_AUDIO 獲取當(dāng)前音頻輸入設(shè)備
VIDIOC_S_AUDIO 設(shè)置音頻輸入設(shè)備
VIDIOC_ENUMAUDOUT 枚舉音頻輸出設(shè)備
VIDIOC_G_OUTPUT 獲取音頻輸出設(shè)備
VIDIOC_S_AUDOUT 設(shè)置音頻輸出設(shè)備

VIDIOC_G_INPUT 和 VIDIOC_S_INPUT 用來查詢和選則當(dāng)前的 input,一個 video 設(shè)備 節(jié)點(diǎn)可能對應(yīng)多個視頻源,比如 saf7113 可以最多支持四路 cvbs 輸入,如果上層想在四 個cvbs視頻輸入間切換,那么就要調(diào)用 ioctl(fd, VIDIOC_S_INPUT, &input) 來切換。

VIDIOC_G_INPUT and VIDIOC_G_OUTPUT 返回當(dāng)前的 video input和output的index.

struct?v4l2_input?{
?__u32?index;?/*?Which?input?*/
?__u8?name[32];?/*?Label?*/
?__u32?type;?/*?Type?of?input?*/
?__u32?audioset;?/*?Associated?audios?(bitfield)?*/
?__u32?tuner;?/*?Associated?tuner?*/
?v4l2_std_id?std;
?__u32?status;
?__u32?reserved[4];
};

我們可以通過VIDIOC_ENUMINPUT and VIDIOC_ENUMOUTPUT 分別列舉一個input或者 output的信息,我們使用一個v4l2_input結(jié)構(gòu)體來存放查詢結(jié)果,這個結(jié)構(gòu)體中有一個 index域用來指定你索要查詢的是第幾個input/ouput,如果你所查詢的這個input是當(dāng)前正 在使用的,那么在v4l2_input還會包含一些當(dāng)前的狀態(tài)信息,如果所 查詢的input/output 不存在,那么回返回EINVAL錯誤,所以,我們通過循環(huán)查找,直到返回錯誤來遍歷所有的 input/output. VIDIOC_G_INPUT and VIDIOC_G_OUTPUT 返回當(dāng)前的video input和output 的index.

4) Video Standards

視頻標(biāo)準(zhǔn)

ID 描述
VIDIOC_ENUMSTD 枚舉設(shè)備支持的所有標(biāo)準(zhǔn)
VIDIOC_G_STD 獲取當(dāng)前正在使用的標(biāo)準(zhǔn)
VIDIOC_S_STD 設(shè)置視頻標(biāo)準(zhǔn)
VIDIOC_QUERYSTD 有的設(shè)備支持自動偵測輸入源的視頻標(biāo)準(zhǔn),此ioctl獲取檢測到的標(biāo)準(zhǔn)
typedef?u64?v4l2_std_id;
?struct?v4l2_standard?{
?u32?index;
?v4l2_std_id?id;
?u8?name[24];
?struct?v4l2_fract?frameperiod;?/*?Frames,?not?fields?*/
?u32?framelines;
?u32?reserved[4];
};

當(dāng)然世界上現(xiàn)在有多個視頻標(biāo)準(zhǔn),如NTSC和PAL,他們又細(xì)分為好多種,那么我們的設(shè) 備輸入/輸出究竟支持什么樣的標(biāo)準(zhǔn)呢?我們的當(dāng)前在使用的輸入和輸出正在使用的是哪 個標(biāo)準(zhǔn)呢?我們怎么設(shè)置我們的某個輸入輸出使用的標(biāo)準(zhǔn)呢?這都是有方法的。

查詢我們的輸入支持什么標(biāo)準(zhǔn),首先就得找到當(dāng)前的這個輸入的index,然后查出它的 屬性,在其屬性里面可以得到該輸入所支持的標(biāo)準(zhǔn),將它所支持的各個標(biāo)準(zhǔn)與所有的標(biāo)準(zhǔn) 的信息進(jìn)行比較,就可以獲知所支持的各個標(biāo)準(zhǔn)的屬性。一個輸入所支持的標(biāo)準(zhǔn)應(yīng)該是一 個集合,而這個集合是用bit與的方式用一個64位數(shù)字表示。因此我們所查到的是一個數(shù)字。

5) Camera Control Reference

控制屬性

ID 描述
VIDIOC_QUERYCTRL 查詢指定的control詳細(xì)信息
VIDIOC_QUERYMENU 查詢menu
VIDIOC_G_CTRL 獲取設(shè)備指定control的當(dāng)前信息
VIDIOC_S_CTRL 設(shè)置設(shè)備指定的control

6) Image Format

圖像格式

圖像由多種格式Y(jié)UV和RGB還有壓縮格式等等,其中每種格式又分有多種格式,比如RGB:RGB565、RGB888…

所以在使用設(shè)備時,需要對格式進(jìn)行設(shè)置

ID 描述
VIDIOC_ENUM_FMT 枚舉設(shè)備支持的圖像格式
VIDIOC_G_FMT 獲取當(dāng)前設(shè)備的圖像格式
VIDIOC_S_FMT 設(shè)置圖像格式
VIDIOC_TRY_FMT 測試設(shè)備是否支持此格式

查詢并顯示所有支持的格式:VIDIOC_ENUM_FMT

struct?v4l2_fmtdesc
{
?u32?index;?//?要查詢的格式序號,應(yīng)用程序設(shè)置
?enum?v4l2_buf_type?type;?//?幀類型,應(yīng)用程序設(shè)置
?u32?flags;?//?是否為壓縮格式
?u8?description[32];?//?格式名稱
?u32?pixelformat;?//?格式
?u32?reserved[4];?//?保留
};

查看或設(shè)置當(dāng)前格式: VIDIOC_G_FMT, VIDIOC_S_FMT

struct?v4l2_format
{
?enum?v4l2_buf_type?type;?//?幀類型,應(yīng)用程序設(shè)置
?union?fmt
?{
??struct?v4l2_pix_format?pix;?//?視頻設(shè)備使用?
??struct?v4l2_window?win;?
??struct?v4l2_vbi_format?vbi;?
??struct?v4l2_sliced_vbi_format?sliced;?
??u8?raw_data[200];?
?};
};

struct?v4l2_pix_format
{
?u32?width;?//?幀寬,單位像素?
?u32?height;?//?幀高,單位像素?
?u32?pixelformat;?//?幀格式?
?enum?v4l2_field?field;?
?u32?bytesperline;?
?u32?sizeimage;?
?enum?v4l2_colorspace?colorspace;?
?u32?priv;
};

7) Cropping, composing and scaling

圖像裁剪、插入與縮放

ID 描述
VIDIOC_CROPCAP 獲取圖像裁剪縮放能力
VIDIOC_G_CROP 獲取當(dāng)前的裁剪矩陣
VIDIOC_S_CROP 設(shè)置裁剪矩陣

Cropping 和 scaling 主要指的是圖像的取景范圍及圖片的比例縮放的支持。Crop 就 是把得到的數(shù)據(jù)作一定的裁剪和伸縮,裁剪可以只取樣我們可以得到的圖像大小的一部分, 剪裁的主要參數(shù)是位置、長度、寬度。而 scale 的設(shè)置是通過 VIDIOC_G_FMT 和 VIDIOC_S_FMT 來獲得和設(shè)置當(dāng)前的 image 的長度,寬度來實(shí)現(xiàn)的。
看下圖:

我們可以假設(shè) bounds 是 sensor 最大能捕捉到的圖像范圍,而 defrect 是設(shè)備默認(rèn) 的最大取樣范圍,這個可以通過 VIDIOC_CROPCAP 的 ioctl 來獲得設(shè)備的 crap 相關(guān)的屬 性 v4l2_cropcap,其中的 bounds 就是這個 bounds,其實(shí)就是上限。每個設(shè)備都有個默 認(rèn)的取樣范圍,就是 defrect,就是 default rect 的意思,它比 bounds 要小一些。這 個范圍也是通過 VIDIOC_CROPCAP 的 ioctl 來獲得的 v4l2_cropcap 結(jié)構(gòu)中的 defrect 來表示的,我們可以通過 VIDIOC_G_CROP 和 VIDIOC_S_CROP 來獲取和設(shè)置設(shè)備當(dāng)前的 crop 設(shè)置。

設(shè)置設(shè)備捕捉能力的參數(shù)

struct?v4l2_cropcap
{
?enum?v4l2_buf_type?type;?//?數(shù)據(jù)流的類型,應(yīng)用程序設(shè)置
?struct?v4l2_rect?bounds;?//?這是?camera?的鏡頭能捕捉到的窗口大小的局限
?struct?v4l2_rect?defrect;?//?定義默認(rèn)窗口大小,包括起點(diǎn)位置及長,寬的大小,大小以像素為單位
?struct?v4l2_fract?pixelaspect;?//?定義了圖片的寬高比
};

設(shè)置窗口取景參數(shù) VIDIOC_G_CROP 和 VIDIOC_S_CROP

struct?v4l2_crop
{
?enum?v4l2_buf_type?type;//?應(yīng)用程序設(shè)置
?struct?v4l2_rect?c;
}

8) ?buf Input/Output

數(shù)據(jù)的輸入和輸出

內(nèi)核中使用緩存隊(duì)列對圖像數(shù)據(jù)進(jìn)行管理,用戶空間獲取圖像數(shù)據(jù)有兩種方式,一種是通過read、write方式讀取內(nèi)核空間的緩存,一種是將內(nèi)核空間的緩存映射到用戶空間,即streaming。在操作v4l2設(shè)備時,通過VIDIOC_QUERYCAP獲取設(shè)備支持哪種方式。
streaming就是在內(nèi)核空間中維護(hù)一個緩存隊(duì)列,然后將內(nèi)存映射到用戶空間,應(yīng)用讀取圖像數(shù)據(jù)就是一個不斷地出隊(duì)列和入隊(duì)列的過程,如下圖所示

ID 描述
VIDIOC_REQBUFS 申請緩存
VIDIOC_QUERYBUF 獲取緩存信息
VIDIOC_QBUF 將緩存放入隊(duì)列中
VIDIOC_DQBUF 將緩存從隊(duì)列中取出

1. 向設(shè)備申請緩沖區(qū) VIDIOC_REQBUFS

struct?v4l2_requestbuffers
{
?u32?count;?//?緩沖區(qū)內(nèi)緩沖幀的數(shù)目
?enum?v4l2_buf_type?type;?//?緩沖幀數(shù)據(jù)格式
?enum?v4l2_memory?memory;?//?區(qū)別是內(nèi)存映射還是用戶指針方式
?u32?reserved[2];
};
enum?v4l2_memoy
{
?V4L2_MEMORY_MMAP,?V4L2_MEMORY_USERPTR
};

獲取緩沖幀的地址,長度:VIDIOC_QUERYBUF

struct?v4l2_buffer
{
?u32?index;?//buffer?序號
?enum?v4l2_buf_type?type;?//buffer?類型
?u32?byteused;?//buffer?中已使用的字節(jié)數(shù)
?u32?flags;?//?區(qū)分是MMAP?還是USERPTR?
?enum?v4l2_field?field;?
?struct?timeval?timestamp;?//?獲取第一個字節(jié)時的系統(tǒng)時間?
?struct?v4l2_timecode?timecode;?
?u32?sequence;?//?隊(duì)列中的序號?
?enum?v4l2_memory?memory;?//IO?方式,被應(yīng)用程序設(shè)置?
?union?m?
?{?
??u32?offset;?//?緩沖幀地址,只對MMAP?有效?
??unsigned?long?userptr;?
?};?
?u32?length;?//?緩沖幀長度?
?u32?input;?
?u32?reserved;
};

2. 內(nèi)存映射MMAP 及定義一個結(jié)構(gòu)體來映射每個緩沖幀。相關(guān)結(jié)構(gòu)體:

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

相關(guān)函數(shù):

#include?<sys/mman.h>

void?*mmap(void?*addr,?size_t?length,?int?prot,?int?flags,?int?fd,?off_t?offset)
參數(shù):
addr?映射起始地址,一般為NULL?,讓內(nèi)核自動選擇
length?被映射內(nèi)存塊的長度
prot?標(biāo)志映射后能否被讀寫,其值為PROT_EXEC,PROT_READ,PROT_WRITE,?PROT_NONE
flags?確定此內(nèi)存映射能否被其他進(jìn)程共享,MAP_SHARED,MAP_PRIVATE
fd,offset,?確定被映射的內(nèi)存地址?返回成功映射后的地址,不成功返回MAP_FAILED?((void*)-1)

3.將所有的緩存放入隊(duì)列

struct?v4l2_buffer?v4l2_buffer;

for(i?=?0;?i?<?nr_bufs;?i++)
{
?memset(&v4l2_buffer,?0,?sizeof(struct?v4l2_buffer));
?v4l2_buffer.index?=?i;?//想要放入隊(duì)列的緩存
?v4l2_buffer.type?=?V4L2_BUF_TYPE_VIDEO_CAPTURE;
?v4l2_buffer.memory?=?V4L2_MEMORY_MMAP;?

????ret?=?ioctl(fd,?VIDIOC_QBUF,?&v4l2_buffer);
????if(ret?<?0)
????{
????????printf("Unable?to?queue?buffer.n");
????????return?-1;
????}
}

9)啟動 或 停止數(shù)據(jù)流

VIDIOC_STREAMON, VIDIOC_STREAMOFF

type?=?V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl?(fd,?VIDIOC_STREAMON,?&type);

5. v4l2設(shè)備抓幀程序編寫

v4l2設(shè)備的命令比較多,其實(shí)常用的并不是很多,下面通過一個實(shí)例來詳細(xì)講解,如何操作v4l2設(shè)備。

1)設(shè)備配置

本例,將常用的攝像頭配置參數(shù)等裝成結(jié)構(gòu)體:

struct?v4l2_dev
{
????int?fd;???//videoO對應(yīng)的設(shè)備描述符
????int?sub_fd;?
????const?char?*path;?//字符設(shè)備?/dev/videoO
????const?char?*name;?//攝像頭名稱
????const?char?*subdev_path;//字符設(shè)備?/dev/v4l-subdev3
????const?char?*out_type;?//輸出圖像格式
????enum?v4l2_buf_type?buf_type;//緩存類型
????int?format;?//像素格式
????int?width;??//圖像寬度
????int?height;?//圖像高度
????unsigned?int?req_count;?//緩存數(shù)量
????enum?v4l2_memory?memory_type;?//讀取圖像的方法,DMA還是MMAP
????struct?buffer?*buffers;?//緩沖區(qū)
????unsigned?long?int?timestamp;//時長度
????int?data_len;//圖像數(shù)據(jù)長度
????unsigned?char?*out_data;//圖像數(shù)據(jù)
};

本例填寫的攝像頭ov13850的配置信息如下:

struct?v4l2_dev?ov13850?=?{
????.fd?=?-1,
????.sub_fd?=?-1,
????.path?=?"/dev/video0",
????.name?=?"ov13850",
????.subdev_path?=?"/dev/v4l-subdev3",
????.out_type?=?"nv12",
????.buf_type?=?V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
????.format?=?V4L2_PIX_FMT_NV12,
????.width?=?800,
????.height?=?600,
????.req_count?=?4,
????.memory_type?=?V4L2_MEMORY_MMAP,
????.buffers?=?NULL,
????.timestamp?=?0,
????.data_len?=?0,
????.out_data?=?NULL,
};

2)v4l2設(shè)備一般操作流程(抓幀)

v4l2設(shè)備一般操作流程如下圖所示:

各功能對應(yīng)的ioctrl命令如下:

測試程序一口君已經(jīng)上傳到gitee:

https://gitee.com/yikoulinux/v4l2-app

git?clone?git@gitee.com:yikoulinux/v4l2-app.git

歡迎各位老鐵star。

3)程序執(zhí)行l(wèi)og

以下是在瑞芯微rk3568實(shí)際測試的log。

rk3568_r:/?#?/data/capture???????????????????????????????????????
/data/capture????????????????????????????????????????????????????
Open?/dev/video0?succeed?-?3?????????????????????????????????????
?????????????????????????????????????????????????????????????????
Open?/dev/v4l-subdev3?succeed????????????????????????????????????
?????????????????????????????????????????????????????????????????
-------?VIDIOC_QUERYCAP?----?????????????????????????????????????
??driver:?rkisp_v5???????????????????????????????????????????????
??card:?rkisp_mainpath???????????????????????????????????????????
??bus_info:?platform:rkisp-vir0??????????????????????????????????
??version:?1.8.0?????????????????????????????????????????????????
??capabilities:?84201000?????????????????????????????????????????
????????Video?Capture?Mplane?????????????????????????????????????
????????Streaming????????????????????????????????????????????????
?????????????????????????????????????????????????????????????????
VIDIOC_S_FMT?succeed!????????????????????????????????????????????
width?800,?height?600,?size?720000,?bytesperline?0,?format?NV12??
?????????????????????????????????????????????????????????????????
VIDIOC_REQBUFS?succeed!??????????????????????????????????????????
?????????????????????????????????????????????????????????????????
Memory?map?succeed!??????????????????????????????????????????????
?????????????????????????????????????????????????????????????????
VIDIOC_QBUF?succeed!?????????????????????????????????????????????
?????????????????????????????????????????????????????????????????
VIDIOC_STREAMON?succeed!?????????????????????????????????????????
?????????????????????????????????????????????????????????????????
image:?sequence?=?0,?timestamp?=?1115378780??????????????????????
image:?sequence?=?1,?timestamp?=?1115511890??????????????????????
image:?sequence?=?2,?timestamp?=?1115645004??????????????????????
image:?sequence?=?3,?timestamp?=?1115778130??????????????????????
image:?sequence?=?4,?timestamp?=?1115911257??????????????????????
image:?sequence?=?5,?timestamp?=?1116044365??????????????????????
image:?sequence?=?6,?timestamp?=?1116177498??????????????????????
image:?sequence?=?7,?timestamp?=?1116310610??????????????????????
image:?sequence?=?8,?timestamp?=?1116443739??????????????????????
image:?sequence?=?9,?timestamp?=?1116576844??????????????????????
Save?one?frame?to?/data/ov13850.nv12?succeed!????????????????????
?????????????????????????????????????????????????????????????????
VIDIOC_STREAMOFF?succeed!????????????????????????????????????????

掌握了這些命令,我們就可以調(diào)試攝像頭了。

后面還會繼續(xù)更新幾篇Camera文章,

建議大家訂閱本專題!

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

公眾號『一口Linux』號主彭老師,擁有15年嵌入式開發(fā)經(jīng)驗(yàn)和培訓(xùn)經(jīng)驗(yàn)。曾任職ZTE,某研究所,華清遠(yuǎn)見教學(xué)總監(jiān)。擁有多篇網(wǎng)絡(luò)協(xié)議相關(guān)專利和軟件著作。精通計算機(jī)網(wǎng)絡(luò)、Linux系統(tǒng)編程、ARM、Linux驅(qū)動、龍芯、物聯(lián)網(wǎng)。原創(chuàng)內(nèi)容基本從實(shí)際項(xiàng)目出發(fā),保持原理+實(shí)踐風(fēng)格,適合Linux驅(qū)動新手入門和技術(shù)進(jìn)階。