Session 分析笔记:Flutter AvPlayer 外接纹理播放异常分析
日期:2026-07-14
会话主题:Flutter 应用使用 AvPlayer + OHOSExternalTexture 播放视频,前 1~2 秒画面正常随后完全冻结
工具与模型
- Agent:Hermes
- 模型:deepseek-v4-pro
- 主要工具:terminal、read_file、search_files、execute_code
一、需求背景与目标
现象
视频播放器通过外接纹理播放,前 1~2 秒正常显示画面,之后完全卡住不动。日志中反复出现:
buffer_producer_listener.h:98-OnBufferReleasedWithSequenceAndFence:
Remote SendRequest fail, ret = 29201
最终 BufferQueue 状态:
buffer_queue.cpp:926-LogAndTraceAllBufferInBufferQueueCacheLocked:
there is no dirty buffer or no dirty buffer ready,
Released: 20 Requested: 0 Flushed: 0 Acquired: 1
目标
- 分析
buffer_producer_listener.h类结构和 IPC 机制 - 定位 29201 错误的真实含义与触发条件
- 追踪 ProducerListener 的完整生命周期与析构原因
- 分析全量日志,给出根因结论与修复方向
二、分析过程与关键证据
2.1 IPC 机制分析:buffer_producer_listener.h 类结构
源码 — ProducerListenerProxy::OnBufferReleasedWithSequenceAndFence(buffer_producer_listener.h:75-102):
GSError OnBufferReleasedWithSequenceAndFence(uint32_t sequence, const sptr<SyncFence>& fence) override
{
MessageOption option;
MessageParcel arguments, reply;
// ... 序列化 sequence 和 fence 到 arguments ...
option.SetFlags(MessageOption::TF_ASYNC); // ← 单向调用,不等回复
sptr<IRemoteObject> remote = Remote();
int32_t ret = remote->SendRequest(
IProducerListener::ON_BUFFER_RELEASED_WITH_SEQUENCE_AND_FENCE,
arguments, reply, option);
if (ret != ERR_NONE) {
BLOGE("Remote SendRequest fail, ret = %{public}d", ret); // ← 29201 在此打印
return GSERROR_BINDER;
}
return GSERROR_OK;
}
该文件定义 4 个类:
| 类 | 角色 | 所在进程 |
|---|---|---|
ProducerListenerProxy |
IPC 客户端代理,所有回调走 TF_ASYNC 单向 IPC | App (pid 28059) |
ProducerListenerStub |
IPC 服务端骨架,OnRemoteRequest 按 code 分派 | media_service (pid 538) |
BufferReleaseProducerListener |
继承 ProducerListenerStub,封装用户回调函数 | media_service (pid 538) |
PropertyChangeProducerListener |
同上,封装属性变化回调 | media_service (pid 538) |
App 侧的 ProducerListenerProxy 持有一个 binder handle (例如 handle:28),指向 media_service 中的 BufferReleaseProducerListener Stub。
2.2 错误恢复路径:BufferQueue 端做了什么
源码 — buffer_queue.cpp:1159-1175:
void BufferQueue::OnReleaseBufferWithSequenceAndFence(
sptr<IProducerListener> &listener,
std::vector<std::pair<uint32_t, sptr<SyncFence>>> &requestBuffersAndFences)
{
for (auto &[sequence, fence] : requestBuffersAndFences) {
auto ret = listener->OnBufferReleasedWithSequenceAndFence(sequence, fence);
if (ret != GSERROR_OK) {
std::unique_lock<std::mutex> lock(mutex_);
auto iter = bufferQueueCache_.find(sequence);
if (iter != bufferQueueCache_.end()) {
CancelBufferLocked(sequence, iter->second.buffer->GetExtraData());
// 将 buffer 状态回退为 RELEASED,重新加入 freeList_
}
}
}
}
发现:IPC 失败后 BufferQueue 会将 buffer 状态回退为 RELEASED 并放回 freeList_。BufferQueue 层面不泄漏,但 Producer 端永远不会收到"buffer 已释放"的回调通知,因此不会调用 RequestBuffer 获取新 buffer,形成缓冲区饥饿 → 画面冻结。
2.3 29201 的真实含义:Binder 驱动返回码
源码 — ipc_object_proxy.cpp:221-250 (SendRequestInner):
int IPCObjectProxy::SendRequestInner(...)
{
if (IsObjectDead()) { // 检查 Proxy 是否已标死
return ERR_DEAD_OBJECT; // -32, 如果之前已经标死则快速返回
}
int status = invoker->SendRequest(handle_, code, data, reply, option); // 实际 IPC
if (status == ERR_DEAD_OBJECT) { // -32
SetObjectDied(true); // 标死自己, 后续 SendRequest 直接返回 -32
}
if (status != ERR_NONE && ProcessSkeleton::IsPrint(status, ...)) {
PrintErrorDetailedInfo(status, desc); // ← 打印 "29201" 日志
}
return status;
}
关键发现:status = 29201,而 ERR_DEAD_OBJECT = -32。29201 ≠ -32,所以 SetObjectDied(true) 不会被调用。这意味着 IPCObjectProxy 不知道目标已死 —— 后续每次 SendRequest 仍然进入驱动,每次都拿到 29201,每次都满足 IsPrint 节流条件继续打印日志。这就解释了为什么 29201 会无限泛滥。
源码 — binder_invoker.cpp:1435-1441:
void BinderInvoker::OnDeadOrFailedReply(..., uint32_t cmd)
{
error = static_cast<int32_t>(cmd); // 直接透传驱动返回值
continueLoop = false; // 停止等待回复
}
源码 — sys_binder.h 中 29201 的定义:
// _IO('r', 17) = (0 << 30) | (0x72 << 8) | (17 << 0) | (0 << 16) = 0x7211 = 29201
BR_FAILED_REPLY = _IO('r', 17),
结论:29201 = BR_FAILED_REPLY,这是 Binder 驱动直接返回的错误码,表示驱动在处理事务时发现目标 binder 实体已不可用。驱动层面的含义与 ERR_DEAD_OBJECT(-32) 不同:前者是"这次调用时驱动发现目标没了",后者是"Proxy 之前已经知道目标死了"。
2.4 全量日志分析:三个并发问题
从全量日志 (17770 行) 中提取视频播放时间窗口:
日志 — 完整播放时间线:
01:26:40.813 AVPlayer Napi notify initialized
01:26:40.814 JsSetSurfaceID
01:26:41.171 notify prepared (Prepare 耗时 357ms)
01:26:42.094 JsPlay
01:26:42.099 DecoderSurfaceFilter::DoStart
01:26:42.100 BUG: CodecServiceStub "In invalid state, running" ← 解码器重复 Start
01:26:42.107 [pid 28059] RegisterBuffer fail (首帧)
01:26:42.168 avail-seq 0 (首帧到外接纹理)
01:26:42.418 external_texture skip one frame (slow consumer)
01:26:42.448 avail-seq 14, 之后不再有新帧
01:26:42.551 ★ IPCObjectProxy handle:28 error:29201 ★
01:26:43.193 BufferQueue Released:20 Flushed:0 ← 画面冻结
01:26:43.267 MediaDemuxer RequestBuffer 61447 errorCnt:8
01:26:44.070 errorCnt:16
01:26:44.872 errorCnt:24
01:26:45.674 errorCnt:32
发现 1:系统级 gralloc VDI 缺陷
日志 — RegisterBuffer/SetMetadata/GetMetadata 失败出现在所有进程中:
// App 进程 (pid 28059)
28059-31431 DISP [RegisterBuffer:86] <private> is not supported
28059-31431 METADATA_SRV [RegisterBuffer:123] fail
// RenderService (pid 651)
651-23263 DISP [SetMetadata:92] <private> is not supported
651-23263 METADATA_SRV [SetMetadata:136] fail
// 解码器服务 (pid 524)
524-31632 DISP [GetMetadata:98] <private> is not supported
// Composer Host (pid 518)
518-649 DISP [RegisterBuffer:86] <private> is not supported
源码 — display_buffer_vdi_impl.cpp:83-87:
int32_t DisplayBufferVdiImpl::RegisterBuffer(const BufferHandle& handle)
{
DISPLAY_LOGE("%s is not supported", __func__); // ← 基类默认实现,直接返回不支持
return DISPLAY_NOT_SUPPORT;
}
DisplayBufferVdiImpl 是 VDI 基类,把所有 metadata 方法都设为"不支持"。设备厂商需要提供自己的 libdisplay_buffer_vdi_impl.z.so 实现来 override。当前镜像中缺少这个实现。
这不是视频播放独有的问题,而是影响整个显示系统的 bug,视频播放只是受害者之一。
发现 2:解码器重复 Start
日志:
524-1025 CodecServiceStub E [17][h.vdec]{Start:282} In invalid state, running
538-31629 CodecClient I [17][h.vdec]{Start:241} the state is not support this operation
Prepare 阶段解码器已被 Configure+Start 预启动(加速首帧),Play 时又触发了一次 Start,被 CodecServiceStub 拒绝。
发现 3:MediaDemuxer 缓冲区饥饿
日志:
538-31626 MediaDemuxer W (RecordErrorCount) Request buffer failed queue:1 ret:61447 errorCnt:8
538-31626 MediaDemuxer W (RecordErrorCount) Request buffer failed queue:1 ret:61447 errorCnt:16
538-31626 MediaDemuxer W (RecordErrorCount) Request buffer failed queue:1 ret:61447 errorCnt:24
538-31626 MediaDemuxer W (RecordErrorCount) Request buffer failed queue:1 ret:61447 errorCnt:32
解封装层(MediaDemuxer)的内部 buffer queue 也在持续失败,说明上游(demuxer→codec 的缓冲区管理)也出了问题,而不仅是下游(codec→surface)。
2.5 ProducerListener 死亡路径:源码追踪
5 条可能的析构路径
| 路径 | 调用链 | 触发条件 |
|---|---|---|
| 正常注销 | SurfaceTools::ReleaseSurface() → UnRegisterReleaseListener() |
业务层主动注销 |
| 解码器进入 Uninitialized | HCodec::UninitializedState::OnStateEntered() → OnEnterUninitializedState() → currSurface_.Release() |
RELEASE 或 STOP 消息 |
| Surface 切换 | SetOutputSurface(new) → UnRegisterListenerToSurface(old) → ReleaseSurface() |
播放器设置新 surface |
| ProducerSurface 析构 | ~ProducerSurface() → listener_ (sptr) 自动释放 |
对象生命周期结束(非正常路径) |
| BufferQueue 析构 | ~BufferQueue() → producerListener_ 自动释放 |
App 侧 BufferQueue 销毁 |
源码 — 正常注销路径,surface_tools.cpp:86-106:
void SurfaceTools::ReleaseSurface(int32_t instanceId, sptr<Surface> surface, bool cleanAll, bool abadon)
{
...
surface->CleanCache(cleanAll);
surface->UnRegisterReleaseListener(); // ★ 注销 listener, Stub 析构
...
}
源码 — 解码器 Uninitialized 路径,hdecoder.cpp:1466-1491:
void HDecoder::OnEnterUninitializedState()
{
currSurface_.Release(); // → SurfaceItem::Release() → ReleaseSurface()
...
}
void HDecoder::SurfaceItem::Release(bool cleanAll)
{
if (surface_) {
SurfaceTools::GetInstance().ReleaseSurface(instanceId_, surface_, cleanAll);
surface_ = nullptr;
}
}
源码 — 状态机触发条件,hcodec_state.cpp:394-401:
void HCodec::InitializedState::OnShutDown(const MsgInfo &info)
{
if (info.type == MsgWhat::STOP) {
// 保持 component 分配
} else {
// RELEASE → 进入 Uninitialized → 上面的 OnEnterUninitializedState
codec_->ChangeStateTo(codec_->uninitializedState_);
}
}
2.6 BufferReleaseProducerListener 持有链分析
源码 — 创建,producer_surface.cpp:852-874:
GSError ProducerSurface::RegisterReleaseListener(OnReleaseFuncWithSequenceAndFence func)
{
sptr<IProducerListener> listener;
...
listener_ = new BufferReleaseProducerListener(nullptr, nullptr, releaseBufferCallback);
listener = listener_;
return producer_->RegisterReleaseListener(listener, true); // → IPC 传给 BufferQueue
}
源码 — BufferQueue 侧存储 Proxy,buffer_queue.cpp:1772-1778:
GSError BufferQueue::RegisterProducerReleaseListener(
sptr<IProducerListener> listener, bool isOnReleaseBufferWithSequenceAndFence)
{
producerListener_ = listener; // 存储 Proxy (handle:28 指向远端 Stub)
isOnReleaseBufferWithSequenceAndFence_ = isOnReleaseBufferWithSequenceAndFence;
return GSERROR_OK;
}
跨进程 sptr 引用链:
Codec 端 (pid 538) Binder 驱动 App 端 (pid 28059)
ProducerSurface::listener_ handle:28 BufferQueue::producerListener_
│ (sptr, 直接持 Stub) │ (node alive) │ (Proxy sptr, 间接持 Stub)
│ │ │
↓ 释放 │ │
listener_ = null 或 析构 │ │
Stub 本地引用归零 │ │
│ │ │
但如果 Proxy 还持有 ──────────────────→│← 驱动仍保留 node │
│ Stub 未完全释放 │
│ │
│ ↓ producerListener_ = null
│ Proxy 释放
├── node 清理 │
│ → handle 失效 │
关键洞察:只有 双方都释放(ProduceSurface::listener_ 和 BufferQueue::producerListener_ 都置空),Binder 驱动的 node 才会被清理。但如果在 BufferQueue 释放 Proxy 之前,codec 端先销毁了 ProducerSurface,Stub 本地引用归零,下次 App 侧 Proxy 调用时驱动就会返回 BR_FAILED_REPLY。
三、结论
根因链
系统级 gralloc VDI 不完整 (RegisterBuffer/SetMetadata/GetMetadata 全部 DISPLAY_NOT_SUPPORT)
│
├─→ 解码器可能因持续的 metadata 操作失败进入错误恢复/状态异常
│ └─→ ProducerSurface 析构或 UnRegisterReleaseListener
│ └─→ BufferReleaseProducerListener (handle:28 对应 Stub) 析构
│ └─→ App 侧 SendRequest → 29201 (BR_FAILED_REPLY)
│ └─→ BufferQueue 缓冲区饥饿 → 画面冻结
│
├─→ 解码器重复 Start (Bug)
│
└─→ MediaDemuxer 缓冲区队列饥饿 (61447 errorCnt 持续增长)
└─→ 解封装→解码→渲染全链路故障
修复方向
| 优先级 | 方向 | 说明 |
|---|---|---|
| P0 | 修复 gralloc VDI metadata 实现 | 设备厂商需在 libdisplay_buffer_vdi_impl.z.so 中正确 override RegisterBuffer/SetMetadata/GetMetadata 等方法 |
| P1 | 修复解码器重复 Start | Prepare 时启动解码器后,Play 不应再次调 Start |
| P1 | 增加 ProducerListener 死亡检测 | 在 SendRequest 返回 29201 时也触发 SetObjectDied(true) 或添加 death recipient |
| P2 | 增加关键路径日志 | 在 Stub 构造/析构、ReleaseSurface、OnEnterUninitializedState 处增加日志定位问题 |
日志定位清单
定位此类问题至少需要以下日志:
| 点位 | 文件:行 | 内容 |
|---|---|---|
| Stub 构造/析构 | buffer_producer_listener.h:237/240 |
BUF_REL_LISTENER ++ CREATE / -- DESTROY |
| ProducerSurface 注册/注销 | producer_surface.cpp:774/924 |
REGISTER/UNREGISTER listener this=%p |
| BufferQueue Proxy 注册/注销 | buffer_queue.cpp:1776/1825 |
REGISTER/UNREGISTER proxy |
| SurfaceTools::ReleaseSurface | surface_tools.cpp:86 |
RELEASE BEGIN instanceId=%d surfaceId=%" PRIu64 |
| HDecoder::OnEnterUninitializedState | hdecoder.cpp:1466 |
OnEnterUninitializedState surface=%p |
| ~ProducerSurface | producer_surface.cpp:68 |
listener=%p backup=%p |

浙公网安备 33010602011771号