【问题标题】:Android resize VirtualDisplayAndroid 调整 VirtualDisplay 大小
【发布时间】:2016-03-29 03:05:46
【问题描述】:

我在使用 Android 的 MediaProjection API 时遇到了一些问题(实际上更多,但这些是更关键的问题)。阅读graphics architecture 并没有真正的帮助,所以我只是想了解我是否在我的代码流中跳过了某些内容。

假设:

  1. 我有一个专用的 GL 渲染线程,已初始化,并在其上生成了 GL 纹理。我为纹理设置了 WxH 的默认缓冲区大小。

  2. 我使用 GL 纹理创建了一个 SurfaceTexture,并为此表面纹理创建了一个 Surface。

  3. 通过 MediaProjection 创建一个大小为 WxH 的虚拟显示器,并将其表面设置为上述表面。

问题 1:一切正常(全帧正确输入)或不正常(所有帧都是黑色的;或者例如每帧只有一半可见 - 所有帧都相同的一半;或者屏幕的某些部分是重复到其他部分,有时甚至歪斜)。

问题 2:虽然在某些全屏 GL 游戏中花费了时间,但在经过 FIXED 时间(大约 4 分钟)后,所有传入的帧都被冻结(例如,我收到“新”帧,但它实际上是一个并且相同的图像)。用 glReadPixels 阅读可以确认结果 - 问题是,实际显示超出了该帧。强制它“恢复”的唯一方法是调出状态栏或导航栏,它会立即开始向我发送正确的帧。当然,再过4分钟,又发生了……

问题 3:在 VirtualDisplay 上调用 resize(),在对 GL 纹理也调用 setDefaultBUfferSize() 之后,最终在 90% 的情况下显示问题 #1(黑色/剪切帧,其他屏幕区域的伪影...)

我在同一个线程中使用 updateTextureImage -> GL 纹理绘制的调用序列,所以我的正常理解是永远不会发生我以某种方式从半满的 GL 缓冲区或其他东西中读取的情况......对吧?

我也通过将 VirtualDisplay 直接渲染到 MediaCodec 的表面(不涉及自定义 GL)来测试这个问题 - 相同的行为。 更新 实际上由于 MediaCodec 有一个固定大小的创建表面,所以这个 bug 无法重现,因为我们只能调整虚拟显示器的大小,而不能调整编码器的表面大小,所以这不是一个真正的 bug(但甚至像这样让 VirtualDisplay 以某种方式相应地调整表面大小会很好)。

我觉得关闭虚拟显示器时有什么东西泄漏了,或者在创建 VirtualDisplay 之间没有正确初始化,因为它不一致。很可能会发生全新的 MediaProjection 屏幕捕获许可,具有全新的虚拟显示器,具有刚刚创建的表面纹理,最终只会给我半切帧......让我有一张大扑克脸。 ..

PS:所有这些都发生在装有 Android 6.0.1 的 Nexus 6 上。

【问题讨论】:

  • 听起来有点像github.com/google/grafika/issues/43,因为它运行了 N 分钟然后停止更新。提交错误的人发现了一个奇怪的解决方法。您是否使用单个 EGL 上下文?重复看到屏幕部分通常是 GPU 平铺的伪影,并且通常是由于未能glClear() 屏幕造成的。 (我认为一些 bigflake 代码故意跳过 glClear(),因为它在全屏上呈现;我建议将屏幕清除为鲜红色,以使任何“洞”变得明显。)
  • 我的设置与您类似 我的问题似乎只发生在屏幕旋转和尝试调整虚拟显示/表面纹理大小时(如 here 所述)。 This 是我看到的一些屏幕“重复”当我第一次创建 VirtualDisplay 和 Surface/SurfaceTexture 时,对我来说没有问题。它不会在轮换中 100% 发生,这让我相信这是一种竞争条件。我在 6.0.1 上使用 Nexus 5x。
  • @fadden - 在直接渲染到 MediaCodec 表面时,我无法控制 glClear。在我的 GL 渲染器中,我确实调用了 glClear(用于信箱化目的,在旋转时清除整个视口),然后渲染纹理(据说是由最新的 updateTextureImage 调用更新的)。问题是纹理本身是不完整的。在它自己的范围内。正如 EncodedNybble 所提到的,当实际显示旋转与虚拟显示尺寸正交(当它自行缩放和居中时)时,它似乎也更频繁地发生。
  • 我注意到的另一件事是关闭屏幕并在几秒钟后重新打开可能会解决问题。我怀疑纹理缓冲区大小存在问题,或者生产者在调用 texture.setDefaultBufferSize 后没有使用正确大小的缓冲区?也许它使用旧缓冲区?文档说相机生产者覆盖了尺寸,为什么 VirtualDisplay 也不这样做呢?我认为如果我最初不设置缓冲区大小 IIRC,则不会呈现任何内容。是否有办法强制删除 GL 纹理的旧缓冲区?

标签: android android-mediacodec android-mediaprojection


【解决方案1】:

我放弃了,只是重新创建了虚拟显示器,使用 new SurfaceTexture 的 new Surface(但保留了 GL 纹理)。这消除了闪烁和半尺寸的帧。我也有一些 logcat 警告,之前使用的 SurfaceTexture 的 BufferQueue 已被放弃,并且在 VirtualDisplay 上调用 release() 后获得了 1 或 2 个 onFrameAvailable 回调(它可能是异步的,应该等待 stop() 回调,但它得到了那时太复杂了)。

即使在完成所有这些之后,仍然会发生一些非常奇怪的事情:有时捕获只会捕获 MY APPLICATION。如果我向下滑动状态栏或转到主页,一切都会变成黑色(即使我的应用程序位于状态栏窗口后面)。之后唯一的解决方案是关闭/打开屏幕,或将 Camera/Camera2 预览渲染到我的 SurfaceTexture,然后重新创建 VirtualDisplay。关于纹理,幕后肯定有问题。

看起来 VirtualDisplay.resize() 已损坏,使用现有 SurfaceTexture 创建新的虚拟显示器也是如此。

【讨论】:

  • 恐怕我在这里没有得到与您相同的行为。即使使用新的VirtualDisplay 和新的Surface 和新的SurfaceTexture 甚至是新的 OpenGL 纹理(我确实收到了废弃的警告),我仍然会收到我提到的“灰色条”和奇怪的撕裂/重复/更小早些时候,当我尝试在方向更改后创建一个新的VirtualDisplay 时。我同意VirtualDisplay 至少在这些设备上 6.0.1 在处理最近旋转的调整大小或显示时似乎被破坏了。
  • 我不会冒险在设备旋转时重新创建 VirtualDisplay :) 注意不要在任何发布的纹理上调用 updateTexImage,这发生在我身上(作为onFrameAvailable 仍然被调用)并且所有后续帧由于某种原因而闪烁。我保留了 GL 纹理以防止任何“外部 oes”重新绑定(而且我不会乱用线程,我只是在主线程/任何线程上重新创建表面纹理),也许这与它有关?
猜你喜欢
  • 2020-07-28
  • 1970-01-01
  • 1970-01-01
  • 2015-04-30
  • 2015-11-12
  • 2017-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多