1. 程式人生 > >Android Framebuffer介紹及使用

Android Framebuffer介紹及使用

如果想加入Android技術交流群,請長按識別二維碼關注下方公眾號,點選“加群”獲取加群方式。


歡迎訂閱我的公眾號

 歡迎關注公眾號:FutureCoder

FrameBuffer 介紹

FrameBuffer中文譯名為幀緩衝驅動,它是出現在2.2.xx核心中的一種驅動程式介面。主裝置號為29,次裝置號遞增。
Linux抽象出FrameBuffer這個裝置來供使用者態程序實現直接寫屏。FrameBuffer機制模仿顯示卡的功能,將顯示卡硬體結構抽象掉,可以通過FrameBuffer的讀寫直接對視訊記憶體進行操作。使用者可以將FrameBuffer看成是顯示記憶體的一個映像,將其對映到程序地址空間之後,就可以直接進行讀寫操作,而寫操作可以立即反應在螢幕上。

這種操作是抽象的,統一的。使用者不必關心物理視訊記憶體的位置、換頁機制等等具體細節,這些都是由FrameBuffer裝置驅動來完成的。
FrameBuffer實際上就是嵌入式系統中專門為GPU所保留的一塊連續的實體記憶體,LCD通過專門的匯流排從framebuffer讀取資料,顯示到螢幕上。
FrameBuffer本質上是一塊顯示快取,往顯示快取中寫入特定格式的資料就意味著向螢幕輸出內容。所以說FrameBuffer就是一塊白板。

螢幕位置從上到下,從左至右與記憶體地址是順序的線性關係

FrameBuffer 使用

framebuffer的裝置檔案在Linux下一般是 /dev/fb0

/dev/fb1 等,但在Android下面一般為/dev/graphics/fb0,/dev/graphics/fb1
注意: 系統中至少要存在一個顯示屏,因此,名稱為“fb0”的裝置是肯定會存在的,否則的話,就是出錯了。

操作framebuffer的主要步驟

1、開啟可用的FrameBuffer裝置;

    fbfd = open("/dev/graphics/fb0", O_RDWR);

O_RDWR是已可讀寫的方式開啟檔案

2、計算對映大小

用ioctrl操作取得當前顯示螢幕的引數,如螢幕解析度,每個畫素點的位元數。根據螢幕引數可計算螢幕緩衝區的大小。

ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)
; // fb_fix_screeninfo 通過fbfd獲取螢幕固定的相關資訊 ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo); // fb_var_screeninfo 通過fbfd獲取可變的資訊,可以呼叫引數為`FBIOPUT_VSCREENINFO`的重新進行設定

從fb_var_screeninfo中可以獲取xoffset ,yoffset的偏移量,以及螢幕可見行列畫素點(xres,yres),以及一個畫素所佔用的位數bits_per_pixel。
從fb_fix_screeninfo 中可以獲取到framebuffer的記憶體空間大小finfo.smem_len,每行佔用的位元組數line_length等。

這些資訊都是對我們下一步來計算需要對映多大的記憶體空間有很大的幫助,size 可以直接等於 finfo.smem_len, 或者 xres * yres * bits_per_pixel >> 3

3、通過mmap對映地址空間

通過mmap函式把顯示卡的實體記憶體空間對映到使用者空間地址上

char *fbp = (char *)mmap(start, size, PROT_READ | PROT_WRITE, MAP_SHARED,fbfd, offsize);  

各個引數的含義如下:

  • start 指向欲對映的記憶體起始地址,通常設為 NULL,代表讓系統自動選定地址,對映成功後返回該地址。
  • size 代表將檔案中多大的部分對映到記憶體。
  • PROT_READ | PROT_WRITE 為可讀可寫模式
  • MAP_SHARED 對對映區域的寫入資料會複製迴文件內,而且允許其他對映該檔案的程序共享。
  • fbfd 要對映到記憶體中的檔案描述符,也就是open檔案後的描述符。
  • offsize 檔案對映的偏移量

4、更改記憶體空間裡的畫素資料並顯示;

fbp則是對映framebuffer後的記憶體首地址,整個framebuffer的地址是線性的,與整個螢幕大小從左到右,從上到下對映的。所以操作framebuffer是按照每個畫素去操作的,每個畫素都需要計算他的偏移量也就是每個畫素的記憶體地址空間.
例如在(x,y)位置寫入顏色 pixel值。

4.1 首先先計算偏移量
offset = (x + y * screen_width) * 4; // (4個位元組) 

上面未考慮多buffer切換的地址空間的情況

4.2 給偏移量的記憶體地址賦值
*((uint32_t *)(fbp + offset)) = pixel;

5、退出時關閉framebuffer裝置。

munmap(fbp, size);  
close(fbfd);

size需要與mmap時一樣,會導致記憶體洩露問題。
檢視一個完整的示例demo :

int main() {
    int fbfd = 0;
    struct fb_var_screeninfo vinfo;
    struct fb_fix_screeninfo finfo;
    long int screensize = 0;
    char *fbp = 0;
    long int location = 0;
    // Open the file for reading and writing
    fbfd = open("/dev/graphics/fb0", O_RDWR);
    if (!fbfd) {
        printf("Error: cannot open framebuffer device.\n");
        exit(1);
    }
    printf("The framebuffer device was opened successfully.\n");
    // Get fixed screen information
    if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
        printf("Error reading fixed information.\n");
        exit(2);
    }
    // Get variable screen information
    if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
        printf("Error reading variable information.\n");
        exit(3);
    }
    screensize =  finfo.smem_len;
    // screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel >> 3  // >>3 表示算出位元組數
    fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED,fbfd, 0);

    if ((int)fbp == -1) {
        printf("Error: failed to map framebuffer device to memory.\n");
    exit(4);
}

FrameBuffer 相關結構

FrameBuffer裝置驅動基於如下兩個檔案:

1) linux/include/linux/fb.h
2) linux/drivers/video/fbmem.c

FrameBuffer 主要包含的結構有以下:fb_info ,fb_ops ,fb_var_screeninfo,fb_fix_screeninfo,上面的結構都定義在fb.h裡。

fb_var_screeninfo

用於記錄使用者可修改的顯示屬性引數,包括螢幕解析度、每個畫素點的位元數等。
顯示卡的顯示屬性,使用者可修改,此資料結構中,定義了偏移量(xoffset ,yoffset)、可見行列畫素點(xres,yres)、每個畫素所佔bit位數(bits_per_pixel), 虛擬解析度(xres_virtual、yres_virtual)在視訊記憶體中包含的解析度等資訊,這些都是我們常見也是常用的到的。
這些資料我們是可以通過ioctl函式來獲取,獲取操作如下:ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo) 。也可以重新進行賦值,之後再將設定進系統:設定如下:ioctl(fbfd, FBIOPUT_VSCREENINFO, &finfo)

資料結構如下:

struct fb_var_screeninfo {  
    __u32 xres;         /* 行可見畫素*/  
    __u32 yres;         /* 列可見畫素*/  
    __u32 xres_virtual; /* 行虛擬畫素*/  
    __u32 yres_virtual; /* 列虛擬畫素*/  
    __u32 xoffset;      /* 水平偏移量*/  
    __u32 yoffset;      /* 垂直偏移量*/  
    __u32 bits_per_pixel;/*每個畫素所佔bit位數*/  
    __u32 grayscale;    /* 灰色刻度*/  
    struct fb_bitfield red; /* bitfield in fb mem if true color, */  
    struct fb_bitfield green;   /* else only length is significant */  
    struct fb_bitfield blue;  
    struct fb_bitfield transp;  /* transparency         */    
    __u32 nonstd;           /* != 0 Non standard pixel format */  
    __u32 activate;         /* see FB_ACTIVATE_*        */  
    __u32 height;           /* 影象高度*/  
    __u32 width;            /* 影象寬度*/  
    __u32 accel_flags;      /* (OBSOLETE) see fb_info.flags */  
    __u32 pixclock;         /* pixel clock in ps (pico seconds) */  
    __u32 left_margin;      /* time from sync to picture    */  
    __u32 right_margin;     /* time from picture to sync    */  
    __u32 upper_margin;     /* time from sync to picture    */  
    __u32 lower_margin;  
    __u32 hsync_len;        /* length of horizontal sync    */  
    __u32 vsync_len;        /* length of vertical sync  */  
    __u32 sync;         /* see FB_SYNC_*        */  
    __u32 vmode;            /* see FB_VMODE_*       */  
    __u32 rotate;           /* angle we rotate counter clockwise */  
    __u32 reserved[5];      /* Reserved for future compatibility */  
};  

fb_fix_screeninfo

這個結構在顯示卡被設定模式後建立,它描述顯示卡的屬性,並且系統執行時不能被修改;比如FrameBuffer記憶體的起始地址。它依賴於被設定的模式,當一個模式被設定後,記憶體資訊由顯示卡硬體給出,記憶體的位置等資訊就不可以修改。
顯示卡的硬體屬性, 使用者不可修改, 驅動程式初始化時設定。比如可以獲取記憶體空間大小,起始地址,每行佔用的位元組數line_length等等。

資料結構如下:

struct fb_fix_screeninfo {  
    char id[16];            /* identification string eg "TT Builtin" */  
    unsigned long smem_start;/* Start of frame buffer mem */  
    __u32 smem_len;         /* Length of frame buffer mem */  
    __u32 type;             /* see FB_TYPE_*        */  
    __u32 type_aux;         /* Interleave for interleaved Planes */  
    __u32 visual;           /* see FB_VISUAL_*      */   
    __u16 xpanstep;         /* zero if no hardware panning  */  
    __u16 ypanstep;         /* zero if no hardware panning  */  
    __u16 ywrapstep;        /* zero if no hardware ywrap    */  
    __u32 line_length;      /* length of a line in bytes    */  
    unsigned long mmio_start;/* Start of Memory Mapped I/O   */  
    __u32 mmio_len;         /* Length of Memory Mapped I/O  */  
    __u32 accel;            /* Indicate to driver which */  
    __u16 reserved[3];      /* Reserved for future compatibility */  
};  

fb_ops

是提供給底層裝置驅動的一個介面,使用者應用可以使用 ioctl() 系統呼叫來操作裝置。

fb_cmap

描述裝置無關的顏色對映資訊。可以通過 FBIOGETCMAP 和 FBIOPUTCMAP 對應的 ioctl 操作設定或獲取顏色對映資訊。主要是顏色對映表,顏色相關一些對映資訊。

fb_info

結構僅在核心中可見,在這個結構中有一個fb_ops指標,指向驅動裝置工作所需的函式集,是Linux為幀緩衝裝置定義的驅動層介面。它不僅包含了底層函式,而且還有記錄裝置狀態的資料。每個幀緩衝裝置都與一個fb_info結構相對應。

結構如下:
FrameBuffer結構圖

ioctl中request引數:

  • FBIOGET_VSCREENINFO 表示使用者獲取螢幕的可變引數;
  • FBIOPUT_VSCREENINFO 表示使用者設定可變的螢幕引數;
  • FBIOGET_FSCREENINFO 表示使用者獲得螢幕的固定引數;
  • FBIOBLANK表示呼叫sep4020fb_blank函式清空液晶屏;
  • FBIOPUTCMAP 表示設定螢幕的顏色表;
  • FBIOGETCMAP 表示獲得顏色表。

雙緩衝機制

Android 使用SurfaceFlinger作為螢幕合成引擎。它管理來自各個視窗的Surface objects,然後將其寫入到framebuffer去。SurfaceFlinger使用前buffer來合成,後buffer來繪製。一旦繪製完成,Android通過頁翻轉操作,交換Y軸座標的偏移量,選擇不同buffer。在EGL顯示服務初始化時,如果虛擬Y軸解析度大於實際Y軸解析度,說明framebuffer可以直接使用雙緩衝。否則,後buffer要複製到前buffer,這樣會導致頁交換延遲。為了提高系統性能,Framebuffer驅動最好提供雙緩衝機制。

雙緩衝機制的原理

所有畫圖操作將它們畫圖的結果儲存在一塊系統記憶體區域中,這塊區域通常被稱作“後緩衝區(backbuffer)”,當所有的繪圖操作結束之後,系統通過換頁機制將繪製區域指向先前的後緩衝區,然後進行繪製顯示,而原來的繪製緩衝區就變為“後緩衝區”,之後按照這種情況不停迴圈切換。這個複製操作通常要跟顯示器的光棧束同步,以避免撕裂。雙緩衝機制必須要求有比單緩衝更多的顯示記憶體和CPU消耗時間,因為“後緩衝區”需要顯示記憶體,而複製操作和等待同步需要CPU時間。

FrameBuffer1
framebuffer2

雙緩衝是一種畫圖技術,使用這種技術可以使得畫圖沒有(至少是減少)閃爍、撕裂等不良效果,並減少等待時間。

緩衝區切換步驟:

  1. 把fb驅動的framebuffer通過mmap對映到應用空間的記憶體地址map_base,一般來說framebuffer size是2*framesize或者3*framesize 大小(和平臺相關)
  2. 把第一幀資料寫入map_base
  3. 呼叫FBIOPAN_DISPLAY顯示
  4. 把第二幀資料寫入map_base+framesize處
  5. 呼叫FBIOPAN_DISPLAY
  6. 重複step2~step5

FBIOPAN_DISPLAY 在linux的註釋裡是“平移顯示”的意思,呼叫FBIOPAN_DISPLAY時,會傳一個y座標偏移量yoffset給驅動,然後驅動會把當前視訊記憶體的指標偏移 “yoffset X 螢幕寬度 X 位色位元組數” 個位元組,這樣就好像實現了影象的y座標平移,也就是“平移顯示”。當這個yoffset等於螢幕高度的時候,就實現了視訊記憶體的切換。

如果想加入Android技術交流群,請長按識別二維碼關注下方公眾號,點選“加群”獲取加群方式。


歡迎訂閱我的公眾號

 歡迎關注公眾號:FutureCoder