Flutter AvPlayer 外接纹理播放异常分析三
Flutter video_player 在 OpenHarmony 上卡死:从 Binder 29201 到一行 SELinux 修复
目录
- 1. 问题现象与调查目标
- 2. 故障链路与验证方法
- 3. 第一轮:排除 ProducerListener 生命周期问题
- 4. 第二轮:锁定 Fence FD 与 Parcel 的严格相关性
- 5. 第三轮:用同步事务否定 TF_ASYNC 假设
- 6. 第四轮:下沉到 Binder Driver 定位失败分支
- 7. 根因:缺少接收端 fd use 权限
- 8. 最小修复方案
- 9. 修复后的端到端验证
- 10. 调试过程中得到的工程经验
- 11. 总结
术语表
| 术语 | 英文 | 说明 |
|---|---|---|
| ExternalTexture | External Texture | Flutter 将平台侧图像 Buffer 接入渲染管线的纹理机制 |
| Release Fence | Release Fence | 表示消费者已经完成 Buffer 使用的同步对象 |
| ProducerListener | Producer Listener | BufferQueue 通过 Binder 通知生产端 Buffer 已释放的回调接口 |
| Binder | Binder IPC | OpenHarmony 中用于跨进程调用和对象传递的 IPC 机制 |
| BR_FAILED_REPLY | Binder Failed Reply | Binder Driver 返回给用户态的事务失败结果,本问题中表现为 29201 |
| MessageParcel | Message Parcel | Binder 用户态序列化参数及 Binder Object 的载体 |
| File Descriptor | File Descriptor | 进程用于引用内核对象的整数句柄,文中简称 FD |
| sync_file | Sync File | Linux 用 FD 表示同步 Fence 的内核对象类型 |
| BufferQueue | Buffer Queue | 管理图形 Buffer 申请、提交、释放及复用的队列 |
| freeList | Free List | BufferQueue 中用于保存可再次请求 Buffer Sequence 的集合 |
| LSM | Linux Security Modules | Linux 内核安全钩子框架,SELinux 通过该框架实施访问控制 |
| SELinux | Security-Enhanced Linux | 基于安全域和对象类型实施强制访问控制的安全机制 |
fd use |
File Descriptor Use Permission | SELinux 中允许目标域使用由另一安全域创建或持有的 FD 的权限 |
| TRACE3 | Third-stage Trace | 本次调查在 Binder Driver FD 翻译边界增加的第三阶段诊断探针 |
注:术语按在本文中出现的先后顺序排列。
1. 问题现象与调查目标
1.1 表面症状
Flutter video_player 在 OpenHarmony 上通过 ExternalTexture 播放视频时,最初可以正常工作,但播放一段时间后画面停止,随后音频和媒体同步链路出现持续异常。日志中的高频症状包括:
IProducerListener SendRequest ret=29201
AudioSink: buffer queue is empty
MediasyncManager: Media progress lag
从用户体验看,这是一个“视频播放一段时间后卡死”的问题;从系统行为看,则是 ProducerListener 的 Buffer Release 回调开始大量失败,并逐步拖垮 Buffer Pool 周转。
调查目标不是让错误暂时消失,而是回答三个问题:
- 29201 发生在哪一层;
- 哪一个事务属性决定成功或失败;
- 如何用最小修改修复根因,而不绕过 Fence、关闭 SELinux 或改变 BufferQueue 语义。
1.2 为什么不能直接修改 BufferQueue
早期日志显示失败后会执行 CancelBufferLocked(),因此很容易把注意力放在 BufferQueue 的失败恢复逻辑上,例如“不再把失败 Buffer 放回 freeList_”或“忽略 ProducerListener 返回值”。
但这些做法只能改变错误后的表现,不能解释 Binder 为什么拒绝事务。更严重的是,它们可能造成 Buffer 泄漏、生产者永久失去 Buffer,或者掩盖跨进程同步语义已经失效的事实。
因此本次调查坚持先定位首个跨层失败点,再决定修复位置。
2. 故障链路与验证方法
2.1 跨层调用链
问题横跨 Flutter Engine、图形 BufferQueue、媒体 Codec、Binder 用户态和 Binder Driver:
Flutter Raster Thread
→ ExternalTexture 释放上一帧
→ 创建 Release Fence
→ NativeImage / ProducerSurface
→ BufferQueue 请求已释放 Buffer
→ ProducerListenerProxy 写入 Sequence 与 Fence
→ MessageParcel 携带一个 FD Binder Object
→ Binder Driver 翻译 FD
→ av_codec_service 中的 Listener Stub
→ Codec 回收 Buffer
这条链路中的任意一层都可能制造相似的“回调失败”表象,因此必须用逐轮排除法,把失败边界从上层一路收窄到内核安全钩子。
2.2 单变量验证原则
每轮只测试一个主要假设,并明确保持不变的变量:
| 轮次 | 单一问题 | 保持不变的主要变量 |
|---|---|---|
| Trace 1 | Listener 是否已销毁或注销 | Fence、Parcel、Binder 模式、BufferQueue 行为 |
| Trace 2 | FD/Parcel 是否决定事务结果 | Listener 生命周期、Binder 异步模式 |
| 实验 A | TF_ASYNC 是否导致 FD 事务失败 |
相同 Fence、Parcel 布局、Listener、BufferQueue |
| Trace 3 | Binder FD 翻译在哪个分支失败 | 恢复原始 TF_ASYNC,不改变用户态行为 |
| 最终修复 | 缺少的 SELinux 权限是否为根因 | Flutter、BufferQueue、Binder Driver、Kernel 行为 |
这种设计保证每次结果都能确认或否定一个清晰假设,避免多个修改叠加后无法知道究竟是哪一项生效。
3. 第一轮:排除 ProducerListener 生命周期问题
3.1 假设与探针
第一轮假设是:ProducerListener Stub 在 29201 出现前已经被 Destroy、Reset 或 Unregister,导致回调对象失效。
为验证该假设,在 Media Codec、ProducerSurface、Listener Stub 和 BufferQueue 中增加统一生命周期标记:
LISTENER_CREATE / RESET / DESTROY
PRODUCER_SURFACE_REGISTER / UNREGISTER / DESTROY
BUFFER_QUEUE_REGISTER / UNREGISTER
HDECODER_REGISTER / CALLBACK / UNINITIALIZED
RELEASE_SURFACE
同时关联 Surface ID、Listener Proxy、远端对象、Stub、Binder Handle 和 Buffer Sequence,避免把不同播放实例的日志错误拼接到一起。
3.2 关键证据
首次失败的 Sequence 为:
seq=36241505
Binder error=29201 / BR_FAILED_REPLY
在该失败之前,没有出现 Listener Reset、Destroy、Unregister、Surface Release 或 Codec Uninitialized。
更关键的是,同一 Listener、同一 Binder Handle 在该失败之后仍成功处理了后续 Sequence:
36241506
36241507
36241508
36241509
36241511
36241512
如果 Listener 对象已经整体死亡,后续事务不可能继续通过同一对象到达 Stub。因此一个失败 Sequence 后仍有成功 Sequence,是反证生命周期假设的决定性证据。
3.3 第一轮结论
29201 是事务级失败,而不是 ProducerListener 对象整体失效。
这一轮排除了:
- Listener Stub 提前销毁;
- Listener Reset 或 Unregister;
- Surface 提前释放;
- Codec 提前进入 Uninitialized;
- Binder Handle 整体死亡。
调查方向由“对象是否还活着”转向“失败事务和成功事务的载荷有什么不同”。
4. 第二轮:锁定 Fence FD 与 Parcel 的严格相关性
4.1 观测维度
第二轮同时记录 ProducerListenerProxy、SyncFence 序列化和 BufferQueue 状态,重点字段包括:
Sequence
Fence 对象与原始 FD
fcntl(fd, F_GETFL) 与 errno
WriteFileDescriptor 返回值
Parcel data_size
Parcel offsets 数量
ContainFileDescriptors
SendRequest 返回值
Buffer State
freeList_ 大小
CancelBufferLocked 前后状态
本轮并不是尝试修复,而是比较相邻的成功与失败事务,寻找没有例外的分流条件。
4.2 无 FD 与有 FD 的精确分流
共分析 286 次 ProducerListener 发送:
| 事务载荷 | Parcel 特征 | 结果 | 数量 |
|---|---|---|---|
| 无有效 Fence FD | data_size=72、offsets=0、has_fd=0 |
成功 | 34/34 |
| 携带有效 Fence FD | data_size=96、offsets=1、has_fd=1 |
29201 | 252/252 |
结果没有例外:
有效 FD 成功次数:0
无 FD 失败次数:0
第一次失败事务中,FD 在用户态仍然有效,序列化也成功:
sequence=33816622
fence_fd=120
fcntl(F_GETFL)=0
errno=0
WriteFileDescriptor=true
Parcel data_size=96
Parcel offsets=1
ContainFileDescriptors=true
SendRequest=29201
紧随其后的无 FD 事务使用同一条 Listener 链路,却成功返回:
sequence=33816623
fence_fd=-1
Parcel data_size=72
Parcel offsets=0
ContainFileDescriptors=false
SendRequest=0
这证明失败条件不是 Sequence、Listener 生命周期、源 FD 已关闭或 Parcel 写入失败,而是“跨进程事务携带一个有效 FD 对象”。
4.3 BufferQueue 如何放大一次 Binder 失败
ProducerListener 发送失败后,BufferQueue 执行 CancelBufferLocked():
失败前:
state=BUFFER_STATE_REQUESTED
free_list_size=0
Cancel 后:
old_state=BUFFER_STATE_REQUESTED
new_state=BUFFER_STATE_RELEASED
free_list_size=1
该 Sequence 被重新放入 freeList_。下一次 Release 时,它又会被 RequestBuffersForListenerLocked() 取出,并携带 Fence FD 再次发送。
于是形成放大循环:
FD 事务失败
→ CancelBufferLocked
→ Sequence 回到 freeList_
→ 下一轮再次 Request
→ 相同类型 FD 事务再次失败
→ 失败 Sequence 和请求数量持续积累
→ Buffer Pool 周转能力下降
→ 视频停止
→ AudioSink 饥饿
→ MediaSync 延迟
BufferQueue 不是首个根因,但它的恢复语义会反复触发同一个安全拒绝,把单次 Binder 失败放大为持续播放故障。
5. 第三轮:用同步事务否定 TF_ASYNC 假设
5.1 单一行为修改
第二轮已经确认“有效 FD”与失败严格相关,但尚不能判断是否只有异步 Binder 事务携带 FD 时才失败。
实验 A 只修改 Sequence+Fence 回调的 MessageOption:
// 原始行为
option.SetFlags(MessageOption::TF_ASYNC);
// 实验 A
option.SetFlags(MessageOption::TF_SYNC);
Fence、FD、Parcel 布局、Listener 对象、BufferQueue 状态机和其他 ProducerListener 方法均保持不变。
5.2 同步实验结果
180 秒运行结果如下:
| 同步事务载荷 | 结果 | 数量 |
|---|---|---|
| 无有效 Fence FD | 成功 | 25/25 |
| 携带有效 Fence FD | 29201 | 218/218 |
附加现象:
有效 FD 成功:0
Listener Callback Enter/End:25/25
SEND_FAIL_STATE:218
CANCEL_BUFFER_DONE:218
AudioSink buffer queue is empty:11811
Media progress lag:1208
只有不携带 FD 的事务到达 Listener Stub;把事务改成同步没有改变 FD 事务的失败结果。
5.3 第三轮结论
TF_ASYNC + FileDescriptor 不是充分根因。同步和异步模式下的分流完全一致:
无 FD → 成功
有 FD → 29201
因此后续不再围绕同步/异步模式试错,而是恢复原始 TF_ASYNC,把诊断边界下沉到 Binder Driver 的 FD 翻译路径。
6. 第四轮:下沉到 Binder Driver 定位失败分支
6.1 binder_translate_fd 的判定树
在 Linux 6.6 Binder Driver 中,FD Binder Object 会进入:
kernel/linux/linux-6.6/drivers/android/binder.c
binder_translate_fd()
该函数的关键失败分支可以归纳为:
target_node->accept_fds == false
→ BINDER_FD_TARGET_REJECT
fget(fd) == nullptr
→ BINDER_FD_INVALID
security_binder_transfer_file() < 0
→ BINDER_FD_SECURITY_REJECT
分配 fd_fixup 失败
→ BINDER_FD_ALLOC_FAIL
全部检查通过
→ BINDER_FD_TRANSLATE_OK
Trace 3 只在这些分支增加诊断日志,不修改 Binder 返回语义。
6.2 第一次 Kernel 构建为何不能作为证据
第一次 Trace 3 顶层 OHOS 构建退出码为 0,镜像也成功生成,但烧录后没有出现任何新增 TRACE3 事件。
进一步核查发现:
源码 binder.c 已修改
但 staged binder.c 仍是旧版本
binder.o 仍是旧产物
vmlinux 与 Kernel Image 不含 TRACE3
boot_linux.img 实际打包了旧 Kernel
原因是 Kernel staged source、OBJ 和 checkpoint 被增量构建错误复用。
因此这轮结果被标记为:
build_success_artifact_invalid
这是一个重要区别:顶层构建成功只证明构建工具正常退出,不证明目标源码进入了最终运行镜像。
6.3 强制重建与二进制门禁
在确认没有其他 rk3568 构建占用输出目录后,定向刷新:
out/kernel/src_tmp/linux-6.6
out/kernel/OBJ/linux-6.6
out/kernel/checkpoint/last_build.info
out/kernel/checkpoint/current_build.info
随后不再只看构建退出码,而是逐层验证 TRACE3:
修改后的 binder.c
→ staged binder.c 含 5 个 TRACE3 事件
→ binder.o LLVM bitcode 含 5 个事件字符串
→ vmlinux 含全部事件
→ Kernel Image 含全部事件
→ boot_linux.img 时间晚于 Kernel Image
其中 binder.o 是 LLVM Bitcode,必须使用仓库内 llvm-dis 反汇编后检查;直接使用 strings 或 grep -a 可能得到假阴性。
6.4 SELinux 拒绝的决定性证据
强制重建后的运行统计为:
Hilog 29201:493
BINDER_FD_TRANSLATE_OK:111
BINDER_FD_SECURITY_REJECT:245
BINDER_FD_TARGET_REJECT:0
BINDER_FD_INVALID:0
BINDER_FD_ALLOC_FAIL:0
第一次目标失败由 Kernel 直接记录:
[PRODUCER_LISTENER_TRACE3]
event=BINDER_FD_SECURITY_REJECT
from=2103:2342
to=489
fd=122
security_ret=-13
对应关系为:
源进程:video_player example
源安全域:u:r:debug_hap:s0
目标进程:av_codec_service
目标安全域:u:r:av_codec_service:s0
FD:anon_inode:sync_file
security_binder_transfer_file():-13 / EACCES
随后 Binder 输出:
translate fd failed
transaction async ... failed .../29201/-1
size 96-8
96-8 与 Trace 2 中有 FD Parcel 的 data_size=96、一个 8 字节 Object Offset 完全对应。用户态载荷证据和 Kernel 失败分支在这里闭合。
7. 根因:缺少接收端 fd use 权限
7.1 为什么 Binder 权限存在仍会失败
策略中已经存在:
allow av_codec_service normal_hap_attr:binder { call transfer };
该规则允许 av_codec_service 与普通 HAP 属性域进行 Binder 调用和 Binder 对象传递,但它不等于允许目标进程使用来源域持有的 FD。
debug_hap 的类型声明包含 normal_hap_attr,因此本次传递中的来源类型可以由该属性覆盖。目标 av_codec_service 在接收 sync_file FD 时,还需要独立的:
normal_hap_attr:fd use
缺少该权限时,Binder Driver 已经拿到有效源 FD,也确认目标节点接受 FD,但在 security_binder_transfer_file() 的 LSM 检查处返回 -EACCES。
7.2 完整故障链
根因确认后的完整链路为:
Flutter ExternalTexture 释放上一帧
→ 创建 Release Fence
→ Fence 表示为 anon_inode:sync_file FD
→ ProducerListenerProxy 成功写入 MessageParcel
→ Binder Driver 进入 BINDER_TYPE_FD
→ binder_translate_fd()
→ 目标节点允许 FD
→ 源 FD 有效
→ security_binder_transfer_file()
→ SELinux 缺少 av_codec_service 对 normal_hap_attr:fd 的 use 权限
→ 返回 -EACCES
→ Binder 将事务失败映射为 -EPERM
→ 用户态收到 BR_FAILED_REPLY / 29201
→ Listener Stub 未收到该次 Release 回调
→ BufferQueue 执行 CancelBufferLocked
→ Sequence 返回 freeList_
→ 后续再次取出并重传
→ 同一安全拒绝反复发生
→ Buffer Pool 周转被拖垮
→ 视频停止、AudioSink 饥饿、MediaSync 延迟
这也解释了为什么无 FD 事务始终成功:它们不会触发 security_binder_transfer_file(),因而不会碰到缺失的 fd use 权限。
8. 最小修复方案
8.1 一行 Policy 修改
最终只修改一个文件:
base/security/selinux_adapter/sepolicy/ohos_policy/multimedia/av_codec/system/av_codec_service.te
在已有 Binder 权限旁增加一条规则:
allow av_codec_service normal_hap_attr:binder { call transfer };
+allow av_codec_service normal_hap_attr:fd { use };
8.2 为什么这是最小权限修复
该规则的四个维度均与已确认的失败边界一致:
| 维度 | 修复范围 | 依据 |
|---|---|---|
| 目标域 | av_codec_service |
Kernel 记录的接收进程安全域 |
| 来源类型 | normal_hap_attr |
debug_hap 具有该属性,且现有 Binder 规则已按该属性授权 |
| 对象类别 | fd |
失败只发生在携带 File Descriptor 的事务 |
| 权限 | use |
security_binder_transfer_file() 检查目标域能否使用来源 FD |
修复没有采用以下高风险方案:
- 不关闭 SELinux;
- 不把目标域设为 permissive;
- 不对所有 HAP 或所有服务开放宽泛权限;
- 不丢弃 Release Fence;
- 不忽略 ProducerListener 错误;
- 不修改 BufferQueue 状态机;
- 不把异步事务永久改为同步。
8.3 构建与镜像验证
最终修复镜像构建结果:
构建命令:./build.sh --product-name rk3568 --ccache --target-cpu arm64
开始:2026-07-17 08:37:00 +08:00
结束:2026-07-17 08:59:48 +08:00
退出码:0
SELinux Check:通过
必需镜像:13/13 通过
镜像包:
C:\tools\dayu200_flash\images\rk3568_OpenHarmony_6.1.0.32_20260717_085852.tar.gz
SHA256:9dc8228d09544f28b3e63ae51513237790a82c3ba6f85d2d6950ec646939d3cb
烧录后的系统保持:
OpenHarmony 6.1.0.32
SELinux Enforcing
Linux 6.6.101
这证明修复是在强制访问控制开启状态下生效,而不是通过弱化系统安全模式绕过问题。
9. 修复后的端到端验证
9.1 首次三分钟运行
应用完成 HAP 构建、安装、EntryAbility 启动、Dart VM Service 连接和 DevFS 同步,并持续运行约三分钟。
统计结果:
BINDER_FD_SECURITY_REJECT:0
Binder 29201:0
BINDER_FD_TRANSLATE_OK:244
ExternalTexture RELEASE_BEGIN:100
ExternalTexture RELEASE_END:100
Release 非零返回:0
AudioSink buffer queue is empty:0
Media progress lag:0
CancelBufferLocked:0
修复后既没有安全拒绝,也没有通过“停止发送 FD”来规避问题,因为 Kernel 明确记录了 244 次 FD 翻译成功。
9.2 五次独立重复运行
为避免单次运行偶然通过,每轮都执行:
- 强制停止应用;
- 清空 Hilog;
- 清空 Kernel dmesg;
- 重新启动 EntryAbility;
- 运行约 60 秒;
- 独立保存 Hilog、dmesg 和进程信息。
| Run | Security Reject | Translate OK | 29201 | Release Begin | Release End | 非零 Release | Audio Empty | Media Lag | CancelBuffer |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 0 | 120 | 0 | 102 | 102 | 0 | 0 | 0 | 0 |
| 2 | 0 | 120 | 0 | 99 | 100 | 0 | 0 | 0 | 0 |
| 3 | 0 | 130 | 0 | 104 | 104 | 0 | 0 | 0 | 0 |
| 4 | 0 | 130 | 0 | 104 | 104 | 0 | 0 | 0 | 0 |
| 5 | 0 | 120 | 0 | 104 | 104 | 0 | 0 | 0 | 0 |
| 合计 | 0 | 620 | 0 | 513 | 514 | 0 | 0 | 0 | 0 |
Run 2 的 Begin/End 相差 1,是 60 秒采集窗口截在一次 Release 过程边界;该轮非零 Release、Security Reject、29201 和 CancelBuffer 均为 0。
9.3 修复前后对比
| 指标 | 根因确认镜像 | 最终修复后 |
|---|---|---|
| BINDER_FD_SECURITY_REJECT | 245 | 首次运行 0;5 次合计 0 |
| BINDER_FD_TRANSLATE_OK | 111 | 首次运行 244;5 次合计 620 |
| Binder 29201 | 493 | 首次运行 0;5 次合计 0 |
| AudioSink buffer queue is empty | 5551 | 首次运行 0;5 次合计 0 |
| Media progress lag | 567 | 首次运行 0;5 次合计 0 |
| Release 非零返回 | 故障链中持续失败 | 首次运行 0;5 次合计 0 |
| CancelBufferLocked 故障放大 | 持续发生 | 首次运行 0;5 次合计 0 |
不同运行的时长和日志窗口并不完全相同,因此这里不比较比例,只比较目标错误是否仍然出现,以及成功 FD 翻译是否持续发生。
10. 调试过程中得到的工程经验
10.1 29201 只是结果码,不是根因
BR_FAILED_REPLY / 29201 只能说明 Binder 事务失败。它不能区分对象死亡、Parcel 错误、FD 无效、目标节点拒绝 FD、内存分配失败或 SELinux 拒绝。
有效的调查顺序是:
生命周期
→ 事务载荷
→ 同步/异步控制实验
→ Binder Driver 失败分支
→ LSM/SELinux 具体权限
如果只围绕 29201 猜测,很容易在 Listener 生命周期、Binder 模式或 BufferQueue 上反复修改,却始终触碰不到真正失败的安全边界。
10.2 顶层构建成功不等于目标代码进入镜像
Trace 3 首次构建退出码为 0,但 Kernel 探针没有进入 staged source 和二进制。若直接基于运行时“没有出现新日志”判断代码分支未执行,会得到错误结论。
Kernel 改动至少需要验证:
source
→ staged source
→ object / LLVM bitcode
→ vmlinux
→ Image
→ boot_linux.img
构建成功是必要条件,不是产物有效性的充分条件。
10.3 失败恢复逻辑可能把单次错误放大成系统卡死
CancelBufferLocked() 的本意是让发送失败的 Buffer 回到可复用状态,但当失败条件是稳定、可重复的安全策略拒绝时,这个恢复动作会把同一 Sequence 再次送回失败路径。
因此分析系统性卡死时,不仅要定位首个失败,还要追踪错误后的状态回流:
失败后状态去了哪里
→ 是否重新进入同一路径
→ 重试是否有退出条件
→ 是否逐步耗尽共享资源
10.4 SELinux 修复应放在使用 FD 的目标域
跨进程 FD 传递不是简单的“发送者能否发送”。Binder Driver 会检查目标域是否有权使用来源域持有的 FD。
本问题的授权方向是:
allow av_codec_service normal_hap_attr:fd { use };
而不是反向给 debug_hap 增加对 av_codec_service 的 FD 权限。理解“谁创建或持有 FD、谁接收并使用 FD”是写对规则方向的关键。
11. 总结
| 阶段 | 核心问题 | 关键证据 | 结论 |
|---|---|---|---|
| Trace 1 | Listener 是否死亡 | 29201 后同一 Listener 仍处理后续 Sequence | 生命周期假设被否定 |
| Trace 2 | 什么载荷决定失败 | 无 FD 34/34 成功,有 FD 252/252 失败 | 失败与有效 FD 严格相关 |
| 实验 A | 是否仅异步 FD 事务失败 | 同步有 FD 218/218 仍失败 | TF_ASYNC 不是根因 |
| Trace 3 | Binder FD 翻译在哪一分支失败 | security_binder_transfer_file=-EACCES |
SELinux/LSM 拒绝得到确认 |
| 最终修复 | 如何最小授权 | 增加 av_codec_service normal_hap_attr:fd use |
一行 Policy 修复 |
| 回归验证 | 是否稳定解决 | 三分钟运行 + 5 次独立运行均无 Reject/29201 | Bug 已解决 |
最终结论可以浓缩为一句话:
Release Fence 本身、Parcel 序列化、Listener 生命周期和 Binder 同步模式都没有问题;真正缺失的是
av_codec_service使用普通 HAP 属性域所传 FD 的 SELinuxuse权限,一行定向 Policy 即可在保持 SELinux Enforcing 的前提下恢复 video_player 连续播放。

浙公网安备 33010602011771号