Android平台截图研究 FrameBuffer(/dev/graphics/fb0) 文件内容研究!(含源码工程)

1. FrameBuffer文件介绍


FrameBuffer 文件是 Linux (Android是基于Linux的) 对显示设备的一种抽象设备,相当于显存。Android 的 SurfaceFlinger 想更新屏幕的时候,就会把相应的改变写入到FrameBuffer里。Android 2.x 的时代,显示开机画面的功能也是通过把图像数据写入到FrameBuffer实现的。所以,你可以认为,FrameBuffer里头一定有当前屏幕内容的图像数据。

Android平台上,FrameBuffer 文件的绝对路径一般是: /dev/graphics/fb0 。

所以,如果我们想截图,其中一种方法就是把FrameBuffer里头的图像数据取出来,转换成bitmap,然后存储起来或者给ImageView来显示出来。

2. FrameBuffer文件格式


现在我们知道 FrameBuffer (/dev/graphics/fb0) 文件里头会有当前屏幕的图像数据,取出来就可以了。但是,如果你直接运行这段代码:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public void test() {  
  2.     byte[] fb_data = new byte[5000000];  
  3.     FileInputStream fis = null;  
  4.     try {  
  5.         fis = new FileInputStream(new File("/dev/graphics/fb0"));  
  6.         DataInputStream dStream = new DataInputStream(fis);  
  7.         dStream.readFully(fb_data);  
  8.         dStream.close();  
  9.           
  10.         Bitmap bm = BitmapFactory.decodeByteArray(fb_data, 0, fb_data.length);  
  11.         mImageView.setBackground(new BitmapDrawable(bm));  
  12.     } catch (Exception e) {  
  13.         e.printStackTrace();  
  14.     }  
  15. }  
会遇到两个问题:
1. /dev/graphics/fb0 文件会拒绝访问,所以你要让你的程序获取root权限后,才能取到/dev/graphics/fb0里头的数据 ,或者获取 root 权限后把 /dev/graphics/fb0 文件改为所有用户可读 (如何获取root权限,这篇文章暂不讨论)
2. 取到数据后,decode成bitmap,让imageview显示,会花屏,或者索性什么都没显示。

花屏或者什么都不显示,那是因为 FrameBuffer 里头的数据并不是常见的图像数据,直接丢给 BitmapFactory 显示,BitmapFactory 也不知道你这一堆什么玩意儿,所以出来的图像要么花屏,要么什么都不显示。

那我们现在进入正题:FrameBuffer 里头的数据到底是怎么样的?

要弄清这个问题,我们需要在我们的jni代码里执行这段代码:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int fd, ret;  
  2. struct fb_fix_screeninfo finfo;  
  3.   
  4.   
  5. // 打开Framebuffer设备  
  6. fd = open("/dev/graphics/fb0", O_RDONLY);  
  7.   
  8.   
  9. if(fd < 0)  
  10. {  
  11. <span style="white-space:pre">    </span>LOGD("======Cannot open /dev/graphics/fb0!");  
  12. <span style="white-space:pre">    </span>return -1;  
  13. }  
  14.   
  15.   
  16. // 获取Framebuffer 的 fixed info 不变信息  
  17. ret = ioctl(fd, FBIOGET_FSCREENINFO, &finfo);  
  18. if(ret < 0 )  
  19. {  
  20. <span style="white-space:pre">    </span>LOGD("Cannot get fixed screen information.");  
  21. <span style="white-space:pre">    </span>close(fd);  
  22. <span style="white-space:pre">    </span>return -1;  
  23. }  

 
 通过这段代码,我们获取到了 Framebuffer 设备的 “不变信息” :其实就是一个名叫 fb_fix_screeninfo 的结构体,这个结构体里包含了我们的 Framebuffer 数据的格式。 
fb_fix_screeninfo这个结构体定义在 linux/include/linux/fb.h 头 文件里头 ( 虽然这个头文件是linux源码里头找的,但是 fb.h 里头定义的很多东西,Android 都直接沿用了)
定义如下:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. struct fb_fix_screeninfo {  
  2.         char id[16];                    /* identification string eg "TT Builtin" */  
  3.         unsigned long smem_start;       /* Start of frame buffer mem */  
  4.                                         /* (physical address) */  
  5.         __u32 smem_len;                 /* Length of frame buffer mem */  
  6.         __u32 type;                     /* see FB_TYPE_*                */  
  7.         __u32 type_aux;                 /* Interleave for interleaved Planes */  
  8.         __u32 visual;                   /* see FB_VISUAL_*              */   
  9.         __u16 xpanstep;                 /* zero if no hardware panning  */  
  10.         __u16 ypanstep;                 /* zero if no hardware panning  */  
  11.         __u16 ywrapstep;                /* zero if no hardware ywrap    */  
  12.         __u32 line_length;              /* length of a line in bytes    */  
  13.         unsigned long mmio_start;       /* Start of Memory Mapped I/O   */  
  14.                                         /* (physical address) */  
  15.         __u32 mmio_len;                 /* Length of Memory Mapped I/O  */  
  16.         __u32 accel;                    /* Indicate to driver which     */  
  17.                                         /*  specific chip/card we have  */  
  18.         __u16 reserved[3];              /* Reserved for future compatibility */  
  19. };  
里面有一个 __u32 type 成员,就是这个成员会告诉我们,我们的FrameBuffer里头的数据的格式。

这个 __ur32 type 可能的取值有5个,分别如下:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #define FB_TYPE_PACKED_PIXELS           0       /* Packed Pixels        */  
  2. #define FB_TYPE_PLANES                  1       /* Non interleaved planes */  
  3. #define FB_TYPE_INTERLEAVED_PLANES      2       /* Interleaved planes   */  
  4. #define FB_TYPE_TEXT                    3       /* Text/attributes      */  
  5. #define FB_TYPE_VGA_PLANES              4       /* EGA/VGA planes       */  

于是我们回到刚刚那段 jni 代码,最后加一句打印:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. LOGD("====== type : %d",  finfo.type);  

运行下,会看到我的三星 i9300 运行的结果是:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. D/termExec(21787): ====== type : 0  

其实,大多数 Android 设备的 fb0 都应该是 type == 0 的,type 为0 意思说 Framebuffer 里头存的是每个像素点的 ARGB 信息。

但是既然存的是每个像素点的 ARGB 信息,为何 BitmapFactory 会解析出花屏图像出来呢? 这是因为 Framebuffer 里头每个像素点的 ARGB 信息是按照 little endian 方式存储的 也就是说,假如屏幕中一个像素点的格式的 ARGB 颜色值是 #FFBBCCDD 的话,存到 Framebuffer 的时候,是存成这样的:DDCCBBFF。所以现在你明白为何你把 Framebuffer 的数据直接丢给 BitmapFactory 的时候会花屏了吧? 因为每个像素点的 ARGB 信息都倒过来了啊,变成 BGRA 了。

那是如果确定每个像素点存的时候存的是 BGRA 的呢?要确定每个像素点的存储格式,需要再运行一段 jni 代码,如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.        int fd, ret;  
  2. static struct fb_var_screeninfo vinfo;  
  3.   
  4.        // 打开Framebuffer设备  
  5. fd = open("/dev/graphics/fb0", O_RDONLY);  
  6.   
  7. if(fd < 0)  
  8. {  
  9.     LOGD("======Cannot open /dev/graphics/fb0!");  
  10.     return -1;  
  11. }  
  12.   
  13. // 获取FrameBuffer 的 variable info 可变信息  
  14. ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);  
  15. if(ret < 0 )  
  16. {  
  17.     LOGD("======Cannot get variable screen information.");  
  18.     close(fd);  
  19.     return -1;  
  20. }  

这是获取到的是一个叫做 fb_var_screeninfo 的结构体的实例,这个结构体同样定义在 linux/include/linux/fb.h 里头,定义如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. struct fb_var_screeninfo {  
  2.          __u32 xres;                     /* visible resolution           */  
  3.          __u32 yres;  
  4.          __u32 xres_virtual;             /* virtual resolution           */  
  5.          __u32 yres_virtual;  
  6.          __u32 xoffset;                  /* offset from virtual to visible */  
  7.          __u32 yoffset;                  /* resolution                   */  
  8.    
  9.          __u32 bits_per_pixel;           /* guess what                   */  
  10.          __u32 grayscale;                /* != 0 Graylevels instead of colors */  
  11.    
  12.          struct fb_bitfield red;         /* bitfield in fb mem if true color, */  
  13.          struct fb_bitfield green;       /* else only length is significant */  
  14.          struct fb_bitfield blue;  
  15.          struct fb_bitfield transp;      /* transparency                 */        
  16.    
  17.          __u32 nonstd;                   /* != 0 Non standard pixel format */  
  18.    
  19.          __u32 activate;                 /* see FB_ACTIVATE_*            */  
  20.    
  21.          __u32 height;                   /* height of picture in mm    */  
  22.          __u32 width;                    /* width of picture in mm     */  
  23.    
  24.          __u32 accel_flags;              /* (OBSOLETE) see fb_info.flags */  
  25.    
  26.          /* Timing: All values in pixclocks, except pixclock (of course) */  
  27.          __u32 pixclock;                 /* pixel clock in ps (pico seconds) */  
  28.          __u32 left_margin;              /* time from sync to picture    */  
  29.          __u32 right_margin;             /* time from picture to sync    */  
  30.          __u32 upper_margin;             /* time from sync to picture    */  
  31.          __u32 lower_margin;  
  32.          __u32 hsync_len;                /* length of horizontal sync    */  
  33.          __u32 vsync_len;                /* length of vertical sync      */  
  34.          __u32 sync;                     /* see FB_SYNC_*                */  
  35.          __u32 vmode;                    /* see FB_VMODE_*               */  
  36.          __u32 rotate;                   /* angle we rotate counter clockwise */  
  37.          __u32 reserved[5];              /* Reserved for future compatibility */  
  38.  };  

注意到 这个结构体里面的四个结构体成员了么? red、green、blue 和 transp 这四个成员。它们的类型是 fb_bitfield,这个fb_bitfield 就是用来告诉我们每个像素点的格式的。
fb_bitfield 也是定义在 linux/include/linux/fb.h 里头,定义如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. struct fb_bitfield {  
  2.         __u32 offset;                   /* beginning of bitfield        */  
  3.         __u32 length;                   /* length of bitfield           */  
  4.         __u32 msb_right;                /* != 0 : Most significant bit is */   
  5.                                         /* right */   
  6. };        
这个结构体里头:
   offset  ------ 颜色值的在整个ARGB二进制数据中的偏移量
   length ------ 颜色值二进制位数
   msb_right ------ 0 代表Big endian 

现在我们在刚刚那段获取 fb_var_screeninfo 结构体实例的 jni 代码的末尾,加上这几句打印:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // 下面这一段是每个像素点的格式  
  2. LOGD("====== fb_bitfield red.offset : %d",  vinfo.red.offset);  
  3. LOGD("====== fb_bitfield red.length : %d",  vinfo.red.length);  
  4. // 如果 == 0,就是Big endian  
  5. LOGD("====== fb_bitfield red.msb_right : %d",  vinfo.red.msb_right);  
  6. LOGD("====== fb_bitfield green.offset : %d",  vinfo.green.offset);  
  7. LOGD("====== fb_bitfield green.length : %d",  vinfo.green.length);  
  8. LOGD("====== fb_bitfield green.msb_right : %d",  vinfo.green.msb_right);  
  9. LOGD("====== fb_bitfield blue.offset : %d",  vinfo.blue.offset);  
  10. LOGD("====== fb_bitfield blue.length : %d",  vinfo.blue.length);  
  11. LOGD("====== fb_bitfield blue.msb_right : %d",  vinfo.blue.msb_right);  
  12. LOGD("====== fb_bitfield transp.offset : %d",  vinfo.transp.offset);  
  13. LOGD("====== fb_bitfield transp.length : %d",  vinfo.transp.length);  
  14. LOGD("====== fb_bitfield transp.msb_right : %d",  vinfo.transp.msb_right);  

看看我的 i9300 的运行结果:
 
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. D/termExec(21988): ====== fb_bitfield red.offset : 16  
  2. D/termExec(21988): ====== fb_bitfield red.length : 8  
  3. D/termExec(21988): ====== fb_bitfield red.msb_right : 0  
  4. D/termExec(21988): ====== fb_bitfield green.offset : 8  
  5. D/termExec(21988): ====== fb_bitfield green.length : 8  
  6. D/termExec(21988): ====== fb_bitfield green.msb_right : 0  
  7. D/termExec(21988): ====== fb_bitfield blue.offset : 0  
  8. D/termExec(21988): ====== fb_bitfield blue.length : 8  
  9. D/termExec(21988): ====== fb_bitfield blue.msb_right : 0  
  10. D/termExec(21988): ====== fb_bitfield transp.offset : 24  
  11. D/termExec(21988): ====== fb_bitfield transp.length : 8  
  12. D/termExec(21988): ====== fb_bitfield transp.msb_right : 0  

从打印结果我们可以看到:
1. 每个像素点的单个颜色值占8 bits 也就是一个字节,一个像素是 8 * 4 = 32 bits。
2. blue offset 是0 也就是 Framebuffer里头存的每个像素点的 前8 bits 是蓝色值
3. green offset 是 8 ,8 到 15 bits 是绿色值
4. red offset 是 16 ,16 到 23 bits 是红色值
5. transp offset 是 24,24 到 31 bits 是透明度值

综合上面所述,Framebuffer 中的数据肯定是这样的 :
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA ...  

啊!Framebuffer 的数据格式现在是搞清楚了,但是当你以为你简简单单的把 每个像素的 BGRA 信息 转换成 ARGB 后,丢给BitmapFactory就能得到屏幕截图了么?
其实还不够。

首先,回头再看看 fb_var_screeninfo 的定义,我们注意到里头有六个这样的成员:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. __u32 xres;                     /* visible resolution           */  
  2.   __u32 yres;  
  3.   __u32 xres_virtual;             /* virtual resolution           */  
  4.   __u32 yres_virtual;  
  5.   __u32 xoffset;                  /* offset from virtual to visible */  
  6.   __u32 yoffset;       

前两个个的的含义如下:
1. xres -------------- 你可以认为这个就是屏幕的宽(单位:像素)
2.yres --------------- 你可以认为这个就是屏幕的高(单位:像素)

前两个很好理解,后面四个就麻烦一点点。稍微查下资料我们就能知道,Android 的 Framebuffer 一般是“双缓冲”的,就是说 Framebuffer里头不止缓存了一个屏幕的像素点数据,而是缓存了两个屏幕的像素点数据。而且两屏数据,是上下摆放的,假设我们的手机屏幕横向有 720 个像素,纵向有 1280 个像素,那 fb0 的实际格式将会是如下所示:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. 第一列是 fb0 文件的相对地址(十进制表示) 720个像素点 * 每个像素点占用4字节 = 2880  
  2.   
  3. 0       BGRA BGRA BGRA BGRA BGRA BGRA BGRA ...  
  4. 2880        BGRA BGRA BGRA BGRA BGRA BGRA BGRA ...  
  5. 2880*2      BGRA BGRA BGRA BGRA BGRA BGRA BGRA ...  
  6.     .  
  7.     .       缓冲的第一屏  
  8.     .  
  9. 2880*1279   BGRA BGRA BGRA BGRA BGRA BGRA BGRA ...  
  10.   
  11. 2880*1280  BGRA BGRA BGRA BGRA BGRA BGRA BGRA ...  
  12. 2880*1281  BGRA BGRA BGRA BGRA BGRA BGRA BGRA ...  
  13. 2880*1282  BGRA BGRA BGRA BGRA BGRA BGRA BGRA ...  
  14.     .  
  15.     .       缓冲的第二屏  
  16.     .  
  17. 2880*2559   BGRA BGRA BGRA BGRA BGRA BGRA BGRA ...  

把这两屏数据当成一副图,那横向像素点个数 就是 xres_virtual,纵向像素点个数就是 yres_virtual。由于Android一般是“双缓冲”,所以下面这个公式对很多手机都应该成立:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. xres == xres_virtual  
  2.   
  3. yres * 2 == yres_virtual  

既然有两屏数据,那哪一屏才是当前的屏幕内容呢?这就是 xoffset 和 yoffset 会告诉你的,我的 i9300 获取到的这样的:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. D/termExec(21988): ====== xres : 720  
  2. D/termExec(21988): ====== yres : 1280  
  3. D/termExec(21988): ====== xres_virtual : 720  
  4. D/termExec(21988): ====== yres_virtual : 2560  
  5. D/termExec(21988): ====== xoffset : 0  
  6. D/termExec(21988): ====== yoffset : 0  

所以我的 i9300 的 Framebuffer 的第一屏就是当前屏幕的内容。 假如我的 i9300 yoffset == 1280 的话,那第二屏才是真正屏幕的当前内容。


fb_fix_screeninfo 里头,另外还有一个需要注意的成员是 line_length。其实我们的 Framebuffer 里头保存的一屏数据并不一定刚好就是我们的屏幕分辨率大小,它的横向像素值有可能比屏幕的横向像素值多!假如你发现你截出来的图片屏幕外有黑边,那原因就在这里了。

Framebuffer里头的图像数据的一行,不应该是 屏幕横向分辨率 * 4,而应该是 line_length (它的单位不是像素点,而是字节)

3. 从Framebuffer中获取图像数据

现在我们已经知道了,Framebuffer 中的数据格式,那到底如何中的屏幕图像数据显示在ImageView中呢?

当然,你已经知道了Framebuffer中的数据格式了,你大可以很自信的自己把一屏的数据截取出来,把BGRA转换成ARGB,然后转换成bitmap,然后丢给ImageView显示出来。比如,下面这段代码:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. long stat2 = System.currentTimeMillis();  
  2. for (int i = 0; i < pixels.length; i+=4) {  
  3.     row = i / line_length;  
  4.       
  5.     if (row >= h) break;  
  6.     if ((i - row * line_length) >= widthBytes) continue;  
  7.     // fb0里面存储的BGRA中的A都是FF  
  8.     pixels[offset] =  (0xFF << 24) | ((Fb0Bytes[i + 2] & 0xFF) << 16) | ((Fb0Bytes[i + 1] & 0xFF) << 8) | (Fb0Bytes[i] & 0xFF);  
  9.     offset++;  
  10. }  
  11. Logger.d("Moce time =  " + (System.currentTimeMillis() - stat2));  

这段代码你可以不用细看,你只要知道我只读取一屏的数据,然后每次读取4bytes,把BGRA转换成了ARGB。但是这段代码的平均执行时间是 250ms 。这实在是太慢了。为了提速,我第一个想到的是改用C语言来写,但是并没有质的提升。最后经过一番搜索,发现了一个很有名的开源库:turbo-jpeg !turbo-jpeg 会调用Arm cpu 的 Neon 协处理器的 SIMD 指令集,效率非常高!

4. 实例截图功能的完整Android demo项目

我上传了一个通过 jni 实现截图的功能完整demo项目到Github了,地址如下:https://github.com/faip520/AndroidFramebufferScreenshot

我简单描述下实现的过程:
1. 打开 /dev/graphics/fb0 设备
2.把 fb0 设备内容中的一屏数据,通过 mmap 映射到自己的内存区
3.通过 turbo-jpeg 的接口,直接读取 fb0 的信息,生成 jpeg 图片数据
4.把得到的 jpeg 图片数据返回到 java 层
5.通过BitmapFactory.decode 方法把 jpeg 图片数据转换成 bm,然后转换成 BitmapDrawable 给 ImageView 显示。

需要说明的是:我这份代码,肯定不是适配所有机型的,你的机型有可能是 “三缓冲”,有可能不是 32位色,而是 RGB565 或者其他格式,xoffset 和 yoffset 的值也有可能
比较特别,甚至有可能 Framebuffer 都不是 fb0 文件。这些情况下,你就要自己去修改我的代码了。 我这里只是介绍一个解决这种问题的分析模型。

5. 黑边问题的处理 (图像裁剪)

其实如果你的截图出来有黑边,或者只想截取其中一部分。可以看看我源码里头的这个地方:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. tjCompress2(handle, framebuffer_memory,  
  2.         // 希望生成的jpeg图片的宽 源数据里头屏幕每行的字节数  
  3.         300, finfo.line_length,  
  4.         // 希望生成的jpeg图片的高  
  5.         100, TJPF_BGRA,  
  6.         &jpeg_data, &jpegSize,  
  7.         TJSAMP_444, 10,  
  8.         TJFLAG_NOREALLOC);  

其中第三个和第五个参数,就是你希望生成的 jpeg 的宽高。比如 100 100,那就是只截取屏幕左上角的 100 * 100 个像素。配置这里就可以去掉黑边,或者图片裁剪。

转自:https://i-blog.csdnimg.cn/blog_migrate/47f90616479b3efdf73dcbf74dc5246f.png
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值