文章目錄
1 V4L2
V4L2全稱是Video for Linux two(Video4Linux2),是V4L2改進衍生版本。V4L2是linux操作系統下的一個標註化的音頻、視頻設備驅動框架,向下屏蔽底層設備的差異,向上提供標準統一的訪問接口,提高用戶在音視頻方面的應用開發效率。只要底層音視頻設備(如攝像頭)兼容V4L2標準,則應用程序可以無縫接入該音視頻設備。本文主要描述V4L2視頻設備(攝像頭)的應用。
1.1 V4L2特點
屏蔽底層差異,向上提供標準的用戶訪問接口,更換支持V4L2標準的物理音視頻設備,原則上應用程序可以完全兼容。V4L2對用戶提供的接口包括:
-
視頻採集接口
-
視頻輸出接口
-
視頻覆蓋/預覽接口
-
視頻輸出覆蓋接口
-
編解碼接口
1.2 V4L2設備
linux思想是一切皆文件。V4L2設備接入並且驅動被加載成功時在"/dev"
生成設備文件,名稱爲"videoX"
(/dev/videoX),X爲設備設備序號;一般按接入的設備順序排序,只有一個設備時爲video0
。V4L2應用程序通過調用linux標準的設備文件系統接口,訪問音視頻設備。
2 V4L2設備訪問接口
V4L2音視頻設備可通過標準文件系統接口open/read/ioctl/close
訪問,設備訪問分爲兩大部分,分別是設備訪問和視頻流獲取。關於V4L2的接口聲明、宏定義、枚舉類型可以在“include/uapi/linux/videodev2.h”頭文件中查閱。
2.1設備訪問
設備訪問包括獲取和設置設備信息,如設備驅動信息、設備屬性、圖像幀格式、控制圖像捕獲等。這些都通過ioctl
來實現訪問,訪問格式如下。
ioctl(fd, cmd, param); /* 文件描述符, 命令字, 參數信息 */
常用命令字:
VIDIOC_QUERYCAP /* 查詢設備屬性 */
VIDIOC_ENUM_FMT /* 查詢設備支持的輸出格式*/
VIDIOC_G_FMT /* 查詢設備輸出幀格式 */
VIDIOC_S_FMT /* 設置設備輸出幀格式 */
VIDIOC_REQBUFS /* 申請幀緩存 */
VIDIOC_QUERYBUF /* 獲取申請的幀緩存 */
VIDIOC_QBUF /* 將幀緩存加入視頻流採集隊列 */
VIDIOC_DQBUF /* 獲取已採集視頻流的緩存幀 */
VIDIOC_STREAMON /* 開啓視頻流採集 */
VIDIOC_STREAMOFF /* 關閉視頻流採集 */
2.1.1 查詢設備屬性
函數原型:
int ioctl(int fd, int cmd, struct v4l2_capability *cap);
-
cmd
,命令字:VIDIOC_QUERYCAP
-
cap
,設備屬性數據結構:
struct v4l2_capability{
__u8 driver[16]; /* driver驅動名字 */
__u8 card[32]; /* device設備名字 */
__u8 bus_info[32]; /* 設備在系統中的位置 */
__u32 version; /* 驅動版本號 */
__u32 capabilities; /* 設備支持的操作 */
__u32 device_caps; /* 特殊設備信息 */
__u32 reserved[4]; /* 保留字段 */
};
【1】capabilities
,視頻設備支持的操作,以每一位表示,常用類型宏位於“/uapi/linux/videodev2.h”
定義。
/* Values for 'capabilities' field */
#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 /* Is a video capture device */
#define V4L2_CAP_VIDEO_OUTPUT 0x00000002 /* Is a video output device */
#define V4L2_CAP_VIDEO_OVERLAY 0x00000004 /* Can do video overlay */
#define V4L2_CAP_VBI_CAPTURE 0x00000010 /* Is a raw VBI capture device */
#define V4L2_CAP_VBI_OUTPUT 0x00000020 /* Is a raw VBI output device */
#define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040 /* Is a sliced VBI capture device */
#define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080 /* Is a sliced VBI output device */
#define V4L2_CAP_RDS_CAPTURE 0x00000100 /* RDS data capture */
#define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200 /* Can do video output overlay */
#define V4L2_CAP_HW_FREQ_SEEK 0x00000400 /* Can do hardware frequency seek */
#define V4L2_CAP_RDS_OUTPUT 0x00000800 /* Is an RDS encoder */
查詢設備屬性僞代碼:
struct v4l2_capability cap = {0};
ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
2.1.2 查詢設備輸出格式
函數原型:
int ioctl(int fd, int cmd, struct v4l2_fmtdesc *fmt);
-
cmd
,命令字:VIDIOC_ENUM_FMT
-
fmt
,設備輸出格式數據結構:
struct v4l2_fmtdesc {
__u32 index; /* 設置信息,查詢格式序號 */
__u32 type; /* 設置信息,設備類型 */
__u32 flags;
__u8 description[32]; /* 返回信息,格式描述 */
__u32 pixelformat; /* 返回信息,格式 */
__u32 reserved[4]; /* 保留字段 */
};
【1】index
,查詢序號,從0開始查詢
【2】type
,設備類型,實際類型爲enum v4l2_buf_type
,位於“/uapi/linux/videodev2.h”
定義;如果是camera設備,則設置爲V4L2_BUF_TYPE_VIDEO_CAPTURE
【3】flags
,一般不用
循環獲取設備輸出格式僞代碼:
struct v4l2_fmtdesc fmtdesc = {0};
fmtdesc.index = 0 ;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("the video device support format:\n");
while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1)
{
printf("%d.%s\n", fmtdesc.index+1, fmtdesc.description);
fmtdesc.index++;
}
2.1.3 設置幀輸出格式
圖像幀輸出格式基本參數包括:
- 像素寬度
- 像素高度
- 數據類型
函數原型:
int ioctl(int fd, int cmd, struct v4l2_format *format);
-
cmd
,命令字:VIDIOC_S_FMT
-
format
,設置輸出格式數據結構:
struct v4l2_format {
__u32 type;/* 設備類型 */
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE 設備(camera)使用 */
struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
struct v4l2_sliced_vbi_formatsliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */
struct v4l2_meta_format meta; /* V4L2_BUF_TYPE_META_CAPTURE */
__u8 raw_data[200]; /* 保留字段 */
} fmt;
};
【1】type
,設備類型,實際類型爲enum v4l2_buf_type
,如果是camera設備,則設置爲V4L2_BUF_TYPE_VIDEO_CAPTURE
【2】fmt
,一個共用體,不同設備的具體配置參數,以camera爲例,其數據結構struct v4l2_pix_format
如下
struct v4l2_pix_format {
__u32 width; /* 像素寬度 */
__u32 height; /* 像素高度 */
__u32 pixelformat;/* 輸出格式,根據camera支持的格式選擇,JPG或YUV */
__u32 field; /* enum v4l2_field */
__u32 bytesperline; /* for padding, zero if unused */
__u32 sizeimage;
__u32 colorspace; /* enum v4l2_colorspace */
__u32 priv; /* private data, depends on pixelformat */
__u32 flags; /* format flags (V4L2_PIX_FMT_FLAG_*) */
__u32 ycbcr_enc; /* enum v4l2_ycbcr_encoding */
__u32 quantization; /* enum v4l2_quantization */
__u32 xfer_func; /* enum v4l2_xfer_func */
};
2.1.4 申請幀緩存
函數原型:
int ioctl(int fd, int cmd, struct v4l2_requestbuffers *reqbuf);
-
cmd
,命令字:VIDIOC_REQBUFS
-
reqbuf
,申請內存數據結構:
struct v4l2_requestbuffers {
__u32 count; /* 內存塊數目 */
__u32 type; /* 設備類型 */
__u32 memory; /* 內存用途 */
__u32 reserved[2];/* 保留字段 */
};
【1】count
,申請內存塊數目,至少爲1
【2】type
,設備類型,實際類型爲enum v4l2_buf_type
,如果是camera設備,則設置爲V4L2_BUF_TYPE_VIDEO_CAPTURE
【3】memory
,內存用途,實際類型爲enum v4l2_memory
,一般用作內存映射V4L2_MEMORY_MMAP
enum v4l2_memory {
V4L2_MEMORY_MMAP = 1, /* 內存映射 */
V4L2_MEMORY_USERPTR = 2, /* 用戶指針 */
V4L2_MEMORY_OVERLAY = 3, /* 內存覆蓋 */
V4L2_MEMORY_DMABUF = 4, /* DMA映射 */
};
申請幀緩存僞代碼:
struct v4l2_requestbuffers req_buf = {0};
req_buf.count = 1;
req_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req_buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_REQBUFS, &req_buf);
2.1.5 內核內存轉換
通VIDIOC_S_FMT
命令字申請的內核態內存,還需轉換爲物理內存,用於映射到用戶態,這樣用戶能直接從物理內存中獲取視頻流數據,提高效率。
函數原型:
int ioctl(int fd, int cmd, struct v4l2_buffer *buf);
-
cmd
,命令字:VIDIOC_QUERYBUF
-
buf
,返回內存數據結構:
struct v4l2_buffer {
__u32 index; /* 內存塊序號 */
__u32 type; /* 類型,與申請的類型一致 */
__u32 bytesused;/* 已使用的內存大小 */
__u32 flags;
__u32 field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
/* memory location */
__u32 memory; /* 內存用途,一般用內存映射 */
union {
__u32 offset; /* 內存塊可用偏移地址,對於內存映射有效 */
unsigned long userptr;
struct v4l2_plane *planes;
__s32 fd;
} m;
__u32 length; /* 內存塊大小 */
__u32 config_store;
__u32 reserved;
};
應用僞代碼:
struct v4l2_requestbuffers req_buf = {0};
struct v4l2_buffer buf = {0};
req_buf.count = 1;
req_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req_buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_REQBUFS, &req_buf);
for(i=0; i<req_buf.count; i++)
{
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
ret = ioctl(pdevice->fd, VIDIOC_QUERYBUF, &buf);
pdevice->mmap_buf[i].size = buf.length;
pdevice->mmap_buf[i].addr = (char *)mmap(NULL, buf.length, PROT_READ|PROT_WRITE,MAP_PRIVATE, pdevice->fd, buf.m.offset);
if(MAP_FAILED == pdevice->mmap_buf[i].addr)
{
perror("mmap failed");
}
}
關於內存映射mmap
使用,參考文章mmap內存映射。
2.1.6 緩衝幀內存入隊操作
該操作是將緩衝內存加入V4L2驅動的採集隊列中,視頻設備採集完成,數據存於該內存中。
函數原型:
int ioctl(int fd, int cmd, struct v4l2_buffer *buf);
cmd
,命令字:VIDIOC_QBUF
buf
,幀緩存數據結構
應用代碼:
int set_video_device_stream_queue(struct _v4l2_video *pdevice, int index)
{
int ret = 0;
struct v4l2_buffer buf = {0};
/* 將內核緩存放入隊列 */
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = index;
ret = ioctl(pdevice->fd, VIDIOC_QBUF, &buf);
if(ret < 0)
{
perror("ioctl call \'VIDIOC_QBUF\' failed");
return -1;
}
}
2.1.7 啓動視頻流採集
執行該命令,視頻設備執行數據採集,採集完成,將視頻流數據存於指定的內存空間。
函數原型:
int ioctl(int fd, int cmd, enum v4l2_buf_type *type);
cmd
,命令字:VIDIOC_STREAMON
type
,設備(緩存幀)類型,camera設備爲V4L2_BUF_TYPE_VIDEO_CAPTURE
2.1.8 讀取數據幀內存序號
實質上,執行“開啓採集”命令成功後,視頻流數據會存於預先申請的物理內存空間。然而,如果申請了多個數量的幀緩存,此時需知道視頻流存於哪個幀緩存中,用戶根據序號訪問內存塊。通過該命令可以獲取存放視頻流數據幀內存序號。
函數原型:
int ioctl(int fd, int cmd, struct v4l2_buffer *buf);
cmd
,命令字:VIDIOC_DQBUF
buf
,幀緩存數據結構
應用代碼:
int read_video_device_stream_frame(struct _v4l2_video *pdevice, int *out_buf_index)
{
int ret = 0;
int i;
struct v4l2_buffer buf = {0};
/* 從隊列取出數據 */
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(pdevice->fd, VIDIOC_DQBUF, &buf);
if(ret < 0)
{
perror("ioctl call \'VIDIOC_DQBUF\' failed");
return -1;
}
if (buf.index > pdevice->mmap_buf_cnt)
{
printf("buf overflow[%d]\n", buf.index);
}
*out_buf_index = buf.index;
return 0;
}
注:
即使只申請一個幀緩存空間,也需要執行該命令,以確定數據是否正確採集完成,否則內存中數據是未知狀態。
2.1.9 關閉視頻流採集
結束視頻流採集後,調用VIDIOC_STREAMOFF
關閉視頻流採集。
函數原型:
int ioctl(int fd, int cmd, enum v4l2_buf_type *type);
cmd
,命令字:VIDIOC_STREAMOFF
type
,設備(緩存幀)類型,camera設備爲V4L2_BUF_TYPE_VIDEO_CAPTURE
2.2 視頻流讀取
視頻流讀取有兩種方式。
- 通過標準文件系統接口
read
讀取 - 將V4L2設備內核態映射(mmap)到用戶態,直接從物理內存獲取視頻流
- 用戶指針模式,內存由用戶分配,與內存映射類似,使用較少
通過read
函數讀取視頻流,需經過物理內存到內核態、內核態到用戶態兩個內存拷貝過程,效率比較低,一般用於靜態圖像的採集獲取,一般比較少使用。內存映射是常用的方式, 省去兩個拷貝過程,效率高。
3 V4L2應用開發流程
應用程序訪問一個V4L2設備的的總體流程如下圖。
關鍵步驟分析:
-
查詢設備屬性,包驅動信息、支持視頻流格式,以方便後續設置視頻輸出屬性
-
設置設備屬性,根據獲取的設備屬性設置,主要是設置視頻流輸出格式,如制式、寬度、高度、編碼方式
-
幀緩存申請,用於存放驅動採集的視頻流,並映射到用戶態
-
幀緩存加入採集隊列,如果需循環採集視頻流,每次獲取視頻流後都需執行將幀緩存加入採集隊列
-
結束過程,包括關閉視頻流採集、釋放內存映射、關閉設備描述符
示例:
- 獲取攝像頭信息
- 獲取視頻流,保存爲圖片信息
#include <linux/videodev2.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/types.h>
#include <malloc.h>
#include <math.h>
#include <errno.h>
#include <assert.h>
struct _mmap_buf
{
void *addr;
int size;
};
struct _v4l2_video
{
int fd;
struct _mmap_buf *mmap_buf;
int mmap_buf_cnt;
};
int open_video_device(const char *device_name)
{
int fd = 0;
if (device_name == NULL)
{
return -1;
}
fd = open(device_name, O_RDWR); /* 以讀寫方式打開;以只讀方式打開導致內存映射出錯 */
if(fd < 0)
{
perror("open video device failed");
return -1;
};
return fd;
}
int close_video_device(struct _v4l2_video *pdevice)
{
int ret = 0;
int i;
for(i = 0; i<pdevice->mmap_buf_cnt; i++)
{
if (pdevice->mmap_buf[i].addr != 0x00)
{
ret = munmap(pdevice->mmap_buf[i].addr, pdevice->mmap_buf[i].size);
if(ret < 0)
{
perror("munmap failed");
continue;
}
}
}
if (pdevice->mmap_buf != 0x00)
{
free(pdevice->mmap_buf);
}
if (pdevice->fd != 0x00)
{
ret = close(pdevice->fd);
if(ret < 0)
{
perror("close fd failed");
}
}
return ret;
}
int query_video_device_cap(struct _v4l2_video *pdevice)
{
int ret = 0;
struct v4l2_capability cap = {0};
struct v4l2_fmtdesc fmtdesc = {0};
/* 查詢攝像頭信息 */
ret = ioctl(pdevice->fd, VIDIOC_QUERYCAP, &cap);
if(ret < 0)
{
perror("ioctl call \'VIDEO_QUERYCAP\' failed \n");
return -1;
}
printf("video driver name:%s\n", cap.driver);
printf("video device name:%s\n", cap.card);
printf("video bus information:%s\n", cap.bus_info);
printf("video driver version:%d\n", cap.version);
printf("video capabilities:%x\n", cap.capabilities);
/* 檢查設備是否支持視頻捕獲 */
if(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
{
printf("the video device support capture\n");
}
/* 檢查設備是否支持數據流 */
if(!(cap.capabilities & V4L2_CAP_STREAMING))
{
printf("the video device support stream\n");
}
/* 查詢設備支持的輸出格式 */
fmtdesc.index = 0 ;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("the video device support format:\n");
while(ioctl(pdevice->fd,VIDIOC_ENUM_FMT,&fmtdesc) != -1)
{
printf("%d.%s\n", fmtdesc.index+1, fmtdesc.description);
fmtdesc.index++;
}
}
int set_video_device_par(struct _v4l2_video *pdevice)
{
int ret = 0;
struct v4l2_format format;
/* 設置幀輸出格式 */
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = 640; /* 像素寬度 */
format.fmt.pix.height = 480; /* 像素高度 */
format.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG; /* 輸出格式,前提是攝像頭支持該格式,V4L2_PIX_FMT_YYUV */
format.fmt.pix.field = V4L2_FIELD_NONE;
ret = ioctl(pdevice->fd, VIDIOC_S_FMT, &format);
if (ret < 0)
{
perror("ioctl call \'VIDIOC_S_FMT\' failed");
}
return ret;
}
int set_video_device_mmap(struct _v4l2_video *pdevice)
{
int ret = 0;
int i = 0;
struct v4l2_requestbuffers req_buf = {0};
struct v4l2_buffer buf = {0};
/* 申請內核緩存區 */
pdevice->mmap_buf_cnt = 1;
req_buf.count = 1; /* 緩存數目 */
req_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req_buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(pdevice->fd, VIDIOC_REQBUFS, &req_buf);
if(ret < 0)
{
perror("ioctl call \'VIDIOC_REQBUFS\' failed");
return -1;
}
pdevice->mmap_buf = malloc(req_buf.count * sizeof(struct _mmap_buf));
if(pdevice->mmap_buf == NULL)
{
perror("malloc memory failed");
return -1;
}
/* 將內核態內存映射到用戶態 */
for(i=0; i<req_buf.count; i++)
{
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
ret = ioctl(pdevice->fd, VIDIOC_QUERYBUF, &buf);
if(ret < 0)
{
perror("ioctl call \'VIDIOC_QUERYBUF\' failed");
return -1;
}
pdevice->mmap_buf[i].size = buf.length;
pdevice->mmap_buf[i].addr = (char *)mmap(NULL, buf.length, PROT_READ|PROT_WRITE,MAP_PRIVATE, pdevice->fd, buf.m.offset);
if(MAP_FAILED == pdevice->mmap_buf[i].addr)
{
perror("mmap failed");
return -1;
}
}
return 0;
}
int set_video_device_stream_queue(struct _v4l2_video *pdevice, int index)
{
int ret = 0;
struct v4l2_buffer buf = {0};
/* 將內核緩存放入隊列 */
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = index;
ret = ioctl(pdevice->fd, VIDIOC_QBUF, &buf);
if(ret < 0)
{
perror("ioctl call \'VIDIOC_QBUF\' failed");
return -1;
}
}
int set_video_device_stream_on(struct _v4l2_video *pdevice)
{
int ret = 0;
int i;
struct v4l2_buffer buf = {0};
enum v4l2_buf_type type;
/* 將內核緩存放入隊列 */
for (i=0; i<pdevice->mmap_buf_cnt; i++)
{
set_video_device_stream_queue(pdevice, i);
if(ret < 0)
{
return -1;
}
}
/* 開啓數據流 */
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(pdevice->fd, VIDIOC_STREAMON, &type);
if(ret < 0)
{
perror("ioctl call \'VIDIOC_STREAMON\' failed");
return 0;
}
return 0;
}
int read_video_device_stream_frame(struct _v4l2_video *pdevice, int *out_buf_index)
{
int ret = 0;
int i;
struct v4l2_buffer buf = {0};
/* 從隊列取出數據 */
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(pdevice->fd, VIDIOC_DQBUF, &buf);
if(ret < 0)
{
perror("ioctl call \'VIDIOC_DQBUF\' failed");
return -1;
}
if (buf.index > pdevice->mmap_buf_cnt)
{
printf("buf overflow[%d]\n", buf.index);
}
*out_buf_index = buf.index;
return 0;
}
int set_video_device_stream_off(struct _v4l2_video *pdevice)
{
int ret = 0;
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(pdevice->fd, VIDIOC_STREAMOFF, &type); /* 關閉數據流 */
if(ret < 0)
{
perror("ioctl call \'VIDIOC_STREAMOFF\' failed");
return -1;
}
return 0;
}
int main(int argc, int **argv)
{
FILE *fp = NULL;
int index = 0;
int ret = 0;
int i = 0;
char buf[12] = {0};
struct _v4l2_video video;
fd_set fds;
struct timeval tv;
if (argc < 2)
{
printf("parameter invalid\n");
return -1;
}
video.fd = open_video_device((const char*)argv[1]);
if (video.fd < 0)
{
return -1;
}
ret = query_video_device_cap(&video);
if (ret < 0)
{
goto __exit;
}
ret = set_video_device_par(&video);
if (ret < 0)
{
goto __exit;
}
ret = set_video_device_mmap(&video);
if (ret < 0)
{
goto __exit;
}
ret = set_video_device_stream_on(&video);
if (ret < 0)
{
goto __exit;
}
for (i=0; i<5; i++)
{/* 採集5張(幀)圖片 */
FD_ZERO(&fds);
FD_SET(video.fd,&fds);
tv.tv_sec = 5; /* wait time */
tv.tv_usec = 0;
ret = select(video.fd + 1, &fds, NULL, NULL, &tv);
if(ret < 0)
{
perror("select error");
goto __exit;
}
else if(ret == 0)
{
printf("select timeout\n");
goto __exit;
}
ret = read_video_device_stream_frame(&video, &index);
if (ret < 0)
{
goto __exit;
}
sprintf(buf, "image%d.jpg", i);
fp = fopen(buf, "wb"); /* 保存爲圖片文件 */
if(fp == NULL)
{
perror("open image file failed\n");
goto __exit;
}
fwrite(video.mmap_buf[index].addr, video.mmap_buf[index].size, 1, fp);
fclose(fp);
set_video_device_stream_queue(&video, index);
usleep(1000);
}
__exit:
set_video_device_stream_off(&video);
close_video_device(&video);
return 0;
}