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

目标

  1. 分析 buffer_producer_listener.h 类结构和 IPC 机制
  2. 定位 29201 错误的真实含义与触发条件
  3. 追踪 ProducerListener 的完整生命周期与析构原因
  4. 分析全量日志,给出根因结论与修复方向

二、分析过程与关键证据

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 = -3229201 ≠ -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 构造/析构、ReleaseSurfaceOnEnterUninitializedState 处增加日志定位问题

日志定位清单

定位此类问题至少需要以下日志:

点位 文件:行 内容
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
posted @ 2026-07-14 17:45  getmoon  阅读(9)  评论(0)    收藏  举报