通常為了提高使用者體驗,在系統啟動階段lcd初始化完畢後,會快速顯示第一屏畫面,避免黑屏時間過長而導致使用者從感官上認為系統運行速度慢,所有電子產品都會這樣設計。而這個畫面經常被用來顯示產品logo。在android中,有兩處可以做logo的列印。第一處是在kernel中,顯示裝置初始化完畢後,會將一個ppm圖片複製到fb中;第二處是在android boot階段,將一個rle格式的圖片解壓縮到fb中。
首先說在kernel中做的logo顯示。kernel顯示的logo使用ppm格式圖片。ppm是一種簡單的linux圖片格式,僅包含格式、映像寬高、bit數等資訊和映像資料。映像資料的儲存格式可以用ASCII碼,也可用二進位。下面列舉ppm格式中比較簡單的一種(24位彩色、二進位儲存的映像):
檔案頭部分——
P6\n
width height\n
255\n
像素部分——
rgbrgb...
其中P6表示ppm的這種格式;\n表示分行符號;width和height表示映像的寬高,用空格隔開;255表示每個顏色分量的最大值;rgb資料從上到下,從左至右排放。
由ppm映像的格式可以看出,這是一種未經壓縮的映像格式。使用它做logo的優點是節省掉瞭解碼的時間,可以使畫面較快顯示,缺點是在一些會使鏡像體積過於龐大。如果沒有極端的需求,其實我們可以採取一些折中的方式來解決這些問題,比如,在編譯階段將映像壓縮後再打包到鏡像中,然後運行階段再解壓映像並顯示。壓縮演算法不是很複雜的時候,顯示畫面的速度應該還是可以接受的。比如,將圖片先轉化成raw資料格式,再壓縮成gz包,打包到root中,當fb初始化完成之後,直接將gz壓縮包解壓到顯示緩衝區中(使用gunzip介面)。解壓介面如下:
/*fileName: logo image file nameframeBuffer: display buffer*/int draw_logo(char* fileName, void* frameBuffer){int fd;char *input;struct stat st;fd =sys_open(fileName,O_RDONLY, 0);if (fd < 0){return -1;}sys_newfstat(fd, &st);len = st.st_size;input = vmalloc(st.st_size);if (!input) {return -1;}sys_read(fd, input, st.st_size);gunzip(input, st.st_size, NULL, NULL, frameBuffer, NULL, NULL);vfree(input);sys_close(fd);load_565rle_imagereturn 0;}
下面探討android的logo顯示。android的啟動始於system/core/init/init.c。啟動流程有很多文章寫的很好,不贅述了,這裡只說說logo顯示函數是如何被調用到的:
main(位於system/core/init/init.c)--> console_init_action(位於system/core/init/init.c)-->load_565rle_image(位於system/core/init/logo.c)。
load_565rle_image用於列印rle格式映像到fb中。先說說rle資料格式。rle是一種無損資料壓縮格式,壓縮時會計算出連續的資料內容和連續長度,用個簡單的例子說明:AAABBBCCC可以壓縮為3A3B3C,這樣原本長度為9Bytes的資料被壓縮到了6Bytes。使用這種壓縮方式,優點是可以既節約磁碟空間,又不使映像受損;缺點是如果遇到了非連續的資料區段,反而會增加資料大小。例如:ABCABCABC,壓縮後變成1A1B1C1A1B1C1A1B1C,這樣原本9Bytes的資料變成了18Bytes。瞭解了rle壓縮格式,再來看load_565rle_image的代碼就比較容易了:
/* 565RLE image format: [count(2 bytes), rle(2 bytes)] */int load_565rle_image(char *fn){ struct FB fb; struct stat s; unsigned short *data, *bits, *ptr; unsigned count, max; int fd; if (vt_set_mode(1)) return -1; fd = open(fn, O_RDONLY); //開啟rle影像檔 if (fd < 0) { ERROR("cannot open '%s'\n", fn); goto fail_restore_text; } if (fstat(fd, &s) < 0) { goto fail_close_file; } data = mmap(0, s.st_size, PROT_READ, MAP_SHARED, fd, 0); //將映像資料對應到記憶體,data指向該記憶體的首地址 if (data == MAP_FAILED) goto fail_close_file; if (fb_open(&fb)) //開啟fb裝置 goto fail_unmap_data; max = fb_width(&fb) * fb_height(&fb); ptr = data; count = s.st_size; bits = fb.bits; while (count > 3) { unsigned n = ptr[0]; if (n > max) break; android_memset16(bits, ptr[1], n << 1); //將rle資料解碼到顯示緩衝區 bits += n; max -= n; ptr += 2; count -= 4; } munmap(data, s.st_size); fb_update(&fb); fb_close(&fb); close(fd); unlink(fn); return 0;fail_unmap_data: munmap(data, s.st_size);fail_close_file: close(fd);fail_restore_text: vt_set_mode(0); return -1;}
load_565rle_image函數看起來沒有什麼問題,但是實際上是有隱患的。當kernel啟動後,fb的rgb設定為565時,顯示沒有問題;但是如果預設設定為其他格式時,就會顯示花屏。因此,修改fb_open函數,重新使用ioctl介面設定fb會比較保險。將fb_open修改如下:
static int fb_open(struct FB *fb){ fb->fd = open("/dev/graphics/fb0", O_RDWR); if (fb->fd < 0) return -1; if (ioctl(fb->fd, FBIOGET_FSCREENINFO, &fb->fi) < 0) goto fail; if (ioctl(fb->fd, FBIOGET_VSCREENINFO, &fb->vi) < 0) goto fail; fb->bits = mmap(0, fb_size(fb), PROT_READ | PROT_WRITE, MAP_SHARED, fb->fd, 0); /* add by Lynn, reset fb format */ fb->vi.bits_per_pixel = 16; fb->vi.yres_virtual=fb->vi.yres*2; fb->vi.red.offset = 11; fb->vi.red.length = 5; fb->vi.green.offset = 5; fb->vi.green.length = 6; fb->vi.blue.offset = 0; fb->vi.blue.length = 5; fb->vi.transp.offset = 0; fb->vi.transp.length = 0; fb->vi.nonstd =4; fb->vi.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; if(ioctl(fb->fd, FBIOPUT_VSCREENINFO, &fb->vi) < 0) { ERROR("Put screen info failed !!!"); goto fail; } /* end add */ if (fb->bits == MAP_FAILED) goto fail; return 0;fail: close(fb->fd); return -1;}
如上修改就可以保證無論kernel對fb設定如何,android中的logo都能正常顯示了。
通過上面對代碼和格式的分析,我們就可以根據自己產品的需要,選擇合適的logo顯示方式了。