android上用C語言讀取fb0實現截圖,並儲存為rgb565的bmp
好久沒有看,這兩天在折騰一下,更新一下:修正了framebuffer bgra_8888格式截圖變紅的問題
=================================
android上用C語言讀取fb0實現截圖,儲存為bmp圖片,
支援16位(rgb565)、24位(rbg888)兩種格式資料,並在android2.2和4.0模擬器上驗證通過。
截圖實現主要有兩個方面的工作,讀取螢幕資料和生成圖片。
1.讀取螢幕資料
只讀方式開啟視訊記憶體裝置 /dev/graphics/fb0,再通過mmap用共享方式(MAP_SHARED)對映到一片記憶體中,其他地方就可以直接以記憶體的方式讀取螢幕資料了。
要注意的是,fb0的虛擬顯示裝置尺寸,大於實際螢幕顯示裝置的尺寸,mmap對映時需將安裝虛擬尺寸進行對映,否則,截圖可能失敗。
針對視訊記憶體裝置的主要程式碼:
/******************************************************************** created: 2012/02/07 filename: myfb.c author: purpose: *********************************************************************/ #ifndef WIN32 //------------------------------------------------------------------- #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <linux/fb.h> #include <linux/kd.h> struct FB { unsigned short *bits; unsigned size; int fd; struct fb_fix_screeninfo fi; struct fb_var_screeninfo vi; }; int fb_bpp(struct FB *fb) { if (fb) { return fb->vi.bits_per_pixel; } return 0; } int fb_width(struct FB *fb) { if (fb) { return fb->vi.xres; } return 0; } int fb_height(struct FB *fb) { if (fb) { return fb->vi.yres; } return 0; } int fb_size(struct FB *fb) { if (fb) { unsigned bytespp = fb->vi.bits_per_pixel / 8; return (fb->vi.xres * fb->vi.yres * bytespp); } return 0; } int fb_virtual_size(struct FB *fb) { if (fb) { unsigned bytespp = fb->vi.bits_per_pixel / 8; return (fb->vi.xres_virtual * fb->vi.yres_virtual * bytespp); } return 0; } void * fb_bits(struct FB *fb) { unsigned short * bits = NULL; if (fb) { int offset, bytespp; bytespp = fb->vi.bits_per_pixel / 8; /* HACK: for several of our 3d cores a specific alignment * is required so the start of the fb may not be an integer number of lines * from the base. As a result we are storing the additional offset in * xoffset. This is not the correct usage for xoffset, it should be added * to each line, not just once at the beginning */ offset = fb->vi.xoffset * bytespp; offset += fb->vi.xres * fb->vi.yoffset * bytespp; bits = fb->bits + offset / sizeof(*fb->bits); } return bits; } void fb_update(struct FB *fb) { if (fb) { fb->vi.yoffset = 1; ioctl(fb->fd, FBIOPUT_VSCREENINFO, &fb->vi); fb->vi.yoffset = 0; ioctl(fb->fd, FBIOPUT_VSCREENINFO, &fb->vi); } } static int fb_open(struct FB *fb) { if (NULL == fb) { return -1; } fb->fd = open("/dev/graphics/fb0", O_RDONLY); if (fb->fd < 0) { printf("open(\"/dev/graphics/fb0\") failed!\n"); return -1; } if (ioctl(fb->fd, FBIOGET_FSCREENINFO, &fb->fi) < 0) { printf("FBIOGET_FSCREENINFO failed!\n"); goto fail; } if (ioctl(fb->fd, FBIOGET_VSCREENINFO, &fb->vi) < 0) { printf("FBIOGET_VSCREENINFO failed!\n"); goto fail; } fb->bits = mmap(0, fb_virtual_size(fb), PROT_READ, MAP_SHARED, fb->fd, 0); if (fb->bits == MAP_FAILED) { printf("mmap() failed!\n"); goto fail; } return 0; fail: close(fb->fd); return -1; } static void fb_close(struct FB *fb) { if (fb) { munmap(fb->bits, fb_virtual_size(fb)); close(fb->fd); } } static struct FB g_fb; struct FB * fb_create(void) { memset(&g_fb, 0, sizeof(struct FB)); if (fb_open(&g_fb)) { return NULL; } return &g_fb; } void fb_destory(struct FB *fb) { fb_close(fb); } //------------------------------------------------------------------- #endif//#ifndef WIN32
2.生成圖片
這裡生成的圖片是bmp格式的,可以根據裝置畫素的位數自動生成16位(rgb565)、24位(rbg888)兩種圖片。
主要工作是要正確填充bmp的檔案頭資訊,24位(rbg888)較簡單。
16位(rgb565)複雜一點,biCompression成員的值必須是BI_BITFIELDS,原來調色盤的前三個位置被三個DWORD變數佔據,稱為紅、綠、藍掩碼,在565格式下,它們則分別為:0xF800、0x07E0、0x001F。
另外,需要主要的是windows要求檔案大小必須是4的倍數,檔案大小需要做下面的處理才能正確顯示。
head->bfSize = head->bfOffBits + size; head->bfSize = (head->bfSize + 3) & ~3; size = head->bfSize - head->bfOffBits;
生成圖片的主要程式碼:
/********************************************************************
created: 2012/02/07
filename: savebmp.c
author:
purpose:
*********************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
//-------------------------------------------------------------------
/*
點陣圖檔案的組成
結構名稱 符 號
點陣圖檔案頭 (bitmap-file header) BITMAPFILEHEADER bmfh
點陣圖資訊頭 (bitmap-information header) BITMAPINFOHEADER bmih
彩色表 (color table) RGBQUAD aColors[]
圖象資料陣列位元組 BYTE aBitmapBits[]
*/
typedef struct bmp_header
{
short twobyte ;//兩個位元組,用來保證下面成員緊湊排列,這兩個字元不能寫到檔案中
//14B
char bfType[2] ;//!檔案的型別,該值必需是0x4D42,也就是字元'BM'
unsigned int bfSize ;//!說明檔案的大小,用位元組為單位
unsigned int bfReserved1;//保留,必須設定為0
unsigned int bfOffBits ;//!說明從檔案頭開始到實際的圖象資料之間的位元組的偏移量,這裡為14B+sizeof(BMPINFO)
}BMPHEADER;
typedef struct bmp_info
{
//40B
unsigned int biSize ;//!BMPINFO結構所需要的字數
int biWidth ;//!圖象的寬度,以象素為單位
int biHeight ;//!圖象的寬度,以象素為單位,如果該值是正數,說明影象是倒向的,如果該值是負數,則是正向的
unsigned short biPlanes ;//!目標裝置說明位面數,其值將總是被設為1
unsigned short biBitCount ;//!位元數/象素,其值為1、4、8、16、24、或32
unsigned int biCompression ;//說明圖象資料壓縮的型別
#define BI_RGB 0L //沒有壓縮
#define BI_RLE8 1L //每個象素8位元的RLE壓縮編碼,壓縮格式由2位元組組成(重複象素計數和顏色索引);
#define BI_RLE4 2L //每個象素4位元的RLE壓縮編碼,壓縮格式由2位元組組成
#define BI_BITFIELDS 3L //每個象素的位元由指定的掩碼決定。
unsigned int biSizeImage ;//圖象的大小,以位元組為單位。當用BI_RGB格式時,可設定為0
int biXPelsPerMeter ;//水平解析度,用象素/米表示
int biYPelsPerMeter ;//垂直解析度,用象素/米表示
unsigned int biClrUsed ;//點陣圖實際使用的彩色表中的顏色索引數(設為0的話,則說明使用所有調色盤項)
unsigned int biClrImportant ;//對圖象顯示有重要影響的顏色索引的數目,如果是0,表示都重要。
}BMPINFO;
typedef struct tagRGBQUAD {
unsigned char rgbBlue;
unsigned char rgbGreen;
unsigned char rgbRed;
unsigned char rgbReserved;
} RGBQUAD;
typedef struct tagBITMAPINFO {
BMPINFO bmiHeader;
//RGBQUAD bmiColors[1];
unsigned int rgb[3];
} BITMAPINFO;
static int get_rgb888_header(int w, int h, BMPHEADER * head, BMPINFO * info)
{
int size = 0;
if (head && info) {
size = w * h * 3;
memset(head, 0, sizeof(* head));
memset(info, 0, sizeof(* info));
head->bfType[0] = 'B';
head->bfType[1] = 'M';
head->bfOffBits = 14 + sizeof(* info);
head->bfSize = head->bfOffBits + size;
head->bfSize = (head->bfSize + 3) & ~3;//windows要求檔案大小必須是4的倍數
size = head->bfSize - head->bfOffBits;
info->biSize = sizeof(BMPINFO);
info->biWidth = w;
info->biHeight = -h;
info->biPlanes = 1;
info->biBitCount = 24;
info->biCompression = BI_RGB;
info->biSizeImage = size;
printf("rgb888:%dbit,%d*%d,%d\n", info->biBitCount, w, h, head->bfSize);
}
return size;
}
static int get_rgb565_header(int w, int h, BMPHEADER * head, BITMAPINFO * info)
{
int size = 0;
if (head && info) {
size = w * h * 2;
memset(head, 0, sizeof(* head));
memset(info, 0, sizeof(* info));
head->bfType[0] = 'B';
head->bfType[1] = 'M';
head->bfOffBits = 14 + sizeof(* info);
head->bfSize = head->bfOffBits + size;
head->bfSize = (head->bfSize + 3) & ~3;
size = head->bfSize - head->bfOffBits;
info->bmiHeader.biSize = sizeof(info->bmiHeader);
info->bmiHeader.biWidth = w;
info->bmiHeader.biHeight = -h;
info->bmiHeader.biPlanes = 1;
info->bmiHeader.biBitCount = 16;
info->bmiHeader.biCompression = BI_BITFIELDS;
info->bmiHeader.biSizeImage = size;
info->rgb[0] = 0xF800;
info->rgb[1] = 0x07E0;
info->rgb[2] = 0x001F;
printf("rgb565:%dbit,%d*%d,%d\n", info->bmiHeader.biBitCount, w, h, head->bfSize);
}
return size;
}
static int save_bmp_rgb565(FILE * hfile, int w, int h, void * pdata)
{
int success = 0;
int size = 0;
BMPHEADER head;
BITMAPINFO info;
size = get_rgb565_header(w, h, &head, &info);
if (size > 0) {
fwrite(head.bfType, 1, 14, hfile);
fwrite(&info, 1, sizeof(info), hfile);
fwrite(pdata, 1, size, hfile);
success = 1;
}
return success;
}
static int save_bmp_rgb888(FILE * hfile, int w, int h, void * pdata)
{
int success = 0;
int size = 0;
BMPHEADER head;
BMPINFO info;
size = get_rgb888_header(w, h, &head, &info);
if (size > 0) {
fwrite(head.bfType, 1, 14, hfile);
fwrite(&info, 1, sizeof(info), hfile);
fwrite(pdata, 1, size, hfile);
success = 1;
}
return success;
}
int save_bmp(const char * path, int w, int h, void * pdata, int bpp)
{
int success = 0;
FILE * hfile = NULL;
do
{
if (path == NULL || w <= 0 || h <= 0 || pdata == NULL) {
printf("if (path == NULL || w <= 0 || h <= 0 || pdata == NULL)\n");
break;
}
remove(path);
hfile = fopen(path, "wb");
if (hfile == NULL) {
printf("open(%s) failed!\n", path);
break;
}
switch (bpp)
{
case 16:
success = save_bmp_rgb565(hfile, w, h, pdata);
break;
case 24:
success = save_bmp_rgb888(hfile, w, h, pdata);
break;
default:
printf("error: not support format!\n");
success = 0;
break;
}
} while (0);
if (hfile != NULL)
fclose(hfile);
return success;
}
//-------------------------------------------------------------------
3.執行方式:
cygwin下用ndk編譯成可執行檔案,再將生成的可執行檔案push到模擬器的/data/local路徑,
修改檔案為可執行的,直接執行便可以截圖,圖片儲存到/mnt/sdcard/s.bmp。
[email protected] /cygdrive/e
$ cd myprj/screenshot/jni/
[email protected] /cygdrive/e/myprj/screenshot/jni
$ ndk-build
Compile thumb : savebmp <= savebmp.c
Compile thumb : savebmp <= screenshot.c
StaticLibrary : libsavebmp.a
Executable : save
Install : save => libs/armeabi/save
[email protected] /cygdrive/e/myprj/screenshot/jni
$ adb shell
error: device offline
[email protected] /cygdrive/e/myprj/screenshot/jni
$ adb shell
#
[email protected] /cygdrive/e/myprj/screenshot/jni
$ adb push ../libs/armeabi/save /data/local
83 KB/s (10636 bytes in 0.125s)
[email protected] /cygdrive/e/myprj/screenshot/jni
$ adb shell
# cd /data/local
cd /data/local
# chmod 777 save
chmod 777 save
# ./save
./save
rgb565:16bit,800*480,768068
# ls -l /mnt/sdcard
ls -l /mnt/sdcard
d---rwxr-x system sdcard_rw 2012-02-19 12:39 LOST.DIR
d---rwxr-x system sdcard_rw 2012-02-20 13:22 DCIM
----rwxr-x system sdcard_rw 768068 2012-02-20 13:22 s.bmp
#
PS:工程原始碼中的VC工程主要用來除錯生成圖片的功能。
=====================================================================================================
目前,在增強版本中添加了32位的支援,但是沒有32位的裝置進行測試,已經有幾位網友測試過,32位截圖會出現顏色偏差和位置偏移,目前我這裡還沒有辦法除錯,所以32位截圖的問題我還沒有解決。
有位網友在小米手機上測試過也是有色差和錯位問題,但後來直接在java中用Runtime.exec("/system/bin/screencap -p path")來截圖,還是png格式的。不是每個裝置上都有screencap。
關於fb0中畫素的格式可以參考
android4.0p-ics-src/system/core/adb# vi framebuffer_service.c,裡面列舉了五種格式,不過有些裝置上可能不是下面的任何一種。
case 1: /* RGBA_8888 */
fbinfo.bpp = 32;
fbinfo.size = w * h * 4;
fbinfo.width = w;
fbinfo.height = h;
fbinfo.red_offset = 0;
fbinfo.red_length = 8;
fbinfo.green_offset = 8;
fbinfo.green_length = 8;
fbinfo.blue_offset = 16;
fbinfo.blue_length = 8;
fbinfo.alpha_offset = 24;
fbinfo.alpha_length = 8;
break;
case 2: /* RGBX_8888 */
fbinfo.bpp = 32;
fbinfo.size = w * h * 4;
fbinfo.width = w;
fbinfo.height = h;
fbinfo.red_offset = 0;
fbinfo.red_length = 8;
fbinfo.green_offset = 8;
fbinfo.green_length = 8;
fbinfo.blue_offset = 16;
fbinfo.blue_length = 8;
fbinfo.alpha_offset = 24;
fbinfo.alpha_length = 0;
break;
case 3: /* RGB_888 */
fbinfo.bpp = 24;
fbinfo.size = w * h * 3;
fbinfo.width = w;
fbinfo.height = h;
fbinfo.red_offset = 0;
fbinfo.red_length = 8;
fbinfo.green_offset = 8;
fbinfo.green_length = 8;
fbinfo.blue_offset = 16;
fbinfo.blue_length = 8;
fbinfo.alpha_offset = 24;
fbinfo.alpha_length = 0;
break;
case 4: /* RGB_565 */
fbinfo.bpp = 16;
fbinfo.size = w * h * 2;
fbinfo.width = w;
fbinfo.height = h;
fbinfo.red_offset = 11;
fbinfo.red_length = 5;
fbinfo.green_offset = 5;
fbinfo.green_length = 6;
fbinfo.blue_offset = 0;
fbinfo.blue_length = 5;
fbinfo.alpha_offset = 0;
fbinfo.alpha_length = 0;
break;
case 5: /* BGRA_8888 */
fbinfo.bpp = 32;
fbinfo.size = w * h * 4;
fbinfo.width = w;
fbinfo.height = h;
fbinfo.red_offset = 16;
fbinfo.red_length = 8;
fbinfo.green_offset = 8;
fbinfo.green_length = 8;
fbinfo.blue_offset = 0;
fbinfo.blue_length = 8;
fbinfo.alpha_offset = 24;
fbinfo.alpha_length = 8;
break;
另外,關於截圖在android4.0的原始碼用有兩個程式可以參考,這兩個程式都可以截圖,一個是c++實現的,另一個是c的。
ics-src\frameworks\base\cmds\screencap\screencap.cpp
ics-src\frameworks\base\cmds\screenshot\screenshot.c