1 数据采集流程
可以参考这些文件:
-
mjpg-streamer\mjpg-streamer-experimental\plugins\input_control\input_uvc.c
-
video2lcd\video\v4l2.c
Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版。V4L2支持三种方式来采集图像:内存映射方式(mmap)、直接读取方式(read)和用户指针。内存映射的方式采集速度较快,一般用于连续视频数据的采集,实际工作中的应用概率更高;直接读取的方式相对速度慢一些,所以常用于静态图片数据的采集;用户指针使用较少,如有兴趣可自行研究。
1.1 buffer的管理
使用摄像头时,核心是"获得数据"。所以先讲如何获取数据,即如何得到buffer。
摄像头采集数据时,是一帧又一帧地连续采集。所以需要申请若干个buffer,驱动程序把数据放入buffer,APP从buffer得到数据。这些buffer可以使用链表来管理。
驱动程序周而复始地做如下事情:
-
从硬件采集到数据
-
从"空闲链表"取出buffer,把数据存入buffer
-
把含有数据的buffer放入"完成链表"
APP也会周而复始地做如下事情:
-
监测"完成链表",等待它含有buffer
-
从"完成链表"中取出buffer
-
处理数据
-
把buffer放入"空闲链表"
链表操作示意图如下:
1.2 完整的使用流程
参考mjpg-streamer和video2lcd,总结了摄像头的使用流程,如下:
主要是:video2lcd\video\v4l2.c
(1)open:打开设备节点/dev/videoX
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;
// 打开设备节点/dev/videoX
iFd = open(strDevName, O_RDWR);
if (iFd < 0)
{
DBG_PRINTF("can not open %s\n", strDevName);
return -1;
}
// ......
}
(2)ioctl VIDIOC_QUERYCAP:Query Capbility,查询能力
确认它是否是"捕获设备",因为有些节点是输出设备
确认它是否支持mmap操作,还是仅支持read/write操作
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;
// 1打开设备节点/dev/videoX
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));
// 2Query Capbility,查询能力
iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
if (iError) {
DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);
goto err_exit;
}
// 2.1捕获设备
if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
DBG_PRINTF("%s is not a video capture device\n", strDevName);
goto err_exit;
}
// 2.2流
if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
DBG_PRINTF("%s supports streaming i/o\n", strDevName);
}
// 2.3读能力
if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE) {
DBG_PRINTF("%s supports read i/o\n", strDevName);
}
// ......
}
(3)ioctl VIDIOC_ENUM_FMT:枚举它支持的格式
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;
// 1打开设备节点/dev/videoX
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));
// 2Query Capbility,查询能力
iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
if (iError) {
DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);
goto err_exit;
}
// 2.1捕获设备
if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
DBG_PRINTF("%s is not a video capture device\n", strDevName);
goto err_exit;
}
// 2.2流
if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
DBG_PRINTF("%s supports streaming i/o\n", strDevName);
}
// 2.3读能力
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;
// 3ioctl VIDIOC_ENUM_FMT:枚举它支持的格式
while ((iError = ioctl(iFd, VIDIOC_ENUM_FMT, &tFmtDesc)) == 0) {
if (isSupportThisFormat(tFmtDesc.pixelformat))
{
ptVideoDevice->iPixelFormat = tFmtDesc.pixelformat;
break;
}
tFmtDesc.index++;
}
// ......
}
(4)ioctl VIDIOC_S_FMT:在上面枚举出来的格式里,选择一个来设置格式
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;
// 1打开设备节点/dev/videoX
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));
// 2Query Capbility,查询能力
iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
if (iError) {
DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);
goto err_exit;
}
// 2.1捕获设备
if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
DBG_PRINTF("%s is not a video capture device\n", strDevName);
goto err_exit;
}
// 2.2流
if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
DBG_PRINTF("%s supports streaming i/o\n", strDevName);
}
// 2.3读能力
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;
// 3ioctl VIDIOC_ENUM_FMT:枚举它支持的格式
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;
/* 如果驱动程序发现无法某些参数(比如分辨率),
* 它会调整这些参数, 并且返回给应用程序
*/
// 4在上面枚举出来的格式里,选择一个来设置格式
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;
// ......
}
(5)ioctl VIDIOC_REQBUFS:申请buffer,APP可以申请很多个buffer,但是驱动程序不一定能申请到
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;
// 1打开设备节点/dev/videoX
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));
// 2Query Capbility,查询能力
iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
if (iError) {
DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);
goto err_exit;
}
// 2.1捕获设备
if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
DBG_PRINTF("%s is not a video capture device\n", strDevName);
goto err_exit;
}
// 2.2流
if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
DBG_PRINTF("%s supports streaming i/o\n", strDevName);
}
// 2.3读能力
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;
// 3ioctl VIDIOC_ENUM_FMT:枚举它支持的格式
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;
/* 如果驱动程序发现无法某些参数(比如分辨率),
* 它会调整这些参数, 并且返回给应用程序
*/
// 4在上面枚举出来的格式里,选择一个来设置格式
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;
// 5申请buffer,APP可以申请很多个buffer,但是驱动程序不一定能申请到
iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
if (iError)
{
DBG_PRINTF("Unable to allocate buffers.\n");
goto err_exit;
}
// ......
}
(6)ioctl VIDIOC_QUERYBUF和mmap:查询buffer信息、映射
如果申请到了N个buffer,这个ioctl就应该执行N次
执行mmap后,APP就可以直接读写这些buffer
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;
// 1打开设备节点/dev/videoX
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));
// 2Query Capbility,查询能力
iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
if (iError) {
DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);
goto err_exit;
}
// 2.1捕获设备
if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
DBG_PRINTF("%s is not a video capture device\n", strDevName);
goto err_exit;
}
// 2.2流,mmap
if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
DBG_PRINTF("%s supports streaming i/o\n", strDevName);
}
// 2.3读能力
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;
// 3ioctl VIDIOC_ENUM_FMT:枚举它支持的格式
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;
/* 如果驱动程序发现无法某些参数(比如分辨率),
* 它会调整这些参数, 并且返回给应用程序
*/
// 4在上面枚举出来的格式里,选择一个来设置格式
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;
// 5申请buffer,APP可以申请很多个buffer,但是驱动程序不一定能申请到
iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
if (iError)
{
DBG_PRINTF("Unable to allocate buffers.\n");
goto err_exit;
}
// 6确定最终分配buffer数量
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;
// 6.1查询
iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);
if (iError)
{
DBG_PRINTF("Unable to query buffer.\n");
goto err_exit;
}
ptVideoDevice->iVideoBufMaxLen = tV4l2Buf.length;
// 6.2把内核中分配的buffer的地址映射给应用程序
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;
}
}
// ......
}
(7)
ioctl VIDIOC_QBUF:把buffer放入"空闲链表"
如果申请到了N个buffer,这个ioctl就应该执行N次
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;
// 1打开设备节点/dev/videoX
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));
// 2Query Capbility,查询能力
iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
if (iError) {
DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);
goto err_exit;
}
// 2.1捕获设备
if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
DBG_PRINTF("%s is not a video capture device\n", strDevName);
goto err_exit;
}
// 2.2流,mmap
if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
DBG_PRINTF("%s supports streaming i/o\n", strDevName);
}
// 2.3读能力
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;
// 3ioctl VIDIOC_ENUM_FMT:枚举它支持的格式
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;
/* 如果驱动程序发现无法某些参数(比如分辨率),
* 它会调整这些参数, 并且返回给应用程序
*/
// 4在上面枚举出来的格式里,选择一个来设置格式
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;
// 5申请buffer,APP可以申请很多个buffer,但是驱动程序不一定能申请到
iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
if (iError)
{
DBG_PRINTF("Unable to allocate buffers.\n");
goto err_exit;
}
// 6确定最终分配buffer数量
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;
// 6.1查询
iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);
if (iError)
{
DBG_PRINTF("Unable to query buffer.\n");
goto err_exit;
}
ptVideoDevice->iVideoBufMaxLen = tV4l2Buf.length;
// 6.2把内核中分配的buffer的地址映射给应用程序
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. */
// 7把buffer放入"空闲链表"
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);
}
ptVideoDevice->ptOPr = &g_tV4l2VideoOpr;
return 0;
err_exit:
close(iFd);
return -1;
}
(8)ioctl VIDIOC_STREAMON:启动摄像头
static int V4l2StartDevice(PT_VideoDevice ptVideoDevice)
{
int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int iError;
// 启动摄像头
iError = ioctl(ptVideoDevice->iFd, VIDIOC_STREAMON, &iType);
if (iError)
{
DBG_PRINTF("Unable to start capture.\n");
return -1;
}
return 0;
}
(9)
这里是一个循环:使用poll/select监测buffer,然后从"完成链表"中取出buffer,处理后再放入"空闲链表"
poll/select
static int V4l2GetFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{
struct pollfd tFds[1];
int iRet;
struct v4l2_buffer tV4l2Buf;
/* poll */
tFds[0].fd = ptVideoDevice->iFd;
tFds[0].events = POLLIN;
iRet = poll(tFds, 1, -1);
if (iRet <= 0)
{
DBG_PRINTF("poll error!\n");
return -1;
}
// ......
}
ioctl VIDIOC_DQBUF:从"完成链表"中取出buffer;处理:前面使用mmap映射了每个buffer的地址,处理时就可以直接使用地址来访问buffer
static int V4l2GetFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{
struct pollfd tFds[1];
int iRet;
struct v4l2_buffer tV4l2Buf;
/* poll */
tFds[0].fd = ptVideoDevice->iFd;
tFds[0].events = POLLIN;
iRet = poll(tFds, 1, -1);
if (iRet <= 0)
{
DBG_PRINTF("poll error!\n");
return -1;
}
/* VIDIOC_DQBUF */
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2Buf.memory = V4L2_MEMORY_MMAP;
iRet = ioctl(ptVideoDevice->iFd, VIDIOC_DQBUF, &tV4l2Buf);
if (iRet < 0)
{
DBG_PRINTF("Unable to dequeue buffer.\n");
return -1;
}
ptVideoDevice->iVideoBufCurIndex = tV4l2Buf.index;
ptVideoBuf->iPixelFormat = ptVideoDevice->iPixelFormat;
ptVideoBuf->tPixelDatas.iWidth = ptVideoDevice->iWidth;
ptVideoBuf->tPixelDatas.iHeight = ptVideoDevice->iHeight;
ptVideoBuf->tPixelDatas.iBpp = (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_YUYV) ? 16 : \
(ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_MJPEG) ? 0 : \
(ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_RGB565) ? 16 : \
0;
ptVideoBuf->tPixelDatas.iLineBytes = ptVideoDevice->iWidth * ptVideoBuf->tPixelDatas.iBpp / 8;
ptVideoBuf->tPixelDatas.iTotalBytes = tV4l2Buf.bytesused;
ptVideoBuf->tPixelDatas.aucPixelDatas = ptVideoDevice->pucVideBuf[tV4l2Buf.index];
return 0;
}
ioclt VIDIOC_QBUF:把buffer放入"空闲链表"
(10)停止摄像头
static int V4l2StopDevice(PT_VideoDevice ptVideoDevice)
{
int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int iError;
iError = ioctl(ptVideoDevice->iFd, VIDIOC_STREAMOFF, &iType);
if (iError)
{
DBG_PRINTF("Unable to stop capture.\n");
return -1;
}
return 0;
}
参考资料:
[1] 01_V4L2应用程序开发_数据采集流程_哔哩哔哩_bilibili
[2] mjpg-streamer:https://github.com/jacksonliam/mjpg-streamer
[3] video2lcd:这是百问网编写的APP,它可以在LCD上直接显示摄像头的图像
[4] 这2个源码都放在GIT仓库里:
[5] UVC文档
[6] 参考文章:https://www.xjx100.cn/news/700965.html?action=onClick