Android Framebuff 分析

来源:互联网 发布:it香港 编辑:程序博客网 时间:2024/06/08 11:11

声明:本博文原型来自CSDNMAXLENG

LinuxFramebuffer用于实现对不同框架显示控制器进行抽象,对用户进程而言复杂的显示过程被简化成了写显存。Framebuffer的第二功能是对输出到屏上的数据进行缓冲,将待显示的数据写入至备用显示buffer,最后执行显存地址切换输出下一帧数据。Android帧缓冲流程框图如下:

SurfaceFlinger将自己的“Surface”投掷到屏幕缓冲区,Surface的最小单位为window,在结构框图中我们称为后台窗口。后台待显示窗口通过Blit(区块搬移)到备用显示缓冲区,然后在通过Framebuffer驱动Post到显示屏幕上,完成窗口的输出工作。

从最根源的硬件帧缓冲机制我们知道显示FrameBuffer在系统中就是一段内存,界面显示的工作就是把需要输出的内容放入到该段内存的某个位置。我们将从基本的点(像素点)和基本的缓冲区操作开始研究。


一、Framebuffer数据格式与操作


1.像素点的格式

对于不同的显示设备来讲FrameBuffer存储一个像素点二进制格式各不相同,我们一般通过单个像素点使用存储单元数目、单元存储单元内部颜色分量排列关系与颜色空间等加以描述。

1)点色彩深度:通常将Depth,即表示多少位表示一个点。

1位表示一个点(单色)

2位表示一个点

16位表示一个点

32位表示一个点(Alpha通道)

2)点内格式:RGB分量分布表示。

下图我们常用的16位表示一个像素点内部颜色分量的几种存储方式:

3)色彩空间:颜色表示方法

RGB色彩空间表示法

YUV色彩空间表示法


2.数据输出到物理设备


屏幕输出实际上是通过硬件加速将显存数据映射到物理设备的过程。我们的硬件必输完成下面工作:

RGBYUV色彩空间相互转换

输出帧数据同步

内存高速数据到低速显示设备的数据DMA支持与缓冲

柔性的多样显示设备硬件支持

源显示数据内容可能为单色位图或彩色位图,但对于具体的目标显示设备来讲,我们的目标格式可能就是一种,例如16位(5/6/5)格式。但是,设计图形显示接口的基本要求为良好的可移植性好,所以我们还是必须考虑对于不同点格式LCD之间的转换操作。


3. 数据搬移


我们在显示缓冲区上做的最多的操作就是区块搬运(Blit)。由此,很多的应用处理器使用了硬件图形加速器来完成区域搬运。从我们的主要操作的对象来看,可以分为以下几个方向:

1)内存区域到屏幕区域

2)屏幕区域到屏幕区域

3)屏幕区域到内存区域

4)内存区域到内存区域

应用处理器都可能带有图形加速器,对于不同的应用处理器对其图形加速器可能有不同的处理方式,对于2D加速来讲,都可归结为Blit。多为数据的搬运,放大缩小,旋转等。

在这里我们需要特别提出的是,由于在Linux不同进程之间的内存不能自由的访问,使得我们的每个Android应用对于内存区域和屏幕缓冲区的使用变得很复杂。在Android的设计中,在屏幕缓冲区和显示内存缓冲区的管理分类很多的层次,最上层的对象是可以在进程间自由传递,但是对于缓冲区内容则使用共享内存的机制。

基于以上的基础知识,我们可以知道:

  1. 代码中需要Config像素点Format的原因

  2. 不同图像采集单元输出的图像格式各不相同(模拟电视信号采用YUVLCD则采用RGB方式)

  3. 图像处理过程必须以像素点为单位,我们必须通过约定的Fomat得到单次处理的像素点数量,采用合适的像素表示方式可节约内存占用,并同时减少数据传输对总线带宽的占用。

    4.所有屏幕上图形的移动都是显示缓冲区搬运的结果。


二、Android的缓冲区抽象定义


·不同的硬件有不同的硬件图形加速设备和缓冲内存实现方法。AndroidGralloc动态库抽象的任务就是消除不同的设备之间的差别,在上层看来都是同样的方法和对象。在Moudle层隐藏缓冲区操作细节。Android使用了动态链接库gralloc.xxx.so,来完成底层细节的封装。

1.关于动态库

动态库实现位置:

hardware/libhandware/modules/gralloc

上层调用:

frameworks/base/services/surfaceflinger/LayerBuffer.cpp

frameworks/base/libs/ui/FramebufferNativeWindow.cpp

frameworks/base/libs/ui/GraphicBufferAllocator.cpp

frameworks/base/opengl/libagl/texture.cpp

frameworks/base/opengl/libagl/egl.cpp

封装与引用:

对于动态库的使用与封装Android使用了统一的调用符号接口HAL_MODULE_INFO_SYM,代码位置如下:

hardware/libhardware

从数据关系上我们来考察动态链接库的抽象行为:在层次HAL代码实现中对动态链接库中的内容作了全新的包装。最终编译得到带HAL_MODULE_INFO_SYM符号数据接口动态库文件(/system/lib/hw/gralloc.xxx.so)。上层引用调用hw_get_modulegralloc.xxx.so提取了HAL_MODULE_INFO_SYMSYM变量)

我们在Gralloc.cpp需要完成这样的布局:

staticstruct hw_module_methods_t gralloc_module_methods = {

open:gralloc_device_open

};

structprivate_module_t HAL_MODULE_INFO_SYM = {

base:{

       common: {

           tag:HARDWARE_MODULE_TAG,

            …

           id:GRALLOC_HARDWARE_MODULE_ID,

           name:"Graphics Memory Allocator Module",

           author:"The Android Open Source Project",

           methods:&gralloc_module_methods

        },

registerBuffer:gralloc_register_buffer,

unregisterBuffer:gralloc_unregister_buffer,

lock: gralloc_lock,

unlock: gralloc_unlock,

   },

    framebuffer:0,

    flags:0,

    numBuffers:0,

    bufferMask:0,

};

引用片段

if(hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module) == 0) {

intstride;

interr;

inti;

err= framebuffer_open(module, &fbDev);

LOGE_IF(err,"couldn't open framebuffer HAL (%s)", strerror(-err));

err= gralloc_open(module, &grDev);

LOGE_IF(err,"couldn't open gralloc HAL (%s)", strerror(-err));

//bail out if we can't initialize the modules

if(!fbDev || !grDev)

return;

…………………

}

我们建立了什么对象来支撑缓冲区的操作?

buffer_handle_t:外部接口。

methods.openregisterBufferunregisterBufferlockunlock

下面是外部接口和内部对象的结构关系,该类型的结构充分利用C Struct的数据排列特性:基本结构体放置在最前面,本地私有放置在后面,满足了抽象的需要。

typedefconst native_handle* buffer_handle_t;

private_module_t HAL_MODULE_INFO_SYM向往暴露的动态链接库接口,通过该接口,我们直接可以使用该对象。

动态库需要完成的工作:

根据帧缓冲HAL层名称Gralloc,可知该部分涉及最多的将是图形内存的管理与分配工作。

1)硬件图形加速器的抽象:BlitEngineCopyBit的加速操作。

2)硬件FrameBuffer内存管理

3)共享缓存管理


几个接口函数的解释:

(1)fb_post

对于帧缓冲区实际地址并不需要向上层报告,所有的操作都是通过fb_post了完成。

fp_post的任务就是将一个Buffer的内容传递到硬件缓冲区。其实现方式有两种:

(方式1)无需拷贝动作,是把Framebuffer的后buffer切为前buffer,然后通过IOCTRL机制告诉FB驱动切换DMA源地地址。这个实现方式的前提是Linux内核必须分配至少两个缓冲区大小的物理内存和实现切换的ioctrol,这个实现快速切换。

(方式2)利用Copy的方式。不修改内核,则在适配层利用从拷贝的方式进行,但是这个是费时了。

(2)gralloc的主要功能是要完成:

     1)打开屏幕设备"/dev/fb0",,并映射硬件显示缓冲区。

     2)提供分配共享显示缓存的接口

    3)提供BiltEngine接口(完成硬件加速器的包装)

3gralloc_alloc输出buffer_handle_t句柄。

    这个句柄是共享的基本依据,其基本原理在后面的章节有详细描述。


三、共享缓冲


1. native_handle_tprivate_handle_t的包裹

 private_handle_tgralloc.so使用的本地缓冲区私有的数据结构,而Native_handle_t是上层抽象的可以在进程间传递的数据结构。在客户端是如何还原所传递的数据结构呢?首先看看native_handle_tprivate_handle_t的抽象包装


numFds=sNumFds=1;

numInts=sNumInts=8;

这个是Parcel中描述句柄的抽象模式。实际上是指的Native_handle所指向句柄对象的具体内容:

numFds=1表示有一个文件句柄:fd/

numInts=8表示后面跟了8INT型的数据:magic,flags,size,offset,base,lockState,writeOwner,pid;

由于在上层系统不要关心buffer_handle_tdata的具体内容。在进程间传递buffer_handle_t(native_handle_t)句柄是其实是将这个句柄内容传递到Client端。在客户端通过Binder读取readNativeHandle@Parcel.cpp新生成一个native_handle

native_handle*Parcel::readNativeHandle() const

{

native_handle*h = native_handle_create(numFds, numInts);

    for(int i=0 ; err==NO_ERROR && i<="" font="">

       h->data[i]= dup(readFileDescriptor());

        if(h->data[i] < 0) err = BAD_VALUE;

    }

    err= read(h->data + numFds, sizeof(int)*numInts);

    ….

returnh;

}

    这里需要提到的是为在构造客户端的native_handle时,对于对方传递过来的文件句柄的处理。由于不是在同一个进程中,所以需要dup(…)一下为客户端使用。这样就将Native_handle句柄中的,客户端感兴趣的从服务端复制过来。这样就将Private_native_t的数据:magic,flags,size,offset,base,lockState,writeOwner,pid;复制到了客户端。

    客户端利用这个新的Native_bufferMapper传回到gralloc.xxx.so中,获取到native_handle关联的共享缓冲区映射地址,从而获取到了该缓冲区的控制权,达到了客服端和Server间的内存共享。从SurfaceFlinger来看就是作图区域的共享。

2.Graphic Mapper是干什么的?

   服务端(SurfaceFlinger)分配了一段内存作为Surface的作图缓冲,客户端怎样在这个作图缓冲区上工作呢?这个就是Mapper(GraphicBufferMapper)要干的事情。两个进程间如何共享内存,如何获取到共享内存?Mapper就是干这个得。需要利用到两个信息:共享缓冲区设备句柄,分配时的偏移量。Mapper利用这样的原理:

   客户端只有lock,unlock,实质上就是mmapummap的操作。对于同样一个共享缓冲区,偏移量才是总要的,起始地址不重要。实际上他们操作了同一物理地址的内存块。我们在上面讨论了native_handle_tprivate_handle_t的包裹过程,从中知道服务端给客户端传递了什么东西。

       进程1在共享内存设备上预分配了8M的内存。以后所有的分配都是在这个8M的空间进行。对以该文件设备来讲,8M物理内存提交后,就实实在在的占用了8M内存。每个每个进程都可以同个该内存设备共享该8M的内存,他们使用的工具就会mmap。由于在mmap都是用0开始获取映射地址,所以所有的客户端进程都是有了同一个物理起始地址,所以此时偏移量和size就可以标识一段内存。而这个偏移量和size是个数值,从服务进程传递到客户端直接就可以使用。

3. GraphicBuffer(缓冲区代理对象)

typedefstruct android_native_buffer_t

{

    structandroid_native_base_t common;

    intwidth;

    intheight;

    intstride;

    intformat;

    intusage;

     …

    buffer_handle_thandle;

     …

}android_native_buffer_t;

关系图表:

GraphicBuffer:EGLNativeBase :android_native_buffer_t

GraphicBuffer(parcel&)建立本地的GraphicBuffer的数据native_buffer_t。在通过接收对方的传递的native_buffer_t构建GraphicBuffer。我们来看看在客户端Surface::lock获取操作缓冲区的函数调用:

Surface::lock(SurfaceInfo*other, Region* dirtyIn, bool blocking)

     {intSurface::dequeueBuffer(android_native_buffer_t** buffer)Surface

       {status_tSurface::getBufferLocked(int index, int usage)

         {

              sp buffer= s->requestBuffer(index, usage);

          {

virtualsp requestBuffer(int bufferIdx, int usage)

{  remote()->transact(REQUEST_BUFFER, data, &reply);

   spbuffer = new GraphicBuffer(reply);

Surface::Lock建立一个在Client端建立了一个新的GraphicBuffer对象,该对象通过(1)描述的原理将SurfaceFlingerbuffer_handle_t相关数据构成新的客户端buffer_handle_t数据。在客户端的Surface对象就可以使用GraphicMapper对客户端buffer_handle_t进行mmap从而获取到共享缓冲区的开始地址了。

总结

gralloc.xxx.so是跟硬件体系相关的一个动态链接库,为AndroidFrambuffer硬件抽象层。他实现了Android的硬件抽象接口标准,提供显示内存的分配机制和Frambuffer数据输出显示功能。而如何具体实现这些功能,则跟硬件平台的配备有关系,所以我们看到了对于与不同的硬件架构,有不同的配置关系。Android在该节使用了共享内存的方式来管理与显示相关的缓冲区,他设计成了两层,上层是缓冲区管理的代理机构GraphicBuffer,及其相关的native_buffer_t,下层是具体的缓冲区的分配管理及其缓冲区本身。上层的对象是可以在经常间通过Binder传递的,而在进程间并不是传递缓冲区本身,而是使用mmap来获取指向共同物理内存的映射地址。

原创粉丝点击