Android 虚拟显示的延迟帧

Delaying frames of an Android virtual display

我试图解决的基本问题是将发送到虚拟显示器的内容延迟一秒左右。所以基本上,我试图在初始录制后将所有帧移动 1 秒。请注意,通过此虚拟显示,一个表面用作输入,另一个表面用作输出。考虑到 Android 框架的修改或非 public API 的使用都可以,我最初的直觉是探索一些想法。 Java 或本机 C/C++ 都可以。

a) 我尝试在 SurfaceFlinger 中将帧 post 延迟到虚拟显示或输出表面一两秒。这不起作用,因为它会导致所有表面延迟相同的时间(帧的同步处理)。

b) MediaCodec 使用表面作为输入进行编码,然后产生解码数据。无论如何使用 MediaCodec 使其实际上不编码并且只产生未编码的原始帧?似乎不太可能。此外,MediaCodec 是如何在幕后做到这一点的?逐帧处理事物。如果我可以推断该方法,我可能能够从我的输入表面逐帧提取并创建一个按我需要的时间延迟的环形缓冲区。

c) 软件解码器,例如 FFmpeg,如何在 Android 中实际执行此操作?我假设他们接受一个表面,但他们将如何逐帧外推和处理

请注意,我当然可以编码和解码以检索帧和 post 它们,但我想避免实际解码。请注意,修改 Android 框架或使用非 public API 都可以。

我还发现了这个:

似乎选项 d) 可以使用 SurfaceTexture 但我想避免 encoding/decoding.

的过程

据我了解,您有一个虚拟显示器正在将其输出发送到 Surface。如果您只是使用 SurfaceView 进行输出,则虚拟显示器输出的帧会立即出现在物理显示器上。目标是在虚拟显示器生成帧和 Surface 消费者接收帧之间引入一秒的延迟,以便(再次以 SurfaceView 为例)物理显示器延迟一秒显示所有内容。

基本概念很简单:将虚拟显示输出发送到 SurfaceTexture,并将帧保存到循环缓冲区中;与此同时,另一个线程正在从循环缓冲区的尾端读取帧并显示它们。问题在于 @AdrianCreşu 在评论中指出:60fps 的一秒全分辨率屏幕数据将占用设备内存的很大一部分。更不用说复制那么多数据将相当昂贵,而且某些设备可能无法跟上。

(无论是在应用程序中还是在 SurfaceFlinger 中执行都没有关系...最多 60 个屏幕大小的帧的数据必须 某处 保存整整一秒。)

您可以通过多种方式减少数据量:

  • 降低分辨率。将 2560x1600 缩放到 1280x800 会移除 3/4 的像素。在大多数显示器上应该很难注意到质量损失,但这取决于您正在查看的内容。
  • 降低颜色深度。从 ARGB8888 切换到 RGB565 会将尺寸减半。不过这会很明显。
  • 降低帧率。您正在为虚拟显示生成帧,因此您可以选择更慢地更新它。动画在 30fps 时仍然相当流畅,内存需求减半。
  • 应用图像压缩,例如PNG 或 JPEG。相当有效,但没有硬件支持太慢了。
  • 对帧间差异进行编码。如果帧与帧之间的变化不大,则增量变化可能非常小。像 VNC 这样的桌面镜像技术可以做到这一点。软件做起来有点慢。

像 AVC 这样的视频编解码器既可以压缩帧,也可以对帧间差异进行编码。这就是您如何将 1GByte/sec 降低到 10Mbit/sec 并且仍然看起来不错。

例如,考虑 Grafika 中的 "continuous capture" 示例。它将 Camera 输出馈送到 MediaCodec 编码器,并将 H.264 编码的输出存储在环形缓冲区中。当您点击 "capture" 时,它会保存最后 7 秒。这可以很容易地播放 7 秒延迟的相机画面,而且只需要几兆字节的内存就可以做到。

"screenrecord" 命令可以通过 ADB 连接转储 H.264 输出或原始帧,但实际上 ADB 的速度不足以跟上原始帧(即使在微型显示器上)。它没有做任何你不能从应用程序做的事情(现在我们有 mediaprojection API),所以我不建议将它用作示例代码。

如果您还没有阅读过 graphics architecture doc

可能会对您有所帮助