众所周知,有一个程序screencap可以截屏,这个程序十分简单,只是使用了surfaceflinger服务的截屏功能。

所以要了解截屏,看surfaceflinger服务的代码是不二首选。但是surfaceflinger也随android系统显示子系统的变更而变更,网上最容易搜到的android资料都在11-14年的文章,都是4.x时代甚至2.x时代的技术,而android代代变化,有不少文章已经不再适用。

2.3.6之前,surfaceflinger放于base目录。这时候的surfaceflinger使用FramebufferNativeWindow。

4.0.x开始,suffaceflinger放于native目录。但仍旧使用FramebufferNativeWindow。

4.2 开始,DisplayHardware类废除(注意DisplayHardware目录或者说组件没有废除,因为显示系统变更了,不再只有一个Display,取而代之的是DisplayDevice类,这个类不在与DisplayHardware目录同级。换句话说,以前surfaceflinger拥有一个DisplayHardware,现在开始就拥有一组DisplayDevice,这些DisplayDevice只代表某一GL上下文,并且不直接访问Hardware层的fbDev,fbDev由HWComposer管理,DisplayDevice只能通过FramebufferSurface去访问Hardware层),不再使用FramebufferNativeWindow,替而引入了FramebufferSurface。

4.3 开始,DisplaySurface,VirutalDisplaySurface。并且废除了libgui中的SurfaceTextureClient。

在4.1.x之前,我称为FramebufferNativeWindow时代,surface是直接从FramebufferNativeWindow创建的。

可以看到这是很常规的egl初始化。

在4.2 开始 FramebufferNativeWindow不再使用,因为在以前单一Display的时代,直接将fbDev和ANativeWindow偶合在一起。现在ANativeWindow和fbDev分离开。SurfaceTextureClient替代了FramebufferNativeWindow,并且不与fbDev偶合(,fbDev由HWComposer接管)。这里必须区分好,android显示系统有一个Surface,而egl也有一个EGLSurface,它们的连结点就是ANavtiveWindow,其定义路径在/system/core/include/system/window.h 。

SurfaceTexture之前是直接继承于BnSurfaceTexture,4.2 开始它与BnSurfaceTexture解偶,转而继承ComsumerBase。ComsumerBase拥有的BufferQueue才是继承于BnSurfaceTexture(,定义在/frameworks/native/include/gui/ISurfaceTexture.h)。SurfaceTexture 与 BnSurfaceTexture生产消费者模式,并只作为消费者。

与此同时,在SurfaceTexture.h同一目录添加了Surface(,定义在/frameworks/native/include/gui ,也就是 libgui.so), 相应地在surfaceflinger目录有另一个对应的FramebufferSurface,是surfaceflinger服务私有的类,它是面向DisplayDevice的,是SurfaceTextureClient与HWComposer的结合点(Framebuffer就是指HWComposer,Surface则是SurfaceTextureClient一侧,以及用于它的ConsumerBase)。这里要注意,Surface继承于(,isa)ANativeWindow,而FramebufferSurface却继承于(,isa)ConsumerBase。SurfaceTextureClient 作为ANativeWindow 依赖 ISurfaceTexture 服务器,去实现 ANativeWindow的hook接口queueBuffer以及dequeueBuffer。

下面是surfaceflinger初始化DisplayDevice数组:

以及DisplayDevice初始化:

每个DisplayDevice使用SurfaceTextureClient这个ANativeWIndow去创建EGLSurface,而且这里的SurfaceTextureClient使用的是FramebufferSurface的BufferQueue。

在 FramebufferNativeWindow时代,截屏直接使用FBO(,Frame Buffer Object)。

然后每一个layer都绘制一次到framebuffer:

最后GL读出像素:

在4.2开始,发生这些变化,首先实现的函数参数变化了

可以看到display不再以整型来作为标识,而是通过binder来标识。回看上面surfaceflinger在初始化DisplayDevice过程,只是简单地 new BBinder() 来作为一个新的DisplayDevice的token,这个token只在binder驱动设备上建立了一个唯一的路径,作用就是用来系统范围内唯一标识。

所以在4.1.x及以前,screencap的代码是:

从4.2开始,screencap的代码是:

虽然上面说了一大堆的4.2变化,但是在截屏实现没有变化,然而接下来的4.3就完全变化了。

4.2,Surface继承于SurfaceTextureClient,但是4.3后,Surface脱离SurfaceTextureClient继承树,实现成另一个ANativeWindow,依赖 producer。记得上面吗,4.2时,SurfaceTextureClient是一个依赖 FramebufferSurface这个consumer的ANativeWinodw。

下面是surfaceflinger对DisplayDevice的初始化:

跟着是DisplayDevice的初始化:

我们来回顾一下,4.1.x以前使用FramebufferNativeWindow来创建EGLSurface,4.2时候使用SurfaceTextureClient(它使用FramebufferSurface),4.3开始,使用Surface。FramebufferNativeWindow, SurfaceTextureClient, Surface都是ANativeWindow。

4.3开始Surface也另外继承DisplaySurface。(...有时间再补充写)。

最重要是captureScreen接口改了

实现上也就使用了BufferQueue,而不是IMemory来接收数据。

最最重要是没有放出screencap的最新代码,只停留在4.2时候的版本,也就是你不可能通过参照screencap.cpp去使用这个功能 。

可参考Transaction_test.cpp

这里勘误一下,screencap的代码路径在 /frameworks/base/cmds/screencap 目录下,虽然这份代码使用的是ScreenshotClient::update,但是其实质还是在使用ISurfaceComposer::captureScreen,然后使用CpuConsumer去lockNextBuffer(,原理就是SurfaceFlinger作为生产者从BufferQueue填充了一帧Buffer,我们用消费者去访问BufferQueue的帧Buffer),以上面Transaction_test.cpp为例过程是一样的。

我们可以直接将这两份代码搬到native项目去尝试一下,当然是在解决了权限和selinux的环境下。不要高兴得太早,ScreenshotClient::update总是返回-22(BAD_VALUE)。而用Transaction_test.cpp的例子代码,则可以清楚SurfaceFlinger服务执行captureScreen没有报错,却是CpuConsumer::lockNextBuffer返回了-22。

现在我们以4.3(分水岭)后的6.0.1系统为例。CpuConsumer执行在本进程,是libgui.so的一部分,可以反汇编结合源代码去跟踪调试。

下面是CpuConsumer::lockNextBuffer的反汇编:

<+68>调用了pthread_mutex_lock,<+84>调用了一个函数,然后<+88>将结果与0比较,<+90>将结果与2比较。

这可以分析对应的源代码

执行的结果是从<+90>跳到了<+574>

也就是说代码从acquireBufferLocked返回结果是NO_BUFFER_AVAIABLE,接着就是退出调用,先将AutoLock析构。

?! captureScreen返回0真的就意味成功了,不是的,logcat日志上报错了,这真让人郁闷一阵子为何不返回直接错误呢。

看到这uid就知道我是在app进程中使用SurfaceFlinger服务,虽然我将selinux设置可访问SurfaceFlinger的binder,并且访问FrameBuffer设备的进程是SurfaceFlinger,但还是逃不出权限的检测,因为作为binder的client同时表明了自己的进程id。

这里就只能去破解(,修改二进制文件分支跳转指令)surfaceflinger。

从整个过程来看,截屏最终还是在访问FrameBuffer设备,并且对它封锁得更严。即使你是超级用户也不可能对FrameBuffer进行读操作。必须经由SurfaceFlinger服务接受有权限的进程的请求,才可以让SurfaceFlinger服务去访问FrameBuffer设备。这样一来,使用ffmpeg通过输入/dev/fb0的录屏方法也就给抹杀了。当然了,所有帧在递交到SurfaceFlinger之前,也就获取当前Window(当前Activity也就对应Window),自己将所有的buffer进行合成render也达到截屏的效果。这一思路分支没有去研究,因为获取当前活动Activity的api早就被禁用了,要么你就只能截自己应用的屏。

下面我们来看Screenshot::getPixels返回的是什么,当然环境是root权限进程,代码是screencap片段。用gdb远程调试:

返回了像素空间地址在 0xf5ccf000,我们来看这地内存的映射:

(暂时不清楚是什么设备的,dma也就是直接内存访问相关,以后再补充,这块内存存在在某个匿名inode)

(暂时不清楚为什么访问不了)

上面不能访问,只限于gdb调试,程序里面是可以访问的。

我们来看两组数据,第一组数据是两部低端机启用screencap进程截屏的性能: (测试项目分别写/dev/null,共享内存,以及sdcard)

手机A

手机B

再来第二组数据,同样是上面两部低端手机,测试项目为ScreenshotClient,以及SurfaceComposerClient,CpuConsumer截屏后写堆内存。

手机A

手机B

这里说明一下,手机A屏是800M像,手机B屏是300M像,8核。

从上面的数据看,进程创建和切换消耗很大。截屏服务实际消耗在0.02到0.04秒之间,但是spawn进程损耗就增加到100ms~200ms上下。手机B的图形处理能力低于手机A,手机B的sdcard好于手机A的sdcard,但内存访问则是手机A更优,多进程却是手机B损耗更高。

下面是SurfaceComposerClient::captureScreen,然后CpuConsumer::lockNextBuffer,其实就是ScreenshotClient::update的步骤展开。

这里要提一下BufferQueue的生产者-消费者模式,IGraphicsBufferProducer生产者接口,IGraphicsBufferConsumer消费者接口。生产者-(enqueue)->BufferQueue-(dequeu)->消费者,一般来说是这样的,但是它的接口有些特别,生产者同时有接口方法enqueueBuffer和dequeueBuffer,还有一个requestBuffer。如果不仔细参考一下头文件的注释,就会被搞头晕。dequeueBuffer是生产者从BufferQueue取出一块空闲Buffer,然后操作这块空闲Buffer,enqueueBuffer则是入队让消费者可见。而消费者读消费队列的接口方法不是dequeue而是acquireBuffer。

下是一帧 Buffer 的流向图。

生产者 <-(dequeueBuffer)- BufferQueue

生产者在buffer上进行读写操作

生产者 -(enqueueBuffer)-> BufferQueue -(acquireBuffer)-> 消费者

20180309 补充:

这里的buffer,并非大小为 width * height * bytesPerPixel, 而是 stride * height * bytesPerPixel 且 stride >= width。在做处理时扫描行就要注意了。

下面是SuffaceFlinger服务处理captureScreen过程,必然进行BufferQueue的连接。

只要 native_window_api_connect 不返回 NO_ERROR,都不会执行并且返回 BAD_VALUE,所有原因都被这个值抹去,无从分析。

native_window_api_connect函数的作用是将producer与native_window连接起来。

这里的native_window是SurfaceFlinger进程在执行函数时创建的Surface(从4.3之后继承于ANativeWindow)。

从surfaceflinger历史变更谈截屏的更多相关文章

  1. 【转】Android截屏

     http://blog.csdn.net/xww810319/article/details/17607749 Android截屏浅析 链接:http://blog.sina.com.cn/s/bl ...

  2. [置顶] Android 应用内禁止截屏功能的实现

    截图介绍   Android的调试工具DDMS提供有截屏功能,很多软件也会有截屏功能,在做支付等安全类应用的时候,为了保证用户的资产和系统安全,往往会禁止应用内截屏,禁止之后,在此应用处于前台的情况下 ...

  3. Android使用C++截屏并显示

    使用android底层自带的截屏源码进行修改后,将截取屏幕的内容再次显示在屏幕上,使屏幕呈现出暂停的效果. android自带的截屏代码在android\JB\frameworks\base\cmds ...

  4. Linux系统安装MySql步骤及截屏

    ➠更多技术干货请戳:听云博客 如下是我工作中的记录,介绍的是linux系统下使用官方编译好的二进制文件进行安装MySql的安装过程和安装截屏,这种安装方式速度快,安装步骤简单! 需要的朋友可以按照如下 ...

  5. FFmpeg 转码和截屏

    转码 (flv转码为MP4,libx264是MP4编码格式 , -b 3000k是码率,比特率) ffmpeg -i /home/ghr/mp4/mp4.flv -vcodec libx264 -b ...

  6. 使用Python中PIL图形库进行截屏

    目的:通过使用Python的一个图形库PIL(Python Image Library)对屏幕进行截图 步骤: 1.下载PIL(路径)并安装 2.新建文件“截屏.py”,右键Edit with IDL ...

  7. MonoGame 3.2 下,截屏与 Texture2D 的保存

    10月20日注:后来发现了这篇博文(英文),XNA 中的 Color 实际上是与 Alpha 值自左乘(premultiplied)的,这也解释了直接用 0xARGB 转译而颜色异常的原因. 注意,由 ...

  8. 纯C#实现屏幕指定区域截屏

    以前在别的地方见过一个通过调用系统API实现屏幕截图的例子,从内心来说我不太喜欢在C#代码中出现这种情况,现在什么都讲“和谐”,我觉得这种做法就是破坏了我们的“和谐”代码,呵呵,开玩笑,有的时候,不通 ...

  9. C#截屏

    本实例代码实现了WinForm截屏保存为图片,亲测可行. 界面截图: 下载:http://hovertree.com/h/bjaf/scjyuanma.htm 以下代码可以实际运行,在项目HoverT ...

随机推荐

  1. hadoop2.x的安装

    可以自己从官网编译打包也可以直接下载官网的.gz包.自己编译打包的过程如下: .查看是否安装cmake.svn.openssl.ncurses,没有的直接安装上 yum list|grep cmake ...

  2. 基于SkyWalking的分布式跟踪系统 - 环境搭建

    前面的几篇文章我们聊了基于Metrics的监控Prometheus,利用Prometheus和Grafana可以全方位监控你的服务器及应用的性能指标,在出现异常时利用Alertmanager告警及时通 ...

  3. 百万年薪python之路 -- 变量及if的练习

    1.简述变量命名规范 1.变量由数字,字母,下划线组成 2.不能以数字开头 3.不能使用python关键字 4.不能使用中文和拼音命名 5.区分大小写 6.变量名要具有描述性 7.推荐写法 7.1驼峰 ...

  4. 使用 pdf.js 跨域问题的处理方法1

    在<使用 pdf.js 在网页中加载 pdf 文件>中详细介绍了 pdf.js 的使用与集成网页开发的基本方法.展示效果如下图: 站点的目录为 http://localhost:8033/ ...

  5. Java自动化测试框架-04 - 来给你的测试报告化个妆整个形 - (上)(详细教程)

    简介 前边通过宏哥的讲解和分享想必小伙伴们和童鞋们都已经见过testng框架生成的测试报告,是不是它的样子和长相实在是不敢让大家伙恭维.那么今天宏哥就当一回美容师,由宏哥来给它美美容:当一回外科医生, ...

  6. 史上最骚最全最详细的IO流教程,没有之一!

    目录 1.告白IO流的四点明确 2.File类 1.1 File概述 1.2 构造方法 1.3 常用方法 1.3.1 获取功能的方法 1.3.2 绝对路径和相对路径 1.3.3判断功能的方法 1.3. ...

  7. vue render

    Vue 的 render 渲染 API vue2 的 vnode tag: 当前节点的标签名 data: 当前节点是数据对象 children: 子节点,数组也是vnode 类型 text: 当前节点 ...

  8. 小白学 Python(12):基础数据结构(字典)(上)

    人生苦短,我选Python 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 小白学 Python(4):变 ...

  9. 设计模式(十六)Mediator模式

    在实际的工作小组的交流过程是,组员向仲裁者报告,仲裁者向组员下达指示,组员之间不再互相询问和指示.Mediator模式是指,当发生麻烦事情的时候,通知仲裁者:当发生涉及全体组员的事情时,也通知仲裁者. ...

  10. mysql如何解除死锁状态

    第一种: 1.查询是否锁表 show OPEN TABLES where In_use > 0; 2.查询进程(如果您有SUPER权限,您可以看到所有线程.否则,您只能看到您自己的线程) sho ...