您的位置:首页 > 大数据 > 人工智能

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

2014-05-05 11:58 344 查看

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?

public class ScreenShot {

/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
try {
//分辨率大小,后续可以通过代码来获取到当前的分辨率
int xResolution = 320;
int yResolution = 480;
//执行adb命令,把framebuffer中内容保存到fb1文件中
Runtime.getRuntime().exec("adb pull /dev/graphics/fb0 C:/fb1");
//等待几秒保证framebuffer中的数据都被保存下来,如果没有保存完成进行读取操作会有IO异常
Thread.sleep(15000);
//读取文件中的数据
InputStream in = (InputStream)new FileInputStream("C:/fb1");
DataInput frameBuffer = new LittleEndianDataInputStream(in);

BufferedImage screenImage = new BufferedImage(
xResolution, yResolution, BufferedImage.TYPE_INT_ARGB);
int[] oneLine = new int[xResolution];
for (int y = 0; y < yResolution; y++) {
//从frameBuffer中计算出rgb值
convertToRgba32(frameBuffer, oneLine);
//把rgb值设置到image对象中
screenImage.setRGB(0, y, xResolution, 1, oneLine, 0, xResolution);
}
Closeables.closeQuietly(in);

ByteArrayOutputStream rawPngStream = new ByteArrayOutputStream();
try {
if (!ImageIO.write(screenImage, "png", rawPngStream)) {
throw new RuntimeException(
"This Java environment does not support converting to PNG.");
}
} catch (IOException exception) {
// This should never happen because rawPngStream is an in-memory stream.
System.out.println("IOException=" + exception);
}
byte[] rawPngBytes = rawPngStream.toByteArray();
String base64Png = new Base64Encoder().encode(rawPngBytes);

File screenshot = OutputType.FILE.convertFromBase64Png(base64Png);
System.out.println("screenshot==" + screenshot.toString());
screenshot.renameTo(new File("C:\\screenshottemp.png"));

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e);
}
}


public static void convertToRgba32(DataInput frameBuffer, int[] into) {
try {
for (int x = 0; x < into.length; x++) {
try{
int rgb = frameBuffer.readShort() & 0xffff;
int red = rgb >> 11;
red = (red << 3) | (red >> 2);
int green = (rgb >> 5) & 63;
green = (green << 2) | (green >> 4);
int blue = rgb & 31;
blue = (blue << 3) | (blue >> 2);
into[x] = 0xff000000 | (red << 16) | (green << 8) | blue;
}catch (EOFException e){
System.out.println("EOFException=" + e);
}
}
} catch (IOException exception) {
System.out.println("convertToRgba32Exception=" + exception);
}
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: