RK3588从零到一实现摄像头视频推流实现(一)

一、前言

我这里使用的是RK3588的主板,摄像头使用的型号是IMX415。

二、V4L2框架配置

1、首先是使用open命令打开设备,然后设置格式参数,代码如下:

int fd = open(device, O_RDWR);
    if (fd < 0) {
        perror("打开设备失败");
        return -1;
    }

    // 设置格式:多平面 NV12(你也可以用 NV16、NV21)
    struct v4l2_format fmt;
    CLEAR(fmt);
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    fmt.fmt.pix_mp.width = WIDTH;
    fmt.fmt.pix_mp.height = HEIGHT;
    fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12;
    fmt.fmt.pix_mp.num_planes = 2;

    if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
        perror("设置格式失败");
        return -1;
    }

对于如何设置上面参数,可以通过命令v4l2-ctl -d /dev/videoX --all 查询:

看输出中:

  • Type: Video Capture 就是 V4L2_BUF_TYPE_VIDEO_CAPTURE

  • Type: Video Capture Multiplanar  就是 V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE

  • Pixel Format: SRGGB10 等类似字段  说明你是 RAW Sensor,输出 Bayer 数据

我这里是如下信息显示:

 2、接下来就是数据缓冲区的设置,我这里使用mmp的方式进行内存的映射,这里需要主要的一点是,缓冲区的声明周期,最好是整个声明周期就在,假如被系统回收了,会发生内存问题导致程序崩溃,特别是在多线程间操作,代码如下:

  // 请求缓冲区
    struct v4l2_requestbuffers req;
    CLEAR(req);
    req.count = BUFFER_COUNT;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    req.memory = V4L2_MEMORY_MMAP;
    if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
        perror("请求缓冲区失败");
        return -1;
    }
   
    
    //映射缓冲区
    for (int i = 0; i < BUFFER_COUNT; ++i) {
        struct v4l2_buffer buf;
        struct v4l2_plane planes[1];
        CLEAR(buf);
        memset(planes, 0, sizeof(planes));

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        buf.length = 1;
        buf.m.planes = planes;

        if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
            perror("查询缓冲区失败");
            return -1;
        }

        req_buffers[i].length = buf.m.planes[0].length;
        req_buffers[i].start = mmap(NULL, buf.m.planes[0].length,
                                PROT_READ | PROT_WRITE, MAP_SHARED,
                                fd, buf.m.planes[0].m.mem_offset);
    }

    // 放入队列
    for (int i = 0; i < BUFFER_COUNT; ++i) {
        struct v4l2_buffer buf;
        struct v4l2_plane planes[1];
        CLEAR(buf);
        memset(planes, 0, sizeof(planes));

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        buf.length = 1;
        buf.m.planes = planes;

        if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
            perror("放入缓冲区失败");
            return -1;
        }
    }

3、接下来就是视频流的获取了,我这边对于原始数据使用的是opencv进行的处理,方便显示到屏幕进行测试,其实更原始的操作,可以用/dev/fb* 直接操作显存地址进行显示,效率更高,我这边只做测试,最终我们需要完成对视频流的编码,然后进行推流的

  // 启动视频流
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
        perror("启动失败");
        return ;
    }
        // 主循环采集并显示
     
    try{
        while (true) {
        //等待视频设备(fd)变得可读(即有新的图像帧可获取)
        fd_set fds;
        FD_ZERO(&fds);// 初始化 fd 集合
        FD_SET(fd, &fds); // 将视频设备 fd 加入监控集合
        struct timeval tv = {2, 0};// 设置超时时间:2 秒
        int r = select(fd + 1, &fds, NULL, NULL, &tv);
        /*r > 0:表示设备可读(即有视频帧到来),你可以安全地调用 VIDIOC_DQBUF 来取出一帧
         r == 0:表示超时(2 秒内没有帧来)
        r == -1:系统调用失败,比如信号中断或其他错误(需要 perror() 查看)*/
        if (r == -1) {
            perror("select");
            break;
        }

        struct v4l2_buffer buf;
        struct v4l2_plane planes[1];
        CLEAR(buf);
        memset(planes, 0, sizeof(planes));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.length = 1;
        buf.m.planes = planes;

        if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
            perror("出队失败");
            break;
        }

        if (buffers[buf.index].start == MAP_FAILED) {
            std::cerr << "[producer] mmap 失败" << std::endl;
            break;
        }

        if (buf.index >= BUFFER_COUNT || !buffers[buf.index].start) {
            std::cerr << "[producer] 非法的 buf.index 或空指针 index:" <<buf.index <<std::endl;
            continue;  

       }
    
        // 将 NV12 数据转换为 BGR(Y 平面 + UV 平面)
        cv::Mat y(HEIGHT, WIDTH, CV_8UC1, buffers[buf.index].start);
        cv::Mat uv(HEIGHT / 2, WIDTH / 2, CV_8UC2,
                   (unsigned char*)buffers[buf.index].start + WIDTH * HEIGHT);
   
        cv::Mat bgr;
  
        cv::cvtColorTwoPlane(y, uv, bgr, cv::COLOR_YUV2BGR_NV12);
       
  
        consumer_queue.push(bgr);
            
        Video_frame_context temp_Data={0};
        
        memcpy(temp_Data.frame_data, buffers[buf.index].start,WIDTH * HEIGHT*3/2);
        consumer_queue.push_cam_data(temp_Data);

        // 放回队列
        if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
            perror("重新入队失败");
            break;
        }
    }

    }
    catch(cv::Exception& e){
        std::cerr << "[producer] 异常: " << e.what() << std::endl;
    }catch (...) {
        std::cerr << "[producer] 未知异常!" << std::endl;
    }
    

    ioctl(fd, VIDIOC_STREAMOFF, &type);
    for (int i = 0; i < BUFFER_COUNT; ++i) {
        munmap(buffers[i].start, buffers[i].length);
    }

三、运行结果

验证效果如下:画面测试目测没有感受到延迟。

下一节,我将获取的的视频通过mpp组件压缩为H265编码格式,然后分别保存到本地和使用之前我发布的zlmediakit组件进行RTSP推流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值