声明:
环境:linux或ubunt下
编辑器:vim
编译工具:gcc
设备:USB接口的摄像头
难点理解:
1.对于缓冲帧的解释:假设我们申请5个缓冲帧,那么这5个缓冲帧就相当于5个盘子,操作系统在内存中开辟5个缓存区队列来存放这5个盘子,当相机拍照后将数据放入盘子中,我们通过VIDIOC_DQBUF取数据可以理解为将盘子取出来,然后对数据操作,操作完成之后,要通过VIDIOC_QBUF将盘子放回去,以保证可以循环接收数据。
2.对于映射的解释:对于映射,可以理解为cpu在处理时会给你分配几个空间,然后把这几个空间的首地址返回给你,如果你有数据,那么他就会把数据放到这个空间内。
程序源码:(包含详细注释)
/*
@sun : 2017-8-18
email: [email protected]
csdn : Mr_sunp
abstract :
under linux,Get an image with v4l2.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define NB_BUFFER 5
#define DBG_DIR "jpg/"
#define VIDEO_L_MASK 0x01
#define VIDEO_R_MASK 0x02
#if 1
/*
头文件 :
函数 :int ioctl(intfd, int request, struct v4l2_capability *argp);
struct v4l2_capability 设备的功能
{
__u8 driver[16]; // 驱动名字
__u8 card[32]; // 设备名字
__u8 bus_info[32]; // 设备在系统中的位置
__u32 version; // 驱动版本号
__u32 capabilities;// 设备支持的操作
__u32 reserved[4]; // 保留字段
};
VIDIOC_QUERYCAP //查询驱动功能
*/
/* fd:文件描述符 */
int showCapability(int fd)
{
printf("-----------------------设备信息---------------------\n");
struct v4l2_capability cap;
if(ioctl(fd,VIDIOC_QUERYCAP,&cap) < 0) {
perror("error ioctl VIDIOC_QUERYCAP !");
return -1;
}
printf("DriverName :%s\nCard Name :%s\nBus info :%s\nDriverVersion:%u.%u.%u\n"
,cap.driver
,cap.card
,cap.bus_info
,(cap.version>>16)&0xff
,(cap.version>>8)&0xff
,cap.version&0xff);
return 0;
}
#endif
#if 1
/*
函数 :int ioctl(intfd, int request, struct v4l2_fmtdesc *argp);
struct v4l2_fmtdesc
{
__u32 index; // 要查询的格式序号,应用程序设置
enum v4l2_buf_type type; // 帧类型,应用程序设置
__u32 flags; // 是否为压缩格式
__u8 description[32]; // 格式名称
__u32 pixelformat; // 格式
__u32 reserved[4]; // 保留
};
VIDIOC_ENUM_FMT //指令含义:获取当前驱动支持的视频格式
*/
/* fd:文件描述符 */
int showFmtdesc(int fd)
{
printf("-----------------------支持格式---------------------\n");
struct v4l2_fmtdesc dis_fmtdesc;
dis_fmtdesc.index=0;
dis_fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("Supportformat:\n");
while(ioctl(fd,VIDIOC_ENUM_FMT,&dis_fmtdesc)!=-1)
{
printf("\t%d.%s\n",dis_fmtdesc.index+1,dis_fmtdesc.description);
dis_fmtdesc.index++;
}
return 0;
}
#endif
#if 1
/*
函数 :int ioctl(intfd, int request, struct v4l2_format *argp);
struct v4l2_format 帧的格式
{
enum v4l2_buf_type type; //帧类型,应用程序设置
union fmt
{
structv4l2_pix_format pix; //视频设备使用
structv4l2_window win;
structv4l2_vbi_format vbi;
structv4l2_sliced_vbi_format sliced;
__u8raw_data[200];
};
};
VIDIOC_G_FMT: //指令含义:读取当前驱动的捕获格式
VIDIOC_S_FMT: //指令含义:设置当前驱动的捕获格式
VIDIOC_ENUM_FMT: //指令含义:获取当前驱动支持的视频格式
*/
/* fd:文件描述符 */
int showFormat(int fd)
{
printf("------------------------帧信息----------------------\n");
struct v4l2_format dis_fmt;
dis_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd,VIDIOC_G_FMT,&dis_fmt);
printf("Currentdata format information:\n\twidth:%d\n\theight:%d\n"
,dis_fmt.fmt.pix.width
,dis_fmt.fmt.pix.height);
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index=0;
fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
{
if(fmtdesc.pixelformat & dis_fmt.fmt.pix.pixelformat)
{
printf("\tformat:%s\n",fmtdesc.description);
break;
}
fmtdesc.index++;
}
return 0;
}
#endif
#if 1
/*
V4L2_FIELD_ANY //指令含义:
V4L2_PIX_FMT_MJPEG //指令含义:MJPEG格式
V4L2_PIX_FMT_UYVY //指令含义:YUYV格式
VIDIOC_S_PARM //指令格式:检查格式和设置
*/
/* fd : 文件描述符 w :宽 h:高 */
int setFormat(int fd ,int w,int h)
{
printf("--------------------设置当前格式--------------------\n");
struct v4l2_format fmt = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.fmt = {
.pix = {
.width = w, //帧宽
.height = h, //帧高
.pixelformat = V4L2_PIX_FMT_MJPEG, //帧格式
.field = V4L2_FIELD_ANY,
}
},
};
//检查格式 和 设置是否成功
if(ioctl(fd,VIDIOC_S_PARM,&fmt) < 0){
perror("error ioctl VIDIOC_S_PARM !");
return -1;
}else{
printf("set image size:\n\twidth = %d\n\theight = %d\n",w,h);
}
return 0;
}
#endif
#if 1
/*
fd :文件描述符
fps:帧率
numerator:这里固定为1
*/
int setFps(int fd ,int fps)
{
printf("--------------------设置当前帧率--------------------\n");
struct v4l2_streamparm setfps = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.parm = {
.capture = {
.timeperframe = {
.numerator=1,
.denominator = fps, //帧率时间长度为 = numerator/denominator
}
}
},
};
if (ioctl(fd, VIDIOC_S_PARM, &setfps) < 0) {
perror("error SETFPS failed !");
return -1;
}else{
printf("\tset fps : %d\n",fps);
}
return 0;
}
#endif
#if 1
/* fd :文件描述符 val:曝光值 */
int setExposure(int fd,int val)
{
printf("--------------------设置曝光模式--------------------\n");
struct v4l2_control con_setup = { //设置手动曝光
.id = V4L2_CID_EXPOSURE_AUTO,
.value = V4L2_EXPOSURE_MANUAL,
};
if(ioctl(fd,VIDIOC_S_CTRL,&con_setup)<0){
perror("set exposure failed !");
}
struct v4l2_control con_set = { //设置曝光绝对值
.id = V4L2_CID_EXPOSURE_ABSOLUTE,
.value = val,
};
if(ioctl(fd,VIDIOC_S_CTRL,&con_set)<0){
perror("set exposure failed !");
}else{
printf("set exposure\n\tmode : 手动\n\tval : %d\n",val);
}
return 0;
}
#endif
#if 1
/* fd :文件描述符 val:焦距 */
int setFocus(int fd ,int val)
{
printf("--------------------设置对焦模式--------------------\n");
struct v4l2_control con_setup = { //关闭自动对焦
.id = V4L2_CID_FOCUS_AUTO,
.value = 0,
};
if(ioctl(fd,VIDIOC_S_CTRL,&con_setup) < 0){
perror("focus set failed !");
}else{
printf("focus off;\n");
}
struct v4l2_control con_set = { //设置焦点值
.id = V4L2_CID_FOCUS_ABSOLUTE,
.value = val,
};
if(ioctl(fd,VIDIOC_S_CTRL,&con_set) < 0){
perror("focus set failed !");
}else{
printf("focus on;\n\tval : %d\n",val);
}
return 0;
}
#endif
#if 1
/*
申请和管理缓冲区,应用程序和设备有三种交换数据的方法,直接read/write ,内存映射(memorymapping) ,用户指针。这里只讨论 memorymapping.
函数 :int ioctl(intfd, int request, struct v4l2_requestbuffers *argp);
struct v4l2_requestbuffers 申请帧缓冲
{
__u32 count; // 缓冲区内缓冲帧的数目
enum v4l2_buf_type type;// 缓冲帧数据格式
enum v4l2_memorymemory; // 区别是内存映射还是用户指针方式
__u32 reserved[2];
};
VIDIOC_REQBUFS //指令含义:分配内存
*/
/* fd :文件描述符 */
int requestBuffers(int fd)
{
printf("---------------------申请缓冲区---------------------\n");
struct v4l2_requestbuffers rb = {
.count = NB_BUFFER, //缓冲帧个数
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,//数据类型:流类型,永远都是
.memory = V4L2_MEMORY_MMAP, //存储类型:V4L2_MEMORY_MMAP或V4L2_MEMORY_USERPTR
};
if (ioctl(fd, VIDIOC_REQBUFS, &rb) < 0){
perror("error VIDIOC_REQBUFS !");
return -1;
}
printf("count : %d\n",NB_BUFFER);
return 0;
}
#endif
#if 1
/*
函数 :int ioctl(intfd, int request, struct v4l2_buffer *argp);
struct v4l2_buffer
{
__u32 index; //buffer 序号
enum v4l2_buf_type type; //buffer 类型
__u32 byteused; //buffer 中已使用的字节数
__u32 flags; //区分是MMAP 还是USERPTR
enum v4l2_fieldfield;
struct timeval timestamp; //获取第一个字节时的系统时间
struct v4l2_timecode timecode;
__u32 sequence; //队列中的序号
enum v4l2_memory memory; //IO 方式,被应用程序设置
union m
{
__u32 offset; //缓冲帧地址,只对MMAP 有效
unsigned longuserptr;
};
__u32 length; //缓冲帧长度
__u32 input;
__u32 reserved;
};
VIDIOC_QUERYBUF //把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
*/
/*
MMAP:定义一个结构体来映射每个缓冲帧
*/
struct buffer{
void *start;
unsigned int length;
} *buffers;
/*
#include
void *mmap(void*addr, size_t length, int prot, int flags, int fd, off_t offset);
//addr 映射起始地址,一般为NULL ,让内核自动选择
//length 被映射内存块的长度
//prot 标志映射后能否被读写,其值为PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE
//flags 确定此内存映射能否被其他进程共享,MAP_SHARED,MAP_PRIVATE
//fd,offset, 确定被映射的内存地址
成功:返回成功映射后的地址,不成功:返回MAP_FAILED ((void*)-1);
int munmap(void*addr, size_t length);// 断开映射
//addr 为映射后的地址,length 为映射后的内存长度
*/
/* fd :文件描述符 addr:二级指针,用来存放开辟的buffers地址 */
int getBufLenAndAddr(int fd,struct buffer **addr)
{
printf("-----------------获取缓冲区地址,长度---------------\n");
buffers = (struct buffer*)calloc(NB_BUFFER,sizeof(*buffers));
*addr = buffers; //记录开辟的空间首地址,便于free
if(!buffers){ //将n_buffers个以申请到的缓冲帧映射到引用程序,用buffers指针记录
perror("error calloc failed !");
return -1;
}
unsigned int n_buffers;
for(n_buffers=0;n_buffers