Flutter AvPlayer 外接纹理播放异常分析二

分析日期:2026-07-15
分析对象:Flutter Engine OHOS 外接纹理、AvPlayer 输出 Surface、BufferQueue ProducerListener
本文是《Flutter AvPlayer 外接纹理播放异常分析》的第二篇,重点记录增加 Engine 诊断日志后得到的新证据,以及由此排除和收敛的根因范围。

目录


术语表

术语 英文 说明
IPC Inter-Process Communication 跨进程通信,本文中主要指 Binder IPC
VDI Vendor Device Interface 厂商设备接口,显示 Buffer 的厂商实现通过该接口接入
EGL Embedded-System Graphics Library OpenGL ES 与底层窗口系统之间的接口层
GPU Graphics Processing Unit 图形处理器,负责外接纹理采样和绘制
FD File Descriptor 文件描述符,本文中主要用于同步 Fence
GL OpenGL 本次运行使用的 Flutter 外接纹理渲染后端
API Application Programming Interface 应用程序接口

注:术语按在本文中出现的先后顺序排列。


1. 问题回顾

Flutter 应用通过 AvPlayer 将视频输出到 Flutter Engine 创建的 OHOS 外接纹理 Surface。播放开始后,画面短时间正常,随后停止更新。系统日志持续出现:

buffer_producer_listener.h:98-OnBufferReleasedWithSequenceAndFence:
Remote SendRequest fail, ret = 29201

前一轮分析确认:

  • 29201 对应 Binder 驱动返回的 BR_FAILED_REPLY
  • 失败对象是 BufferQueue 保存的远端 IProducerListener
  • Producer 收不到 Buffer Release 回调后可能发生缓冲区饥饿;
  • 系统同时存在 RegisterBufferGetMetadataSetMetadata 不支持的问题;
  • 但仅依靠原始日志,无法确认是不是 Flutter 的 OHOSExternalTexture 被提前注销、析构或切换了 OH_NativeImage

因此,本轮在 Flutter Engine 中增加了统一的 [EXT_TEX_TRACE] 日志,跟踪 Texture 生命周期、帧回调、Buffer Acquire/Release、丢帧路径以及 GL Fence。


2. 本轮诊断方法

2.1 Engine 新增日志范围

新增日志覆盖五条链路:

链路 主要事件 目标
Texture 生命周期 CREATE、UNREGISTER、DESTROY、RESET、GrContext Destroy 判断 Texture 或 NativeImage 是否被提前释放
帧通知链 NativeImage Callback、Platform Task、MarkTextureFrameAvailable 判断回调是否在 Flutter 内部丢失
Buffer 消费链 ACQUIRE_BEGIN、ACQUIRE_END 判断 Flutter 是否还能取得新 Buffer
Buffer 归还链 RELEASE_BEGIN、RELEASE_END、Release Reason 确认哪一类 Release 首先触发 29201
GPU 同步链 GL Set Release Fence、Wait Acquire Fence 判断 Fence 是否生成失败或无效

所有诊断日志使用 Engine 原有的 FML_LOG(INFO/ERROR),前缀统一为:

[EXT_TEX_TRACE]

2.2 日志样本概况

本轮运行日志中共记录:

事件 次数
[EXT_TEX_TRACE] 742
NativeImage Frame Callback 61
MarkNewFrameAvailable 61
Buffer Acquire 69
Buffer Release 47
29201 238
OnBufferReleasedWithSequenceAndFence 失败 235
Acquire 返回 40601000 48

日志中的设备时间为 08-04 17:24:xx,与分析日期不一致,应视为设备时钟或日志环境时间,不用于推导真实日期。


3. 新日志给出的核心结论

本轮可以明确排除以下假设:

  • OHOSExternalTexture 在播放期间提前析构;
  • Texture 被 Flutter 插件或 Engine 注销;
  • OH_NativeImage 被 Reset、Destroy 或替换;
  • GrContext 销毁导致 Frame Listener 被替换;
  • TextureId 到 PlatformView 的映射丢失;
  • NativeImage 回调到 Flutter Raster 线程之间发生丢帧;
  • OpenGL 无法创建有效的 Release Fence;
  • 第一次 29201 是由 Flutter 的延迟帧丢弃机制触发。

新日志支持的直接故障链为:

Flutter 正常 Acquire 新 Buffer
  → OpenGL 为上一帧生成有效 Release Fence
  → Flutter 正常调用 ReleaseNativeWindowBuffer
  → BufferQueue 回调远端 IProducerListener
  → handle 31 返回 29201
  → ReleaseNativeWindowBuffer 对 Flutter 仍返回 0
  → Flutter 不知道 ProducerListener 回调已经失败
  → 后续每次正常 Release 都继续触发 29201
  → Producer 最终不再提交新 Buffer
  → NativeImage Frame Callback 停止
  → Flutter Acquire 持续得到空 Buffer
  → 画面冻结

所以,Flutter 外接纹理是失效 Listener 的触发方和受害方,不是 Listener 被销毁的地方。


4. 第一次 29201 的完整现场

第一次 29201 出现在设备日志时间:

08-04 17:24:03.605

4.1 Flutter 成功取得新 Buffer

首先,Flutter Raster 线程开始消费下一帧:

[EXT_TEX_TRACE] ACQUIRE_BEGIN
texture_id=1
native_image=0x7f1f83be30
last_buffer=0x7f133bbce0
new_seq=29
paint_seq=21

OH_NativeImage_AcquireNativeWindowBuffer() 返回:

[EXT_TEX_TRACE] ACQUIRE_END
buffer=0x7f133b8ef0
buffer_seq=33488939
fence_fd=-1
ret=0
new_seq=29
paint_seq=21

这说明在第一次错误发生前:

  • NativeImage 仍有效;
  • BufferQueue 中仍有新 Buffer;
  • Flutter 成功完成 Acquire;
  • 当前问题不是 Consumer 无法获取 Buffer 引发的前置错误。

4.2 OpenGL 成功生成 Release Fence

Flutter 在归还上一帧之前,为上一帧创建 GPU Release Fence:

[EXT_TEX_TRACE] GL_SET_RELEASE_FENCE_BEGIN
texture_id=1
buffer=0x7f133bbce0
input_fence_fd=-1

返回:

[EXT_TEX_TRACE] GL_SET_RELEASE_FENCE_END
buffer_seq=33488959
output_fence_fd=120
egl_error=12288

12288 对应 EGL_SUCCESS,并且得到有效的 fence_fd=120。因此第一次失败不是以下原因导致:

  • EGLDisplay 无效;
  • EGL Sync 创建失败;
  • Fence FD 创建失败;
  • Flutter 在 GPU 尚未使用完 Buffer 时无 Fence 归还。

4.3 正常归还上一帧时 Listener 回调失败

Flutter 随后执行正常帧替换释放:

[EXT_TEX_TRACE] RELEASE_BEGIN
reason=replace_last_buffer
native_image=0x7f1f83be30
buffer=0x7f133bbce0
buffer_seq=33488959
fence_fd=120

在这次 Release 内部依次发生:

SetMetadata is not supported
SetMetadata Failed with -5
IPCObjectProxy handle:31 error:29201
OnBufferReleasedWithSequenceAndFence:
Remote SendRequest fail, ret = 29201

这证明第一次 29201 是由正常 replace_last_buffer 路径触发,而不是由跳过延迟帧或销毁 Texture 触发。

4.4 Release API 对 Flutter 仍返回成功

错误发生后,Engine 记录:

[EXT_TEX_TRACE] RELEASE_END
reason=replace_last_buffer
buffer_seq=33488959
input_fence_fd=120
ret=0

这里暴露了一个关键的错误传播断点:

IProducerListener IPC 回调失败
        ≠
OH_NativeImage_ReleaseNativeWindowBuffer 返回失败

BufferQueue 内部虽然记录了 29201,但上层 Release API 仍向 Flutter 返回 0。Flutter 因此没有任何依据停止消费、重建 Surface 或通知播放器恢复。


5. 为什么可以排除 OHOSExternalTexture 生命周期问题

5.1 Texture 对象没有注销或析构

整个日志只出现一次 Texture 创建状态:

[EXT_TEX_TRACE] STATE reason=CREATE_DONE
texture_id=1
this=0x7f1f30f928
source_is_external=0

播放和冻结期间未出现:

UNREGISTER_BEGIN
ON_TEXTURE_UNREGISTERED_BEGIN
DESTROY_BEGIN
DESTROY_NATIVE_IMAGE_BEGIN
RESET_BEGIN

因此 OHOSExternalTexture 没有被插件或 Engine 提前销毁。

5.2 NativeImage 和 NativeWindow 始终未变化

从创建到最后一次帧回调,关键对象保持一致:

texture_id=1
this=0x7f1f30f928
native_image=0x7f1f83be30
native_window=0x7f1fafb970
platform_view=0x7f2455af90
surface_id=11918534246400

没有出现新地址,也没有出现 Destroy 或 Reset 日志。这排除了 Flutter 内部切换 BufferQueue Consumer 的可能。

5.3 Frame Callback 链路完整

四个关键事件的次数完全一致:

事件 次数
FRAME_CALLBACK_ENTER 61
FRAME_CALLBACK_POST_PLATFORM 61
FRAME_CALLBACK_PLATFORM_RUN 61
MARK_TEXTURE_FRAME_AVAILABLE 61
MARK_NEW_FRAME_ENTER 61

所有回调均显示:

map_contains=1

说明每个 NativeImage 回调都成功经过:

NativeImage Callback
  → PlatformViewOHOS
  → Platform Task Runner
  → MarkTextureFrameAvailable
  → Raster Thread MarkNewFrameAvailable

Flutter 内部没有丢失任何一次已到达的 Frame Callback。

5.4 GrContext 和外部 NativeImage 切换路径未触发

日志中不存在:

GR_CONTEXT_DESTROY_BEGIN
GR_CONTEXT_DESTROY_LISTENER_REPLACED
SET_EXTERNAL_NATIVE_IMAGE_BEGIN
RESET_CREATED

所以本轮问题与以下代码风险无关:

  • GrContext 销毁后使用默认 Listener Drain Buffer;
  • SetExternalNativeImage() 切换 NativeImage 所有权;
  • source_is_external_ 在 Reset 后未恢复;
  • NativeImage 切换过程中发生回调竞态。

这些代码仍值得单独审计,但不是本次视频冻结的现场路径。


6. 正常释放与跳帧释放的关系

6.1 第一次失败不是由跳帧触发

第一次失败的 Release Reason 是:

reason=replace_last_buffer

它表示 Flutter 已取得新帧,准备正常归还上一帧。这是每帧消费都会执行的标准路径。

第一次错误之后才出现:

DELAYED_FRAME_SKIP_BEGIN

因此关闭 delayed_frame_skip 只能减少后续错误数量,不能消除第一次错误。即使完全不跳帧,正常 replace_last_buffer 仍会命中失效的 ProducerListener。

6.2 三类 Release 的统计结果

Release 原因 总次数 Release 期间出现 29201 的次数 说明
slow_consumer_drain 11 0 第一次错误前用于缓解队列积压,全部成功
replace_last_buffer 20 20 第一次错误及后续正常帧替换全部失败
delayed_frame_skip 16 7 部分发生在 Listener 失效之后

这组统计说明:

  • Flutter 的慢消费者 Drain 本身没有立即破坏 ProducerListener;
  • Listener 在后续正常帧替换前已经变得不可用;
  • 跳帧路径只是加快触发已存在的 IPC 错误;
  • 把问题归因于“Flutter 跳帧太激进”不符合日志事实。

7. 第一轮 21 个 Buffer 周转的异常边界

Texture 创建时 BufferQueue size 为 3,播放过程中扩展到:

queue_size=21

第一次 29201 前,Flutter 依次释放了完整的一组连续 Buffer Sequence:

33488939
33488940
33488941
...
33488958
33488959

共覆盖 21 个不同 Sequence,正好与运行时 queue_size=21 对应。第一次失败发生在释放 33488959 时,之后 Sequence 又从 33488939 开始复用。

这个边界具有较强的诊断价值:

第一轮 21 个 Buffer 建立并周转
  → 最后一个 Buffer 归还时首次出现 29201
  → 下一轮开始复用旧 Sequence
  → ProducerListener 回调持续失败

当前不能仅凭这一现象断言 Codec 一定在第 21 个 Buffer 时切换 Listener,但它比“随机时刻 Listener 死亡”更有规律。需要重点检查:

  • Codec 是否在首轮输出 Buffer Pool 建立完成后重建输出 Surface;
  • ProducerSurface 是否重新注册了 Release Listener;
  • 旧 Listener Stub 是否被析构,而 Consumer BufferQueue 仍持有旧 Proxy;
  • Metadata 失败是否让 Codec 进入了某个输出 Surface 恢复或替换路径。

8. 冻结后的确定状态

8.1 Frame Callback 停止

最后一次 NativeImage Callback:

08-04 17:24:04.257
FRAME_CALLBACK_ENTER texture_id=1 map_contains=1

最后一次 MarkNewFrameAvailable

new_seq=47
paint_seq=47

完成最后一次帧处理后状态到达:

new_seq=48
paint_seq=48

此后不再出现新的 NativeImage Callback。这说明不是 Flutter 收到 Callback 后没有处理,而是 Producer 不再向该 NativeImage Flush 新 Buffer。

8.2 Acquire 持续返回空 Buffer

08-04 17:24:04.351 开始,Flutter 后续绘制不断尝试 Acquire:

[EXT_TEX_TRACE] ACQUIRE_END
buffer=0
buffer_seq=4294967295
fence_fd=-1
ret=40601000
new_seq=48
paint_seq=48

该状态持续到日志结束,共出现 48 次,时间跨度超过 11 秒。

本地 Engine 仓库未找到 40601000 的符号定义,因此本文不对该数值强行命名。但结合以下事实:

  • buffer=null
  • Frame Callback 已停止;
  • new_seq 不再增长;
  • Texture 对象仍然存活;

可以确定它表示当前 NativeImage 无法提供新的 Consumer Buffer。Flutter 仍在绘制旧图像,因此用户看到的是画面冻结,而不是 Texture 消失或变黑。


9. VDI Metadata 缺失与本问题的关系

系统在 Texture 创建后立即报告:

load libdisplay_buffer_vdi_impl_default.z.so failed
display buffer load vendor vdi default library failed:
libdisplay_buffer_vdi_impl.z.so

本轮日志中的相关错误统计:

错误 次数
VDI 库加载失败相关日志 12
RegisterBuffer 失败 56
GetMetadata 不支持 97
SetMetadata 相关失败 524

第一次 29201 的顺序也非常稳定:

RELEASE_BEGIN
  → SetMetadata is not supported
  → SetMetadata Failed with -5
  → IProducerListener handle 31 error 29201
  → RELEASE_END ret=0

由此可以得出两层结论:

  1. 确定事实:设备显示 Buffer VDI 实现缺失或不可加载,Metadata 能力不可用;
  2. 待验证因果:Metadata 失败是否直接触发 Codec 输出 Surface 或 ProducerListener 生命周期异常,当前仍缺少 Media/Codec 侧日志。

不能仅凭时间相邻就断言:

SetMetadata 失败 = ProducerListener 被析构

但在当前证据中,VDI 缺失仍然是最强的系统环境异常,应优先修复或通过正常设备实现做对照。


10. 当前最可信的根因模型

综合本轮日志,当前最可信的模型是:

设备缺少 display buffer VDI 实现
  → RegisterBuffer / GetMetadata / SetMetadata 持续失败
  → Codec 输出 Surface 或 Buffer 生命周期进入异常路径
  → 第一轮 21 个 Buffer 周转完成附近
  → Codec 侧旧 ProducerListener Stub 被注销、替换或析构
  → Flutter NativeImage 对应的 BufferQueue 仍持有旧 Proxy(handle 31)
  → Flutter 正常归还上一帧
  → OnBufferReleasedWithSequenceAndFence 发送到失效对象
  → Binder 返回 BR_FAILED_REPLY(29201)
  → ReleaseNativeWindowBuffer 对 Flutter 仍返回 0
  → Flutter 无法感知远端回调失败
  → Producer 收不到 Buffer Release 通知
  → Producer 不再持续提交新 Buffer
  → NativeImage Callback 停止
  → Flutter Acquire 持续返回空 Buffer
  → 画面冻结

其中已经得到直接证据支持的是:

正常 Acquire
→ 正常 GL Fence
→ 正常 Release 调用
→ Listener IPC 失败
→ Release 对 Flutter 返回成功
→ Callback 停止
→ Acquire 持续为空

仍待 Media/Codec 日志证明的是:

为什么 handle 31 对应的 ProducerListener Stub 会失效

11. 下一步排查方案

11.1 ProducerListener 构造与析构

BufferReleaseProducerListener 构造和析构位置增加:

[PRODUCER_LISTENER_TRACE] CREATE
[PRODUCER_LISTENER_TRACE] DESTROY
listener=this
remote_object=...
pid=...
tid=...
surface_id=...
codec_instance_id=...

目标是确认 handle 31 对应的 Stub:

  • 在何时创建;
  • 在第一次 29201 前是否析构;
  • 是否被另一个 Listener 替换;
  • 析构由哪个 Codec 状态或 Surface 操作触发。

11.2 ProducerSurface Listener 注册链

在以下位置记录相同关联字段:

ProducerSurface::RegisterReleaseListener
ProducerSurface::UnRegisterReleaseListener
BufferQueue::RegisterProducerReleaseListener
BufferQueue::UnRegisterProducerReleaseListener

建议字段:

surface_id
producer_surface
buffer_queue
listener
listener_remote_object
codec_instance_id
register_sequence
reason

重点检查:

  • Consumer BufferQueue 保存的 Listener Proxy 是否与 Codec 当前 Listener 一致;
  • Codec 重新注册 Listener 时,旧 Proxy 是否被正确更新;
  • 注销 Listener 后是否仍有 BufferQueue Release 回调指向旧对象。

11.3 Codec Surface 生命周期

需要跟踪:

SurfaceTools::ReleaseSurface
HDecoder::SurfaceItem::Release
HDecoder::OnEnterUninitializedState
HCodec::SetOutputSurface
HCodec 状态迁移

建议输出:

codec_instance_id
old_state
new_state
trigger_message
surface_id
producer_surface
listener

如果第一次 29201 前出现 ReleaseSurface、SetOutputSurface 或 Listener 重新注册,即可把根因闭环到具体状态迁移。

11.4 对照实验

优先进行三组实验:

实验 目的 判定
补齐正常 VDI 实现后重测 验证 Metadata 缺失是否为上游触发条件 VDI 错误和 29201 同时消失,则因果关系很强
同一视频输出到非 Flutter 原生 Surface 排除 Flutter NativeImage 特有影响 仍出现 29201,则问题位于媒体/图形系统侧
保留正常 Release、临时关闭跳帧 Drain 验证跳帧是否只放大问题 第一次 replace_last_buffer 仍失败,则进一步排除跳帧根因

第三组实验不是修复方案,只用于验证。由于第一次失败已发生在正常 Release,预计关闭跳帧不能解决根因。


12. 关键问题速查

问题 当前答案 证据
Texture 是否被提前注销? 无 UNREGISTER、OnTextureUnregistered 日志
OHOSExternalTexture 是否提前析构? 无 DESTROY_BEGIN,this 始终不变
NativeImage 是否被替换或 Reset? 地址始终为 0x7f1f83be30,无切换日志
GrContext 是否销毁? 无 GR_CONTEXT_DESTROY 日志
Frame Callback 是否在 Flutter 内部丢失? 五级事件均为 61 次,且 map_contains=1
第一次失败是否由 delayed-frame skip 触发? 第一次 Release Reason 是 replace_last_buffer
GL Release Fence 是否创建失败? fence_fd=120egl_error=12288
Flutter Release API 是否报告失败? Listener IPC 失败,但 RELEASE_END ret=0
真正失效的对象是什么? IProducerListener 远端对象 固定 handle 31 返回 29201
冻结后 Flutter 是否还在尝试绘制? 48 次 Acquire 尝试
冻结后是否还有新 Buffer? buffer=nullret=40601000、newSeq 固定为 48
VDI 缺失是否已经确认? 库加载失败且 Metadata API 全部不支持
VDI 是否直接导致 Listener 析构? 尚未闭环 需要 Codec/ProducerListener 生命周期日志

13. 总结

本轮最重要的进展,是把“Flutter 外接纹理生命周期问题”从主要嫌疑中排除,并将故障点收敛到 BufferQueue 到 Codec ProducerListener 的跨进程回调链。

层级 当前结论
Flutter Texture 生命周期 正常,没有注销、析构、Reset 或 NativeImage 切换
Flutter 帧通知链 正常,每个已到达的 Callback 都进入 Raster 线程
Flutter Buffer Acquire 第一次错误前正常,冻结后持续返回空 Buffer
Flutter GPU Fence 正常创建,有效 FD,EGL 返回成功
Flutter Buffer Release API 返回成功,但内部 Listener IPC 已失败
BufferQueue ProducerListener handle 31 对应远端对象失效,是直接异常点
Codec/Media 生命周期 是下一步需要补充日志的核心区域
Display Buffer VDI 确定缺失,是最强的系统级上游异常

一句话结论:

这不是 Flutter 提前释放外接纹理,而是 Flutter 在正常归还 Buffer 时,BufferQueue 持有的 Codec ProducerListener 已经失效;上层 Release 仍返回成功,使错误无法向 Flutter 传播,最终 Producer 停止提交新帧,画面冻结。

posted @ 2026-07-15 17:41  getmoon  阅读(11)  评论(0)    收藏  举报