野火电子论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 328|回复: 4

[求助] mpp编码延时

[复制链接]
发表于 2024-10-21 16:25:44 | 显示全部楼层 |阅读模式
我使用LuBanCat5上的CSI摄像头进行图像采集,获取1920*1080分辨率的图片,然后使用MPP库将图片压缩为MJPEG格式,平均每张图片耗时为10ms,请问这个耗时是否达到了最好的性能。
另外,压缩后的图像最底部生成了一个绿色长条,这是什么问题导致的。

回复

使用道具 举报

发表于 2024-10-22 09:30:18 | 显示全部楼层
官方手册没有找到压缩相关的最优数据,一般用官方的mpi_dec_test测试脚本测出来的数据就是准的,看了一圈外面的资料,10ms 算好的了。
压缩之后有绿条,是不是压缩过程出问题了,或者图片需要裁剪?这个倒是没见过。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-10-22 18:04:29 | 显示全部楼层
YiangYiang 发表于 2024-10-22 09:30
官方手册没有找到压缩相关的最优数据,一般用官方的mpi_dec_test测试脚本测出来的数据就是准的,看了一圈外 ...

好的谢谢你,绿条的问题解决了,出现的原因是步符设置的问题,下面是我的代码,欢迎大家进行点评

回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-10-22 18:04:54 | 显示全部楼层
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>              /* 低级I/O */
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <time.h>
#include <rockchip/rk_mpi.h>    /* Rockchip 多媒体处理平台(MPP)头文件 */

/* 定义缓冲区结构体 */
struct buffer {
    void   *start;   /* 缓冲区起始地址 */
    size_t length;   /* 缓冲区长度 */
};

static char *dev_name = "/dev/video11";  /* 摄像头设备名称 */
static int fd = -1;                      /* 设备文件描述符 */
struct buffer *buffers;                  /* 缓冲区数组 */
static unsigned int n_buffers;           /* 缓冲区数量 */
MppCtx ctx = NULL;                       /* MPP 上下文 */
MppApi *mpi = NULL;                      /* MPP API 接口 */

/* 定义分辨率和格式 */
#define WIDTH 1920
#define HEIGHT 1080
#define PIXEL_FORMAT V4L2_PIX_FMT_NV12   /* 使用 NV12 格式 */
#define MPP_FORMAT MPP_FMT_YUV420SP      /* 对应 MPP 的 NV12 格式 */

int init_camera(void);
int start_capturing(void);
int capture_frame(int *index);
int stop_capturing(void);
int uninit_camera(void);
int init_mpp(void);
int encode_frame(void *input_buffer, size_t input_size);
int deinit_mpp(void);

int main(int argc, char **argv)
{
    int ret;
    struct timespec start_time, end_time;
    double elapsed_time;

    /* 初始化摄像头 */
    ret = init_camera();
    if (ret < 0) {
        fprintf(stderr, "初始化摄像头失败\n");
        return -1;
    }

    /* 初始化 MPP */
    ret = init_mpp();
    if (ret < 0) {
        fprintf(stderr, "初始化 MPP 失败\n");
        return -1;
    }

    /* 开始视频捕获 */
    ret = start_capturing();
    if (ret < 0) {
        fprintf(stderr, "开始捕获失败\n");
        return -1;
    }

    /* 捕获并编码 1000 帧图像 */
    for (int i = 0; i < 1000; i++) {
        int buf_index;
        ret = capture_frame(&buf_index);
        if (ret < 0) {
            fprintf(stderr, "捕获第 %d 帧失败\n", i);
            break;
        }
        /* 开始计时(仅统计压缩部分时间) */
        clock_gettime(CLOCK_MONOTONIC, &start_time);
      for (int i = 0; i < 1000; i++){
        ret = encode_frame(buffers[buf_index].start, buffers[buf_index].length);
        if (ret < 0) {
            fprintf(stderr, "编码第 %d 帧失败\n", i);
            break;
        }
        }
        /* 结束计时 */
        clock_gettime(CLOCK_MONOTONIC, &end_time);

        /* 计算耗时 */
        elapsed_time = (end_time.tv_sec - start_time.tv_sec) +
                       (end_time.tv_nsec - start_time.tv_nsec) / 1e9;

        printf("第 %d 帧压缩耗时 %.6f 秒\n", i, elapsed_time);

        /* 重新将缓冲区入队,供下一次捕获使用 */
        struct v4l2_buffer buf;
        struct v4l2_plane planes[VIDEO_MAX_PLANES];

        memset(&buf, 0, sizeof(buf));
        memset(planes, 0, sizeof(planes));

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = buf_index;
        buf.m.planes = planes;
        buf.length = 1;  /* NV12 格式有 1 个平面 */

        ret = ioctl(fd, VIDIOC_QBUF, &buf);
        if (ret == -1) {
            perror("重新队列缓冲区失败");
            return -1;
        }
    }

    /* 停止捕获并释放资源 */
    stop_capturing();
    uninit_camera();
    deinit_mpp();

    return 0;
}

/* 初始化摄像头设备 */
int init_camera(void)
{
    struct v4l2_capability cap;
    struct v4l2_format fmt;
    int ret;

    /* 打开摄像头设备 */
    fd = open(dev_name, O_RDWR | O_NONBLOCK, 0);
    if (fd == -1) {
        perror("打开视频设备失败");
        return -1;
    }

    /* 查询设备能力 */
    ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
    if (ret == -1) {
        perror("查询设备能力失败");
        return -1;
    }

    /* 检查设备是否支持多平面视频捕获 */
    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE)) {
        fprintf(stderr, "设备不支持多平面视频捕获\n");
        return -1;
    }

    /* 设置视频格式 */
    memset(&fmt, 0, sizeof(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 = PIXEL_FORMAT;  /* 使用 NV12 格式 */
    fmt.fmt.pix_mp.field       = V4L2_FIELD_NONE;
    fmt.fmt.pix_mp.num_planes  = 1;             /* NV12 格式有 1 个平面 */

    ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
    if (ret == -1) {
        perror("设置像素格式失败");
        return -1;
    }

    /* 请求缓冲区 */
    struct v4l2_requestbuffers req;
    memset(&req, 0, sizeof(req));

    req.count  = 4;   /* 请求 4 个缓冲区 */
    req.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    req.memory = V4L2_MEMORY_MMAP;

    ret = ioctl(fd, VIDIOC_REQBUFS, &req);
    if (ret == -1) {
        perror("请求缓冲区失败");
        return -1;
    }

    if (req.count < 2) {
        fprintf(stderr, "内存映射失败,缓冲区数量不足\n");
        return -1;
    }

    /* 分配缓冲区 */
    buffers = calloc(req.count, sizeof(*buffers));
    if (!buffers) {
        fprintf(stderr, "内存分配失败\n");
        return -1;
    }

    /* 逐个缓冲区进行映射 */
    for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
        struct v4l2_buffer buf;
        struct v4l2_plane planes[VIDEO_MAX_PLANES];

        memset(&buf, 0, sizeof(buf));
        memset(planes, 0, sizeof(planes));

        buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index  = n_buffers;
        buf.m.planes = planes;
        buf.length = 1;  /* NV12 格式有 1 个平面 */

        /* 查询缓冲区信息 */
        ret = ioctl(fd, VIDIOC_QUERYBUF, &buf);
        if (ret == -1) {
            perror("查询缓冲区失败");
            return -1;
        }

        /* 映射缓冲区到用户空间 */
        buffers[n_buffers].length = buf.m.planes[0].length;
        buffers[n_buffers].start =
            mmap(NULL, buf.m.planes[0].length, PROT_READ | PROT_WRITE, MAP_SHARED,
                 fd, buf.m.planes[0].m.mem_offset);

        if (MAP_FAILED == buffers[n_buffers].start) {
            perror("内存映射失败");
            return -1;
        }
    }

    return 0;
}

/* 开始视频捕获 */
int start_capturing(void)
{
    unsigned int i;
    enum v4l2_buf_type type;
    int ret;

    /* 将所有缓冲区入队,准备开始捕获 */
    for (i = 0; i < n_buffers; ++i) {
        struct v4l2_buffer buf;
        struct v4l2_plane planes[VIDEO_MAX_PLANES];

        memset(&buf, 0, sizeof(buf));
        memset(planes, 0, sizeof(planes));

        buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index  = i;
        buf.m.planes = planes;
        buf.length = 1;  /* NV12 格式有 1 个平面 */

        ret = ioctl(fd, VIDIOC_QBUF, &buf);
        if (ret == -1) {
            perror("VIDIOC_QBUF 失败");
            return -1;
        }
    }

    /* 启动视频流 */
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;

    ret = ioctl(fd, VIDIOC_STREAMON, &type);
    if (ret == -1) {
        perror("VIDIOC_STREAMON 失败");
        return -1;
    }

    return 0;
}

/* 捕获一帧图像 */
int capture_frame(int *index)
{
    struct v4l2_buffer buf;
    struct v4l2_plane planes[VIDEO_MAX_PLANES];
    fd_set fds;
    struct timeval tv;
    int r, ret;

    FD_ZERO(&fds);
    FD_SET(fd, &fds);

    /* 设置超时时间 */
    tv.tv_sec = 2;
    tv.tv_usec = 0;

    /* 等待摄像头设备准备好 */
    r = select(fd + 1, &fds, NULL, NULL, &tv);
    if (-1 == r) {
        if (EINTR == errno)
            return -1;
        perror("select 失败");
        return -1;
    }

    if (0 == r) {
        fprintf(stderr, "select 超时\n");
        return -1;
    }

    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;  /* NV12 格式有 1 个平面 */

    /* 从队列中取出已捕获的缓冲区 */
    ret = ioctl(fd, VIDIOC_DQBUF, &buf);
    if (ret == -1) {
        perror("VIDIOC_DQBUF 失败");
        return -1;
    }

    /* 返回缓冲区索引 */
    *index = buf.index;

    return 0;
}

/* 初始化 MPP 编码器 */
int init_mpp(void)
{
    MPP_RET ret;
    MppEncCfg cfg = NULL;
    int quality = 100 ;

    /* 创建 MPP 上下文 */
    ret = mpp_create(&ctx, &mpi);
    if (ret) {
        fprintf(stderr, "mpp_create 失败\n");
        return -1;
    }

    /* 初始化 MPP 上下文为编码器类型,指定编码格式为 MJPEG */
    ret = mpp_init(ctx, MPP_CTX_ENC, MPP_VIDEO_CodingMJPEG);
    if (ret) {
        fprintf(stderr, "mpp_init 失败\n");
        mpp_destroy(ctx);
        ctx = NULL;
        return -1;
    }

    /* 初始化编码器配置 */
    ret = mpp_enc_cfg_init(&cfg);
    if (ret) {
        fprintf(stderr, "mpp_enc_cfg_init 失败\n");
        mpp_destroy(ctx);
        ctx = NULL;
        return -1;
    }

    /* 计算对齐后的步幅,通常对齐到 16 字节 */
    int hor_stride = (WIDTH + 15) & (~15);
    int ver_stride = (HEIGHT + 15) & (~15);

    /* 设置编码器配置参数 */
    ret  = mpp_enc_cfg_set_s32(cfg, "prep:format", MPP_FORMAT);     /* 输入格式 */
    ret |= mpp_enc_cfg_set_s32(cfg, "prep:width", WIDTH);           /* 图像宽度 */
    ret |= mpp_enc_cfg_set_s32(cfg, "prep:height", HEIGHT);         /* 图像高度 */
    ret |= mpp_enc_cfg_set_s32(cfg, "prep:hor_stride", hor_stride); /* 水平步幅 */
    ret |= mpp_enc_cfg_set_s32(cfg, "prep:ver_stride", ver_stride); /* 垂直步幅 */
    ret |= mpp_enc_cfg_set_s32(cfg, "codec:type", MPP_VIDEO_CodingMJPEG); /* 编码器类型 */
    ret |= mpp_enc_cfg_set_s32(cfg, "prep:format",MPP_FORMAT);

    if (ret) {
        fprintf(stderr, "mpp_enc_cfg_set_s32 失败\n");
        mpp_enc_cfg_deinit(cfg);
        mpp_destroy(ctx);
        ctx = NULL;
        return -1;
    }

    /* 应用编码器配置 */
    ret = mpi->control(ctx, MPP_ENC_SET_CFG, cfg);
    if (ret) {
        fprintf(stderr, "mpi->control MPP_ENC_SET_CFG 失败\n");
        mpp_enc_cfg_deinit(cfg);
        mpp_destroy(ctx);
        ctx = NULL;
        return -1;
    }

    /* 释放编码器配置 */
    mpp_enc_cfg_deinit(cfg);

    return 0;
}

/* 编码一帧图像 */
int encode_frame(void *input_buffer, size_t input_size)
{
    MPP_RET ret;
    MppFrame frame = NULL;
    MppPacket packet = NULL;
    MppBuffer frame_buffer = NULL;

    /* 初始化 MppFrame */
    ret = mpp_frame_init(&frame);
    if (ret) {
        fprintf(stderr, "mpp_frame_init 失败\n");
        return -1;
    }

    /* 计算对齐后的步幅 */
    int hor_stride = (WIDTH + 15) & (~15);
    int ver_stride = (HEIGHT + 15) & (~15);
    size_t frame_size = hor_stride * ver_stride * 3 / 2;

    /* 分配 MppBuffer,用于存储输入帧数据 */
    ret = mpp_buffer_get(NULL, &frame_buffer, frame_size);
    if (ret) {
        fprintf(stderr, "mpp_buffer_get 失败\n");
        mpp_frame_deinit(&frame);
        return -1;
    }

    /* 将帧缓冲区初始化为零 */
    memset(mpp_buffer_get_ptr(frame_buffer), 0, frame_size);

    /* 将输入数据拷贝到 MppBuffer,考虑步幅对齐 */
    uint8_t *dst = (uint8_t *)mpp_buffer_get_ptr(frame_buffer);
    uint8_t *src = (uint8_t *)input_buffer;

    /* 复制 Y 平面 */
    for (int i = 0; i < HEIGHT; i++) {
        memcpy(dst + i * hor_stride, src + i * WIDTH, WIDTH);
    }

    /* 复制 UV 平面 */
    dst = mpp_buffer_get_ptr(frame_buffer) + hor_stride * ver_stride;
    src = (uint8_t *)input_buffer + WIDTH * HEIGHT;
    for (int i = 0; i < HEIGHT / 2; i++) {
        memcpy(dst + i * hor_stride, src + i * WIDTH, WIDTH);
    }

    /* 设置 MppFrame 参数 */
    mpp_frame_set_buffer(frame, frame_buffer);
    mpp_frame_set_fmt(frame, MPP_FORMAT);          /* 图像格式 */
    mpp_frame_set_width(frame, WIDTH);             /* 图像宽度 */
    mpp_frame_set_height(frame, HEIGHT);           /* 图像高度 */
    mpp_frame_set_hor_stride(frame, hor_stride);   /* 水平步幅 */
    mpp_frame_set_ver_stride(frame, ver_stride);   /* 垂直步幅 */

    /* 将帧发送到编码器 */
    ret = mpi->encode_put_frame(ctx, frame);
    if (ret) {
        fprintf(stderr, "mpi->encode_put_frame 失败\n");
        mpp_frame_deinit(&frame);
        mpp_buffer_put(frame_buffer);
        return -1;
    }

    /* 获取编码后的数据 */
    ret = mpi->encode_get_packet(ctx, &packet);
    if (ret) {
        fprintf(stderr, "mpi->encode_get_packet 失败\n");
        mpp_frame_deinit(&frame);
        mpp_buffer_put(frame_buffer);
        return -1;
    }

    if (packet) {
        /* 编码后的数据处理(根据需要保存或传输) */

        /* 释放 MppPacket */
        mpp_packet_deinit(&packet);
    }

    /* 释放资源 */
    mpp_frame_deinit(&frame);
    mpp_buffer_put(frame_buffer);

    return 0;
}

/* 释放 MPP 资源 */
int deinit_mpp(void)
{
    if (ctx) {
        mpp_destroy(ctx);
        ctx = NULL;
    }

    return 0;
}

/* 停止视频捕获 */
int stop_capturing(void)
{
    enum v4l2_buf_type type;

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;

    if (-1 == ioctl(fd, VIDIOC_STREAMOFF, &type))
        perror("VIDIOC_STREAMOFF 失败");

    return 0;
}

/* 释放摄像头资源 */
int uninit_camera(void)
{
    unsigned int i;

    /* 解除映射缓冲区 */
    for (i = 0; i < n_buffers; ++i)
        if (-1 == munmap(buffers[i].start, buffers[i].length))
            perror("munmap 失败");

    /* 释放缓冲区数组 */
    free(buffers);

    /* 关闭设备文件 */
    if (fd != -1)
        close(fd);

    return 0;
}
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

联系站长|手机版|野火电子官网|野火淘宝店铺|野火电子论坛 ( 粤ICP备14069197号 ) 大学生ARM嵌入式2群

GMT+8, 2024-11-23 00:44 , Processed in 0.111048 second(s), 25 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表