http://blog.csdn.net/androiddeveloper_lee/article/details/9215901

来源:互联网 发布:g71车内孔编程实例 编辑:程序博客网 时间:2024/06/06 02:30


Android framebuffer 截屏原理

原帖地址:http://www.ctoandroid.com/?p=56

项目的原因需要将android设备的屏幕截图,并以流媒体的形式传输。
万事开头难,android截屏网上有很多种方法,但是大多数只是在应用内截屏,使用view提供的方法得到,但是这显然太有局限性了。
后面找到可以使用读取framebuffer实现截屏,下面这篇文章是我在学习的过程中找到的一篇相当不错的文章,贴在此处。

1.
首先让我们来说说Android的屏幕是怎么显示出来的。 众所周知,Android也是linux派生出来的,因此Android的显示机制用的是Linux一样的机制:Framebuffer
FrameBuffer是一种机制。他提共接口将显示设备抽象为帧缓冲区。用户可以将它看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,这种机制是把屏幕上的每个点映射成一段线性内存空间,程序可以简单的改变这段内存的值来改变屏幕上某一点的颜色。例如如果你想把一张bitmap图片显示到屏幕上去,你只要解析bitmap之后把数据bit copy进framebuffer,屏幕就会立刻显示出来。一般Linux的framebuffer 对应/dev/fb0这个字符设备文件。 Android稍微改了改,放在/dev/graphics/fb0下。

2.
知道了Android的显示机制,和android framebuffer的文件位置,那么截屏就可以变得非常高效率了:将framebuffer里的显示区读出来就行了。但是读出来还是有很多细节的, 你知道这里面存了多少帧? 你知道图像以什么格式存在里面的?

3.
关于这个问题,Android不同机型还真不同,我在网上找到过一篇文章说的是G1机型的,G1的framebuffer一次存2帧, 帧的格式是RGB565,也就是说一个点的颜色由Red Blue Green 一共16个bit, 2 bytes形成。而对于N1, 经过摸索, 实际上framebuffer里存了3帧, 颜色格式是BGR32, 一共4bytes, RGBA各8bit顺序排列,注意不是ARGB, A:透明度。

以上是原理,知道原理,那么人人都能用命令行作截屏了。不需要用DDMS里的驱动帮你截屏。具体动手步骤可以参看如下
> adb shell
> $ su
> $ cat /dev/graphics/fb0 /sdcard/frame.raw
> $ exit
上面是进去手机用命令把framebuffer原始数据直接导出来到一个文件里去, 因为访问/dev/graphics/fb0需要系统权限,这也是为什么程序实现截屏都需要申请系统权限的原因。
>adb pull /sdcard/frame.raw frame.raw
这样就把这个文件从手机里拷贝出来到本地电脑了。接下来从这原始数据里取出1帧。Ubuntu下有很好的命令行可以直接把文件按raw data裁剪。
>dd bs=1920 count=800 if=frame.raw of=xxx.raw
上面这条命令的意思是把输入文件frame.raw 一次读1920bytes, 一共读800次, 读完后写出来到xxx.raw文件里去。 为什么是1920? 因为N1屏幕分辨率宽480像素,上面原理里解释过,N1的颜色格式一个像素要占4bytes的颜色存储,所以480 * 4 = 1920,所以1920就相当于一次读一行的像素出来,读800次,大家就好理解了,因为N1屏幕高800像素。
我们这个命令就相当于把framebuffer中正好800X480一屏幕的数据截了出来。

接下来就用解码工具按照颜色格式将这1帧数据转换成看图工具可以支持的格式了,这里我采用png格式。
> ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt bgr32 -s 480X800 -i xxx.raw -f image2 -vcodec png frame-%d.png
上面命令很简单,注意的一个参数是-pix_fmt bgr32颜色格式千变万化,rgb32, rgb565, yuv等等等等,找不到正确的格式参数你就怎么也解不对这张图片,颜色会很诡异。我也是试了一会才自己找到的,没有去找相关的文档。不过还好,因为大致知道应该是RBGA某种排列,按照排列组合大家也可以算出来一共24种组合,另外可以猜测RGB肯定会是一块的,不至于A插到他们中间某个位置,所以就剩12钟组合了,试了一会就出来了。
打开输出来的文件,截图就好了

自己动手的步骤有了,程序实现自然也就不难了。 剩下的就是截屏的时候响应哪两个组合键阿,播放咔嚓声阿,这些杂事了。

=========================这里有一个C程序实现了,请参考(未验证)

[cpp] view plaincopyprint?
  1. public class ScreenShot {  
  2.   
  3.     /** 
  4.      * @param args 
  5.      * @throws InterruptedException  
  6.      */  
  7.     public static void main(String[] args) throws InterruptedException {      
  8.         try {  
  9.             //分辨率大小,后续可以通过代码来获取到当前的分辨率  
  10.             int xResolution = 320;  
  11.             int yResolution = 480;  
  12.             //执行adb命令,把framebuffer中内容保存到fb1文件中  
  13.              Runtime.getRuntime().exec("adb pull /dev/graphics/fb0 C:/fb1");  
  14.              //等待几秒保证framebuffer中的数据都被保存下来,如果没有保存完成进行读取操作会有IO异常  
  15.              Thread.sleep(15000);  
  16.              //读取文件中的数据  
  17.              InputStream in = (InputStream)new FileInputStream("C:/fb1");  
  18.              DataInput frameBuffer = new LittleEndianDataInputStream(in);  
  19.                
  20.              BufferedImage screenImage = new BufferedImage(  
  21.                      xResolution, yResolution, BufferedImage.TYPE_INT_ARGB);  
  22.                  int[] oneLine = new int[xResolution];  
  23.                 for (int y = 0; y < yResolution; y++) {  
  24.                     //从frameBuffer中计算出rgb值  
  25.                     convertToRgba32(frameBuffer, oneLine);  
  26.                     //把rgb值设置到image对象中  
  27.                     screenImage.setRGB(0, y, xResolution, 1, oneLine, 0, xResolution);  
  28.                 }  
  29.                 Closeables.closeQuietly(in);  
  30.                   
  31.                 ByteArrayOutputStream rawPngStream = new ByteArrayOutputStream();  
  32.                 try {  
  33.                       if (!ImageIO.write(screenImage, "png", rawPngStream)) {  
  34.                         throw new RuntimeException(  
  35.                             "This Java environment does not support converting to PNG.");  
  36.                       }  
  37.                     } catch (IOException exception) {  
  38.                       // This should never happen because rawPngStream is an in-memory stream.  
  39.                      System.out.println("IOException=" + exception);  
  40.                     }  
  41.                 byte[] rawPngBytes = rawPngStream.toByteArray();  
  42.                 String base64Png = new Base64Encoder().encode(rawPngBytes);  
  43.                   
  44.                 File screenshot = OutputType.FILE.convertFromBase64Png(base64Png);  
  45.                 System.out.println("screenshot==" + screenshot.toString());  
  46.                 screenshot.renameTo(new File("C:\\screenshottemp.png"));  
  47.                   
  48.         } catch (IOException e) {  
  49.             // TODO Auto-generated catch block  
  50.             e.printStackTrace();  
  51.             System.out.println(e);  
  52.         }  
  53.     }  
  54.       
  55.       
  56.     public static void convertToRgba32(DataInput frameBuffer, int[] into) {  
  57.         try {  
  58.             for (int x = 0; x < into.length; x++) {  
  59.                 try{  
  60.                 int rgb = frameBuffer.readShort() & 0xffff;  
  61.                 int red = rgb >> 11;  
  62.                 red = (red << 3) | (red >> 2);  
  63.                 int green = (rgb >> 5) & 63;  
  64.                 green = (green << 2) | (green >> 4);  
  65.                 int blue = rgb & 31;  
  66.                 blue = (blue << 3) | (blue >> 2);  
  67.                 into[x] = 0xff000000 | (red << 16) | (green << 8) | blue;  
  68.                 }catch (EOFException e){  
  69.                     System.out.println("EOFException=" + e);  
  70.                 }  
  71.               }  
  72.         } catch (IOException exception) {  
  73.             System.out.println("convertToRgba32Exception=" + exception);  
  74.       }  
  75.     }  
  76.       

0 0
原创粉丝点击