Linux 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

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值