本文介绍了通过v4l2接口获取video数据的主要步骤。在android平台camera hal层调用v4l2接口实现video功能。根据android camera hal接口逻辑把v4l2接口的调用分为如下步骤。
四大主要步骤:
- 枚举码流格式,分辨率,帧率信息
- 配置流参数和初始化buffer
- 启动流并获取数据
- 关闭流和反初始化buffer。
1.获取video节点信息
在camera provider进程启动时或者收到video设备插入事件时会读取节点信息用于初始化metadata。
1.1 获取设备属性
首先通过VIDIOC_QUERYCAP ioctl接口获取video节点的设备属性,比如:是否具备视频输入功能。
struct v4l2_capability capability;
int ret_query = ioctl(fd, VIDIOC_QUERYCAP, &capability);
if (ret_query < 0) {
ALOGE("v4l2 QUERYCAP %s failed: %s", strerror(errno));
}
//是否具有视频输入能力
v4l2_buf_type capture_type;
if (capability.device_caps & V4L2_CAP_VIDEO_CAPTURE_MPLANE) {
capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
} else if (capability.device_caps & V4L2_BUF_TYPE_VIDEO_CAPTURE) {
capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
} else {
ALOGW("Does not support VIDEO_CAPTURE");
}
1.2 枚举设备码流格式、分辨率、帧率
三重循环进行枚举,主要步骤:
- 调用VIDIOC_ENUM_FMT枚举码流格式
- 针对每一种格式调用VIDIOC_ENUM_FRAMESIZES枚举分辨率
- 针对每一种码流格式和每一种分辨率组合条件调用VIDIOC_ENUM_FRAMEINTERVALS枚举帧率
struct v4l2_fmtdesc fmt;
struct v4l2_frmsizeenum frmsize;
struct v4l2_frmivalenum fival;
memset(&fmt,0,sizeof(fmt));
fmt.type = capture_type; //支持的设备视频输入类型
fmt.index = 0;
while (ioctl(this->fd, VIDIOC_ENUM_FMT, &fmt) >= 0){ //码流格式
ALOGI("index=%d format:%c%c%c%c Begin=====", fmt.index,
format.fourcc & 0xFF, (format.fourcc >> 8) & 0xFF,
(format.fourcc >> 16) & 0xFF, (format.fourcc >> 24) & 0xFF);
memset(&frmsize, 0, sizeof(frmsize));
frmsize.pixel_format = fmt.pixelformat;
frmsize.index = 0;
std::vector<FrameConfiguration> frameConfList;
while(ioctl(this->fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) >=0){ //分辨率
//only support this type
if(frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE){
ALOGI("index=%d width:%d height:%d Begin*****", frmsize.index,
frmsize.discrete.width, frmsize.discrete.height);
memset(&fival, 0, sizeof(fival));
std::vector<int> frameRates;
fival.pixel_format = frmsize.pixel_format;
fival.width = frmsize.discrete.width;
fival.height = frmsize.discrete.height;
while (ioctl(this->fd, VIDIOC_ENUM_FRAMEINTERVALS, &fival) >= 0){ //帧率
if (fival.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
int frameRate = fival.discrete.denominator / fival.discrete.numerator;
ALOGI("index=%d frameRate:%d", fival.index, frameRate)
frameRates.push_back(frameRate);
}
fival.index++;
}
ALOGI("index=%d width:%d height:%d End*****", frmsize.index,
frmsize.discrete.width, frmsize.discrete.height);
}
frmsize.index++;
}
ALOGI("index=%d format:%c%c%c%c End=====", fmt.index,
format.fourcc & 0xFF, (format.fourcc >> 8) & 0xFF,
(format.fourcc >> 16) & 0xFF, (format.fourcc >> 24) & 0xFF);
fmt.index++;
}
2 配流和初始化buffer
打开相机时会执行配流逻辑,同时在camera hal层会根据配流的分辨率初始化buffer。
2.1 配流
2.1.1 调用VIDIOC_S_FMT配置码流
调用Hal层configureStreams接口时会传入分辨率参数。根据这个分辨率配置v4l2码流。如果配流失败可能是分辨率不支持或者设备被占用。
v4l2_format fmt;
fmt.type = capture_type; //支持的设备视频输入类型
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = fourcc;
int ret = ioctl(mV4l2Fd, VIDIOC_S_FMT, &fmt);
if (ret < 0) {
ALOGE("S_FMT %s error, %s", strerror(errno));
}
2.1.2 Hal pixelformat
pixelformat参数在hal层做过一次转换。将Android系统的format转为v4l2 format。
通常configureStreams传入的格式为:
HAL_PIXEL_FORMAT_BLOB = 33, //照片
HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 34,
HAL_PIXEL_FORMAT_YCBCR_420_888 = 35,
2.1.3 v4l2 pixelformat
在Hal层调用VIDIOC_S_FMT时传入的码流格式为枚举到的格式。通常会将取出的数据转为nv12或nv21返回给camera server。
v4l2 pix format定义的头文件:kernel-5.10/include/uapi/linux/videodev2.h
#define V4L2_PIX_FMT_NV12 v4l2_fourcc('N', 'V', '1', '2') /* 12 Y/CbCr 4:2:0 */
#define V4L2_PIX_FMT_NV21 v4l2_fourcc('N', 'V', '2', '1') /* 12 Y/CrCb 4:2:0 */
#define V4L2_PIX_FMT_MJPEG v4l2_fourcc('M', 'J', 'P', 'G') /* Motion-JPEG */
#define V4L2_PIX_FMT_H264 v4l2_fourcc('H', '2', '6', '4') /* H264 with start codes */
2.2 初始化buffer
在v4l2的调用中v4l2_buffer相关操作比较重要。也是经常需要debug的逻辑,比如:丢帧、帧乱序问题。
该过程主要分四步:
- 调用VIDIOC_REQBUFS请求buffer
- 调用VIDIOC_QUERYBUF获取每个v4l2_buffer的参数
- 根据v4l2_buffer参数调用mmap映射数据地址到hal层。
- 调用VIDIOC_QBUF将v4l2_buffer送到驱动队列准备接收数据
int ret = 0;
// VIDIOC_REQBUFS: 创建buffer
v4l2_requestbuffers req_buffers{};
req_buffers.type = capture_type; //支持的设备视频输入类型
req_buffers.memory = V4L2_MEMORY_MMAP;
req_buffers.count = 4; //请求的buffer数目
if (ioctl(mV4l2Fd, VIDIOC_REQBUFS, &req_buffers) < 0) {
ALOGE("VIDIOC_REQBUFS failed: %s", strerror(errno));
return -errno;
}
//初始化每一个buffer
for (uint32_t i = 0; i < req_buffers.count; i++) {
v4l2_buffer buffer;
buffer.index = i;
buffer.memory = V4L2_MEMORY_MMAP;
buffer.type = capture_type; //支持的设备视频输入类型
// VIDIOC_QUERYBUF 获取buffer参数
if (ioctl(mV4l2Fd, VIDIOC_QUERYBUF, &buffer) < 0) {
ALOGE("%s: QUERYBUF %d failed: %s", __FUNCTION__, i, strerror(errno));
return -errno;
}
// 映射buffer地址到Hal层保存到一个结构体
ALOGD("mmap offset:%d !\n", buffer.m.offset);
struct VideoBuffer {
uint8_t* data;
int len;
};
mVideoBuffer[i].data= (uint8_t*)mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, mV4l2Fd, buffer.m.offset);
mVideoBuffer[i].len = buffer.length;
ALOGD("buffer %d, size:%d\n", i, buffer.length);
// VIDIOC_QBUF: 将buffer送到驱动队列,准备接收数据
if (ioctl(mV4l2Fd, VIDIOC_QBUF, &buffer) < 0) {
ALOGE("%s: QBUF %d failed: %s", __FUNCTION__, i, strerror(errno));
return -errno;
}
}
3. 启动流并获取数据
码流配置完毕,buffer就位后就可以启动流。
3.1 启动流
int ret = ioctl(mV4l2Fd, VIDIOC_STREAMON, &mCaptureType);
if (ret < 0) {
ALOGE("%s: VIDIOC_STREAMON failed, return EINVAL", __FUNCTION__);
return;
}
3.2 获取数据
在Android平台Hal层接口processCaptureRequest被调用时会传入帧号,GraphicBuffer等。开始获取数据。主要是调用VIDIOC_DQBUF和VIDIOC_QBUF。循环调用这两个接口就可以不断获取每一帧数据。在调用VIDIOC_DQBUF前需要调用select监听是否有数据,否则当驱动一直没数据时直接调用VIDIOC_DQBUF会阻塞在这里。usb等设备数据不稳定时select可能会连续超时。可以采取重试或重置设备等恢复操作。
主要分三步:
- 调用select设置超时监听。
- 调用VIDIOC_DQBUF从队列取数据
- 调用VIDIOC_QBUF将v4l2_buffer送回队列
//通过select设置超时监听
fd_set fds;
struct timeval tv;
FD_ZERO(&fds);
FD_SET(mV4l2Fd, &fds);
tv.tv_sec = 1;
tv.tv_usec = 0;
if(select(mV4l2Fd + 1, &fds, NULL, NULL, &tv) == 0) {
ALOGE("select time out");
return -errno;
}
// VIDIOC_DQBUF 从队列获取数据
v4l2_buffer buffer;
buffer.type = capture_type; //支持的设备视频输入类型
buffer.memory = V4L2_MEMORY_MMAP;
if (ioctl(mV4l2Fd, VIDIOC_DQBUF, &buffer) < 0) {
ALOGE("DQBUF fails: %s", strerror(errno));
return -errno;
}
if (buffer.flags & V4L2_BUF_FLAG_ERROR) {
ALOGE("v4l2 buf error! buf flag 0x%x, index=%d", buffer.flags, buffer.index);
return -errno;
}
// VIDIOC_QBUF 将v4l2_buffer送回队列
if (ioctl(mV4l2Fd, VIDIOC_QBUF, &buffer) < 0) {
ALOGE("camera %s: QBUF index fails: %s", mCameraId, strerror(errno));
return -errno;
}
在Android平台获取数据后根据需要会做一些缩放、裁剪和格式转换,然后将数据拷贝到processCaptureRequest传入的GraphicBuffer中。最后调用processCaptureResult通知camera server数据返回。
4. 关闭流和反初始化buffer。
当调用hal的close接口时需要关闭流并反初始化buffer。重新配流时也需要确保流已经关闭。
该过程分为三步:
- 调用VIDIOC_STREAMOFF关闭流
- 调用munmap释放之前的映射
- 调用VIDIOC_REQBUFS释放buffer。和之前申请buffer的差异是传入buffer个数参数为0。
4.1 关闭流
// VIDIOC_STREAMOFF 关闭流
if (ioctl(mV4l2Fd.get(), VIDIOC_STREAMOFF, &capture_type) < 0) {
ALOGE("STREAMOFF failed: %s", strerror(errno));
return -errno;
}
4.2 反初始化buffer
//释放buffer映射
for (int i=0; i<mV4L2BufferCount; i++) {
munmap(mVideoBuffer[i].data, mVideoBuffer[i].len);
}
// VIDIOC_REQBUFS: 清除buffer
v4l2_requestbuffers req_buffers{};
req_buffers.type = capture_type; //支持的设备视频输入类型
req_buffers.memory = V4L2_MEMORY_MMAP;
req_buffers.count = 0;
if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_REQBUFS, &req_buffers)) < 0) {
ALOGE("REQBUFS failed: %s", strerror(errno));
return -errno;
}
5 主要结构体
结构体定义的文件路径:件kernel-5.10/include/uapi/linux/videodev2.h
5.1 获取设备属性
- v4l2_capability结构体
struct v4l2_capability
{
__u8 driver[16]; //驱动名
__u8 card[32]; //厂家信息
__u8 bus_info[32]; //bus信息
__u32 version; //版本信息
__u32 capabilities; //能力集
__u32 device_caps; //capture类型
__u32 reserved[4]; //保留
};
5.2 枚举码流格式、分辨率、帧率
- 码流格式v4l2_fmtdesc结构体
/*
* F O R M A T E N U M E R A T I O N
*/
struct v4l2_fmtdesc {
__u32 index; /* Format number */ 枚举时自加
__u32 type; /* enum v4l2_buf_type */
__u32 flags;
__u8 description[32]; /* Description string */
__u32 pixelformat; /* Format fourcc */ 主要成员
__u32 mbus_code; /* Media bus code */
__u32 reserved[3];
};
- 分辨率v4l2_frmsizeenum结构体
/*
* F R A M E S I Z E E N U M E R A T I O N
*/
enum v4l2_frmsizetypes {
V4L2_FRMSIZE_TYPE_DISCRETE = 1, 一般支持这种类型
V4L2_FRMSIZE_TYPE_CONTINUOUS = 2,
V4L2_FRMSIZE_TYPE_STEPWISE = 3,
};
struct v4l2_frmsize_discrete {
__u32 width; /* Frame width [pixel] */
__u32 height; /* Frame height [pixel] */
};
struct v4l2_frmsize_stepwise {
__u32 min_width; /* Minimum frame width [pixel] */
__u32 max_width; /* Maximum frame width [pixel] */
__u32 step_width; /* Frame width step size [pixel] */
__u32 min_height; /* Minimum frame height [pixel] */
__u32 max_height; /* Maximum frame height [pixel] */
__u32 step_height; /* Frame height step size [pixel] */
};
struct v4l2_frmsizeenum { /*主要结构体*/
__u32 index; /* Frame size number */ //枚举时自加
__u32 pixel_format; /* Pixel format */ //枚举时传入参数
__u32 type; /* Frame size type the device supports. 枚举时传出参数,会判断 是否是V4L2_FRMSIZE_TYPE_DISCRETE。目前android只支持这种类型。*/
union { /* Frame size */
struct v4l2_frmsize_discrete discrete; //枚举时重要的输出参数,包含宽高值
struct v4l2_frmsize_stepwise stepwise;
};
__u32 reserved[2]; /* Reserved space for future use */
};
- 帧率v4l2_frmivalenum结构体
/*
* F R A M E R A T E E N U M E R A T I O N
*/
enum v4l2_frmivaltypes {
V4L2_FRMIVAL_TYPE_DISCRETE = 1,
V4L2_FRMIVAL_TYPE_CONTINUOUS = 2,
V4L2_FRMIVAL_TYPE_STEPWISE = 3,
};
struct v4l2_frmival_stepwise {
struct v4l2_fract min; /* Minimum frame interval [s] */
struct v4l2_fract max; /* Maximum frame interval [s] */
struct v4l2_fract step; /* Frame interval step size [s] */
};
struct v4l2_frmivalenum { /*主要结构体*/
__u32 index; /* Frame format index 枚举时自加*/
__u32 pixel_format; /* Pixel format 枚举时传入参数*/
__u32 width; /* Frame width 枚举时传入参数*/
__u32 height; /* Frame height 枚举时传入参数*/
__u32 type; /* Frame interval type the device supports.输出参数,会判断是否是V4L2_FRMIVAL_TYPE_DISCRETE类型*/
union { /* Frame interval */
struct v4l2_fract discrete; /*主要输出参数,包含了计算帧率数值*/
struct v4l2_frmival_stepwise stepwise;
};
__u32 reserved[2]; /* Reserved space for future use */
};
5.3 配流
- 结构体v4l2_format
/*
* V I D E O I M A G E F O R M A T
*/
struct v4l2_pix_format { /*配流时关注的结构体*/
__u32 width; /*重要成员*/
__u32 height; /*重要成员*/
__u32 pixelformat; /*重要成员*/
__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_*) */
union {
/* enum v4l2_ycbcr_encoding */
__u32 ycbcr_enc;
/* enum v4l2_hsv_encoding */
__u32 hsv_enc;
};
__u32 quantization; /* enum v4l2_quantization */
__u32 xfer_func; /* enum v4l2_xfer_func */
};
struct v4l2_format {
__u32 type;
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE 主要输入参数成员*/
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_format sliced; /* 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]; /* user-defined */
} fmt;
};
5.4 初始化buffer,获取数据
- v4l2_requestbuffers结构体
struct v4l2_requestbuffers {
__u32 count; /*申请的buffer个数*/
__u32 type; /* enum v4l2_buf_type 赋值为device_caps值*/
__u32 memory; /* enum v4l2_memory 一般赋值为V4L2_MEMORY_MMAP*/
__u32 capabilities;
__u32 reserved[1];
};
- v4l2_buffer结构
非常重要的结构体,数据传输的重要载体。
struct v4l2_buffer {
__u32 index; /*申请的buffer索引, DQBuf操作时为输出参数。QUERYBUF和QBuf为输入参数*/
__u32 type; /*输入参数 capture_type*/
__u32 bytesused; /*输出参数,数据字节数*/
__u32 flags; /*输出参数*/
__u32 field;
#ifdef __KERNEL__
struct __kernel_v4l2_timeval timestamp;
#else
struct timeval timestamp;
#endif
struct v4l2_timecode timecode;
__u32 sequence; /*输出参数,帧序号*/
/* memory location */
__u32 memory; /*输入参数,一般为V4L2_MEMORY_MMAP*/
union {
__u32 offset; /*输出参数,map时传入的偏移*/
unsigned long userptr;
struct v4l2_plane *planes;
__s32 fd;
} m;
__u32 length;
__u32 reserved2;
union {
__s32 request_fd;
__u32 reserved;
};
};