# [Bug] 4K HEVC 103Mbps DirectPlay 反复缓冲、只有 0.48× 实时:ExoPlayer 的 byte 预算被 tier 固定在 80MiB(约 6.2 秒) ## 环境 | 项 | 值 | |---|---| | 设备 | Xiaomi TV S Pro Mini LED(MiTV-MFTR0) | | SoC | MediaTek MT9653(`ro.board.platform=mt5879`),4×Cortex-A73 | | 内存 | 3.86 GB(`totalMem` 3860 MiB) | | 系统 | Android 14(SDK 34),HyperOS `3.0.101.0.UFTMATV` | | ABI | `armeabi-v7a,armeabi`(纯 32 位,无 arm64) | | 客户端 | `com.entryway.tv` v1.0.13(versionCode 57,targetSdk 36) | | 服务端 | Emby 4.10.0.40,局域网,DirectPlay | | 网络 | Wi-Fi 5GHz,RSSI -20dBm,协商 1201Mbps(5320MHz) | | 客户端设置 | 优先直连播放=开;服务器转码=关;禁用 HEVC=关;串流画质=Original;播放内核分别测 ExoPlayer / mpv;「播放缓存大小」250MB 与 1000MB 各测一轮 | ## 现象 - 服务端 `PlayMethod=DirectPlay`,**无** `TranscodingInfo`(不是转码导致) - 每约 10 秒周期性停顿转圈,`IsPaused` 反复切换 - 系统时间过 118 秒,播放位置只推进 60 秒(**0.51×** 实时) - 吞吐呈锯齿:20 → 116Mbps 来回跳,均值 53–55Mbps ## 复现步骤 1. 片源:4K HEVC 10bit HDR10,平均码率 **103.2Mbps**(76.3GB / 99min / DTS-HD 7.1,REMUX 级别) 2. Entryway 直接播放该片(DirectPlay),内核选 ExoPlayer;再把内核切 mpv 各测一轮 3. 采样:每 2 秒读 `/proc/net/dev` 的 wlan0 rx 换算 Mbps,同时读 Emby 会话的 `PlayState.PositionTicks` 推进量 > 复现关键不是具体片源,而是**码率**:只要平均码率 ≥100Mbps(即 80MiB 的 byte 预算撑不到 6.5 秒),任何 4K HEVC 直链都能复现。作者若需素材,用本地 HTTP 服务挂一个 100Mbps+ 的 4K HEVC 文件即可,不需要我这条链。 ## 量化数据(2 秒采样) ``` t(s) Mbps pos t(s) Mbps pos t(s) Mbps pos 2 56.3 107 44 34.7 128 84 61.3 147 7 38.2 112 48 46.3 129 88 75.7 147 11 60.6 112 51 64.9 130 92 39.0 152 15 66.9 113 56 49.7 135 96 53.5 153 19 22.9 117 60 68.9 136 101 73.2 157 23 64.6 117 64 74.0 137 105 27.2 161 27 67.7 117 68 20.9 141 108 73.7 161 31 28.9 122 72 28.2 141 112 69.8 162 35 43.7 123 75 70.1 141 116 30.0 166 39 72.1 123 80 47.3 145 120 69.1 167 ``` - 均值 53.3Mbps,**峰值 116.3Mbps(已超过片源码率 103.2)**,最低 20.9;一半采样点低于码率 - 播放推进 60s / 实际 118s = **0.48×** ### 对照实验 | 改动 | 结果 | |---|---| | 内核切 mpv | 均值 59.7Mbps,峰值 80.4,推进 0.53×(同样卡) | | 同机同片同 URL 换 Kodi 21.3 | **105.4Mbps / 1.05×**,预缓冲期均值 136Mbps | | 「播放缓存大小」250MB → 1000MB | 均值 55.0 / 峰值 116.3 / 0.48×,**无任何变化**;进程 PSS 仍 319MB | | 同链路换设备(NAS curl 同一直链) | 见下方「已排除」 | ## 关键日志 ``` I EntrywayTier: tier=MID source=AUTOMATIC reasons=high-base-not-met, high-gpu-unverified I EntrywayTier: memory-pressure level=20 tier=MID W/DefaultLoadControl: Target buffer size reached with less than 500ms of buffered media data D/MediaCodec: [FWK][onReleaseOutputBuffer] OMX.MS.HEVC.Decoder: discardBuffer: ... ``` 第一条是启动即打出的档位判定。按 1.0.13 的代码(`defpackage/ry6.c()`): ```java long[] jArr = {maxMemory / heapDivisor, totalMem / ramDivisor, maxMiB * 1048576L}; targetBytes = min(传入值, jArr[0], jArr[1], jArr[2]); // 再与 minMiB 取大 new DefaultLoadControl(..., targetBytes, false /* prioritizeTimeOverSizeThresholds */, ...); ``` MID 档参数:`heapDivisor=5, ramDivisor=32, minMiB=24, maxMiB=80, minMs=30000, maxMs=60000, startMs=2000, rebufferMs=4000`。 代入本机:`min(512MB/5=102MiB, 3860/32=120MiB, 80MiB) = 80MiB = 83,886,080 字节`,即 **103.2Mbps 片源只能缓冲 6.2 秒**。因为 `prioritizeTimeOverSizeThresholds=false`,30 秒的 `minBufferMs` 完全用不上。 mpv 侧同理:`pa4` 里 `min(minAheadMiB, mpv.getMaxAheadMiB())`,MID = 96MiB ≈ 7.4 秒,对应它 0.53× 的表现。 补充一条排错线索:「播放缓存大小」这个 UI 项对应的是 `player_cache_size_mb` → `SimpleCache`(`cacheDir/player_media_cache`,钳在 50MiB–51.2GB)**磁盘**缓存,与内存缓冲无关。所以调它不会改变这个现象,PSS 319MB 也不能用来判断内存缓冲大小——这两个观察互相不矛盾,别被它们带偏。 ## 已排除 | 环节 | 实测 | |---|---| | 服务端转码 | 无 `TranscodingInfo`;日志无 `StreamTranscode` | | CDN 节点 / UA 差异化 | 同一 URL 分别用 `Entryway-TV/1.0.13 (android; 34)` 与 Kodi UA 取 302:**Location 的 host 与对象 key 完全相同**,只有签名和时间戳不同 | | HTTP/2 | 强制 `--http2` 仍协商回 HTTP/1.1(服务端不支持 h2) | | CDN 取流上限 | 局域网 5 种 UA(Entryway / Kodi / ExoPlayerLib / okhttp / curl)12 秒连续拉流:**52–93MB/s(420–745Mbps)**;把 URL 钉到电视自己解析出的 CDN IP(`--resolve`)仍是 **92.1MB/s** | | 请求形态(分片 / 连接复用) | 20×4MB 单连接 8.3s、每片新连接 7.8s;10×1MB 每片新连接 2.86s(28Mbps)——都远高于 55Mbps | | 设备闪存 | `dd` 写 400MB:**178MB/s**;读 1.7GB/s(磁盘缓存不是瓶颈) | | 设备内网链路 | Wi-Fi6 协商 1201Mbps,RSSI -20dBm | 结论:`55Mbps` **不是取流上限**,而是消费速率——同一会话峰值 116Mbps 已经超过片源码率,说明链路和 CDN 都能供得上;真正限制是 80MiB 的字节预算只够 6 秒,填满 → 播 6 秒 → 再填 6 秒,平均自然落在码率的一半左右。 ## 推断 1. **不是网络、不是 CDN、不是 UA、不是服务端转码**(数据见上)。 2. **不是用户设置失效**:UI 里的缓存项是磁盘缓存,本来就不作用于内存缓冲。 3. **是 byte 型 targetBytes 与「高码率片源」不匹配**:`targetBytes` 按设备档位写死上限(MID=80MiB),而没有按当前码率折算时长;同时 `prioritizeTimeOverSizeThresholds=false` 让 byte 预算压过 30 秒的时间预算。103Mbps 下 80MiB ≈ 6.2 秒,缓冲一满就停止加载,网络再快也用不上。 4. 两台内核都只有 0.5× 也吻合:mpv 的 `maxAheadMiB` 同样是档位写死的 96MiB(≈7.4 秒),不是各自内核的实现差异。 5. 32 位进程 `maxMemory=512MB`,`heap/5` 把上限压在 102MiB,这是 MID 档在 32 位设备上的天花板。 ## 建议排查点(供参考,不强制) - **按码率折算字节预算**:`targetBytes = max(tierBytes, videoBitrate/8 * 30)`(30 秒),或至少对 `bitrate > 50Mbps` 的片源放开到 300MiB 级。 - **`prioritizeTimeOverSizeThresholds = true`**:让 `minBufferMs`(30s)在冲突时优先,避免 6 秒预算把高码率片源锁死。 - **32 位机型的内存来源**:`Runtime.maxMemory()/heapDivisor` 对媒体缓冲偏保守(媒体数据本来就在 native 层)。可考虑改用 DirectByteBuffer / `Debug.getNativeHeapSize` 一侧的估算,或对 32 位进程单独放宽 `heapDivisor`。 - **可选**:把「缓冲时长」而不是「缓存大小」暴露成设置项;或在 `EntrywayTier` 日志里已有 `targetBytes` 的基础上,再打一行「当前码率下可缓冲秒数」,用户自查会快很多。 - `EntrywayTier` 这条日志非常好用,排查时一眼定位档位,建议保留。 ## 备注 - 本机为纯 32 位系统(`abilist=armeabi-v7a`),无法安装 64 位构建;按 `DeviceTierPolicy` 的规则,HIGH 档要求 `totalMem≥3500 && sdk≥30 && is64BitProcess`,这台机器结构上永远进不了 HIGH。 - 我也试过把「性能模式」手动切到高端做对照(HIGH 档 `maxMiB=256`,理论上 `targetBytes` 会到 `512/3≈170MiB`、约 13 秒);如果作者需要这一组数据我可以补测,但那是绕过自动判定的临时手段,不是修复。 - 另一个独立问题(杜比视界 P7 切换/跳转瞬间 `dequeueInputBuffer failed` 刷屏)不属本 issue,另行反馈。