Linux VPU驱动

Linux VPU驱动1 前言 限于作者能力水平 本文可能存在谬误 因此而给读者带来的损失 作者不做任何承诺 2 概述 VPU 是用来进行图像 视频数据进行硬件编 解码的硬件模块 内部集成了 Encoder Decoder 功能部件进行图像 视频数据进行硬件编 解码 以加速处理 3 VPU 工作原理 3 1 VPU

大家好,我是讯享网,很高兴认识大家。

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 概述

VPU 是用来进行图像、视频数据进行硬件编、解码的硬件模块。内部集成了 EncoderDecoder 功能部件进行图像、视频数据进行硬件编、解码,以加速处理。

3. VPU 工作原理

3.1 VPU 编码工作流程

 --------------- | --------- | 输入数据 -->|->| Encoder |->|-> 编码后的输出数据 | --------- | | | | --------- | | | Decoder | | | --------- | ---------------

讯享网

3.2 VPU解码工作流程

讯享网 --------------- | --------- | | | Encoder | | | --------- | | | | --------- | 输入数据 -->|->| Decoder |->|-> 解码后的输出数据 | --------- | ---------------

4. Linux 下的 VPU

4.1 驱动架构

VPU驱动 可基于 V4L2子系统 框架完成。
1. 分别为 EncoderDecoder 各注册1个 /dev/videoX 设备(总共2个video设备)。

/* 注册 Encoder 设备 */ vfd->vfl_dir = VFL_DIR_M2M; video_register_device(vfd, VFL_TYPE_GRABBER, ...) /* 注册 Decoder 设备 */ vfd->vfl_dir = VFL_DIR_M2M; video_register_device(vfd, VFL_TYPE_GRABBER, ...)

设备数据传输方向为 VFL_DIR_M2M , 表明设备是设备完成的功能内存间的数据传输拷贝
2. 在 open() 调用中,在打开文件句柄的私有数据 file_private 绑定设备 buffer 队列(vb2_queue)的类型、接口、IO模式、数据传输方向等。
这里以 Encoder 的 open() 调用为例加以说明:

讯享网/* Encoder【输入】数据队列初始化 */ encoder_vq_input.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; encoder_vq_input.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; encoder_vq_input.ops = &xxx_vpu_encoder_qops; encoder_vq_input.mem_ops = &vb2_dma_contig_memops; ... vb2_queue_init(&encoder_vq_input); /* Encoder【输出】数据队列初始化 */ encoder_vq_output.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; encoder_vq_output.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; encoder_vq_output.ops = &xxx_vpu_encoder_qops; encoder_vq_output.mem_ops = &vb2_dma_contig_memops; ... vb2_queue_init(&encoder_vq_output); ...

4.2 用户空间编程框架(Encoder编码示例)

/* 打开设备(/dev/videoX为Encoder设备) */ fd = open("/dev/videoX", O_RDWR); /* 设置输入、输出数据格式 */ /* 设置编码【输入】数据格式 */ fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; ... ioctl(fd, VIDIOC_S_FMT, &fmt); /* 设置编码【输出】数据格式 */ fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; ... ioctl(fd, VIDIOC_S_FMT, &fmt); /* 请求输入、输出buffer,然后映射内核buffer到用户空间(IO模式为 V4l2_MEMORY_MMAP) */ /* 请求【输入】buffer并映射到用户空间 */ rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; rb.memory = V4l2_MEMORY_MMAP; rb.count = 1; ioctl(fd, VIDIOC_REQBUFS, &rb); buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; buf.memory = V4l2_MEMORY_MMAP; buf.length = num_input_planes; buf.m.planes = input_planes; ioctl(fd, VIDIOC_QUERYBUF, &buf); input_buffer.start = mmap(0, ..., PROT_READ|PROT_WRITE, ...); input_buffer.length = ...; /* 请求【输出】buffer并映射到用户空间 */ rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; rb.memory = V4l2_MEMORY_MMAP; rb.count = 1; ioctl(fd, VIDIOC_REQBUFS, &rb); buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; buf.memory = V4l2_MEMORY_MMAP; buf.length = num_output_planes; buf.m.planes = output_planes; ioctl(fd, VIDIOC_QUERYBUF, &buf); output_buffer.start = mmap(0, ..., PROT_READ|PROT_WRITE, ...); output_buffer.length = ...; /* 将【输出】buffer入队,然后开启【输出流】 */ buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; buf.memory = V4l2_MEMORY_MMAP; buf.length = num_output_planes; buf.m.planes = output_planes; output_planes[i].bytesused = output_planes[i].length; ioctl(fd, VIDIOC_QBUF, &buf); type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; ioctl(fd, VIDIOC_STREAMON, &type); /* 设置编码输入数据,将【输入】buffer入队,然后开启【输入流】 */ /* 设置编码输入数据 */ memcpy(input_buffer.start, input_data, input_data_size); buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; buf.memory = V4l2_MEMORY_MMAP; buf.length = num_input_planes; buf.m.planes = input_planes; input_planes[i].bytesused = input_planes[i].length; ioctl(fd, VIDIOC_QBUF, &buf); type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; ioctl(fd, VIDIOC_STREAMON, &type); /* 出队编码队列(vb2_queue)中就绪的【输出缓冲】 */ (vb2_buffer/v4l2_buffer, vb2_plane/v4l2_plane) buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; buf.memory = V4L2_MEM_TYPE; buf.length = num_output_planes; buf.m.planes = output_planes; ioctl(fd, VIDIOC_DQBUF, &buf); /* 拷贝编码好的数据到目的缓冲(假定 output plane 数目为1) */ memcpy(output_data, output_buffer.start, buf.m.planes[0].bytesused); /* 关闭设备 */ close(fd);

4.3 VPU 驱动工作流程小结

讯享网 VPU ----------------------------- | ----------------------- | | | Encoder | | | | ----------------- | | --->|->|->| encoding buffer |->|->|---> ^ | | ----------------- | | | 输入数据队列(vb2_queue) | | ----------------------- | | 输出数据队列(vb2_queue) ----------------------- | | | | ----------------------- | vb2_buffer[] |-->| | ----------------------- | |--> | vb2_buffer[] | ----------------------- | | | Decoder | | | ----------------------- v | | ----------------- | | | --->|->|->| decoding buffer |->|->|---> | | ----------------- | | | ----------------------- | -----------------------------

Encoder/Decoder完成编、解码动作后:


讯享网

(1) 拷贝编、解码后的数据到输出队列中某个vb2_buffer的缓冲: memcpy(output_buffer, input_buffer, size); (2) 标记输入数据队列中某个vb2_buffer中的数据编、解码完成: vb2_buffer_done(&in_vb, VB2_BUF_STATE_DONE); (3) 设置输出缓冲负载(输出数据大小): vb2_set_plane_payload(&out_vb, 0, size); (4) 标记输出数据队列中某个vb2_buffer中的数据编、解码输出数据就绪: vb2_buffer_done(&out_vb, VB2_BUF_STATE_DONE);

4.4 示例

这是一个实际的范例,来自 FrienlyARM 的方案 :NanoPC-T3 Plus 。该方案基于 S5P6818 的 SoC 。

4.4.1 FrienlyARM的方案内核NX VPU驱动补丁

官方自带的VPU驱动编解码的部分有些问题,我对它做了如下修改:

讯享网/* * drivers/media/platform/nx-vpu/nx_vpu_enc_v4l2.c */ void vpu_enc_get_seq_info(struct nx_vpu_ctx *ctx) { 
    ... /* 注释下面这一段代码 */ /*{ struct nx_vpu_buf *dst_mb; unsigned long flags; spin_lock_irqsave(&ctx->dev->irqlock, flags); dst_mb = list_entry(ctx->strm_queue.next, struct nx_vpu_buf, list); list_del(&dst_mb->list); ctx->strm_queue_cnt--; vb2_set_plane_payload(&dst_mb->vb, 0, ctx->strm_size); vb2_buffer_done(&dst_mb->vb, VB2_BUF_STATE_DONE); spin_unlock_irqrestore(&ctx->dev->irqlock, flags); }*/ } static void nx_vpu_enc_buf_queue(struct vb2_buffer *vb) { 
    ... if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { 
    ... } else if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { 
    buf->used = 0; if (ctx->img_fmt.num_planes == 1) NX_DbgMsg(INFO_MSG, "adding to src: %p(%08lx)\n", vb, (unsigned long)nx_vpu_mem_plane_addr(ctx, vb, 0)); else if (ctx->img_fmt.num_planes == 2) NX_DbgMsg(INFO_MSG, "adding to src: %p(%08lx, %08lx)\n", vb, (unsigned long)nx_vpu_mem_plane_addr(ctx, vb, 0), (unsigned long)nx_vpu_mem_plane_addr(ctx, vb, 1)); else if (ctx->img_fmt.num_planes == 3) NX_DbgMsg(INFO_MSG, "adding to src: %p(%08lx, %08lx, %08lx)\n", vb, (unsigned long)nx_vpu_mem_plane_addr(ctx, vb, 0), (unsigned long)nx_vpu_mem_plane_addr(ctx, vb, 1), (unsigned long)nx_vpu_mem_plane_addr(ctx, vb, 2)); } ... } int nx_vpu_enc_open(struct nx_vpu_ctx *ctx) { 
    ... ctx->vq_img.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; ... ... ctx->vq_strm.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; ... }
/* * drivers\media\platform\nx-vpu\nx_vpu_v4l2.c */ #define DST_QUEUE_OFF_BASE (1 << 30) int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *buf) { 
    struct nx_vpu_ctx *ctx = fh_to_ctx(file->private_data); int ret = 0; FUNC_IN(); ... if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { 
    ... } else if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { 
    ... //buf->m.planes[0].m.mem_offset += DST_QUEUE_OFF_BASE; /* Adjust MMAP memory offsets for the CAPTURE queue */ if (buf->memory == V4L2_MEMORY_MMAP /*&& !V4L2_TYPE_IS_OUTPUT(ctx->vq_img->type)*/) { 
    if (V4L2_TYPE_IS_MULTIPLANAR(ctx->vq_img.type)) { 
    int i; for (i = 0; i < buf->length; ++i) buf->m.planes[i].m.mem_offset += DST_QUEUE_OFF_BASE; } else { 
    buf->m.offset += DST_QUEUE_OFF_BASE; } } } else { 
    ... } return ret; }

我为 S5P6818 的 VPU 编写了一个测试程序 nxvpu-yuv2jpg.c ,该程序用于将 YUV420 或 GREY 格式数据转换为 MJEPG 格式数据,实现代码见 这里 或 这里 。

5. 参考资料

https://wiki.friendlyelec.com/wiki/index.php/NanoPC-T3_Plus/zh

小讯
上一篇 2025-02-19 12:07
下一篇 2025-02-16 16:19

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/24861.html