菜鸟修炼笔记-音视频开发基础知识-drm简介

前言

最近工作上有些需求需要使用到drm中的plane,作为一名小菜鸟,在面对这个需求的时候,一堆问号又不由自主地从我的小脑瓜里面冒了出来。drm是什么?plane是什么?它们有什么用?它们有什么关系?它们要怎么用?所幸,万能的互联网存在各种知识,只需要我们花时间去寻找和学习,就能把各种各样的问题解决。
本文将会结合网上的资料和我自己的理解,来尝试回答以上的问题。

特别声明

本文很大部分信息来自其他博文,具体的博文网址见文末的参考资料。

一。DRM的基础概念

1. DRM是什么?

DRM是Linux目前主流的图形显示框架,相比FB架构,DRM更能适应当前日益更新的显示硬件。比如FB原生不支持多层合成,不支持VSYNC,不支持DMA-BUF,不支持异步更新,不支持fence机制等等,而这些功能DRM原生都支持。同时DRM可以统一管理GPU和Display驱动,使得软件架构更为统一,方便管理和维护。

2. DRM的组成

DRM从模块上划分,可以简单分为3部分:libdrm、KMS、GEM。

其中KMS还包括:CRTC,ENCODER,CONNECTOR,PLANE,FB,VBLANK,property。
而GEM还包括:DUMB、PRIME、fence。

由于我目前需要了解的是plane属于KMS模块,我们将着重了解的是KMS。

2.1 libdrm

libdrm对底层接口进行封装,向上层提供通用的API接口,主要是对各种IOCTL接口进行封装。应用层可以直接使用libdrm中的接口来达到图像显示和图像处理等功能。

2.2 GEM

Graphic Execution Manager,主要负责显示buffer的分配和释放,也是GPU唯一用到DRM的地方。

属于GEM模块的几个专有名词的意义如下:

元素说明
DUMB只支持连续物理内存,基于kernel中通用CMA API实现,多用于小分辨率简单场景
PRIME连续、非连续物理内存都支持,基于DMA-BUF机制,可以实现buffer共享,多用于大内存复杂场景
fencebuffer同步机制,基于内核dma_fence机制实现,用于防止显示内容出现异步问题

2.3 KMS

Kernel Mode Setting,所谓Mode setting,其实说白了就两件事:更新画面和设置显示参数
更新画面:显示buffer的切换,多图层的合成方式,以及每个图层的显示位置。
设置显示参数:包括分辨率、刷新率、电源状态(休眠唤醒)等。

属于KMS的几个专有名词的意义如下:

元素说明
CRTC对显示buffer进行扫描,并产生时序信号的硬件模块,通常指Display Controller
ENCODER负责将CRTC输出的timing时序转换成外部设备所需要的信号的模块,如HDMI转换器或DSI Controller
CONNECTOR连接物理显示设备的连接器,如HDMI、DisplayPort、DSI总线,通常和Encoder驱动绑定在一起
PLANE硬件图层,有的Display硬件支持多层合成显示,但所有的Display Controller至少要有1个plane
FBFramebuffer,单个图层的显示内容,唯一一个和硬件无关的基本元素
VBLANK软件和硬件的同步机制,RGB时序中的垂直消影区,软件通常使用硬件VSYNC来实现
propertybuffer同步机制,基于内核dma_fence机制实现,用于防止显示内容出现异步问题

它们之间的关系和作用如下图所示,值得注意的是与显示屏连接的是connector,而plane可以存在多个也可以同时使用多个plane来进行图像显示。
在这里插入图片描述(图片来源:The DRM/KMS subsystem from a newbie’s point of view

二。KMS各个元素详解

本文主要整理plane和property的内容,其余内容详见原博文。(文末有链接)

1. plane

1.1 定义:

Plane指的是Display Controller中用于多层合成的单个硬件图层模块。
Plane是连接FB与CRTC的纽带,是内存的搬运工。意思就是说,plane的作用是将帧缓存搬到CRTC中。

1.2 类型

类型说明
Cursor光标图层,一般用于PC系统,用于显示鼠标
Overlay叠加图层,通常用于YUV格式的视频图层
Primary主要图层,通常用于仅支持RGB格式的简单图层

其实随着现代半导体技术的飞速发展,Overlay PlanePrimary Plane之间已经没有明显的界限了,许多芯片的图层处理能力已经非常强大,不仅仅可以处理简单的RGB格式,也可以处理YUV视频格式,甚至FBC压缩格式。针对这类硬件图层,它既可以是Overlay Plane,也可以是Primary Plane,至于驱动如何定义,就要看工程师的喜好了。
而对于一些早期处理能力比较弱的硬件,为了节约成本,每个图层支持的格式并不一样,比如将平常使用格式最多的RGB图层作为Primary Plane,而将平时用不多的YUV视频图层作为Overlay Plane,那么这个时候上层应用程序在使用这两种plane的时候就需要区别对待了。

1.3 功能

一个高级的Plane,通常具有如下功能:

功能说明
Crop裁剪,如上图
Scaling缩放,放大或缩小
Rotation旋转,90° 180° 270° X/Y镜像
Z-OrderZ-顺序,调整当前层在总图层中的Z轴顺序
Blending合成,pixel alpha / global alpha
Format颜色格式,ARGB888 XRGB888 YUV420 等

1.4 使用:

伪代码:

int main(void)
{
	...
	drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
	drmModeGetPlaneResources(fd);
	drmModeSetPlane(plane_id, crtc_id, fb_id, 0,
			crtc_x, crtc_y, crtc_w, crtc_h,
			src_x << 16, src_y << 16, src_w << 16, src_h << 16);
	...
}

(1)设置DRM_CLIENT_CAP_UNIVERSAL_PLANES:

drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
//fd为前面打开的文字描述符

因为如果不设置DRM_CLIENT_CAP_UNIVERSAL_PLANES,drmModeGetPlaneResources()就只会返回Overlay Plane,其他Plane都不会返回。而如果设置了,DRM驱动则会返回所有支持的Plane资源,包括cursor、overlay和primary。

(2)获取plane资源(主要需要获取所需的plane_id):

drmModePlaneRes *plane_res;
uint32_t plane_id;
plane_res = drmModeGetPlaneResources(fd);
plane_id = plane_res->planes[0];

(3)实现裁剪、平移和放大的效果:drmModeSetPlane()drmModeSetPlane的详细定义

	drmModeSetPlane(plane_id, crtc_id, fb_id, 0,
			crtc_x, crtc_y, crtc_w, crtc_h,
			src_x << 16, src_y << 16, src_w << 16, src_h << 16);

这个函数的参数含义如下:
在这里插入图片描述

1.5 注意:

(1)要与YUV/YCbCr图形格式中的plane区分开来:

DRM中的Plane和我们常说的YUV/YCbCr图形格式中的plane完全是两个不同的概念。YUV图形格式中的plane指的是图像数据在内存中的排列形式,一般Y通道占一段连续的内存块,UV通道占另一段连续的内存块,我们称之为YUV-2plane
(也叫YUV 2平面),属于软件层面。而DRM中的Plane指的是Display
Controller中用于多层合成的单个硬件图层模块,属于硬件层面。二者概念上不要混淆。

(2)并不是所有的Display Controller都支持Plane,即使没有plane_id,屏幕也能正常显示。比如s3c2440这种骨灰级ARM9 SoC,它的LCDC就没有Plane的概念。但是DRM框架规定,任何一个CRTC,必须要有1个Primary Plane。 即使像S3C2440这种不带真实Plane硬件的Display Controller,我们也认为它的Primary Plane就是LCDC本身,因为它实现了从Framebuffer到CRTC的数据搬运工作,而这正是一个Plane最基本的功能。

2. property

2.1 定义和作用:

用来控制drm各个环节参数的全局属性,Property的结构简单概括主要由3部分组成:nameidvalue,不同的property控制不同的环节,也有不同的nameidvalue。通过获取不同的property并进行相应的设置,便可以控制drm各个环节。

2.2 常用的property及含义

DRM应用程序进阶 (Property)

三。drm应用程序调用流程

 注意:以下都是伪代码,实际函数的参数并不如下面代码所示

1. 初始化:

1.1 打开设备

open(/dev/dri/card0) 

1.2 设置drm的flag:

drmSetClientCap(DRM_CLIENT_CAP_UNIVERSAL_PLANES) 
drmSetClientCap(DRM_CLIENT_CAP_ATOMIC)

(1)为什么要设置 DRM_CLIENT_CAP_ATOMIC

凡是被DRM_MODE_PROP_ATOMIC修饰过的property,只有在drm应用程序支持Atomic操作时才可见,否则该property对应用程序不可见。因此通过设置DRM_CLIENT_CAP_ATOMIC这个flag,来告知DRM驱动该应用程序支持Atomic操作。

(2)为什么要设置 DRM_CLIENT_CAP_UNIVERSAL_PLANES

因为如果不设置DRM_CLIENT_CAP_UNIVERSAL_PLANESdrmModeGetPlaneResources()就只会返回Overlay Plane,其他Plane都不会返回。而如果设置了,DRM驱动则会返回所有支持的Plane资源,包括cursor、overlay和primary。

1.3 获取crtc,encoder,connector,plane

drmModeGetResources()
drmModeGetConnector() //found connector DSI 
drmModeObjectGetProperties() //found connector dpms prop 
drmModeGetEncoder() //found encoder DSI
drmModeGetCrtc() //found crtc that connect to DSI 
drmGetPlaneByType(DRM_PLANE_TYPE_PRIMARY) //get PRIMARY/OVERLAY type drm plane info
drmModeObjectGetProperties(plane_id,DRM_MODE_OBJECT_PLANE) //get drm plane properties 
drmModeGetProperty() //get drm info like crtc_id,fb_id and so on,they be used for commit

获取crtc,encoder,connector,plane的先后顺序为connector,encoder,crtc,plane,每个前者的结构中都有后者的id号,发现connector后可以用过其结构下的encoder_id找到与自己连接的encoder模块,同理直到找到从plane到connector的连接通路。

2. 创建dumb空间并通过mmap映射到应用层

2.1 创建dumb buffer

【什么是dumb buffer:关于 DRM 中 DUMB 和 PRIME 名字的由来

drmIoctl(DRM_IOCTL_MODE_CREATE_DUMB)

2.2 通过mmap映射到应用层

【什么是mmap:【深入浅出Linux】关于mmap的解析

drmIoctl(DRM_IOCTL_MODE_MAP_DUMB) //获取dumb buffer的映射偏移值
mmap() //通过mmap映射内核空间到应用层

3. 将相机数据传入前面mmap映射的地址中

drmPrimeHandleToFD(fd,handle,0,&fd2) //handle已和fd绑定,在此将fd2与handle绑定,即fd2同fd相同
drmModeAddFB2() //添加framebuffer

可以将相机数据从相机buf拷贝到mmap空间,但该方式效率太低,对于高帧率的应用场景可以使用DMA_BUF机制将相机数据buf和mmap空间建立连接,使数据通过dma通道直达mmap空间。

4. 提交数据

drmModeAtomicAlloc() //申请Atomic结构
drmModeAtomicAddProperty() //将前面获取的crtc_id,fb_id等参数都传入申请的Atomic结构中
drmModeAtomicCommit() //提交数据到display

【 Atomic Commit具体的用法见:DRM应用程序进阶 (atomic-plane)

5. 资源释放

drmModeRmFB(fb_id) //删除drmModeAddFB2()添加的framebuffer,不然会造成shmem泄露
munmap() //释放mmap映射的内存
close() //关闭打开的drm句柄

总结:

以上便为应用程序调用libdrm的流程,其中的3和4在循环中,1和2只需要调用一次,但在某些场景中需要释放2中申请的资源,但1中初始化的不能释放;
该场景为:单一plane,crtc等,需要多次打开和关闭display,且系统中有多个功能块在使用drm中的一个或多个模块(比如视频编码用到encoder模块),这样,若将1中初始化的资源释放掉,再次获取时可能会被其他进程占用导致无法获取,这种场景下就需要保留1中资源,但多次申请/是否2中资源。
但这样会引入一个问题:调用drmModeRmFB函数释放fb,但若释放了正在用于提交的fb,内核会将crtc关闭,导致vop数据传输通路断裂,而且没有framebuffer时Encoder也会被disable,此时再打开vop也无法获取Encoder,致使vop不通;虽然内核宏CONFIG_DRM_FBDEV_EMULATION使能时,在所有的drmfd关闭后fbdev会重新配置crtc,但由于其他进程也打开了drm,因此crtc不会再次被设置,除非重启程序。
解决这一问题的方法是:打开vop时调用drmModeSetCrtc函数手动设置crtc,即使无法找到Encoder,该函数也会使用一个默认的Encoder将crtc,encoder,connector三者建立连接,从而打通vop通路。
也就是说在以上的应用场景下即使内核默认使能了宏CONFIG_DRM_FBDEV_EMULATION,也需要调用到drmModeSetCrtc()函数,非以上场景可不用调用(对于Atomic模式来说)。

参考资料

  1. 特别推荐:DRM(Direct Rendering Manager)学习简介
  2. 特别推荐:linux drm原理及应用
  3. DRM应用程序进阶 (atomic-plane)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值