Video4linux 編程

Video4linux 編程
2.1 Video4linux 簡介
Video4Linux是爲市場現在常見的電視捕獲卡和並口及USB口的攝像頭提供
統一的編程接口。同時也提供無線電通信和文字電視廣播解碼和垂直消隱的數據
接口。本文主要針對USB攝像頭設備文件/dev/video0,進行視頻圖像採集方面的
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 4 -
程序設計。
2.2 Video4linux 編程指南
1.視頻編程的流程
(1)打開視頻設備:
(2)讀取設備信息
(3)更改設備當前設置(可以不做)
(4)進行視頻採集,兩種方法:
a.內存映射
b.直接從設備讀取
(5)對採集的視頻進行處理
(6)關閉視頻設備。
定義的數據結構及使用函數
struct _v4l_struct
{
int fd;
struct video_capability capability;
struct video_buffer buffer;
struct video_window window;
struct video_channel channel[8];
struct video_picture picture;
struct video_mmap mmap;
struct video_mbuf mbuf;
unsigned char *map;
};
typedef struct _v4l_struct v4l_device;
extern int v4l_open(char *, v4l_device *);
extern int v4l_close(v4l_device *);
extern int v4l_get_capability(v4l_device *);
extern int v4l_set_norm(v4l_device *, int);
extern int v4l_get_picture(v4l_device *);
extern int v4l_grab_init(v4l_device *, int, int);
extern int v4l_grab_frame(v4l_device *, int);
extern int v4l_grab_sync(v4l_device *);
extern int v4l_mmap_init(v4l_device *);
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 5 -
extern int v4l_get_mbuf(v4l_device *);
extern int v4l_get_picture(v4l_device *);
extern int v4l_grab_picture(v4l_device *, unsigned int);
extern int v4l_set_buffer(v4l_device *);
extern int v4l_get_buffer(v4l_device *);
extern int v4l_switch_channel(v4l_device *, int);
3.Video4linux支持的數據結構及其用途
(1)video_capability 包含設備的基本信息(設備名稱、支持的最大最小分辨
率、信號源信息等)
name[32] 設備名稱
maxwidth
maxheight
minwidth
minheight
Channels 信號源個數
type 是否能capture , 彩色還是黑白, 是否能裁剪等等。值如
VID_TYPE_CAPTURE等
(2)video_picture 設備採集的圖象的各種屬性
Brightness 0~65535
hue
colour
contrast
whiteness
depth 8 16 24 32
palette VIDEO_PALETTE_RGB24 | VIDEO_PALETTE_RGB565|
VIDEO_PALETTE_JPEG| VIDEO_PALETTE_RGB32
(3)video_channel 關於各個信號源的屬性
Channel 信號源的編號
name
tuners
Type VIDEO_TYPE_TV | IDEO_TYPE_CAMERA
Norm 制式 PAL|NSTC|SECAM|AUTO
(4)video_window 包含關於capture area的信息
x x windows 中的座標.
y y windows 中的座標.
width The width of the image capture.
height The height of the image capture.
chromakey A host order RGB32 value for the chroma key.
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 6 -
flags Additional capture flags.
clips A list of clipping rectangles. (Set only)
clipcount The number of clipping rectangles. (Set only)
(5)video_mbuf 利用mmap進行映射的幀的信息
size 每幀大小
Frames 最多支持的幀數
Offsets 每幀相對基址的偏移
(6)video_mmap 用於mmap
4.關鍵步驟介紹
【注】接多個攝像頭。方法如下:買一個usb hub接到開發板的usb host上。cat
/proc/devices可以知道video capture device的major是81,再ls –l /dev看到video0
的次設備號是0。兩個攝像頭當然要兩個設備號,所以mknod /dev/video1 c 81 1,
如果接4個,就mknod /dev/video2 c 81 2,mknod /dev/video3 c 81 3。依次類推。
(1)打開視頻:
int v4l_open(char *dev, v4l_device *vd)
{
if (!dev)
dev = ”/dev/video0”;
if ((vd ->fd = open(dev, O_RDWR)) < 0) {
perror("v4l_open:");
return -1;
}
if (v4l_get_capability(vd))
return -1;
if (v4l_get_picture(vd))
retu rn -1;
return 0;
}
(2)讀video_capability 中信息
int v4l_get_capability(v4l_device *vd)
{
if (ioctl(vd ->fd, VIDIOCGCAP, &(vd->capability)) < 0) {
perror("v4l_get_capability:");
return -1;
}
return 0;
}
成功後可讀取vd->capability各分量
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 7 -
(3)讀video_picture中信息
int v4l_get_picture(v4l_device *vd)
{
if (ioctl(vd ->fd, VIDIOCGPICT, &(vd->picture)) < 0) {
perror("v4l_get_picture:");
return -1;
}
return 0;
}
成功後可讀取圖像的屬性
(4)改變video_picture中分量的值 (可以不做的)
先爲分量賦新值,再調用VIDIOCSPICT
vd->picture.colour = 65535;
if(ioctl(vd->fd, VIDIOCSPICT, &(vd->picture)) < 0)
{
perror("VIDIOCSPICT");
return -1;
}
(5)初始化channel (可以不做的)
必須先做得到vd->capability中的信息
int v4l_get_channels(v4l_device *vd)
{
int i;
for (i = 0; i < vd ->capability.channels; i++) {
vd ->channel.channel = i;
if (ioctl(vd ->fd, VIDIOCGCHAN, &(vd->channel)) < 0) {
perror("v4l_get_channel:");
return -1;
}
}
return 0;
}
(6)關閉設備
int v4l_close(v4l_device *vd)
{
close(vd ->fd);
return 0;
}
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 8 -
重點:截取圖象的兩種方法
一、用mmap(內存映射)方式截取視頻
mmap( )系統調用使得進程之間通過映射同一個普通文件實現共享內存。普
通文件被映射到進程地址空間後,進程可以向訪問普通內存一樣對文件進行訪
問,不必再調用read(),write()等操作。兩個不同進程A、B共享內存的意思是,
同一塊物理內存被映射到進程A、B各自的進程地址空間。進程A可以即時看到進
程B對共享內存中數據的更新,反之亦然。
採用共享內存通信的一個顯而易見的好處是效率高,因爲進程可以直接讀寫
內存,而不需要任何數據的拷貝
(1)設置picture的屬性
(2) 初始化video_mbuf,以得到所映射的buffer的信息
ioctl(vd->fd, VIDIOCGMBUF, &(vd->mbuf))
(3)可以修改video_mmap和幀狀態的當前設置
(4)將mmap與video_mbuf綁定
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
len:映射到調用進程地址空間的字節數,它從被映射文件開頭offset個字節開始
算起
Prot:指定共享內存的訪問權限 PROT_READ(可 讀), PROT_WRITE (可寫),
PROT_EXEC (可執行)
Flags:MAP_SHARED MAP_PRIVATE中必選一個,MAP_ FIXED不推薦使用
Addr:共內存享的起始地址,一般設0,表示由系統分配
Mmap( ) 返回值是系統實際分配的起始地址
int v4l_mmap_init(v4l_device *vd)
{
if (v4l_get_mbuf(vd) < 0)
return -1;
if ((vd ->map = mmap(0, vd->mbuf.size, PROT_READ|PROT_WRITE,
MAP_SHARED, vd->fd, 0)) < 0) {
perror("v4 l_mmap_init:mmap");
return -1;
}
return 0;
}
(5)Mmap方式下真正做視頻截取的 VIDIOCMCAPTURE
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 9 -
ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) ;
若調用成功,開始一幀的截取,是非阻塞的,
是否截取完畢留給VIDIOCSYNC來判斷
(6)調用VIDIOCSYNC等待一幀截取結束
if(ioctl(vd->fd, VIDIOCSYNC, &frame) < 0)
{
perror("v4l_sync:VIDIOCSYNC");
return -1;
}
若成功,表明一幀截取已完成。可以開始做下一次 VIDIOCMCAPTURE
frame是當前截取的幀的序號。
********關於雙緩衝************
video_bmuf bmuf.frames = 2;一幀被處理時可以採集另一幀
int frame; //當前採集的是哪一幀
int framestat[2]; //幀的狀態 沒開始採集|等待採集結束
幀的地址由vd->map + vd->mbuf.offsets[vd->frame]得到。
採集工作結束後調用munmap取消綁定
munmap(vd->map, vd->mbuf.size)
在實際應用時還可以採用緩衝隊列等方式。
二、視頻截取的第二種方法:直接讀設備
關於緩衝大小,圖象等的屬性須由使用者事先設置
調用read();
int read (要訪問的文件描述符;指向要讀寫的信息的指針;應該讀寫的字符數);
返回值爲實際讀寫的字符數
int len ;
unsigned char
*vd->map=
(unsigned char *) malloc(vd◊capability.maxwidth*vd◊capability.maxheight );
len = read(vd◊fd,vd◊ vd->map,
vd◊capability.maxwidth*vd◊capability.maxheight*3 );
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 10 -
2.3 編程實例(mouse_capture)
不管是ov511還是zc301的攝像頭,它們採集的方式都是相同的,只不過採集
到的數據有所差異,ov511的就是rgb的位流,而zc301是jpeg編碼的位流。
mouse_capture是根據servfox改編的一個專門從zc301攝像頭獲取一張jpeg圖片,
用來測試攝像頭是否加載成功的小程序。這樣就可以不用cat /dev/video0>1.jpg來
測試攝像頭是否正常。cat命令一運行,就源源不斷地採集jpeg流。但是採到的圖
片只能顯示第一個jpeg頭和jpeg尾之間的數據。mouse_capture僅僅獲得一張完整
的jpeg。 可以從
http://www.hhcn.com/cgi-bin/topic.cgi?forum=1&topic=247&start=144&show=0
處下載參考。
現將主要函數的功能介紹如下:
static int GetVideoPict (struct vdIn *vd);//獲取圖片屬性信息。
static int SetVideoPict (struct vdIn *vd);//設置圖片屬性。
static int isSpcaChip (const char *BridgeName);//測試芯片類型
static int GetStreamId (const char *BridgeName); //測試輸出數據的格式
static int GetDepth (int format);//獲取顏色深度。
void exit_fatal(char *messages);//錯誤顯示。
int init_videoIn(struct vdIn *vd,char *device,int width,int height,int format,int
grabmethod);//初始化設備。
int convertframe(unsigned char *dst,unsigned char *src, int width,int height, int
formatIn, int size);//把共享緩衝區中的數據放到一個變量中,通知系統已獲得一
幀。
int v4lGrab (struct vdIn *vd,char *filename );//從攝像頭採集圖片。
int close_v4l (struct vdIn *vd);//關閉攝像頭。
int get_jpegsize (unsigned char *buf, int insize);//獲取jpeg圖片大小。
三 實例程序
3.1 LCD 實時顯示從ov511 上採集的圖像
參考HHARM9-EDU/applications/usbcam2lcd。從攝像頭獲取bmp位流直接顯示
在framebuffer中。此程序圖像的採集採用read的方式,注意由於lcd液晶屏顯示的
是16bits的RGB圖片,所以,ov511輸出的圖片格式也應該是16bits的RGB圖片數
據,宏VIDEO_PALETTE_RGB565定義的就是16bits的RGB數據圖片。而linux自
帶的ov511驅動中圖像採集是32位的,這樣採集到的圖片顯示在lcd上是雪花點。
因此需要修改驅動。 在kernet/driver/usb/目錄下有ov511芯片的驅動ov511.c,驅
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 11 -
動裏的ov51x_set_default_params函數是設置芯片默認的輸出圖片的格式,該函數
中的
for (i = 0; i < OV511_NUMFRAMES; i++)
{
ov511->frame.width = ov511->maxwidth;
ov511->frame.height = ov511->maxheight;
ov511->frame.bytes_read = 0;
if (force_palette)
ov511->frame.format = force_palette;
else
ov511->frame.format = VIDEO_PALETTE_RGB24;
ov511->frame.depth = ov511_get_depth(ov511->frame.format);
}
部分語句是主要設置ov511默認輸出圖片格式的,其中maxwidth和maxheight
設置了圖片的最大的寬度和高度。Ifelse語句設置了圖片的格式,作如下的修改:
for (i = 0; i < OV511_NUMFRAMES; i++)
{
ov511->frame.width = ov511->maxwidth;
ov511->frame.height = ov511->maxheight;
ov511->frame.bytes_read = 0;
ov511->frame.format = VIDEO_PALETTE_RGB565;
ov511->frame.depth = ov511_get_depth(ov511->frame.format);
}
如果需要,也可以改變圖片的默認輸出大小。
3.2 LCD 實時顯示從zc301 上採集的圖像
編程思想:從攝像頭採集到的圖片存放在本地文件夾,通過minigui加載jpeg
來實現顯示。
具體過程:
1.從網上下載jpegsrc-6b的jpeg庫,交叉編譯。
(1)./configure –enable-static –enable-shared –prefix=.libs
(2)修改Makefile,將編譯器改成交叉編譯器。
例如:我改成/opt/host/armv4l/bin/armv4l-unknown-linux-gcc
(3)make 後即在.libs目錄中生成for arm的
libjpeg.a, libjpeg.la,libjpeg.so,libjpeg.so.62,libjpeg.so.62.0.0。將這些文件拷貝到系
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 12 -
統庫文件目錄,我的是/usr/lib中。
2.因爲看從zc301採集的圖片的二進制位流,jpeg頭是ff d8 ff db。而在minigui庫
文件libminigui的源文件src/mybmp/jpeg.c中,load_jpg和check_jpg的時候測試的頭
位EXIF和JFIF兩種格式的jpeg圖片。這兩種對應的二進制分別是ff d8 ff e1和ff d8
ff e0。所以我們minigui通過判斷認爲這是錯誤的jpeg格式而不加載,故無法顯示。
實際上通過測試,在源碼中去掉這兩個判斷就能正確加載。
3.交叉編譯minigui
( 1 ) 編譯庫: ./configure --host=arm-unknown-linux --enable-jpgsupport=yes
--enable-pngsupport=no --enable-gifsupport=no --disable-lite
--prefix=/HHARM9-EDU/applications/minigui-free/nfsroot
--enable-smdk2410ial=yes
make
make install
(2)編譯實例程序時,要加上jpeg庫的支持,即在Makefile中加上-ljpeg。此時
將在nfsroot生成的庫文件和可執行文件移到ramdisk.image.gz相應的目錄下。(具
體參考華恆的2410開發手冊)。
3.Minigui程序的編寫
編程小技巧,我採取的方法是一刻不停地從攝像頭採集到圖片存儲在
/tmp/1.jpg中,在minigui中通過loadbitmap函數來加載圖片。而圖片加載後不會自
動更新,不能自動根據1.jpg的改變自動變化。因此,我在程序中設定一個timer。
每隔100ms刷新屏幕,基本上實現實時更新了。而出現另外一個問題,刷新時會
以背景色來填充桌面,導致屏幕閃爍嚴重。故想到採用MSG_ERASEBKGND的
方式,用前一張圖片做爲刷新屏幕時的填充背景圖片。這樣就保證了lcd上圖像
的連續性啦。
Minigui程序如下:其中一些自定義的函數跟mouse_capture中的一樣,只是
變採集單幅到採集多幅。具體您可以自己改一下:)。也可以向我索取源碼。
#include <minigui/common.h>
#include <minigui/minigui.h>
#include <minigui/gdi.h>
#include <minigui/window.h>
#include <minigui/control.h>
#include "spcav4l.h"
#define IDTIMER 100
static BITMAP bmp;
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 13 -
static int LoadBmpWinProc(HWND hWnd, int message, WPARAM wParam,
LPARAM lParam)
{
HDC hdc;
RECT rc={0,0,240,320};
switch (message) {
case MSG_CREATE:
SetTimer(hWnd,IDTIMER,100);
return 0;
case MSG_ERASEBKGND:
{
RECT rcTemp;
if( LoadBitmap(HDC_SCREEN,&bmp,"/tmp/1.jpg"))
{
printf("load wrong!\n");
return -1;
}
GetClientRect(hWnd, &rcTemp);
hdc = BeginPaint (hWnd);
FillBoxWithBitmap (hdc, rcTemp.left, rcTemp.top, rcTemp.right-rcTemp.left,
rcTemp.bottom-rcTemp.top, &bmp);
EndPaint(hWnd, hdc);
return 0;
}
case MSG_TIMER:
InvalidateRect(hWnd,&rc,TRUE);
return 0;
case MSG_CLOSE:
UnloadBitmap (&bmp);
DestroyMainWindow (hWnd);
PostQuitMessage (hWnd);
return 0;
}
return DefaultMainWinProc(hWnd, message, wParam, lParam);
}
int MiniGUIMain (int argc, const char* argv[])
{
MSG Msg;
HWND hMainWnd;
MAINWINCREATE CreateInfo;
char videodevice[] = "/dev/video0";
char jpegfile[] = "/tmp/1.jpg";
int grabmethod = 0;
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 14 -
int format = VIDEO_PALETTE_JPEG;
int width = 240;
int height = 320;
int i;
#ifdef _LITE_VERSION
SetDesktopRect(0, 0, 1024, 768);
#endif
CreateInfo.dwStyle = WS_VISIBLE | WS_BORDER | WS_CAPTION;
CreateInfo.dwExStyle = WS_EX_NONE;
CreateInfo.spCaption = "Load and display a bitmap";
CreateInfo.hMenu = 0;
CreateInfo.hCursor = GetSystemCursor(0);
CreateInfo.hIcon = 0;
CreateInfo.MainWindowProc = LoadBmpWinProc;
CreateInfo.lx = 0;
CreateInfo.ty = 0;
CreateInfo.rx = 240;
CreateInfo.by = 320;
CreateInfo.iBkColor = PIXEL_lightwhite;
CreateInfo.dwAddData = 0;
CreateInfo.hHosting = HWND_DESKTOP;
hMainWnd = CreateMainWindow (&CreateInfo);
if (hMainWnd == HWND_INVALID)
return -1;
ShowWindow (hMainWnd, SW_SHOWNORMAL);
memset(&videoIn, 0, sizeof (struct vdIn));
if(init_videoIn(&videoIn, videodevice, width, height, format,grabmethod) == 0)
{
printf("init is ok!\n");
}
else printf("init is wrong!\n");
while (GetMessage(&Msg, hMainWnd)) {
TranslateMessage(&Msg);
v4lGrab(&videoIn, jpegfile);
DispatchMessage(&Msg);
}
close_v4l (&videoIn);
MainWindowThreadCleanup (hMainWnd);
return 0;
}
#ifndef _LITE_VERSION
#include <minigui/dti.c>
#endif
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 15 -
先寫到這裏吧,呵呵,希望能對您有所幫助。如果您在閱讀的過程中發現問
題,歡迎和我交流。
2006-7-7 晚
參考文獻
1.HHARM2410攝像頭調試記錄 華恆科技

Video4linux 編程
2.1 Video4linux 簡介
Video4Linux是爲市場現在常見的電視捕獲卡和並口及USB口的攝像頭提供
統一的編程接口。同時也提供無線電通信和文字電視廣播解碼和垂直消隱的數據
接口。本文主要針對USB攝像頭設備文件/dev/video0,進行視頻圖像採集方面的
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 4 -
程序設計。
2.2 Video4linux 編程指南
1.視頻編程的流程
(1)打開視頻設備:
(2)讀取設備信息
(3)更改設備當前設置(可以不做)
(4)進行視頻採集,兩種方法:
a.內存映射
b.直接從設備讀取
(5)對採集的視頻進行處理
(6)關閉視頻設備。
定義的數據結構及使用函數
struct _v4l_struct
{
int fd;
struct video_capability capability;
struct video_buffer buffer;
struct video_window window;
struct video_channel channel[8];
struct video_picture picture;
struct video_mmap mmap;
struct video_mbuf mbuf;
unsigned char *map;
};
typedef struct _v4l_struct v4l_device;
extern int v4l_open(char *, v4l_device *);
extern int v4l_close(v4l_device *);
extern int v4l_get_capability(v4l_device *);
extern int v4l_set_norm(v4l_device *, int);
extern int v4l_get_picture(v4l_device *);
extern int v4l_grab_init(v4l_device *, int, int);
extern int v4l_grab_frame(v4l_device *, int);
extern int v4l_grab_sync(v4l_device *);
extern int v4l_mmap_init(v4l_device *);
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 5 -
extern int v4l_get_mbuf(v4l_device *);
extern int v4l_get_picture(v4l_device *);
extern int v4l_grab_picture(v4l_device *, unsigned int);
extern int v4l_set_buffer(v4l_device *);
extern int v4l_get_buffer(v4l_device *);
extern int v4l_switch_channel(v4l_device *, int);
3.Video4linux支持的數據結構及其用途
(1)video_capability 包含設備的基本信息(設備名稱、支持的最大最小分辨
率、信號源信息等)
name[32] 設備名稱
maxwidth
maxheight
minwidth
minheight
Channels 信號源個數
type 是否能capture , 彩色還是黑白, 是否能裁剪等等。值如
VID_TYPE_CAPTURE等
(2)video_picture 設備採集的圖象的各種屬性
Brightness 0~65535
hue
colour
contrast
whiteness
depth 8 16 24 32
palette VIDEO_PALETTE_RGB24 | VIDEO_PALETTE_RGB565|
VIDEO_PALETTE_JPEG| VIDEO_PALETTE_RGB32
(3)video_channel 關於各個信號源的屬性
Channel 信號源的編號
name
tuners
Type VIDEO_TYPE_TV | IDEO_TYPE_CAMERA
Norm 制式 PAL|NSTC|SECAM|AUTO
(4)video_window 包含關於capture area的信息
x x windows 中的座標.
y y windows 中的座標.
width The width of the image capture.
height The height of the image capture.
chromakey A host order RGB32 value for the chroma key.
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 6 -
flags Additional capture flags.
clips A list of clipping rectangles. (Set only)
clipcount The number of clipping rectangles. (Set only)
(5)video_mbuf 利用mmap進行映射的幀的信息
size 每幀大小
Frames 最多支持的幀數
Offsets 每幀相對基址的偏移
(6)video_mmap 用於mmap
4.關鍵步驟介紹
【注】接多個攝像頭。方法如下:買一個usb hub接到開發板的usb host上。cat
/proc/devices可以知道video capture device的major是81,再ls –l /dev看到video0
的次設備號是0。兩個攝像頭當然要兩個設備號,所以mknod /dev/video1 c 81 1,
如果接4個,就mknod /dev/video2 c 81 2,mknod /dev/video3 c 81 3。依次類推。
(1)打開視頻:
int v4l_open(char *dev, v4l_device *vd)
{
if (!dev)
dev = ”/dev/video0”;
if ((vd ->fd = open(dev, O_RDWR)) < 0) {
perror("v4l_open:");
return -1;
}
if (v4l_get_capability(vd))
return -1;
if (v4l_get_picture(vd))
retu rn -1;
return 0;
}
(2)讀video_capability 中信息
int v4l_get_capability(v4l_device *vd)
{
if (ioctl(vd ->fd, VIDIOCGCAP, &(vd->capability)) < 0) {
perror("v4l_get_capability:");
return -1;
}
return 0;
}
成功後可讀取vd->capability各分量
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 7 -
(3)讀video_picture中信息
int v4l_get_picture(v4l_device *vd)
{
if (ioctl(vd ->fd, VIDIOCGPICT, &(vd->picture)) < 0) {
perror("v4l_get_picture:");
return -1;
}
return 0;
}
成功後可讀取圖像的屬性
(4)改變video_picture中分量的值 (可以不做的)
先爲分量賦新值,再調用VIDIOCSPICT
vd->picture.colour = 65535;
if(ioctl(vd->fd, VIDIOCSPICT, &(vd->picture)) < 0)
{
perror("VIDIOCSPICT");
return -1;
}
(5)初始化channel (可以不做的)
必須先做得到vd->capability中的信息
int v4l_get_channels(v4l_device *vd)
{
int i;
for (i = 0; i < vd ->capability.channels; i++) {
vd ->channel.channel = i;
if (ioctl(vd ->fd, VIDIOCGCHAN, &(vd->channel)) < 0) {
perror("v4l_get_channel:");
return -1;
}
}
return 0;
}
(6)關閉設備
int v4l_close(v4l_device *vd)
{
close(vd ->fd);
return 0;
}
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 8 -
重點:截取圖象的兩種方法
一、用mmap(內存映射)方式截取視頻
mmap( )系統調用使得進程之間通過映射同一個普通文件實現共享內存。普
通文件被映射到進程地址空間後,進程可以向訪問普通內存一樣對文件進行訪
問,不必再調用read(),write()等操作。兩個不同進程A、B共享內存的意思是,
同一塊物理內存被映射到進程A、B各自的進程地址空間。進程A可以即時看到進
程B對共享內存中數據的更新,反之亦然。
採用共享內存通信的一個顯而易見的好處是效率高,因爲進程可以直接讀寫
內存,而不需要任何數據的拷貝
(1)設置picture的屬性
(2) 初始化video_mbuf,以得到所映射的buffer的信息
ioctl(vd->fd, VIDIOCGMBUF, &(vd->mbuf))
(3)可以修改video_mmap和幀狀態的當前設置
(4)將mmap與video_mbuf綁定
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
len:映射到調用進程地址空間的字節數,它從被映射文件開頭offset個字節開始
算起
Prot:指定共享內存的訪問權限 PROT_READ(可 讀), PROT_WRITE (可寫),
PROT_EXEC (可執行)
Flags:MAP_SHARED MAP_PRIVATE中必選一個,MAP_ FIXED不推薦使用
Addr:共內存享的起始地址,一般設0,表示由系統分配
Mmap( ) 返回值是系統實際分配的起始地址
int v4l_mmap_init(v4l_device *vd)
{
if (v4l_get_mbuf(vd) < 0)
return -1;
if ((vd ->map = mmap(0, vd->mbuf.size, PROT_READ|PROT_WRITE,
MAP_SHARED, vd->fd, 0)) < 0) {
perror("v4 l_mmap_init:mmap");
return -1;
}
return 0;
}
(5)Mmap方式下真正做視頻截取的 VIDIOCMCAPTURE
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 9 -
ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) ;
若調用成功,開始一幀的截取,是非阻塞的,
是否截取完畢留給VIDIOCSYNC來判斷
(6)調用VIDIOCSYNC等待一幀截取結束
if(ioctl(vd->fd, VIDIOCSYNC, &frame) < 0)
{
perror("v4l_sync:VIDIOCSYNC");
return -1;
}
若成功,表明一幀截取已完成。可以開始做下一次 VIDIOCMCAPTURE
frame是當前截取的幀的序號。
********關於雙緩衝************
video_bmuf bmuf.frames = 2;一幀被處理時可以採集另一幀
int frame; //當前採集的是哪一幀
int framestat[2]; //幀的狀態 沒開始採集|等待採集結束
幀的地址由vd->map + vd->mbuf.offsets[vd->frame]得到。
採集工作結束後調用munmap取消綁定
munmap(vd->map, vd->mbuf.size)
在實際應用時還可以採用緩衝隊列等方式。
二、視頻截取的第二種方法:直接讀設備
關於緩衝大小,圖象等的屬性須由使用者事先設置
調用read();
int read (要訪問的文件描述符;指向要讀寫的信息的指針;應該讀寫的字符數);
返回值爲實際讀寫的字符數
int len ;
unsigned char
*vd->map=
(unsigned char *) malloc(vd◊capability.maxwidth*vd◊capability.maxheight );
len = read(vd◊fd,vd◊ vd->map,
vd◊capability.maxwidth*vd◊capability.maxheight*3 );
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 10 -
2.3 編程實例(mouse_capture)
不管是ov511還是zc301的攝像頭,它們採集的方式都是相同的,只不過採集
到的數據有所差異,ov511的就是rgb的位流,而zc301是jpeg編碼的位流。
mouse_capture是根據servfox改編的一個專門從zc301攝像頭獲取一張jpeg圖片,
用來測試攝像頭是否加載成功的小程序。這樣就可以不用cat /dev/video0>1.jpg來
測試攝像頭是否正常。cat命令一運行,就源源不斷地採集jpeg流。但是採到的圖
片只能顯示第一個jpeg頭和jpeg尾之間的數據。mouse_capture僅僅獲得一張完整
的jpeg。 可以從
http://www.hhcn.com/cgi-bin/topic.cgi?forum=1&topic=247&start=144&show=0
處下載參考。
現將主要函數的功能介紹如下:
static int GetVideoPict (struct vdIn *vd);//獲取圖片屬性信息。
static int SetVideoPict (struct vdIn *vd);//設置圖片屬性。
static int isSpcaChip (const char *BridgeName);//測試芯片類型
static int GetStreamId (const char *BridgeName); //測試輸出數據的格式
static int GetDepth (int format);//獲取顏色深度。
void exit_fatal(char *messages);//錯誤顯示。
int init_videoIn(struct vdIn *vd,char *device,int width,int height,int format,int
grabmethod);//初始化設備。
int convertframe(unsigned char *dst,unsigned char *src, int width,int height, int
formatIn, int size);//把共享緩衝區中的數據放到一個變量中,通知系統已獲得一
幀。
int v4lGrab (struct vdIn *vd,char *filename );//從攝像頭採集圖片。
int close_v4l (struct vdIn *vd);//關閉攝像頭。
int get_jpegsize (unsigned char *buf, int insize);//獲取jpeg圖片大小。
三 實例程序
3.1 LCD 實時顯示從ov511 上採集的圖像
參考HHARM9-EDU/applications/usbcam2lcd。從攝像頭獲取bmp位流直接顯示
在framebuffer中。此程序圖像的採集採用read的方式,注意由於lcd液晶屏顯示的
是16bits的RGB圖片,所以,ov511輸出的圖片格式也應該是16bits的RGB圖片數
據,宏VIDEO_PALETTE_RGB565定義的就是16bits的RGB數據圖片。而linux自
帶的ov511驅動中圖像採集是32位的,這樣採集到的圖片顯示在lcd上是雪花點。
因此需要修改驅動。 在kernet/driver/usb/目錄下有ov511芯片的驅動ov511.c,驅
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 11 -
動裏的ov51x_set_default_params函數是設置芯片默認的輸出圖片的格式,該函數
中的
for (i = 0; i < OV511_NUMFRAMES; i++)
{
ov511->frame.width = ov511->maxwidth;
ov511->frame.height = ov511->maxheight;
ov511->frame.bytes_read = 0;
if (force_palette)
ov511->frame.format = force_palette;
else
ov511->frame.format = VIDEO_PALETTE_RGB24;
ov511->frame.depth = ov511_get_depth(ov511->frame.format);
}
部分語句是主要設置ov511默認輸出圖片格式的,其中maxwidth和maxheight
設置了圖片的最大的寬度和高度。Ifelse語句設置了圖片的格式,作如下的修改:
for (i = 0; i < OV511_NUMFRAMES; i++)
{
ov511->frame.width = ov511->maxwidth;
ov511->frame.height = ov511->maxheight;
ov511->frame.bytes_read = 0;
ov511->frame.format = VIDEO_PALETTE_RGB565;
ov511->frame.depth = ov511_get_depth(ov511->frame.format);
}
如果需要,也可以改變圖片的默認輸出大小。
3.2 LCD 實時顯示從zc301 上採集的圖像
編程思想:從攝像頭採集到的圖片存放在本地文件夾,通過minigui加載jpeg
來實現顯示。
具體過程:
1.從網上下載jpegsrc-6b的jpeg庫,交叉編譯。
(1)./configure –enable-static –enable-shared –prefix=.libs
(2)修改Makefile,將編譯器改成交叉編譯器。
例如:我改成/opt/host/armv4l/bin/armv4l-unknown-linux-gcc
(3)make 後即在.libs目錄中生成for arm的
libjpeg.a, libjpeg.la,libjpeg.so,libjpeg.so.62,libjpeg.so.62.0.0。將這些文件拷貝到系
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 12 -
統庫文件目錄,我的是/usr/lib中。
2.因爲看從zc301採集的圖片的二進制位流,jpeg頭是ff d8 ff db。而在minigui庫
文件libminigui的源文件src/mybmp/jpeg.c中,load_jpg和check_jpg的時候測試的頭
位EXIF和JFIF兩種格式的jpeg圖片。這兩種對應的二進制分別是ff d8 ff e1和ff d8
ff e0。所以我們minigui通過判斷認爲這是錯誤的jpeg格式而不加載,故無法顯示。
實際上通過測試,在源碼中去掉這兩個判斷就能正確加載。
3.交叉編譯minigui
( 1 ) 編譯庫: ./configure --host=arm-unknown-linux --enable-jpgsupport=yes
--enable-pngsupport=no --enable-gifsupport=no --disable-lite
--prefix=/HHARM9-EDU/applications/minigui-free/nfsroot
--enable-smdk2410ial=yes
make
make install
(2)編譯實例程序時,要加上jpeg庫的支持,即在Makefile中加上-ljpeg。此時
將在nfsroot生成的庫文件和可執行文件移到ramdisk.image.gz相應的目錄下。(具
體參考華恆的2410開發手冊)。
3.Minigui程序的編寫
編程小技巧,我採取的方法是一刻不停地從攝像頭採集到圖片存儲在
/tmp/1.jpg中,在minigui中通過loadbitmap函數來加載圖片。而圖片加載後不會自
動更新,不能自動根據1.jpg的改變自動變化。因此,我在程序中設定一個timer。
每隔100ms刷新屏幕,基本上實現實時更新了。而出現另外一個問題,刷新時會
以背景色來填充桌面,導致屏幕閃爍嚴重。故想到採用MSG_ERASEBKGND的
方式,用前一張圖片做爲刷新屏幕時的填充背景圖片。這樣就保證了lcd上圖像
的連續性啦。
Minigui程序如下:其中一些自定義的函數跟mouse_capture中的一樣,只是
變採集單幅到採集多幅。具體您可以自己改一下:)。也可以向我索取源碼。
#include <minigui/common.h>
#include <minigui/minigui.h>
#include <minigui/gdi.h>
#include <minigui/window.h>
#include <minigui/control.h>
#include "spcav4l.h"
#define IDTIMER 100
static BITMAP bmp;
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 13 -
static int LoadBmpWinProc(HWND hWnd, int message, WPARAM wParam,
LPARAM lParam)
{
HDC hdc;
RECT rc={0,0,240,320};
switch (message) {
case MSG_CREATE:
SetTimer(hWnd,IDTIMER,100);
return 0;
case MSG_ERASEBKGND:
{
RECT rcTemp;
if( LoadBitmap(HDC_SCREEN,&bmp,"/tmp/1.jpg"))
{
printf("load wrong!\n");
return -1;
}
GetClientRect(hWnd, &rcTemp);
hdc = BeginPaint (hWnd);
FillBoxWithBitmap (hdc, rcTemp.left, rcTemp.top, rcTemp.right-rcTemp.left,
rcTemp.bottom-rcTemp.top, &bmp);
EndPaint(hWnd, hdc);
return 0;
}
case MSG_TIMER:
InvalidateRect(hWnd,&rc,TRUE);
return 0;
case MSG_CLOSE:
UnloadBitmap (&bmp);
DestroyMainWindow (hWnd);
PostQuitMessage (hWnd);
return 0;
}
return DefaultMainWinProc(hWnd, message, wParam, lParam);
}
int MiniGUIMain (int argc, const char* argv[])
{
MSG Msg;
HWND hMainWnd;
MAINWINCREATE CreateInfo;
char videodevice[] = "/dev/video0";
char jpegfile[] = "/tmp/1.jpg";
int grabmethod = 0;
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 14 -
int format = VIDEO_PALETTE_JPEG;
int width = 240;
int height = 320;
int i;
#ifdef _LITE_VERSION
SetDesktopRect(0, 0, 1024, 768);
#endif
CreateInfo.dwStyle = WS_VISIBLE | WS_BORDER | WS_CAPTION;
CreateInfo.dwExStyle = WS_EX_NONE;
CreateInfo.spCaption = "Load and display a bitmap";
CreateInfo.hMenu = 0;
CreateInfo.hCursor = GetSystemCursor(0);
CreateInfo.hIcon = 0;
CreateInfo.MainWindowProc = LoadBmpWinProc;
CreateInfo.lx = 0;
CreateInfo.ty = 0;
CreateInfo.rx = 240;
CreateInfo.by = 320;
CreateInfo.iBkColor = PIXEL_lightwhite;
CreateInfo.dwAddData = 0;
CreateInfo.hHosting = HWND_DESKTOP;
hMainWnd = CreateMainWindow (&CreateInfo);
if (hMainWnd == HWND_INVALID)
return -1;
ShowWindow (hMainWnd, SW_SHOWNORMAL);
memset(&videoIn, 0, sizeof (struct vdIn));
if(init_videoIn(&videoIn, videodevice, width, height, format,grabmethod) == 0)
{
printf("init is ok!\n");
}
else printf("init is wrong!\n");
while (GetMessage(&Msg, hMainWnd)) {
TranslateMessage(&Msg);
v4lGrab(&videoIn, jpegfile);
DispatchMessage(&Msg);
}
close_v4l (&videoIn);
MainWindowThreadCleanup (hMainWnd);
return 0;
}
#ifndef _LITE_VERSION
#include <minigui/dti.c>
#endif
基於Video4Linux 的USB 攝像頭圖像採集實現 戴小鼠 daily3
- 15 -
先寫到這裏吧,呵呵,希望能對您有所幫助。如果您在閱讀的過程中發現問
題,歡迎和我交流。
2006-7-7 晚
參考文獻
1.HHARM2410攝像頭調試記錄 華恆科技

發佈了2 篇原創文章 · 獲贊 1 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章