在Android平台通过V4L2接口获取video流

本文介绍了通过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 -1;
    }

    // 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;
    };
};

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值