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

Linux VPU驱动相关推荐

  1. 【Linux内核驱动】驱动模块Modules

    整理自宋宝华老师的<Linux设备驱动开发详解>, 文章内容包括: lsmod命令与/proc/modules, /sys/modules的关系 内核模块的程序结构:这是写内核模块驱动的框 ...

  2. Linux音频设备驱动

    在Linux中,先后出现了音频设备的两种框架OSS和ALSA,本节将在介绍数字音频设备及音频设备硬件接口的基础上,展现OSS和ALSA驱动的结构. 17.1-17.2节讲解了音频设备及PCM.IIS和 ...

  3. linux设备驱动第五篇:驱动中的并发与竟态

    目录[-] 综述 信号量与互斥锁 Completions 机制 自旋锁 其他的一些选择 不加锁算法 原子变量与位操作 seqlock(顺序锁) 读取-拷贝-更新(RCU) 小结 综述 在上一篇介绍了l ...

  4. linux串口驱动分析

    linux串口驱动分析 硬件资源及描写叙述 s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 DMA 模式下操作 ...

  5. linux音频驱动dma数据,Linux音频驱动简述

    3.2 mixer接口 int register_sound_mixer(structfile_operations *fops, int dev); 上述函数用于注册1个混音器,第1个参数fops即 ...

  6. Linux驱动无硬件设备,Linux设备驱动与硬件通信

    Linux物理设备驱动,主要有几种类型,如:IO类.内存类.总线类.IO类我们平时接触的最多,其主要特点是,通过IO设备的寄存器操作硬件,具体需要去查看硬件手册. 1. IO端口和IO内存 在硬件层, ...

  7. Linux主机驱动与外设驱动分离思想

    - by 宋宝华(Barry Song) 1主机.外设驱动分离的意义 在Linux设备驱动框架的设计中,除了有分层设计实现以外,还有分隔的思想.举一个简单的例子,假设我们要通过SPI总线访问某外设,在 ...

  8. Linux 网卡驱动学习(一)(分析一个虚拟硬件的网络驱动样例)

    在Linux,网络分为两个层,各自是网络堆栈协议支持层,以及接收和发送网络协议的设备驱动程序层. 网络堆栈是硬件中独立出来的部分.主要用来支持TCP/IP等多种协议,网络设备驱动层是连接网络堆栈协议层 ...

  9. 《Android深度探索(卷1):HAL与驱动开发》——1.6节 Linux设备驱动

    本节书摘来自异步社区<Android深度探索(卷1):HAL与驱动开发>一书中的第1章,第1.6节 Linux设备驱动,作者李宁,更多章节内容可以访问云栖社区"异步社区" ...

最新文章

  1. 补习系列(11)-springboot 文件上传原理
  2. IOS之@property 的理解
  3. springboot 访问html_Spring Boot中使用Spring Security进行安全控制
  4. 基于flink+clickhouse构建亿级电商全端用户画像平台训练营
  5. 无法加载安装安装程序:Wbemupgd.dll
  6. C语言学习及应用笔记之四:C语言volatile关键字及其使用
  7. java用户角色权限管理 只显示姓_快递物流管理系统SSM,JQUERYEASYUI,MYSQL
  8. linux编译内核的步骤
  9. mysql三高讲解(一):1.1 客户端怎样连接mysql数据库
  10. vue element-UI的树形结构,父级关联,返回数据反选的问题
  11. Python必不可少的小技巧,一行代码减少一半内存占用!
  12. ThinkPhp项目部署到Linux file_put_contents() 报错:failed to open stream: Permission denied
  13. c语言怎么随机生成迷宫地图,C++实现随机生成迷宫地牢
  14. 7628刷breed_路由器刷breed_Web控制台助手v5.9版本.7z
  15. 【对讲机的那点事】玩无线电,你知道无线电信号是怎样发送和接收的?
  16. PHP: Fatal error:Call to undefined function com_create_guid()
  17. Unity游戏开发客户端面经——数学(初级)
  18. 描述并简要比较TCP/IP协议体系及0SI/RM协议体系
  19. [原创]TenJi Game-线下玩法技巧
  20. c语言中格式符号错误,C语言中符号格式说明

热门文章

  1. visual assist
  2. Mac下查看Tomcat版本
  3. Monospace/Fixed Width Programmer's Fonts
  4. js三级联动案例(省份,城市,县区,街道)
  5. 推荐一个车载软件的学习目录
  6. 基于机器学习的二手车价格预测及应用实现(预测系统实现)
  7. python字典实现switch功能
  8. 在html中写响应式布局的代码,手机端自适应响应式框架,移动端响应式布局代码...
  9. idea切换主题与背景颜色
  10. 《出身寒门状元之死》系伪造 咪蒙被指是真正用力骗过的人