Flutter AvPlayer 外接纹理播放异常分析二
分析日期:2026-07-15
分析对象:Flutter Engine OHOS 外接纹理、AvPlayer 输出 Surface、BufferQueue ProducerListener
本文是《Flutter AvPlayer 外接纹理播放异常分析》的第二篇,重点记录增加 Engine 诊断日志后得到的新证据,以及由此排除和收敛的根因范围。
目录
- 1. 问题回顾
- 2. 本轮诊断方法
- 3. 新日志给出的核心结论
- 4. 第一次 29201 的完整现场
- 5. 为什么可以排除 OHOSExternalTexture 生命周期问题
- 6. 正常释放与跳帧释放的关系
- 7. 第一轮 21 个 Buffer 周转的异常边界
- 8. 冻结后的确定状态
- 9. VDI Metadata 缺失与本问题的关系
- 10. 当前最可信的根因模型
- 11. 下一步排查方案
- 12. 关键问题速查
- 13. 总结
术语表
| 术语 | 英文 | 说明 |
|---|---|---|
| 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 回调后可能发生缓冲区饥饿;
- 系统同时存在
RegisterBuffer、GetMetadata、SetMetadata不支持的问题; - 但仅依靠原始日志,无法确认是不是 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
由此可以得出两层结论:
- 确定事实:设备显示 Buffer VDI 实现缺失或不可加载,Metadata 能力不可用;
- 待验证因果: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=120,egl_error=12288 |
| Flutter Release API 是否报告失败? | 否 | Listener IPC 失败,但 RELEASE_END ret=0 |
| 真正失效的对象是什么? | IProducerListener 远端对象 |
固定 handle 31 返回 29201 |
| 冻结后 Flutter 是否还在尝试绘制? | 是 | 48 次 Acquire 尝试 |
| 冻结后是否还有新 Buffer? | 否 | buffer=null、ret=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 停止提交新帧,画面冻结。

浙公网安备 33010602011771号