feat: 集成 rtsp-client-android 零缓冲 RTSP 渲染,替换 ExoPlayer

- 新增本地模块 rtspclientlibrary/(拷贝自 rtsp-client-android v5.6.5 library-client-rtsp)
- RtspVideoPlayer.kt 重写:RtspSurfaceView + AndroidView,VideoCodec 枚举映射
  DecoderType.HARDWARE/SOFTWARE;指数退避自动重连(1s→30s,URL/codec 变化重置)
- 延迟统计:每 2s 轮询 statistics 输出 videoDecoderLatencyMsec/networkLatencyMsec
- 本地修复两个已知坑:CSD 参数集顺序 sps+pps+vps -> vps+sps+pps(H.265 起播黑屏根因),
  codecType 硬编码 H264 -> 按实际 MIME 标记(H.265 宽高解析)
- FrameQueue 60 -> 20 压低积压延迟;剔除 camera 依赖(删除 bitmap 渲染路径)
- app 移除 media3-exoplayer-rtsp/ui 依赖(media3-exoplayer 由库模块提供)
This commit is contained in:
2026-08-13 09:24:28 +08:00
parent d3b2ef1102
commit 18b326d416
30 changed files with 6203 additions and 165 deletions
+18 -11
View File
@@ -37,7 +37,7 @@ com.example.m20_gamepad/
│ ├── RobotConnection.kt # 连接状态机管理(连接/订阅/心跳/断线判定)
│ └── JoystickController.kt # 摇杆输入 → 协议指令映射,含 HandStyle 多手型支持
├── video/
│ └── RtspVideoPlayer.kt # RTSP 视频流播放(ExoPlayer + Composable 包装)
│ └── RtspVideoPlayer.kt # RTSP 视频流播放(rtspclientlibrary + Composable 包装)
├── data/
│ ├── SettingsRepository.kt # 设置项读写(DataStore 封装)
│ └── AppSettings.kt # 配置数据类定义 + 默认值
@@ -115,28 +115,35 @@ Yaw = (左履带 - 右履带) / 2
视频层作为 UI 最底层背景,摇杆等控件覆盖其上(半透明)。
### 当前实现(将废弃
### 当前实现(已集成 rtsp-client-android
- 使用 **Media3 ExoPlayer + RTSP 扩展**`media3-exoplayer-rtsp:1.5.1`),实现见 `video/RtspVideoPlayer.kt`
- 已做:最小缓冲 LoadControl200/500/100/200ms、无回退缓存、时间优先)、RTSP 自动重连(指数退避 1s→30s)、`VideoCodec` 枚举(AUTO/FORCE_HW/FORCE_SW_H264/FORCE_SW_H265)→ `MediaCodecSelector`
- 默认 `MediaItem.fromUri(url)`,未自定义 `RtspMediaSource.Factory`Media3 默认 UDP 优先、收不到数据自动回退 TCP interleaved
- 本地模块 **`rtspclientlibrary/`**(拷贝自 rtsp-client-android v5.6.5 的 `library-client-rtsp`),实现见 `video/RtspVideoPlayer.kt`Compose 包装 `RtspSurfaceView` + AndroidView
- 架构:RTSP **仅 TCP interleaved**(无 UDP)→ RTP 解析(H.264/H.265)→ `FrameQueue(20)` → MediaCodec 直解 → 立即渲染。**无播放时钟 pacing,帧到即解即渲染** = 零缓冲低延迟
- `VideoCodec` 枚举(AUTO/FORCE_HW/FORCE_SW_H264/FORCE_SW_H265)→ `DecoderType.HARDWARE/SOFTWARE`(库仅区分硬/软解,FORCE_SW_* 不支持按 MIME 分别指定
- 自动重连:指数退避 1s→30s(URL/codec 变化重置);`RtspSurfaceView` 无内置重连,重连逻辑在 Composable 层
- 延迟统计:轮询 `rtspView.statistics` 每 2s 输出 `videoDecoderLatencyMsec`/`networkLatencyMsec` 到 Logcattag `RtspVideoPlayer`
- 本地修改(相对上游):
- **CSD 参数集顺序修正**`vps+sps+pps`(上游为 `sps+pps+vps`H.265 起播黑屏根因)
- **codecType 按实际 MIME 标记**(上游硬编码 H264,H.265 无法解析宽高)
- **`FrameQueue(60)``FrameQueue(20)`**(压低积压延迟)
- 剔除 camera 依赖(删除 `RtspImageView`/bitmap 渲染路径,仅保留 SurfaceView 路径)
### 延迟问题与决策(2026-08 调研结论)
- 现象:RTSP/TCP 延迟高、缓冲大;H.265 解码不高效
- **根因**:不是解码器也不是 TCP 协议本身,而是 ExoPlayer 播放器架构(缓冲水位 + presentationTime pacing 渲染同步)为"平滑播放"设计,非实时
- **决策**:弃用 ExoPlayer 的视频渲染路径,集成第三方库 **rtsp-client-android**alexeyvasilyevv5.6.5,纯 Kotlin 零缓冲直出架构)
- 库源码位于`/tmp/rtsp-client-android`(工作区外临时目录,后续需拷入项目,见 TODO
- 上游源码备份`/tmp/rtsp-client-android`(工作区外临时目录);本地拷贝见 `rtspclientlibrary/`
### rtsp-client-android 关键技术要点(集成时必读)
- 架构:RTSP **仅 TCP interleaved**(无 UDP)→ RTP 解析(H.264/H.265)→ `FrameQueue(60)` → MediaCodec 直解 → 立即渲染。**无播放时钟 pacing,帧到即解即渲染** = 零缓冲低延迟(宣传 20ms 解码延迟)
- 架构:RTSP **仅 TCP interleaved**(无 UDP)→ RTP 解析(H.264/H.265)→ `FrameQueue` → MediaCodec 直解 → 立即渲染。**无播放时钟 pacing,帧到即解即渲染** = 零缓冲低延迟(宣传 20ms 解码延迟)
- H.265`RtpH265Parser` 支持 single NAL + FU 分片重组;**AP 聚合包未实现**(有 TODO
- 硬解优先,`MediaCodecUtils.getLowLatencyDecoder` 挑专用低延迟解码器,失败自动回退软解;`MediaCodecHelper.setDecoderLowLatencyOptions``KEY_LOW_LATENCY`
- 实验性 SPS 改写(仅 H.264):`maxDecFrameBuffering=1, numReorderFrames=0`,可砍半部分硬解器延迟
- 实验性 SPS 改写(仅 H.264):`maxDecFrameBuffering=1, numReorderFrames=0`,可砍半部分硬解器延迟(已在 Composable 层开启)
- **内置延迟统计**`RtspSurfaceView.statistics``videoDecoderLatencyMsec`(解码渲染)+ `networkLatencyMsec`(网络),无需自写埋点
- **已知坑**`RtspProcessor` 拼 CSD 顺序为 `sps+pps+vps`H.265 标准应为 VPS→SPS→PPS),多数硬解器容错,若 H.265 起播黑屏第一嫌疑在此
- 依赖:`androidx.media3:media3-exoplayer`(仅用工具类 NalUnitUtil,可剔除)、`androidx.camera`YUV→BMP)、`org.jcodec`SPS 改写
- **已知坑(均已本地修复)**`RtspProcessor` 拼 CSD 顺序为 `sps+pps+vps`H.265 标准应为 VPS→SPS→PPS,本地已改);`codecType` 硬编码 H264(本地已按 MIME 修正)
- 依赖:`androidx.media3:media3-exoplayer`(仅用工具类 NalUnitUtil/MediaCodecUtil)、`org.jcodec`SPS 改写);已剔除 `androidx.camera`YUV→BMP 路径,本项目用 SurfaceView 直出无需
- 库活跃:2026-06 更新(v5.6.5),全库约 6300 行,无 JNI/无预编译 so,全量可改
---
@@ -183,7 +190,7 @@ gradlew installDebug
- 虚拟摇杆库:本地模块 `joysticklibrary/`Compose 原生,Kotlin DSL
- JSON 序列化:`kotlinx-serialization-json:1.7.3`
- RTSP 播放:`androidx.media3:media3-exoplayer-rtsp:1.5.1`
- RTSP 播放:本地模块 `rtspclientlibrary/`rtsp-client-android,内部依赖 `media3-exoplayer:1.5.1` + `jcodec:0.2.5`
- 持久化:`androidx.datastore:datastore-preferences:1.1.3`
- 导航:`androidx.navigation:navigation-compose:2.8.6`