Android P摄像头开发和V4l2视频处理

  • Post author:
  • Post category:其他


基于安卓9.0系统,简单实现打开摄像头并获取视频流数据。

以下例子是C++代码,可以做成jni so库方式给安卓应用程序使用,获取到视频数据用OpenGL方式显示出来。



1、打开摄像头

摄像头设备为:/dev/video0

//打开摄像头 deviceName为/dev/video0
if ((fd = open(deviceName, O_RDWR, 0)) < 0){
	return false;
}



2、查询视频设备的能力,是否具有视频输入,或者音频输入功能

VIDIOC_QUERYCAP

    v4l2_capability caps;
    {
        if (ioctl(fd, VIDIOC_QUERYCAP, &caps) < 0) {
            ALOGE("V4l2Capture::%s,failed to get device caps for %s (%d = %s)",__func__,deviceName, errno, strerror(errno));
            close(fd);
            return false;
        }
        // Report device properties
        ALOGI("V4l2Capture::%s,Open Device: %s (fd=%d)",__func__ , deviceName, fd);
        ALOGI("  Driver: %s", caps.driver);
        ALOGI("  Card: %s", caps.card);
        ALOGI("  Version: %u.%u.%u",(caps.version >> 16) & 0xFF,(caps.version >> 8)  & 0xFF,(caps.version)       & 0xFF);
        ALOGI("  All Caps: %08X", caps.capabilities);
        ALOGI("  Dev Caps: %08X", caps.device_caps);
    }



3、查询或者设置视频的制式或者帧格式

VIDIOC_ENUM_FMT

    v4l2_fmtdesc formatDescriptions;
    formatDescriptions.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    {
        for (int i=0; true; i++) {
            formatDescriptions.index = i;
            // query supported format
            if (ioctl(fd, VIDIOC_ENUM_FMT, &formatDescriptions) == 0) {
                ALOGI("V4l2Capture::%s, %2d: %s 0x%08X 0x%X",__func__,i,formatDescriptions.description,formatDescriptions.pixelformat,formatDescriptions.flags);
            }else {
                // No more formats available
                break;
            }
        }

        // Verify we can use this device for video capture
        if (!(caps.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) ||
            !(caps.capabilities & V4L2_CAP_STREAMING)) {
            // Can't do streaming capture.
            ALOGE("V4l2Capture::%s,Streaming capture not supported by %s.",__func__, deviceName);
            return false;
        }
    }



4、设置视频采集的帧率

VIDIOC_S_PARM

    struct v4l2_streamparm param;
    memset(&param, 0, sizeof(param));
    param.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    param.parm.capture.timeperframe.numerator   = 1;
    param.parm.capture.timeperframe.denominator = 30;
    param.parm.capture.capturemode = getCaptureMode(fd, mWidth, mHeight);
    int ret = ioctl(fd, VIDIOC_S_PARM, &param);
    if (ret < 0) {
        ALOGE("V4l2Capture::%s: VIDIOC_S_PARM Failed: %s", __func__, strerror(errno));
        return false;
    }



5、设置视频的帧格式,包括帧的点阵格式,宽度和高度等

VIDIOC_S_FMT

    v4l2_format format;
    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    format.fmt.pix_mp.pixelformat = mV4lFormat;
    format.fmt.pix_mp.width = mWidth;
    format.fmt.pix_mp.height = mHeight;
    // TODO:  Do we need to specify this?
    //format.fmt.pix_mp.field = V4L2_FIELD_ALTERNATE;
    format.fmt.pix_mp.num_planes = 1;
    ALOGI("V4l2Capture::%s,Requesting format %c%c%c%c (0x%08X)",__func__,((char*)&format.fmt.pix.pixelformat)[0],((char*)&format.fmt.pix.pixelformat)[1],((char*)&format.fmt.pix.pixelformat)[2],((char*)&format.fmt.pix.pixelformat)[3],format.fmt.pix.pixelformat);
    if (ioctl(fd, VIDIOC_S_FMT, &format) < 0) {
        ALOGE("V4l2Capture::%s,IDIOC_S_FMT: %s",__func__, strerror(errno));
    }



6、查看视频的帧格式,包括帧的点阵格式,宽度和高度等

VIDIOC_G_FMT

    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    if (ioctl(fd, VIDIOC_G_FMT, &format) == 0) {

        mV4lFormat = format.fmt.pix_mp.pixelformat;
        mWidth  = format.fmt.pix_mp.width;
        mHeight = format.fmt.pix_mp.height;
        ALOGI("V4l2Capture::%s,Current output format:  fmt=0x%X, %dx%d",__func__,format.fmt.pix_mp.pixelformat,format.fmt.pix_mp.width,format.fmt.pix_mp.height);
    }else {
        ALOGE("V4l2Capture::%s,VIDIOC_G_FMT: %s",__func__, strerror(errno));
        return false;
    }



7、向驱动请求申请帧缓冲区

VIDIOC_REQBUFS

这一步是比较重要的一步,申请若干个帧缓冲区,一般不少于3个。申请缓冲区已在内核空间中申请了一段内存空间。

    struct v4l2_requestbuffers req; // Parameters of the device buffers

    // Initiate Memory Mapping, User Pointer I/O or DMA buffer I/O
    memset(&req, 0, sizeof(req));
    req.count = BUFFER_NUM; // The number of buffers requested or granted
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    req.memory = mMemoryType;
    if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0){
        ALOGE("V4l2Capture::%s,VIDIOC_REQBUFS: %s",__func__, strerror(errno));
    }



8、查询帧缓冲区在内核空间中的长度和偏移量

VideoBuffer mBuffers[BUFFER_NUM]; // buffers

VIDIOC_QUERYBUF

内存方式为:V4L2_MEMORY_MMAP

    struct v4l2_buffer buf;
    struct v4l2_plane planes = { 0 };
    enum v4l2_buf_type type;
 
    memset(&buf, 0, sizeof(buf));
    memset(&planes, 0, sizeof(planes));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.m.planes = &planes;
    buf.length = 1;
    buf.index = i;

    // Query the status of a buffer at any time after mBuffers have been allocated with the VIDIOC_REQBUFS ioctl
    if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
        cout << "VIDIOC_QUERYBUF error" << endl;
        ALOGE("V4l2Capture::%s VIDIOC_QUERYBUF error", __func__);
        ALOGE("V4l2Capture::%s VIDIOC_QUERYBUF error:%s", __func__,strerror(errno));
        return false;
    }
    //用mmap申请内存空间,将VIDIOC_REQBUFS 申请到内核空间帧缓冲区地址映射到用户空间
        mBuffers[i].length = buf.m.planes->length;
        mBuffers[i].offset = (size_t) buf.m.planes->m.mem_offset;
        mBuffers[i].filled = 0;
        mBuffers[i].invalid = 0;
        mBuffers[i].start = (unsigned char*)mmap(NULL, mBuffers[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mBuffers[i].offset);
        memset(mBuffers[i].start, 0xFF, mBuffers[i].length);



9、将申请到的帧缓冲区全部放入视频采集输出队列,以便存放采集的数据

VIDIOC_QBUF

        memset(&buf, 0, sizeof(buf));
        memset(&planes, 0, sizeof(planes));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        buf.memory = mMemoryType;
        buf.m.planes = &planes;
        buf.index = i;
        buf.m.planes->length = mBuffers[i].length;
        buf.length = 1;
        buf.m.planes->m.mem_offset = mBuffers[i].offset;

        // Enqueue an empty (capturing) or filled (output) buffer in the driver's incoming queue
        if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
            cout << "start VIDIOC_QBUF error" << endl;
            ALOGE("V4l2Capture::%s start VIDIOC_QBUF error", __func__);
            return(-1);
        }



10、开始视频流数据的采集

VIDIOC_STREAMON

    // Start streaming I/O
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
        cout << "VIDIOC_STREAMON error" << endl;
        ALOGE("V4l2Capture::%s VIDIOC_STREAMON error", __func__);
        return(-1);
    } else{
        cout << "VIDIOC_STREAMON success" << endl;
        ALOGE("V4l2Capture::%s VIDIOC_STREAMON success", __func__);
    }



11、app从视频采集输出列列出已含有采集数据的帧缓冲区,获取该帧缓冲区的原始视频数据

//最终得到的视频数据就保存在mCaptureBuf中

v4l2_buffer* mCaptureBuf[BUFFER_NUM];

VIDIOC_DQBUF

    struct v4l2_plane planes={0};

    memset(mCaptureBuf[index], 0, sizeof(capture_buf[index]));

    memset(&planes, 0, sizeof(planes));
    mCaptureBuf[index]->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    mCaptureBuf[index]->memory = V4L2_MEMORY_MMAP;
    mCaptureBuf[index]->m.planes = &planes;
    mCaptureBuf[index]->length = 1;

    ALOGE("V4l2Capture::VIDIOC_DQBUF index: %d", index);
    // Wait for a buffer to be ready
    std::unique_lock <std::mutex> lock(mLock);
    fill_buffer_inx = index;
    if (ioctl(fd, VIDIOC_DQBUF, mCaptureBuf[index]) < 0) {	//index可以一直为0
        ALOGE("V4l2Capture::%s VIDIOC_DQBUF: %s", __func__,strerror(errno));
        return nullptr ;
    }else{
        ALOGE("V4l2Capture::%s VIDIOC_DQBUF: %s",__func__, strerror(errno));
    }



12、停止视频的采集

VIDIOC_STREAMOFF

    // Stop the underlying video stream (automatically empties the buffer queue)
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0) {
        ALOGE("VIDIOC_STREAMOFF: %s", strerror(errno));
    }



13、释放申请的视频帧缓冲区

munmap

    // Tell the L4V2 driver to release our streaming buffers
    if (fd >= 0)
    {
        ALOGI("munmap before");
        for (int i = 0; i < BUFFER_NUM; i++)
            munmap(mBuffers[i].start, mBuffers[i].length);
        ALOGI("munmap after");
    }



14、关闭视频设备文件

    ALOGI("closing video device file handled %d", fd);
    close(fd);



版权声明:本文为Sunxiaolin2016原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。