写了三个文件:video_manager.h; video_manager.c; v4l2.c;
首先从数据结构(video_manager.h):
#ifndef _VIDEO_MANAGER_H
#define _VIDEO_MANAGER_H
#include <config.h>
#include <pic_operation.h>
#define NB_BUFFER 4
struct VideoDevice;
struct VideoOpr;
typedef struct VideoDevice T_VideoDevice, *PT_VideoDevice;
typedef struct VideoOpr T_VideoOpr, *PT_VideoOpr;
typedef struct VideoDevice {
int iFd;
int iPixelFormat;
int iWidth;
int iHeight;
int iVideoBufCnt;
int iVideoBufMaxLen;
int iVideoBufCurIndex;
unsigned char *pucVideBuf[NB_BUFFER];
/* 函数 */
PT_VideoOpr ptOPr;
}T_VideoDevice, *PT_VideoDevice;
typedef struct VideoBuf {
T_PixelDatas tPixelDatas;
int iPixelFormat;
}T_VideoBuf, *PT_VideoBuf;
typedef struct VideoOpr {
char *name;
int (*InitDevice)(char *strDevName, PT_VideoDevice ptVideoDevice);
int (*ExitDevice)(PT_VideoDevice ptVideoDevice);
int (*GetFrame)(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf);
int (*PutFrame)(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf);
int (*StartDevice)(PT_VideoDevice ptVideoDevice);
int (*StopDevice)(PT_VideoDevice ptVideoDevice);
}T_VideoOpr, *PT_VideoOpr;
int V4l2Init(void);
#endif /* _VIDEO_MANAGER_H */
struct VideoDevice 定义了描述符,像素格式,宽度,高度,videobuf计数器,videobuf长度,videobuf指示器。并且命名为T_VideoDevice,*PT_VideoDevice
struct VideoBuf中定义了pic_operation.h文件中的T_PixelDatas 的数据类型,同时包含了iPixelFormat。
定义了一个VideoOpr结构体,里面含有各种操作函数。
最后声明了一下V4l2.c文件中的V4l2Init()函数,以便在manager.c文件中(#include<video_manager.h>)调用它
第二个是video_manager.c文件
#include <config.h>
#include <video_manager.h>
#include <string.h>
static PT_VideoOpr g_ptVideoOprHead = NULL;
int RegisterVideoOpr(PT_VideoOpr ptVideoOpr)
{
PT_VideoOpr ptTmp;
if (!g_ptVideoOprHead)
{
g_ptVideoOprHead = ptVideoOpr;
ptVideoOpr->ptNext = NULL;
}
else
{
ptTmp = g_ptVideoOprHead;
while (ptTmp->ptNext)
{
ptTmp = ptTmp->ptNext;
}
ptTmp->ptNext = ptVideoOpr;
ptVideoOpr->ptNext = NULL;
}
return 0;
}
void ShowVideoOpr(void)
{
int i = 0;
PT_VideoOpr ptTmp = g_ptVideoOprHead;
while (ptTmp)
{
printf("%02d %s\n", i++, ptTmp->name);
ptTmp = ptTmp->ptNext;
}
}
PT_VideoOpr GetVideoOpr(char *pcName)
{
PT_VideoOpr ptTmp = g_ptVideoOprHead;
while (ptTmp)
{
if (strcmp(ptTmp->name, pcName) == 0)
{
return ptTmp;
}
ptTmp = ptTmp->ptNext;
}
return NULL;
}
int VideoInit(void)
{
int iError;
iError = V4l2Init();
return iError;
}
定义了一个链表g_ptVideoOprHead
RegisterVideoOpr(PT_VideoOpr ptVideoOpr) 函数{
if(链表空) 链表 = ptVideoOpr; ptVideoOpr->ptNext = NULL;
else 1.定义一个新表头ptTmp,循环直到while (ptTmp->ptNext)的下一项为空, 2.g_ptVideoOprHead的最后一项设置为ptVideoOpr,并且ptVideoOpr->ptNext = NULL;
}
void ShowVideoOpr(void)
{
一项一项得打印链表中得name;
}
PT_VideoOpr GetVideoOpr(char *pcName)
{
将链表中得name一个个与传入参数pcName比较,如果一致,则返回链表节点,否则return null;
}
第三个文件 V4l2.c
static int g_aiSupportedFormats[] = {V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_MJPEG,
V4L2_PIX_FMT_RGB565};
static int isSupportThisFormat(int iPixelFormat)
{
int i;
for (i = 0; i < sizeof(g_aiSupportedFormats)/sizeof(g_aiSupportedFormats[0]); i++)
{
if (g_aiSupportedFormats[i] == iPixelFormat)
return 1;
}
return 0;
}
定义了三个格式,然后写一个函数,查询是否支持这种格式。如果有support得就返回1.没有返回0;
整个流程
/* 参考 luvcview */
/* open
* VIDIOC_QUERYCAP 确定它是否视频捕捉设备,支持哪种接口(streaming/read,write)
* VIDIOC_ENUM_FMT 查询支持哪种格式
* VIDIOC_S_FMT 设置摄像头使用哪种格式
* VIDIOC_REQBUFS 申请buffer
对于 streaming:
* VIDIOC_QUERYBUF 确定每一个buffer的信息 并且 mmap
* VIDIOC_QBUF 放入队列
* VIDIOC_STREAMON 启动设备
* poll 等待有数据
* VIDIOC_DQBUF 从队列中取出
* 处理....
* VIDIOC_QBUF 放入队列
* ....
对于read,write:
read
处理....
read
* VIDIOC_STREAMOFF 停止设备
*
*/
static int V4l2InitDevice(char *strDevName, PT_VideoDevice ptVideoDevice)
{
int i;
int iFd;
int iError;
struct v4l2_capability tV4l2Cap;
struct v4l2_fmtdesc tFmtDesc;
struct v4l2_format tV4l2Fmt;
struct v4l2_requestbuffers tV4l2ReqBuffs;
struct v4l2_buffer tV4l2Buf;
int iLcdWidth;
int iLcdHeigt;
int iLcdBpp;
iFd = open(strDevName, O_RDWR);
if (iFd < 0)
{
DBG_PRINTF("can not open %s\n", strDevName);
return -1;
}
ptVideoDevice->iFd = iFd;
iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));
iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
if (iError) {
DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);
goto err_exit;
}
if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
DBG_PRINTF("%s is not a video capture device\n", strDevName);
goto err_exit;
}
if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
DBG_PRINTF("%s supports streaming i/o\n", strDevName);
}
if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE) {
DBG_PRINTF("%s supports read i/o\n", strDevName);
}
memset(&tFmtDesc, 0, sizeof(tFmtDesc));
tFmtDesc.index = 0;
tFmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while ((iError = ioctl(iFd, VIDIOC_ENUM_FMT, &tFmtDesc)) == 0) {
if (isSupportThisFormat(tFmtDesc.pixelformat))
{
ptVideoDevice->iPixelFormat = tFmtDesc.pixelformat;
break;
}
tFmtDesc.index++;
}
if (!ptVideoDevice->iPixelFormat)
{
DBG_PRINTF("can not support the format of this device\n");
goto err_exit;
}
/* set format in */
GetDispResolution(&iLcdWidth, &iLcdHeigt, &iLcdBpp);
memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2Fmt.fmt.pix.pixelformat = ptVideoDevice->iPixelFormat;
tV4l2Fmt.fmt.pix.width = iLcdWidth;
tV4l2Fmt.fmt.pix.height = iLcdHeigt;
tV4l2Fmt.fmt.pix.field = V4L2_FIELD_ANY;
/* 如果驱动程序发现无法某些参数(比如分辨率),
* 它会调整这些参数, 并且返回给应用程序
*/
iError = ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt);
if (iError)
{
DBG_PRINTF("Unable to set format\n");
goto err_exit;
}
ptVideoDevice->iWidth = tV4l2Fmt.fmt.pix.width;
ptVideoDevice->iHeight = tV4l2Fmt.fmt.pix.height;
/* request buffers */
memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
tV4l2ReqBuffs.count = NB_BUFFER;
tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;
iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
if (iError)
{
DBG_PRINTF("Unable to allocate buffers.\n");
goto err_exit;
}
ptVideoDevice->iVideoBufCnt = tV4l2ReqBuffs.count;
if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING)
{
/* map the buffers */
for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++)
{
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.index = i;
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2Buf.memory = V4L2_MEMORY_MMAP;
iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);
if (iError)
{
DBG_PRINTF("Unable to query buffer.\n");
goto err_exit;
}
ptVideoDevice->iVideoBufMaxLen = tV4l2Buf.length;
ptVideoDevice->pucVideBuf[i] = mmap(0 /* start anywhere */ ,
tV4l2Buf.length, PROT_READ, MAP_SHARED, iFd,
tV4l2Buf.m.offset);
if (ptVideoDevice->pucVideBuf[i] == MAP_FAILED)
{
DBG_PRINTF("Unable to map buffer\n");
goto err_exit;
}
}
/* Queue the buffers. */
for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++)
{
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.index = i;
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2Buf.memory = V4L2_MEMORY_MMAP;
iError = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);
if (iError)
{
DBG_PRINTF("Unable to queue buffer.\n");
goto err_exit;
}
}
}
else if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE)
{
g_tV4l2VideoOpr.GetFrame = V4l2GetFrameForReadWrite;
g_tV4l2VideoOpr.PutFrame = V4l2PutFrameForReadWrite;
/* read(fd, buf, size) */
ptVideoDevice->iVideoBufCnt = 1;
/* 在这个程序所能支持的格式里, 一个象素最多只需要4字节 */
ptVideoDevice->iVideoBufMaxLen = ptVideoDevice->iWidth * ptVideoDevice->iHeight * 4;
ptVideoDevice->pucVideBuf[0] = malloc(ptVideoDevice->iVideoBufMaxLen);
}
return 0;
err_exit:
close(iFd);
return -1;
}
整个init做了以下几件事:
1.以RDWR可读可写打开了strDevName,设置ptVideoDevice->iFd = iFd;
2.进行ioctl来进行VIDIOC_QUERYCAP
3.while((iError = ioctl(iFd, VIDIOC_ENUM_FMT, &tFmtDesc))==0)
找到支持得format就break出来,否则就一直列举fmt来查询是否支持。
4.获得lcd得高度宽度,以及bpp值。然后设置它得高度宽度,格式,以及field。设置完参数
调用 ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt);
5.设置tV4l2ReqBuffs上得属性,然后调用REQBUFS
ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
6.将
#define NB_BUFFER 4 -------------查询缓存信息-VIDIOC_QUERYBUFioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);
ptVideoDevice->pucVideBuf[i] = mmap(0 /* start anywhere */ ,
tV4l2Buf.length, PROT_READ, MAP_SHARED, iFd,
tV4l2Buf.m.offset); 然后进行ioremap
7. for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++)
将四个缓冲区一一放入队列中。ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);
总结
VIDIOC_QUERYCAP 确定它是否视频捕捉设备,支持哪种接口(streaming/read,write)
VIDIOC_ENUM_FMT 查询支持哪种格式
VIDIOC_S_FMT 设置摄像头使用哪种格式
VIDIOC_REQBUFS 申请buffer
对于 streaming:
VIDIOC_QUERYBUF 确定每一个buffer的信息 并且 mmap
VIDIOC_QBUF 都在init函数里完成。
start函数-stop函数
static int V4l2StartDevice(PT_VideoDevice ptVideoDevice){
ioctl(ptVideoDevice->iFd, VIDIOC_STREAMON, &iType);-调用ioctl来完成streaon
}
static int V4l2StopDevice(PT_VideoDevice ptVideoDevice)
{
ioctl(ptVideoDevice->iFd, VIDIOC_STREAMOFF, &iType);
}
getframe函数-putframe函数
static int V4l2GetFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{ iRet = poll(tFds, 1, -1);
iRet = ioctl(ptVideoDevice->iFd, VIDIOC_DQBUF, &tV4l2Buf);
}
static int V4l2PutFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{
iError = ioctl(ptVideoDevice->iFd, VIDIOC_QBUF, &tV4l2Buf);
}