每天一点点音视频_SurfaceFlinger3

前面说道 WindowManager 会向 SurafceFlinger 请求创建一个层, 在 WindowManager 那就是一个 Window, 而一个 Window 包括了一个 Surface 和 这个 Surface 在屏幕上的显示参数,比如显示位置等。

View, Window 和 Surface 的关系

Window 是一个容器对象, 它里面有 View, Surface, 还有其他一些参数

View 负责绘制, 比如 TextView 负责绘制文字。

Surface 负责存储, View 所绘制的东西到哪了呢, 就会知道了 Surface 上。

完结撒花

明天: 每天一点点音视频_HWC

每天一点点音视频_SurfaceFlinger2

之前说到, App 向 WindowManager 请求一个 Window 或者叫 Surface, WindowManager 进而想 SurfaceFlinger 请求一个层, 然后 SurfaceFlinger 返回。

App 通过 Surface 可以在任意时刻提交一个图像缓存, 但是 SurfaceFlinger 只在屏幕刷新时才会苏醒来接受图像缓存。屏幕的刷新是与设备相关的。这会减少内存的使用, 避免屏幕刷新时的图像显示撕裂(反正就是那种,数据在屏幕不全的状态)

当屏幕在两次刷新之间时, 会给 SurafceFlinger 发送 VSYNC 信号。VSYNC 信号指示这时候刷新屏幕不会产生画面撕裂的效果。当 SurfaceFlinger 接受到 VSYNC 会遍历层的列表, 查找有没有新的图像缓冲区,如果由就请求过来,如果没有使用之前的。SurfaceFlinger 对每一个层,总要显示点东西,如果那一层没有缓冲区,就会把这一层忽略掉。

在 SurfaceFlinger 从所有可见层收集了所有图像缓冲区,它会询问 Hardware Composer (HWC) 如何操作。如果 HWC 标记某一层需要客户端合成, SuraceFlinger 会合成, 然后把输出的缓冲区输出给 HWC。

明天: 每天一点点音视频_SurfaceFlinger3

每天一点点音视频_SurfaceFlinger

SurfaceFlinger 负责接受, 合并,然后发送图像缓存到显示屏。WindowManager 负责发送图像缓存和 Window 的元数据给 SurfaceFlinger 以便于它合并 Surface 到显示屏。

SurfaceFlinger

SurfaceFling 由两种方式接受图像缓存, 一种通过 BufferQueue 和 SurfaceControl, 另一种通过 ASurfaceControl, 这里只说明前一种, 因为后一种是 Android 10 新加入的,太新了(这也算个理由??)

当一个应用显示到前台时, 它会向 WindowManager 请求图像缓存, WindowManager 会 SurfaceFlinger 请求一个层, 这里的层包括一个 Surface 和一个 SurfaceConroler, Surface 里时 BufferQueue, SurafceControler 里是这一帧的元数据,比如这一层的显示位置呀什么的。SuraceFlinger 创建层发送个 WindowManager, WindowManger 把 Surface 发送个应用,但是留下 SurfaceControler 以便来控制 App 在屏幕上的显示。

这里三个角色

  1. APP 请求显示 –> Surface

  2. WindowManager 请求层 –> Surace 和 SuraceControler

  3. SurfaceFlinger 制造一个层返回、

明天: 每天一点点音视频_SurfaceFlinger2

每天一点点音视频_TextureView

TextureView 绑定了一个 View 和一个 SurfaceTexture

使用 GLES 渲染

TextureView 里的 SurfacceTexture, 可以传给 EGL 创建 EGLSurface, 作为 GLES 的渲染目标。 TextureView 负责从 SufrfaceTexture 读取数据渲染到 View 上。

TextView VS SurfaceView

之前说过了, 我选 TextureView

TextureView 负责从 SurfaceTexture 读取数据绘制到 View 上,然后就可以把 TextureView 作为一个普通的 View 了, 透明度呀,旋转呀都可以。

SurfaceView 的 View 只是一个代表, 实际显示的内容时在单独的一个层上,由 SurfaceFlinger 负责对层做融合。

TextureView 里 SurfaceTexture 的复用

当横竖屏切换时, Acitivity 重启, TextureView 和 SurfaceView 都会先销毁。但是 TextureView 的回调 onSurfaceDestroyed() 的返回值可以决定它里面的 SurfaceTexture 是否销毁。 当返回 false 时, 就不会销毁, 在 Activity 重启后, TextureView.setSurfaceTexture() 就可以重复利用之前的 SurfaceTexture。 这样就可以无间断显示了。SurfaceView 没有提供这种复用机制。

明天: 每天一点点音视频_SurfaceFlinger

一个Android应用一生的开始

每个应用的一生,或短暂,或漫长,它们的开始都是相思的,这里记录的一个 Android 应用项目的搭建过程,需要的配置可以复制过去。

这个文章会分步来列出, 会不断补充完整。

1. IDE

就是用官方的专用 IDE 吧, Android Studio, 要下载最新的。

2. 创建一个 Android 项目

打开 Android Studio, 创建一个新项目, 会用不同的模板, 看你需要了。Kotlin 选上。

3. Jetpack 库

Android 官网提供了很大的一套工具库, 叫做 Jetpack, 意思是喷气式包, 寓意用了它们,就像背上喷气式背包一样爽,飞的快。

里面的工具库真的很多, 素以如果你需要什么库的时候,先来这里看看,没有再去别的地方。

在选择了使用那个库后, 可以在这里找到需要依赖

4. 其他三方库

1.屏幕适配的库

屏幕的适配方案有很多, 但是我只推荐一种:

AndroidAutoSize
Github

implementation 'me.jessyan:autosize:1.2.1'

待续…

每天一点点音视频_SurfaceTexture

SurfaceTexture 将 Surface 和 GLES 里的文理绑定在一块。 它被用来往 GLES 输入图像数据

SurafceTexture 里包含了一个 BufferQueue, 应用作为这个 BufferQueue 的消费者, 另一端可能是在另一个进程里的 解码器, 相机等等。

当 BufferQueue 的生产者产生新数据时, 消费端,通过 onFrameAailable 获取到通知,然后可以调用 updateTexImage() 来释放之前获取到的图像缓冲区,重新获取新的。需要注意的是 updateTexImage() 必须要在 GLES 上下文, 因为更新获取新的缓冲区实际是将数据 保存到了文理里。

外部 GLES 纹理

有外部纹理就有内部纹理。

内部纹理是 OpenGL 里的普通纹理, 需要通过绑定 GL_Texture_2D 使用, 一般我们读如一个 Bitmap, 通过这种纹理上传到 GPU。

外部纹理, 需要绑定 GL_Texture_EXTERNAL_OES 使用, 在渲染文件里需要添加:

#extension GL_OES_EGL_image_external : require

使用的格式不是 sample2D 而是 samplerExternalOES。

外部纹理的主要优势是能够直接渲染 BufferQueue 的数据。SurfaceTexture 会在创建 BufferQueue 的时候设置 GRALLOC_USAGE_HW_TEXTURE, 意思是可以被 GPU 使用, 被 GLES 识别。

时间戳 和 变换矩阵

SurfaceTexture 有一个方法 getTimeStamp() 可以获取当前缓冲区从生产者那产生的时间。比如相机的预览, 相机传感器作为生产者, 会带着时间,肯定这个时间更准确的指明了图像的显示时间。

SurfaceTexture 还有个方法 getTransformMatrax() 可以获取一个变换矩阵。因为生产者产生的图像可能消费者需要的图像的方向什么的不一致, 需要变换一下。其实可以对数据纠正了在送给消费者,但是消费者的需要可能还会做变换,这样就不如传个矩阵,消费者把自己的变换再加上就行,这样就避免了一次对数据的操作。举个例子 相机摄像头 输出数据图像是有特定的旋转角度的, 但是,我们的应用, 可能会横平竖屏,就需要做自己的变换。

这两个属性都会在 updateTexImage() 调用后更新,每一帧都可能不同

setDefaultBufferSize()

SurfaceTexture 作为消费者,会创建 BufferQueue, setDefaultBufferSize() 用来设置缓冲区大小的。但是这是个默认大小, 还会被生产者改变。

每次调用该方法后, 生产者下次创建数据会生效。但是如果生产者是 GLES, 每次调用该方法后, 要重新将 SurfaceTexture 设置成 GLES 的 Surface。

现在, 我在项目中用到该方法的地方就是相机的预览, 因为我们在 Camera2 中,没有设置输出缓冲区的大小的地方, 通过该方法, 我们选择 Camera2 输出 Surface 的大小。

反思:

感觉还是不够深入的理解

坏习惯已然开始, 连续的几天晚睡, 连续的几天晚期, 连续的几天没有9点写作。不过赶紧纠正就好,最高优先级。

明天: 每天一点点音视频_TextureView

每天一点点音视频_SurfaceView

Android 中的显示 UI,通过 View, 形成一棵树, 绘制到了一个 surface 上。这些 UI 操作都是在 UI 线程。

SurfaceView 也会一个 View, 可以被添加到 UI 树里, 但是 View 的内容是透明的, 它要显示的内容通过一个单独的 Surface 显示

当 SurfaceView 变为可见时, SurfaceControler 会请求 SurfaceFlinger 创建一个新的 Surafce,默认新创建的 Surface 会在 UI Surface 的后面。可以通过 Z 轴顺序让新 Surface 在上面。

SurfaceView 与 TextureView 相比, 效率更高, 因为它使用了单独的 Surface, 这样就不用在将图像数据从 Surafce 复制到 UI 的Surface 上了。而是直接又硬件合成器来合成。

SurafceView 和 Activity 的生命周期

SurfaceView 的渲染在非UI线程,独立的线程。

当 Activity 启动时:

  1. Activity.onCreated()
  2. Activity.onResume()
  3. surfaceCreate()
  4. surafceChanged()

当 关闭 Activity 时:

  1. activity.onPaused()
  2. surfaceDestroyed()

当旋转屏幕时, Activity会迅速的重启,这时候,surafceCreated() 可能会在 activity.onPaused() 之后,这是后可以通过判断 activity.isFinsihing(),如果时直接推出执行。

当按电源健息屏后, Activity会 onPaused() 但是 surfaceDestroyed(), 也就是说 SurfaceView 依然在,依然绘制。如果所平界面会强制转屏, Activity 会重启,但是 SurfaceView 依然时之前的(1. 看来 surfaceDestroyed 是在 onPaused 里执行的, 一旦 onPaused 里没执行, activity销毁也不会执行。2. 这也谈蛋疼了吧)

SuraceView 与 Activity 的生命周期已经乱了, 在加上一个渲染线程,简直时大乱特乱。我建议不用它,主要时我们怎么用过它,不用它有几个原因。与 TextureView 比,它的优势就是效率高一点,但是,缺点很多, 单独的 Surface 导致对 View 的一些参数设置 SurfaceView 不起作用, 比如背景色,旋转呀其他变换呀。

GLSurfaceView

GLSurfaceView 从名就可以看出, 它是在 SurfaceView 的基础上,加上了 GLES, 在单独的线程创建了 GLES 上下文, 很容易就可以进行 GLES 的操作, 不用执行 EGL 的操作。

但是它依然有 SurfaceView 的那些缺点, View 的一些属性对它不起作用。

反思: 今天起晚, 半小时直接压缩没了。

明天: 每天一点点音视频_SurfaceTexture

每天一点点音视频_Surface和SurfaceHolder

之前写过一篇关于 Surface 的文章,那只是我自己的理解,现在的是翻译的官方文档,这是一个系列。

Surface 使得应用能够渲染图像到屏幕上, SurfaceHolder使得应用能够编辑控制 Surface。(这个说的是一般情况吧)

Surface 是图像生产者和消费者交换缓冲区的接口。真正负责交换功能的是 BufferQueue。

Canvas 渲染

Canvas 的绘制是由 [Skia] 实现的。为了确保一块缓冲区不被多个客户同时更新, 或者一边写一边读,从 API 上对缓冲区的操作做了限制。如下:

  • lockCanvas 锁住缓冲区,使用 CPU 渲染,然后返回 Canvas
  • unlockCanvasAndPost 解索缓冲区并且提交给混合器
  • lockHardwareCanvas 锁住缓冲区,使用 GPU 渲染, 然后返回 Canvas

注意的是, 使用 lockCanvas 锁住的 Surface 以后都不支持硬件加速了。这是因为 lockCanvas 会将 CPU 渲染器连接作为 BufferQueue 的生产者, 知道 Surface 销毁时才断开。CPU渲染器不想 GLES/Vulkan 渲染器,可以断开重联。

生产者第一次从 BufferQueue 获取缓冲区的时候, 缓冲区被初始化成 0, 初始化时必须的。但是, 如果重用了缓冲区, 缓冲区内容保留,如果如果在锁住缓冲区后没有做任何操作解索缓冲区,内容依然不会改变,生产者会循环之前渲染的帧。

Surface 的 lock/unlock 会保持对之前渲染的缓冲区的引用,如果在 lock 指定了一个脏区域,也就是指明这次绘制指挥在该区域进行,它就会把之前缓冲区非脏区域的像素考过来, 这会快一些。

SurfaceHolder

SurfaceHolder 使得系统将所有权分给应用。因为一些 API 使用 SurfaceHolder 来的到 Surface。SurfaceView 内部就由一个 SurfaceHolder, 大部分与 View 交互的组件都是用 SurfaceHolder。其他的 MediaCodec 呀,直接使用 Surface。(为什么呢)

反思:

又离道了,不过偶尔的离道没关系,只要不连续就好,重复的一个怀行为才是一个坏习惯的开始。

原因, 内容多了, 半小时没写完, 然后就忘了提交了。

另外,开始陷于翻译内容了,没有自己全剧的理解了。

明天: 每天一点点音视频_SurfaceView

经验之谈_Camera2的输出图像大小的确定

Camera2 使用 Surface 输出一帧的图像信息, 我们可以使用 SurfaceView, TextureView, 获取到 Surface, 来作为 Camera2 的预览输出。 可以使用从 MediaRecorder 或者 MediaCodec, 获取到的 Surface 作为录制视频的输出。另外,我们可以获取相机支持的尺寸大小, 但是我们却没有明确告诉 Camera 输出尺寸。

那如何确定 Camera 的输出大小呢?

对于 SurfaceView, TextureView, 根据显示的大小

对于 MediaRecorder, MediaCodec, 根据编码的大小

实际上, 相机作为生产者, 同过 BufferQueue 连接消费者, 而 BufferQueue 里的缓存区一个的大小是由消费者决定的。

在项目中,我要实现,从 Camera 输出数据到 OpenGL, 使用了 SurfaceTexture, 将 Surface 转成了 OpenGL 里的 Texture, 那如何确定大小呢, 是通过给 SurfaceTexture 设置默认缓冲区大小, 这样如果该大小在 Camera 支持范围内, Camera就会输出相应的大小。

TextureView 内部也有个 SurfaceTexture, TextureView 的大小变化后,SurfaceTexture默认缓冲区的大小也会相应的改变。

我们可以给 TextureView 内部的 SurfaceTexture 设置支持的相机输出大小, 这样可能相机的输出大小和 TextureView 的显示大小不同, 相机输出到 TextureView 作为它的内容, 我们可以设置它的 setTransform(), 设置背后的内容显示到 View 上的样子。

更进一步,我们可能动态的改变 TextureView 的大小, 比如可以选择拍摄的比例, 这时候,需要监听 TextureView 大小的改变,重新设置 SurfaceTexture 的默认缓冲区大小,避免随着 TextureView 的大小变化。

昨天终于把相机预览的问题彻底搞明白了,牵扯的东西的确是多,之所以现在解决了是因为,对 Surface 那些东西有了深一点的了解。

每天一点点音视频_BufferQueue

BuferQueue 连接了图形数据的生产者和消费者,消费者不仅仅负责把数据显示出来,还可以负责对数据进一步处理,比如 GLES。几乎所有图像数据缓冲的移动都依赖 BufferQueue。

Gralloc 负责分配图形缓存, 通过两个供应商特定的 HIDL 接口实现, (hardware/interfaces/graphics/allocator/ 和 hardware/interfaces/graphics/mapper/)

消费者和生产者

消费着创建并拥有 BufferQueue 数据结构,并可以在与生产者不同的进程存在。

当一个生产者需要缓冲区,它会通过 dequeueBuffer() 来请求一个空的缓冲区,并且会声明好宽高和像素格式,其他一个使用的控制项。然后生产者会将数据填入,通过 queueBuffer() 返还缓冲区。下一步,消费者通过 acquireBuffer() 获取填了数据的缓冲区,使用缓冲区的内容,最后通过 releaseBuffer() 返还数据。

BufferQueue 的很多特性(比如最大的缓冲区数量)都是由生产者和消费者决定的。BufferQueue会根据自己的需要来分配缓冲区。除非特性改变,否则,缓冲区会被一直保持。比如生产者请求不同大小的缓冲区,就的缓冲区就会被释放,新的会被分配。

缓冲区内容不会被 BuferQueue 复制,因为移动这么大的数据太低效了。缓冲区通过句柄来传递。

Gralloc

Gralloc 在分配内存时会根据几个 flags, 比如:

  • 内存多久会被软件访问(CPU)
  • 内存多久会被硬件访问(GPU)
  • 是否可以作为 GLES texture
  • 是否会被视频编码器使用

比如一个生产者声明缓冲区为 RGBA_8888, 这就表示该缓冲区将被软件访问(意味着从 CPU 访问像素), Gralloc 分配了4个字节的像素,格式为 RGBA 的顺序。

Gralloc 返回的内存的句柄可以在进程间通过 Binder 传递。

反思:

我现在对 BufferQueue 比较了解了,但是只是对它的使用上,不了解内部机制,实现。

昨天没写完,破了规矩,偶尔的脱轨一次没关系,回来就是,禁止连续脱轨,那时一个还习惯的开始。

明天: 每天一点点音视频_Surface和SurfaceHolder