v4l2打开相机的基本过程

     ~~~~     之前在linux下使用v4l2打开相机,现在记录一下,写博客是个好习惯,要好好培养,要坚持。
     ~~~~     使用v4l2打开相机,如果相机支持输出yuv,其实很多时候,写起来,感觉还是很爽的,但是有些相机yuv输出的帧率太低了,这个时候你会发现还支持一个格式,叫Motion-JPEG,这就很烦了,虽然帧率够了,但是需要解码呀,需要使用第三方解码库,这里我使用的就是ffmpeg,但是这就比较耗cpu的性能了。
     ~~~~     比较喜欢输出格式是nv21,或者yuyv的,当然这是个人看法。

     ~~~~     下面我们开始打开v4l2的设备,我们需要找到设备的节点,这里我们在dev下面找就行了,我这里是/dev/video0就是我要找的设备

// open V4L2 device
int mCameraFd = open(dev_name, O_RDWR | O_NONBLOCK, 0);
if (mCameraFd == -1) {
    //打开相机失败,可以通过strerror(errno),获取到错误的信息
    privtf("error:%s\n", strerror(errno));
    return -1;
}

打开设备成功之后,我们需要获取设备的信息,这样我们可以判断这个设备是不是我们想要打开的设备,得到一些设备信息,同时可以查询设备支持得操作模式。

//这个是v4l2获取的信息的结构体
struct v4l2_capability {
  __u8 driver[16];    //驱动名。
  __u8 card[32];      // Device名
  __u8 bus_info[32];  //在Bus系统中存放位置
  __u32 version;      //driver 版本
  __u32 capabilities; //能力集 通常为:V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING
  __u32 reserved[4];  //保留字段
};

能力集里面类型很多,但是我们取摄像头视频,只需要看它是否支持V4L2_CAP_VIDEO_CAPTURE和V4L2_CAP_STREAMING就行了,通常也只有这两个,这两个的意思就是这是一个视频捕捉设备和具有数据流控制模式,只要有这两个,我们就可以从设备获取码流。

#define V4L2_CAP_VIDEO_CAPTURE 0x00000001
#define V4L2_CAP_VIDEO_OUTPUT 0x00000002
#define V4L2_CAP_VIDEO_OVERLAY 0x00000004
#define V4L2_CAP_VBI_CAPTURE 0x00000010
#define V4L2_CAP_VBI_OUTPUT 0x00000020
#define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040
#define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080
#define V4L2_CAP_RDS_CAPTURE 0x00000100
#define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200
#define V4L2_CAP_HW_FREQ_SEEK 0x00000400
#define V4L2_CAP_RDS_OUTPUT 0x00000800
#define V4L2_CAP_VIDEO_CAPTURE_MPLANE 0x00001000
#define V4L2_CAP_VIDEO_OUTPUT_MPLANE 0x00002000
#define V4L2_CAP_VIDEO_M2M_MPLANE 0x00004000
#define V4L2_CAP_VIDEO_M2M 0x00008000
#define V4L2_CAP_TUNER 0x00010000
#define V4L2_CAP_AUDIO 0x00020000
#define V4L2_CAP_RADIO 0x00040000
#define V4L2_CAP_MODULATOR 0x00080000
#define V4L2_CAP_SDR_CAPTURE 0x00100000
#define V4L2_CAP_EXT_PIX_FORMAT 0x00200000
#define V4L2_CAP_SDR_OUTPUT 0x00400000
#define V4L2_CAP_META_CAPTURE 0x00800000
#define V4L2_CAP_READWRITE 0x01000000
#define V4L2_CAP_ASYNCIO 0x02000000
#define V4L2_CAP_STREAMING 0x04000000
#define V4L2_CAP_TOUCH 0x10000000
#define V4L2_CAP_DEVICE_CAPS 0x80000000

我们可以使用VIDIOC_QUERYCAP 命令通过结构 v4l2_capability 获取设备支持的操作模式:

ret = ioctl(mCameraFd, VIDIOC_QUERYCAP, &cap);
if (ret < 0) {
    //无法查看设备
    close(mCameraFd);
    mCameraFd = -1;
    return -1;
}

if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
    //设备不支持视频捕捉
    close(mCameraFd);
    mCameraFd = -1;
    return-1;
}

if ((cap.capabilities & V4L2_CAP_STREAMING) == 0) {
    //设备不支持数据流控制模式
    close(mCameraFd);
    mCameraFd = -1;
    return -1;
}

然后我们需要查询设备支持得格式,同时设置设备输出的格式,当然我们设置格式,需要是设备支持得格式才能成功

//获取所有支持得格式
struct v4l2_fmtdesc fmtdesc;

fmtdesc.index=0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
LOGE("Support format:");

while(ioctl(mCameraFd, VIDIOC_ENUM_FMT, &fmtdesc) != -1) {
    printf("description = %s. pixelformat = %d", fmtdesc.description, fmtdesc.pixelformat);
    fmtdesc.index++;
}

//获取当前格式
struct v4l2_format fmt;
fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret = ioctl(mCameraFd, VIDIOC_G_FMT, &fmt);
if(ret != -1) {
	printf("format information : width:%d. height:%d. pixelformat = %d.", 
		fmt.fmt.pix.width, fmt.fmt.pix.height, fmt.fmt.pix.pixelformat);
}

//设置当前格式
int ret = 0;
struct v4l2_format format;
memset(&format, 0, sizeof(format));
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width  = width;
format.fmt.pix.height = height;
format.fmt.pix.pixelformat = pix_fmt;   //nv21   825382478
format.fmt.pix.field = V4L2_FIELD_NONE;   //V4L2_FIELD_NONE  1

ret = ioctl(mCameraFd, VIDIOC_S_FMT, &format);
if (ret < 0)
{
    //失败,设置格式失败, 可以通过strerror(errno),获取到错误的信息
    privtf("error:%s\n", strerror(errno));
    return ret;
}

然后我们可以设置帧率,有时候我们不需要太高的帧率,这样可以减低系统运算的负荷,保证实际帧率不会超过你设置的帧率。

int ret = 0;
struct v4l2_streamparm params;
params.parm.capture.timeperframe.numerator = 1;
params.parm.capture.timeperframe.denominator = (__u32)30;//帧率默认是30
params.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
params.parm.capture.capturemode = 4;//V4L2_MODE_PREVIEW;
ret = ioctl(mCameraFd, VIDIOC_S_PARM, &params);
if (ret < 0) {
    printf("v4l2setCaptureParams failed, %s", strerror(errno));
}
else {
    printf("v4l2setCaptureParams ok");
}
return ret;

然后就是向设备申请缓冲区了,我这申请了7个。

int ret = 0;
struct v4l2_requestbuffers rb;
memset(&rb, 0, sizeof(rb));
rb.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
rb.memory = V4L2_MEMORY_MMAP;
rb.count  = 7;

ret = ioctl(mCameraFd, VIDIOC_REQBUFS, &rb);
if (ret < 0)
{
    printf("Init: VIDIOC_REQBUFS failed: %s", strerror(errno));
    return ret;
}

return 0;

设置好了缓冲区,我们就需要去映射内存区,这里我们先定义一个结构体,用来映射内存区,保存图像信息。

typedef struct v4l2_mem_map_t{
    void *	mem[NB_BUFFER];//这里就是对应你申请了几个缓冲区
    int 	length;
}v4l2_mem_map_t;
/**
 * 内存映射
 */
v4l2_mem_map_t mMapMem;//在下面会使用到
struct v4l2_buffer buf;

for (int i = 0; i < NB_BUFFER; i++)//NB_BUFFER 为7,这就是之前申请的缓冲区数量
{
    memset (&buf, 0, sizeof (struct v4l2_buffer));
    buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index  = (__u32)i;

    ret = ioctl (mCameraFd, VIDIOC_QUERYBUF, &buf);
    if (ret < 0)
    {
        printf("Unable to query buffer (%s)", strerror(errno));
        return ret;
    }


    mMapMem.mem[i] = mmap (0, buf.length,
                           PROT_READ | PROT_WRITE,
                           MAP_SHARED,
                           mCameraFd,
                           buf.m.offset);
    mMapMem.length = buf.length;
    if (mMapMem.mem[i] == MAP_FAILED)
    {
        printf("Unable to map buffer (%s)", strerror(errno));
        return -1;
    }

    // start with all buffers in queue
    ret = ioctl(mCameraFd, VIDIOC_QBUF, &buf);
    if (ret < 0)
    {
        printf("VIDIOC_QBUF Failed");
        return ret;
    }
}

我们前面申请了我们就要释放:

int ret = 0;
for (int i = 0; i < NB_BUFFER; i++)
{
    if(mMapMem.mem[i] != NULL) {
        ret = munmap(mMapMem.mem[i], mMapMem.length);
        if (ret < 0) {
            printf("v4l2CloseBuf Unmap failed");
            return ret;
        }

        mMapMem.mem[i] = NULL;
    }
}

然后就是打开视频流开关,设备就开始给我们传输数据了。

int ret = 0;
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

ret = ioctl (mCameraFd, VIDIOC_STREAMON, &type);
if (ret < 0)
{
    printf("StartStreaming: Unable to start capture: %s", strerror(errno));
    return ret;
}

对应一个关闭视频流传输:

int ret = 0;
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl (mCameraFd, VIDIOC_STREAMOFF, &type);
if (ret < 0)
{
    printf("StopStreaming: Unable to stop capture: %s", strerror(errno));
    return ret;
}
printf("V4L2Camera::v4l2StopStreaming OK");

视频传输出来了,在内存映射的地方,我们就需要取出来,取出来也是有方法的:

//等待相机传输码流出来
int TestCamera::v4l2WaitCameraReady() {
    fd_set fds;
    struct timeval tv;
    int r;
    FD_ZERO(&fds);
    FD_SET(mCameraFd, &fds);
    //超时的时间
    tv.tv_sec  = 2;
    tv.tv_usec = 0;

    r = select(mCameraFd + 1, &fds, NULL, NULL, &tv);
    if (r == -1)
    {
        printf("select err, %s", strerror(errno));
        return -1;
    }
    else if (r == 0)
    {
        printf("select timeout");
        return -2;
    }

    return 0;
}
//获取一帧数据,其实就是告诉v4l2,我要访问下一个内存区,访问之后就需要释放,然后设备才可以写你访问的内存区
//如果你不释放,就会发现,前几帧有数据,然后就卡住不动了
int TestCamera::getPreviewFrame(v4l2_buffer *buf){
    int ret = 0;
    buf->type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf->memory = V4L2_MEMORY_MMAP;
    ret = ioctl(mCameraFd, VIDIOC_DQBUF, buf);
    if (ret < 0)
    {
        printf("GetPreviewFrame: VIDIOC_DQBUF Failed, %s, line : %d", strerror(errno), __LINE__);
        return ERROR_LOCAL; 			// can not return false
    }

    return SUCCESS_LOCAL;
}
//释放我们之前申请访问的内存区
void TestCamera::releasePreviewFrame(int index){
    int ret = 0;
    struct v4l2_buffer buf;
    memset(&buf, 0, sizeof(v4l2_buffer));
    buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = index;
    ret = ioctl(mCameraFd, VIDIOC_QBUF, &buf);
    if (ret != 0)
    {
        printf("releasePreviewFrame: VIDIOC_QBUF Failed: index = %d, ret = %d, %s",
             buf.index, ret, strerror(errno));
    }
}

取数据的实例:

    int ret = 0;

    ret = v4l2WaitCameraReady();
    if(ret != SUCCESS_LOCAL || m_threadRun != 1){
        return false;
    }

    // get one video frame
    struct v4l2_buffer buf;
    memset(&buf, 0, sizeof(v4l2_buffer));

    buf.index = (__u32)-1;
    ret = getPreviewFrame(&buf);
    if(ret != SUCCESS_LOCAL){
        //失败返回重新等待
        return false;
    }

    if(m_threadRun != 1){
        //释放release
        releasePreviewFrame(buf.index);
        return true;
    }

    //指针p保存所有Y的位置
    uint8_t *p = NULL;

    //指针p,保存的就是码流了
    p = (uint8_t *) mMapMem.mem[buf.index];
    
    //操作码流
    
    //释放release
    releasePreviewFrame(buf.index);

至于码流怎么处理,就是不同的事情,做不同的操作了。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值