RV1126B开发板学习篇(二)v4l2+mpp编码

本篇主要内容

一、v4l2多平面采集

1. v4l2的Multi-Plane(多平面)

(1)什么是平面(plane)

🏸在v4l2框架里,本质是存储视频帧分量的一块内存区域,或者叫缓冲区,是v4l2框架中的一个抽象概念,对承载单分量数据的内存块的属性封装。

为什么在这里提到plane这个概念呢?是因为前面在使用像ov5640或者usb摄像头比较低端的sensor,通常只支持单平面,使用的V4L2_BUF_TYPE_VIDEO_CAPTURE这种方式;相对高端的sensor,如imx415的时候这种方式是不适用的,查阅资料后可以发现imx415支持的是单平面和多平面存储分量视频帧的方式,所以在这里简单记录plane这一概念

(2)单平面(Single-Plane)和多平面(Multi-Plane)

维度 单平面(Single-planar) 多平面(Multi-planar)
内存布局 所有分量数据连续存储在同一块内存 不同分量数据存在独立的内存块(平面)
V4L2 类型标识 V4L2_BUF_TYPE_VIDEO_CAPTURE V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
核心结构体 struct v4l2_buffer(无平面数组) struct v4l2_buffer + struct v4l2_plane
适用场景 简单格式(如 RGB24、MJPEG) 复杂格式(YUV420、H.264/H.265、NV12)
内存效率 低(需预留最大帧长度的连续内存) 高(各平面按需分配内存)

🏸以NV12为例,在v4l2中有以下几个宏定义V4L2_PIX_FMT_NV12、V4L2_PIX_FMT_NV12M、V4L2_PIX_FMT_NV12MT。这三个宏所描述的都是NV12格式(YUV420,Y整行存储,UV交错存储),区别仅在于内存布局、是否支持多平面、是否为平铺(Tile)模式,直接决定摄像头驱动和应用层如何读取/写入图像数据,从我们使用的角度,只需要关心以下区别:

宏定义 类型 一般使用场景
V4L2_PIX_FMT_NV12 单平面打包格式(num_planes=1) 通用软解码、普通级sensor
V4L2_PIX_FMT_NV12M 多平面分离格式(num_planes=2) 硬件编解码、高性能处理器
V4L2_PIX_FMT_NV12MT 平铺多平面格式(num_planes=2) 硬件ISP、GPU、高端图像处理器

(3)v4l2_plane结构体

🏸如何计算一帧NV12(YUV420)数据的大小?

首先要知道YUV420是指Y、U、V分量的比例为4:2:0(亮度Y,色度U和V),也就是4个像素点里,有4个Y亮度和2个U色度。那么在分辨率为1920x1080的一帧数据中,一共有Y:1920x1080=2073600,U:1920x1080/2=1036800。所以一帧NV12(YUV420)的数据大小是,Y+U:1920x1080x3/2=3110400(byte)

🏸这个结构体主要由底层填充,用于我们获取缓冲区信息或者说内存的信息,使用上需要区别v4l2_buffer这个结构体,以单平面一帧1920x1080的NV12图像为例,其含义如下:

字段 含义 示例值(NV12)
planes.length 对应的内核缓冲区总内存大小(预分配的字节数) 3133440字节(对齐后)
planes.m.mem_offset 在内核内存中的偏移量(标识内存位置) 0x0
planes.bytesused 该plane中实际有效数据的字节数 3110400字节(有效数据)
planes.m.fd DMA-BUF 模式下有效 0

2. v4l2多平面捕获摄像头数据

🏸这里实现效果是将NV12数据保存在testfile文件中,可以使用mpi_enc_test这个rk例程转h264播放验证,命令如下:

shell 复制代码
mpi_enc_test -i ./testfile -o ./test.h264 -w 1920 -h 1080 -f YUV420SP -t h.264 -fps 30
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <pthread.h>
#include <linux/videodev2.h>
#include <rockchip/rk_mpi.h>
#include <rockchip/rk_mpi_cmd.h>
#include <rockchip/mpp_err.h>
#include <rockchip/rk_venc_kcfg.h>
#include "v4l2_capture.h"

static int planes_num;

int camera_init(struct camera *cam) {

    if (cam == NULL) return -1;
    /* 打开摄像头设备 */
    cam->fd = open(cam->device_node, O_RDWR | O_NONBLOCK);
    if (cam->fd < 0) {
        perror("open()");
        return -1;
    }

    /* 设置格式 */
    struct v4l2_format fmt = {
        .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, // 多平面采集类型
        .fmt.pix_mp.width = cam->width,
        .fmt.pix_mp.height = cam->height,
        .fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M,    
        .fmt.pix_mp.field = V4L2_FIELD_NONE,      
    };

    if (ioctl(cam->fd, VIDIOC_S_FMT, &fmt) < 0) {
        perror("VIDIOC_S_FMT");
        close(cam->fd);
        return -1;
    }

    /* 实际生效的格式 */
    if (ioctl(cam->fd, VIDIOC_G_FMT, &fmt) < 0){
        perror("VIDIOC_G_FMT");
        close(cam->fd);
        return -1;
    }
    printf("%s: using format:%c%c%c%c\n", cam->device_node,
       fmt.fmt.pix_mp.pixelformat & 0xff,
       (fmt.fmt.pix_mp.pixelformat >> 8) & 0xff,
       (fmt.fmt.pix_mp.pixelformat >> 16) & 0xff,
       (fmt.fmt.pix_mp.pixelformat >> 24) & 0xff);
    printf("%s: using plane:%d\n", cam->device_node,fmt.fmt.pix_mp.num_planes);
    planes_num = fmt.fmt.pix_mp.num_planes;

    /* 申请buffer */
    struct v4l2_requestbuffers reqbuf = {
        .count = 4,
        .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,  
        .memory = V4L2_MEMORY_MMAP
    };

    if (ioctl(cam->fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
        perror("VIDIOC_REQBUFS");
        close(cam->fd);
        return -1;
    }
    if (reqbuf.count < 0) {
        fprintf(stderr, "alloc buf occurred error.%d \n", reqbuf.count);
        ioctl(cam->fd, VIDIOC_REQBUFS, &reqbuf);
        close(cam->fd);
        return -1;
    }

    /* 映射内核缓冲区到用户空间,并入队 */
    struct v4l2_buffer buf_msg;
    struct v4l2_plane plane_msg[2] = {0};
    for (int i = 0; i <  fmt.fmt.pix_mp.num_planes; i++) {
        buf_msg.index = i;
        buf_msg.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        buf_msg.memory = V4L2_MEMORY_MMAP;
        buf_msg.m.planes = plane_msg;  
        buf_msg.length = fmt.fmt.pix_mp.num_planes;  

        // 查询缓冲区信息
        if (ioctl(cam->fd, VIDIOC_QUERYBUF, &buf_msg) < 0) {
            perror("VIDIOC_QUERYBUF");
            ioctl(cam->fd, VIDIOC_REQBUFS, &reqbuf);
            close(cam->fd);
            return -1;
        }
        // mmap映射
        cam->user_buf[i] = mmap(NULL, buf_msg.m.planes[i].length, PROT_READ | PROT_WRITE,
                                MAP_SHARED, cam->fd, buf_msg.m.planes[i].m.mem_offset);
        if (cam->user_buf[i] == MAP_FAILED) {
            perror("mmap");
            return -1;
        }
        // 缓冲区入队
        if (ioctl(cam->fd, VIDIOC_QBUF, &buf_msg) < 0) {
            perror("VIDIOC_QBUF");
            munmap(cam->user_buf[i], plane_msg[i].length);
            cam->user_buf[i] = NULL;
            return -1;
        }
    }
    return 0;
}

int camera_start_stream(struct camera *cam) {
    if (cam == NULL || cam->fd < 0) return -1;
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    if (ioctl(cam->fd, VIDIOC_STREAMON, &type) < 0) {
        perror("VIDIOC_STREAMON");
        return -1;
    }
    printf("capture stream on.\n");
    return 0;
}

void *capture_thread(void *arg){
    struct camera *cam = arg;
    /* select监听fd读事件 */
    fd_set read_set;
    int nready;
    int frame_count = 0;
    struct timeval tv = {
        .tv_sec = 5, 
        .tv_usec = 0
    };

    /* NV12数据保存成文件 */
    FILE *file = fopen("testfile","wb");
    if (!file) {
        perror("fopen file");
    }

    /* 获取采集到的帧 */
    struct v4l2_plane planes[VIDEO_MAX_PLANES];
    struct v4l2_buffer v4l2_buf = {
        .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
        .memory = V4L2_MEMORY_MMAP,
        .m.planes = planes,
        .length = VIDEO_MAX_PLANES
    };

    FD_ZERO(&read_set);
    FD_SET(cam->fd, &read_set);
    while(1){
        nready = select(cam->fd + 1, &read_set, NULL, NULL, 0);
        if (nready < 0) {
            perror("select");
            break;
        }
        if (nready == 0) {
            fprintf(stderr, "select() times out.\n");
            sleep(1);
            continue;
        }
        //出队
        if (ioctl(cam->fd, VIDIOC_DQBUF, &v4l2_buf) < 0) {
            perror("VIDIOC_DQBUF");
        }
        frame_count++;
        for (int i = 0; i < planes_num; i++) {
            fwrite(cam->user_buf[i], v4l2_buf.m.planes[i].bytesused, 1, file);
            printf("NV12 frame %d captured.\n",frame_count);
            printf("--------->  v4l2_buf.m.planes[%d].length=%d.\n", i,v4l2_buf.m.planes[i].length);
            printf("--------->  v4l2_buf.m.planes[%d].bytesused=%d.\n",i,v4l2_buf.m.planes[i].bytesused);
            printf("--------->  v4l2_buf.m.planes[%d].m.mem_offset=%d.\n",i, v4l2_buf.m.planes[i].m.mem_offset);
        }

        //缓冲区重新入队列(循环使用)
        if (ioctl(cam->fd, VIDIOC_QBUF, &v4l2_buf) < 0) {
            perror("ioctl VIDIOC_QBUF failed (Multiplanar)");
        }
        
    }
    fclose(file);
}

int camera_capture_frame(struct camera *cam) {
    camera_start_stream(cam);
    pthread_t tid;
    if(pthread_create(&tid,NULL,capture_thread,cam) != 0){
        fprintf(stderr,"pthread_create() occurred error.\n");
        return -1;
    }
   return 0;
}

int camera_deinit(struct camera *cam){
    //TODO
}
c 复制代码
#ifndef     _V4L2_CAPTURE_H_
#define     _V4L2_CAPTURE_H_

#include <linux/videodev2.h>

struct camera{
    int fd;
    int buffer_count;                                 
    int width;
    int height;
    struct v4l2_buffer *user_buf[4];                   
    char *device_node;                
};

int camera_init(struct camera *cam);
int camera_capture_frame(struct camera *cam);
int camera_deinit(struct camera *cam);

#endif
c 复制代码
#include <stdio.h>
#include <unistd.h>
#include "v4l2_capture.h"

int main(){

    struct camera cam = {
        .device_node = "/dev/video31",
        .width = 1920,
        .height = 1080,
    };
    camera_init(&cam);
    camera_capture_frame(&cam);
    while(1)sleep(1);
    return 0;
}

二、v4l2+mpp编码

从摄像头录制h.264视频

🏸上面我们已经大致封装好v4l2捕获摄像头数据的代码,本小节再此基础上,整合前一篇mpp编码相关的代码,实现从摄像头直接录制出h.264文件这一效果,目录结构如下:

🏸由于多平面捕获摄像头在这里不好处理,于是此处代码改成了单平面格式,demo实现的效果和以下两条命令类似。这里写得也比较简单,很多处理并没有做,编译和运行结果如图。参考代码

shell 复制代码
gst-launch-1.0 -e v4l2src device=/dev/video31 num-buffers=300 ! video/x-raw,format=NV12,width=1920,height=1080,framerate=30/1 ! filesink location=raw.nv12
mpi_enc_test -i ./raw.nv12 -o ./encoded.h264 -w 1920 -h 1080 -f YUV420SP -t h.264


相关推荐
探索宇宙真理.2 小时前
WordPress CMS Commander 插件SQL漏洞 | CVE-2026-3334概念复现&研究
经验分享·eureka·安全漏洞
reembarkation4 小时前
vue3中使用howler播放音频列表
前端·vue.js·音视频
泓铮5 小时前
【无标题】
经验分享
我不是懒洋洋6 小时前
预处理详解
c语言·开发语言·c++·windows·microsoft·青少年编程·visual studio
计算机安禾6 小时前
【数据结构与算法】第14篇:队列(一):循环队列(顺序存储
c语言·开发语言·数据结构·c++·算法·visual studio
爱编码的小八嘎6 小时前
C语言完美演绎6-9
c语言
程序阿北6 小时前
飞书 CLI 昨天开源,我用 Claude Code 打通了公众号写作全流程
经验分享·ai·飞书
weixin_649555676 小时前
C语言程序设计第四版(何钦铭、颜晖)第十一章指针进阶之奇数值结点链表
c语言·开发语言·链表
卡梅德生物科技小能手6 小时前
免疫检查点核心机制解析:CD274(分化抗原274)的信号通路与药物研发进展
经验分享·深度学习·生活