树莓派学习专题<5>:使用V4L2驱动获取摄像头数据--概览
树莓派学习专题<5>:使用V4L2驱动获取摄像头数据--概览
- 1. 树莓派的基本设置
- 2. 使用方法
- 3. 代码
1. 树莓派的基本设置
使用V4L2驱动之前,需要修改文件 /boot/firmware/config.txt 文件。如果不修改,使用V4L2时会报错。修改后需要重启树莓派。
#camera_auto_detect=1 // 注释此句。
start_x=1 // 添加此句
2. 使用方法
使用文章末尾的代码,在树莓派上用gcc编译,并运行。
文件保存为out.yuv,可以使用 RawPlayer 打开并播放。
需要注意:
- YUV或者RGB格式的数据,码流非常大。例如格式设置为RGB888,分辨率设置为1280 X 720,帧率为30fps,则码流速度大约为 :
1280 X 720 X 30fps X 3B = 82.9MB/s
,该速率一般TF卡是扛不住的。所以捕获帧数不能太多,否则会导致图像错误(写TF卡来不及)。 - 注意设定的格式。RawPlayer打开时要选对格式。否则播放会异常。
3. 代码
#include <asm/types.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <linux/fb.h>
#include <linux/videodev2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>#define FRAME_NUMBER ( 10u)/* 从摄像头获取图像的分辨率 */
#define CAMERA_RESOLUTION_WIDTH ( 1280u)
#define CAMERA_RESOLUTION_HEIGHT ( 720u)/* Video 设备文件描述符 */
int g_iFDVideo ;/* 写入(保存)文件描述符 */
int g_iFDStore ;/* 数据缓冲区 */
typedef struct tag_BufDesc
{void *pvBufPtr ;size_t szBufSize ;
} stBufDesc ;stBufDesc *g_pstBufDesc ;/*******************************************************************************
- Function : __QueryCameraCapability
- Description : 本函数查询摄像头能力。
- Input : VOID
- Output : NULL
- Return : VOID
- Others :
*******************************************************************************/
int __QueryCameraCapability(void)
{struct v4l2_capability stCap ;struct v4l2_fmtdesc stFormatDesc ;/* 获取摄像头能力 */if(-1 == ioctl(g_iFDVideo, VIDIOC_QUERYCAP, &stCap)){perror("VIDIOC_QUERYCAP");return -1;}/* 打印摄像头能力 */printf("--Camera capability --------------------------------------\n") ;printf("-- driver name : %s \n", stCap.driver) ;printf("-- card name : %s \n", stCap.card) ;printf("-- bus info : %s \n", stCap.bus_info) ;printf("-- driver version : %u.%u.%u \n", (stCap.version >> 16) & 0xFF, (stCap.version >> 8) & 0xFF, (stCap.version) & 0xFF) ;printf("----------------------------------------------------------\n\n\n") ;/* 获取并打印摄像头支持的格式 */stFormatDesc.index = 0 ;stFormatDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;printf("--Format descriptor --------------------------------------\n") ;while(-1 != ioctl(g_iFDVideo, VIDIOC_ENUM_FMT, &stFormatDesc)){printf("-- %d.%s \n", stFormatDesc.index + 1, stFormatDesc.description) ;stFormatDesc.index++ ;}printf("----------------------------------------------------------\n\n\n") ;return 0 ;
}/*******************************************************************************
- Function : __SetCameraFormat
- Description : 本函数设置摄像头输出格式。
- Input : VOID
- Output : NULL
- Return : VOID
- Others :
*******************************************************************************/
int __SetCameraFormat(void)
{struct v4l2_format stFormat;memset(&stFormat, 0, sizeof(stFormat)) ;/* 设置输出格式 */stFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;stFormat.fmt.pix.width = CAMERA_RESOLUTION_WIDTH ;stFormat.fmt.pix.height = CAMERA_RESOLUTION_HEIGHT ;stFormat.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV ; /* 根据需要的格式填写 */if(-1 == ioctl(g_iFDVideo, VIDIOC_S_FMT, &stFormat)) {printf("Set camera format failed.\n") ;return -1 ;}/* 检查设置效果 */stFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;if(-1 == ioctl(g_iFDVideo, VIDIOC_G_FMT, &stFormat)){printf("Get camera format failed.\n") ;return -1;}printf("--Set format result---------------------------------------\n") ;printf("-- Resolution width : %d\n""-- Resolution height : %d\n""-- Pixel format : %c%c%c%c\n",stFormat.fmt.pix.width, stFormat.fmt.pix.height,stFormat.fmt.pix.pixelformat & 0xFF,(stFormat.fmt.pix.pixelformat >> 8) & 0xFF,(stFormat.fmt.pix.pixelformat >> 16) & 0xFF,(stFormat.fmt.pix.pixelformat >> 24) & 0xFF);printf("----------------------------------------------------------\n\n\n") ;return 0 ;
}/*******************************************************************************
- Function : __AllocateBuffers
- Description : 本函数获取缓冲区,并将缓冲区映射到用户空间。
- Input : VOID
- Output : NULL
- Return : VOID
- Others :
*******************************************************************************/
int __AllocateBuffers(void)
{struct v4l2_requestbuffers stReqBuf ;struct v4l2_buffer stBuf ;int iLoop ;memset(&stReqBuf, 0, sizeof(stReqBuf)) ;/* 申请缓冲区 */stReqBuf.count = 4 ;stReqBuf.memory = V4L2_MEMORY_MMAP ;stReqBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;if(-1 == ioctl(g_iFDVideo, VIDIOC_REQBUFS, &stReqBuf)){printf("Request buffers failed.\n") ;return -1;}/* 申请用户空间缓冲区描述符空间 */g_pstBufDesc = (stBufDesc *)calloc(stReqBuf.count, sizeof(stBufDesc)) ;if(NULL == g_pstBufDesc){printf("Calloc buffer descriptor failed.\n") ;return -1;}/* 将申请的缓冲区映射到用户空间 */for(iLoop = 0 ; iLoop < stReqBuf.count ; iLoop++){memset(&stBuf, 0, sizeof(stBuf)) ;stBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;stBuf.index = iLoop;stBuf.memory = V4L2_MEMORY_MMAP;if(-1 == ioctl(g_iFDVideo, VIDIOC_QUERYBUF, &stBuf)){printf("Get buffer information failed.\n") ;return -1;}g_pstBufDesc[iLoop].szBufSize = stBuf.length;g_pstBufDesc[iLoop].pvBufPtr = mmap(NULL,stBuf.length,PROT_READ | PROT_WRITE,MAP_SHARED,g_iFDVideo,stBuf.m.offset);if(MAP_FAILED == g_pstBufDesc[iLoop].pvBufPtr){printf("Map V4L2 buffer to user space failed.\n") ;return -1;}if(-1 == ioctl(g_iFDVideo, VIDIOC_QBUF, &stBuf)){printf("Queue buffer failed.\n") ;return -1;}}printf("--Buffer information---------------------------------------\n") ;for(iLoop = 0 ; iLoop < stReqBuf.count ; iLoop++){printf("-- Frame buffer :%d address :0x%x size:%d\n", iLoop, g_pstBufDesc[iLoop].pvBufPtr, g_pstBufDesc[iLoop].szBufSize);}printf("-----------------------------------------------------------\n") ;return 0 ;
}/*******************************************************************************
- Function : __StartCapture
- Description : 本函数启动数据捕获。
- Input : VOID
- Output : NULL
- Return : VOID
- Others :
*******************************************************************************/
void __StartCapture(void)
{enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if(-1 == ioctl(g_iFDVideo, VIDIOC_STREAMON, &type)){printf("Start capture failed.\n") ;exit(1) ;}return ;
}/*******************************************************************************
- Function : __StopCapture
- Description : 本函数停止数据捕获。
- Input : VOID
- Output : NULL
- Return : VOID
- Others :
*******************************************************************************/
void __StopCapture(void)
{enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if(-1 == ioctl(g_iFDVideo, VIDIOC_STREAMOFF, &type)){printf("Stop capture failed.\n") ;exit(1) ;}return ;
}/*******************************************************************************
- Function : __ReadFrame
- Description : 本函数读取摄像头数据并写入到文件。
- Input : VOID
- Output : NULL
- Return : VOID
- Others :
*******************************************************************************/
int __ReadFrame(void)
{struct v4l2_buffer stBuf;memset(&stBuf, 0, sizeof(stBuf)) ;stBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;stBuf.memory = V4L2_MEMORY_MMAP;if(-1 == ioctl(g_iFDVideo, VIDIOC_DQBUF, &stBuf)){printf("De-queue buffer failed.\n") ;return -1;}if(-1 == write(g_iFDStore, g_pstBufDesc[stBuf.index].pvBufPtr, stBuf.bytesused)){printf("Write camera data failed.\n") ;return -1;}if(-1 == ioctl(g_iFDVideo, VIDIOC_QBUF, &stBuf)){printf("Re-Queue buffer failed.\n") ;return -1;}return 0;
}/*******************************************************************************
- Function : __UnmapBuffer
- Description : 本函数撤除缓冲区映射。
- Input : VOID
- Output : NULL
- Return : VOID
- Others :
*******************************************************************************/
void __UnmapBuffer(void)
{int iLoop ;for(iLoop = 0 ; iLoop < 4 ; iLoop++) {munmap(g_pstBufDesc[iLoop].pvBufPtr, g_pstBufDesc[iLoop].szBufSize) ;}free(g_pstBufDesc) ;return ;
}/*******************************************************************************
- Function : __CaptureFrame
- Description : 本函数捕获帧数据。
- Input : VOID
- Output : NULL
- Return : VOID
- Others :
*******************************************************************************/
int __CaptureFrame(void)
{struct timeval stTimeVal ;fd_set stFDRead ;int iRetVal ;stTimeVal.tv_usec = 0 ;stTimeVal.tv_sec = 2 ; FD_ZERO(&stFDRead) ;FD_SET(g_iFDVideo, &stFDRead) ;iRetVal = select(g_iFDVideo + 1, &stFDRead, NULL, NULL, &stTimeVal) ;if(-1 == iRetVal){perror("select") ;exit(1) ;}else if(0 == iRetVal){printf("timeout.\n") ;return -1 ;}__ReadFrame() ;return 0 ;
}int main(int argc, char *argv[])
{time_t currtime ;int iFrame ;/* 1. 打开摄像头和存储文件 */g_iFDVideo = open("/dev/video0", O_RDWR | O_NONBLOCK, 0) ;if(g_iFDVideo == -1){printf("Can not open /dev/video0.\n") ;return -1;}else{printf("Open /dev/video0 success.\n") ;}g_iFDStore = open("./out.yuv", O_RDWR | O_CREAT, 0777) ;if(g_iFDStore == -1){printf("Open out file is failed.\n") ;return -1;}else{printf("Open out.yuv success.\n") ;}/* 2. 查询摄像头能力 */__QueryCameraCapability() ;/* 3. 设置摄像头输出格式 */__SetCameraFormat() ;/* 4. 申请并映射缓冲区 */__AllocateBuffers() ;/* 5. 启动获取视频数据 */__StartCapture() ;/* 6. 处理视频帧 */time(&currtime) ;printf("Start time : %ld\n", currtime) ;for(iFrame = 0 ; iFrame < FRAME_NUMBER ; iFrame++){__CaptureFrame() ;}time(&currtime) ;printf("start time : %ld\n", currtime) ;/* 7. 停止获取视频数据 */__StopCapture() ;/* 8. 撤销缓存映射并释放缓存 */__UnmapBuffer() ;/* 9. 关闭设备文件 */close(g_iFDVideo) ;close(g_iFDStore) ;return 0 ;
}