Skip to content

[RFC] 通用输入源框架:统一 CarPlay、Android Auto、ICCOA、Android 桌面与 VNC 接入 #28

Open

Description

@fython

背景与目标

本 Issue 立项时 WheelPlay 仅支持 CarPlay,浏览器触控、后台会话、媒体接口及启动条件与 CarPlay 耦合。目标是抽象视频、触控及控制流,并保留现有音频/麦克风能力,为 Android Auto、ICCOA CarLink、Android 虚拟桌面和 VNC 提供统一接入框架。当前已合入公共框架、CarPlay 兼容适配、实验性 Android Auto/VNC/Shizuku 本机桌面,以及独立 APK 插件框架;ICCOA 协议仍未实现。具体完成度见下方“主线进展”。

本 Issue 作为技术设计与实施跟踪入口。目标是先在不退化现有 CarPlay 体验的前提下建立协议边界,再分阶段加入新输入源。

核心决策:

  • 使用协议无关的 ProjectionSourceProvider / ProjectionSession 抽象,分离协议适配、会话运行时、媒体管线及浏览器输出。
  • 支持压缩访问单元、Surface producer、framebuffer 三种视频入口,保留兼容视频的 WebRTC 直通。
  • 按能力声明和后端协商开放输入、控制及媒体路由;仅声明实际实现的能力,通用音频/麦克风 broker 和完整文本等扩展仍待完善。
  • 首版一个活动源、一个浏览器控制者,统一管理资源租约与切源代次。
  • ICCOA 先验证外部 SDK 的可获得性、离屏输出和交互能力,不将公开包装代码等同于完整协议实现。

已确认的实施优先级(2026-10-21)

优先在现有 Kotlin/C++ 技术栈内建立多输入源框架,先验证协议边界与真实接入,再评估逐步迁移 Rust。Rust 后端重构与普通 Linux 宿主暂不作为本阶段交付前提。

实施顺序:

  1. 包装现有 CarPlay,实现通用会话、输入和媒体接口,解除 Web 层对具体 CarPlay 控制器的依赖,保留现有协议兼容性及直通/回退行为。
  2. 使用模拟源验证压缩帧、Surface 和 framebuffer 三类入口,以及背压、切源、输入取消和资源释放。
  3. 将 Android Auto 作为第二个真实输入源,优先利用 DiAuto 的 Kotlin 实现验证通用接口,完成视频、触控、音频与焦点闭环。
  4. ICCOA 的 SDK 可行性验证可提前进行;正式接入依据验证结果推进,不阻塞公共框架和 AA 接入。
  5. 在第二个真实源能够通过公共接口接入、无需在公共层大量增加协议分支后,再选择稳定边界逐步迁移 Rust。

为后续跨平台迁移保留的约束:

  • 公共数据模型不携带 AirPlay、AAP protobuf 或 ICCOA SDK 类型。
  • 核心接口不依赖 Activity/Context;Surface 等放在 Android 专用接口中。
  • 明确缓冲区所有权、线程、背压、取消与停止完成语义。
  • 协议适配器与浏览器输出互不依赖。
  • 保留协议样本和契约测试,供未来 Rust 实现复用。

本节确认的是技术路线与优先级;下方阶段任务仍按实际实现和验证情况勾选。

主线进展(2026-10-27)

核对主线:ccd01f0。#33 建立公共框架与 CarPlay 兼容适配,#39/#51 接入并修正 Android Auto,#43 接入 VNC,#50 提供独立 APK 插件框架,#52 接入 Shizuku 本机桌面并扩展公共生命周期契约。本 Issue 保持开放:代码交付、自动化验证和目标设备验收分别记录,不将实验性接入或 PR 合并等同于完整验收。

方向 当前主线状态 验证边界 / 剩余工作
公共框架 provider/session、单活动源、资源占用、三类视频入口、控制租约与旧会话隔离;公共 Pointer/Scroll/Key/Back 能力、独立取消、状态/错误及启动取消机制已落地 设备性能基线、长期运行和慢网络验收待补;通用音频/麦克风 broker、完整 Text/IME/Rotary 和细粒度资源预算尚未完成
CarPlay 兼容适配保留发现、认证、协议和音频路径;宿主启动不依赖 MFi 就绪;取消语义为 RELEASE_ONLY #33 合并前用户确认人工测试通过;缺少设备矩阵和量化性能对比,不能据此宣称所有场景均无退化
三类诊断源 encoded / Surface / framebuffer 已实现;有序增量/CopyRect 合成,编码源复用直通/回退 自动化已覆盖多项所有权、切源与清理场景;真实车机和性能待验收。Surface/framebuffer 当前输出 JPEG/WebP,无 WebRTC 重编码
Android Auto 实验性 AAP 本机 Head Unit Server、USB AOA、显式 TCP;H.264、触控、夜间模式、宿主 PCM 和可选本机麦克风;原生 ACTION_CANCEL #51 修复 Annex-B 图像回退及录音权限流程。真实手机/USB、焦点/语音助手、拔插和跨源切换仍待验收;无浏览器音频/麦克风、自动无线发现/配对或自动重连
VNC #43 已实现 RFB 3.3/3.7/3.8、传统认证、Raw/CopyRect/Hextile/DesktopSize、framebuffer、Pointer/Scroll/Key 和只读模式;取消为 RELEASE_ONLY 实际服务端/车机及性能待验收;无 TLS/VeNCrypt、音频、剪贴板、完整 IME、真正多点触控或自动重连。它连接已有服务端,不创建本机桌面
Android 虚拟桌面 #52 已实现 Shizuku UserService、本机应用选择、启动尺寸/DPI 设置、Surface→JPEG/WebP、多指/Back、原生取消、显示/会话状态与清理机制 #40 跟踪验收;尚无非 root 真机 P0 结果。Android 10+/Shizuku 13+ 是代码门槛,不是已验证支持范围;无 Launcher/Home、动态布局、浏览器音频/麦克风或直接 ADB/VDM 后端
独立 APK 插件 #50 提供 API v2 SDK/示例、发现和用户启用、独立 UID/进程、Surface/触控/夜间模式、停止确认及可选专用进程退出;#52 增加可选原生取消 自动化含跨进程回归;OEM/设备和性能待验收。插件端口尚不支持音频/麦克风、键盘/鼠标、编码帧/framebuffer IPC 或多显示器
ICCOA CarLink 宿主已有插件关联、设置、启用和连接入口;正式适配采用独立插件 APK 没有 ICCOA 协议实现。仍需适用的专有 SDK、真实 Surface/输入/音频能力验证和网络/服务清理验收;示例插件不能代替 CarLink
Rust / 普通 Linux 宿主 继续使用 Kotlin/C++,保留迁移边界 后续评估,不作为本阶段交付前提

当前公共契约(#43 / #50 / #52)

  • 接入边界:内置源保留应用内 provider/session;插件通过显式 Service 绑定、Binder 控制和 Surface 句柄接入,宿主不加载插件 dex/native,也不通过 Binder 逐帧搬运图像。Shizuku 辅助进程与插件 APK 是不同的后端模型,公共接口不携带它们的协议类型。
  • 启动与就绪:实际 API 使用 CompletableFuture。open() 完成表示运行时已接纳会话并调用 activate(),awaitReady(session) 单独等待 RUNNING。ProjectionStartup 是协作取消信号;调用 runtime.stop() 请求取消并等待清理,取消信号或 future.cancel 本身不证明资源已释放。已加入晚到会话清理、获取/就绪默认各 60 秒及取消/停止清理默认 15 秒机制,失败/超时保留资源隔离;这些是实现机制,不替代竞态及设备验收。
  • 状态与错误:ProjectionSessionState 描述阶段、有效能力、输入就绪和错误;stateSnapshot()/subscribeState() 提供 ProjectionSnapshot,另含清理状态并保留终止原因。阶段为 CONNECTING / NEGOTIATING / RUNNING / STOPPING / STOPPED / FAILED,清理为 NONE / PENDING / RELEASED / UNCONFIRMED;只读源也可 RUNNING。错误按公共类别处理,backend/backendCode 和独立 diagnostic 保留后端信息。UI 本地化展示,浏览器不接收原始诊断。原设计的 StateFlow、完整轨道/配对快照及通用 ControlPort 并未原样落地。
  • 取消语义:cancelInput(reason) 用于失焦、断连、租约失效、布局变化及切源;touch(emptyList()) 仍是普通抬手。AA/Desktop 声明 NATIVE,CarPlay/VNC 为 RELEASE_ONLY,后者不能保证没有点击副作用。插件 API v2 的 nativeCancelInput 缺省 false;只有声明支持时才调用末尾追加的取消事务,旧插件继续收到兼容释放。
  • 能力与接纳结果:已提供触控上限、Pointer/Scroll/Key、独立 Back、取消语义及本机/浏览器媒体路由标志。Back 不代表完整键盘支持,本机播放也不代表浏览器音频。输入 Boolean 只表示非阻塞接纳;当前 ProjectionCommandResult 为 SENT / UNSUPPORTED / UNAVAILABLE / FAILED,不承诺远端已执行。Desktop v7 的服务支持→连接协商→会话最终启用是该后端的分层协议,尚非所有源统一的握手格式。
  • 浏览器与布局:版本化输入使用 sessionId/videoEpoch 校验,配置增加 sessionPhase/sourceError 及 Back 能力。当前会话/代次的取消可在画面或输入暂停时提交,仍校验租约所有者。Desktop 的 layoutRevision/inputEpoch 位于其 AIDL 边界,不与浏览器字段混同;旧浏览器保留兼容释放,切源仍需重新连接观看。
  • 资源与媒体边界:公共资源类型目前为 USB / WIRELESS / MICROPHONE / DISPLAY,是单活动源下的粗粒度协调;尚未实现原设计的独立 BLE/VPN、音频焦点和 codec 预算仲裁。Surface/framebuffer 重编码、通用音频/麦克风 broker、Text/IME/Rotary 仍属后续工作。

#39 阶段记录(2026-10-23,保留历史)

  • 底部中间 Tab 改为“输入源”,首页依次展示 CarPlay (iPhone)、Android Auto、ICCOA CarLink 和诊断源大卡片;具体连接/设置在二级页面,页面导航本身不切源。
  • AAP 版本交换、TLS 1.2、服务发现、通道管理、焦点及退出;H.264 1280×720 / 30 fps、逐片 ACK、有界媒体重组、公共视频输出和最多十点触控。参考 DiAuto 协议资料实现独立适配,未把整个上游 App 当作依赖。
  • 本机入口固定 127.0.0.1:5277,提供首次引导及设置跳转;用户仍需手动开启 AA 开发者模式和 Head Unit Server。远程 TCP 需要实际可访问的服务器,不等于自动无线发现/配对。
  • 音频在 WheelPlay 宿主播放;仅在已授权并选择 Android 麦克风时声明录音能力。AA 浏览器音频/麦克风尚未实现,也不包含 Now Playing、HFP、动态分辨率、HEVC 或自动重连。
  • Review 的三项修复已随 feat: 接入实验性 Android Auto 本机/USB/TCP 协议源 #39 合入:保留并重试未写完的 PCM;关键帧通知无效时执行有预算和超时的焦点恢复,仍失败则提示重连;修复 USB 授权等待注册与停止之间的竞态。回归覆盖取消、错误、恢复限频及媒体 stop/start 后继续输出。

验证证据(按提交保留,不合并为当前测试总数)

当前实现说明(固定至本次核对提交):公共框架、Android Auto、VNC、插件 API、Shizuku 桌面。

实施跟踪

以下将代码交付与设备验收拆开勾选,避免把整个 P0–P5 计划误标为完成。

下一步优先完成 AA 真机验收、#40 的非 root P0、VNC/插件验收及 CarPlay/公共框架性能与跨源切换记录。ICCOA 仍待适用 SDK;桌面专项按 #40 进展评论 跟踪。Rust/Linux 维持后续评估顺序。

以下保留原始技术方案及第 9 节阶段验收目标;接口示例与规划能力不代表当前主线已全部实现。


以下为 2026-10-21 的研究与设计基线,保留用于追溯;当前实现与验证状态以本 Issue 上方 2026-10-27 的进展及关联 PR 为准。下文保留原始设计;各节的实施注记说明与当前代码的差异。

研究基线:WheelPlay 4ed3536822998babd086e4fcb1a384e791b0e2d4;DiAuto d53dad71e02e70340d968896140b1c601b38b917;MiAuto 059b2bb6b4d7fd3b506e7c29bbf153a8e38a0c4d。本次检查了源码和项目文档,没有构建外部项目或进行协议真机验证。下文区分已观察到的实现、设计建议和待验证能力。

1. 建议与范围

2026-10-27 实施注记:本节“不引入动态插件加载”是首版研究范围。当前已支持独立 APK 的 Binder/Surface 接入,仍不向宿主加载插件 dex/native,不进行逐帧图像 Binder IPC;详见上方当前公共契约。

将 WheelPlay 定义为双向投影宿主:输入源提供画面、音频和状态,宿主返回触控、键盘、麦克风和控制请求。内部建议命名 ProjectionSource,避免与触摸输入设备的 InputSource 混淆。

采用「协议适配器 → 会话运行时 → 媒体与交互路由 → 输出端」架构。协议适配器拥有发现、认证、传输、协议状态机;运行时拥有活动源、资源仲裁、生命周期;媒体管线拥有共享编解码和输出选择;浏览器只依赖通用会话接口。

首版保持一个活动投影源、一个浏览器控制者。允许多个源被注册,不承诺多个无线协议同时广播或连接。先采用应用内模块和显式注册,不引入动态插件加载、跨进程帧 IPC 或全能协议总线。

2. 研究发现与架构约束

WheelPlay

当前基础能力已经适合复用,但接口仍携带 CarPlay 语义:

证据位置 观察 设计影响
common/.../web/WebSession.kt,TouchLease.kt 触控直接查找 CarPlayBackgroundSession;租约输入为 AirPlayContact,固定两个槽位 租约与协议编码分离;浏览器路由只捕获通用活动会话
shared/.../airplay/CarPlayMediaEngine.kt 的 MediaSink 同一接口含视频、RTP 音频、麦克风、iAP 消息、解密器创建 保留为 CarPlay 内部接口,外面增加适配层,不直接改名作为通用接口
shared/.../media/EncodedVideoSink.kt,CompressedVideoFrame.kt 已有压缩帧与 retain/close 约定;仍依赖 airplay.VideoCodec、整数 type 和解密密钥 提取中立媒体模型;保留缓冲区生命周期;解密服务留在 CarPlay 边界
common/.../web/WebVideoSource.kt 可配置 H.264/H.265 直通、请求关键帧,还创建 NativeVideoDecoder(key) 拆开源侧解密、通用视频格式、浏览器输出协商
shared/.../media/AndroidMediaSink.kt WebRTC 已显示后可关闭本地解码;回退等待随机访问帧 保留该优化与恢复语义,不统一强制解码再编码
shared/.../web/OffscreenVideoOutput.kt Activity 无关的 SurfaceTexture/EGL/JPEG 输出 可演进为共享离屏画面入口;当前没有通用 Surface→WebRTC 编码链
common/.../CarPlayHostActivity.kt 中 CarPlayBackgroundSession 快照暴露 controller、sink、尺寸;已有所有者与停止完成回调 先用兼容适配器包装,逐步迁到通用运行时
common/.../DiPlaySessionService.kt DiPlayBootstrap.ensure 失败会停止 Web 服务和 CarPlay 会话 将 CarPlay 认证就绪检查下沉到 CarPlay provider;宿主/Web 自身检查独立保留
shared/.../media/CarPlayMediaSource.kt 实际是 Now Playing 和媒体命令接口,并非视频源 可演进成可选 MediaControlPort,不要把它当整个投影抽象

上述相对路径中的 ... 均为 src/main/java/com/shilapi/xcertplay。

DiAuto:适合提取协议栈,不适合整个应用作为库接入

项目提供 USB/无线 AA 接收,README 的已验证环境是 BYD DiLink 5.1 / Android 13。不能据此推定普通 Android 服务手机的网络能力和后台行为已经验证。

  • AapVideo.process() 在分片组装完成后直接调用 VideoDecoder.decode(...):这是引入 EncodedVideoIngress 的合适接缝,但须把配置参数、时间戳和完整访问单元整理清楚,不能把任意网络分片直接作为帧交给下游。
  • AapMessageHandlerType 在视频/音频处理后发送协议 ACK。AapControl 的 maxUnacked 是消息窗口,不能把它误当完整帧数量,也不能将 ACK 绑定浏览器显示完成。
  • TouchEvent 明确包含 pointer action、actionIndex、pointerId 和坐标。现有 CarPlay 两槽位快照不能无条件当作 AA 事件使用。
  • AapTransport.hasVideoSurface() 和 gainVideoFocus() 与本地解码器、Activity 广播耦合。抽取时须改为投影输出就绪通知和语义焦点请求,否则纯压缩帧直通可能因没有本地 Surface 而无法启动。
  • 主屏与仪表视频有不同 channel;音频、媒体状态、导航、传感器也是独立通道。因此从第一版就保留 streamId / displayId,UI 可以只开放主屏。

MiAuto:公开部分是 SDK 集成层,不能视为完整开源 ICCOA 协议栈

  • app/libs/README.md 明确说明 icar-sdk.dex 是不随仓库提供的第三方专有运行库;仓库仅有编译期签名存根。缺运行库也能构建成功,CI APK 没有 CarLink 能力。
  • IccoaManager 通过 UCarAdapter.startCast(surface, w, h) 接入画面;公开桥接层没有压缩视频帧回调。IcocaProjectionActivity 的注释对解码职责表述不够清楚,应以实际 API 边界为准:宿主交入 Surface,内部处理细节待 SDK 验证。
  • IcocaBridge 提供连接状态、PIN、导航与 Surface 绑定;所检查的 Activity/bridge/manager 没有形成可供 WheelPlay 使用的触控回传链路,不能宣称已验证交互完整。
  • 配置中有 setUseCustomAudioRecord(false),音乐、通话、音频焦点回调也有空实现。音频截取、麦克风注入与控制 API 需要单独确认,不能默认为可走浏览器路由。
  • 集成文档明确禁用了继承自 AA 的 P2P/SoftAP 等副作用以避免争抢无线资源。框架不能仅靠“当前选择哪个源”来隔离资源。
  • README 仅报告 Android 11 / arm64 Newland 设备完成一次 CarWith 会话;其他品牌手机、量产车机、仪表/HUD 均不能按成熟支持处理。
  • 文档列出的特权权限并不意味着普通应用声明后就会获得;应在目标服务手机上检测实际权限和网络能力。

3. 分层与依赖方向

以下按 2026-10-27 主线 ccd01f0 展示单一架构拓扑:所有适配器集中在左侧,插件与 Shizuku 辅助进程直接连接各自的宿主适配器,画面统一汇入同一个公共视频管线,控制统一经过同一个运行时与浏览器入口。每个组件只出现一次,不再用上下两幅视图重复表示同一组件。

本地 Mermaid 11 渲染约 1502×932,宽高比约 1.61:1,处于 4:3–16:9 范围内;GitHub 字体及渲染器版本可能使尺寸略有变化。

%%{init: {"flowchart": {"nodeSpacing": 70, "rankSpacing": 25, "padding": 12, "subGraphTitleMargin": {"top": 8, "bottom": 12}}}}%%
flowchart LR
  subgraph ADAPTERS["输入源适配器 · 宿主"]
    PORT["统一会话契约<br/>ProjectionSession"]
    CPAA["CarPlay<br/>Android Auto"]
    VNC["VNC"]
    DIAG["诊断源"]
    DESKTOP["Android 桌面<br/>ShizukuDesktop<br/>Backend"]
    REMOTE["插件适配器<br/>RemotePlugin<br/>Session<br/>发现 / 用户启用门控"]
  end
  subgraph SHIZUKU["Shizuku · shell/root"]
    HELPER["Desktop UserService<br/>虚拟屏 / 输入注入"]
  end
  subgraph PLUGIN["独立 APK · 自有 UID"]
    SERVICE["ProjectionPlugin<br/>Service<br/>SDK / 画面 producer"]
    ICCOA["CarLink 插件<br/>待实现"]
    SERVICE -.-> ICCOA
  end
  RT["ProjectionRuntime<br/>生命周期 / 资源占用"]
  MP["公共视频管线<br/>创建 Surface<br/>消费画面"]
  OUT["浏览器<br/>视频输出"]
  INPUT["WebSession<br/>控制租约"]
  WEB["车机浏览器"]

  PORT <-->|"生命周期 / 控制<br/>状态"| RT
  CPAA -->|"编码视频"| MP
  VNC -->|"framebuffer"| MP
  DIAG -->|"三类入口"| MP
  DESKTOP <-->|"Desktop v7 AIDL<br/>配置 / Surface / 输入<br/>状态查询 / 清理结果"| HELPER
  REMOTE <-->|"API v2 AIDL<br/>start / Surface / 输入<br/>状态回调 / 停止确认"| SERVICE
  HELPER -->|"虚拟屏写入 Surface"| MP
  SERVICE -->|"producer 写入 Surface"| MP
  MP -->|"直通 / 图像"| OUT
  OUT -->|"WebRTC<br/>JPEG / WebP"| WEB
  RT <-->|"输入投递<br/>状态查询"| INPUT
  INPUT <-->|"触控 / 按键<br/>取消 / 状态"| WEB
Loading

所有适配器实现公共 provider/session 契约;ProjectionSession 节点表示这一共用接口,不是额外输入源。CarPlay/AA 提供编码视频,VNC 提供 framebuffer,诊断源覆盖三类入口;插件和 Shizuku 助手作为 Surface producer 直接进入公共视频管线,画面不经过 ProjectionRuntime。WebRTC 直通仅适用于兼容编码源,Surface/framebuffer 当前走 JPEG/WebP。

插件 API v2 与 Desktop v7 是两套独立协议,分别终止于对应适配器及远端进程。图中 Surface 句柄传递包含在各自 AIDL 连线中:Surface 由公共视频管线创建,宿主适配器在 start 时将句柄交给远端;producer 写入画面的方向另有明确箭头,不通过 Binder 逐帧传 Bitmap/字节数组。插件发现/用户启用由宿主注册与授权逻辑负责,图中归入插件接入入口;不是插件 Service 自行注册或自动获得权限。

双向 AIDL 连线表示请求与响应/回调关系,不表示所有调用都是同步 RPC:插件 API v2 方法为 oneway,状态与停止确认通过回调返回;Desktop 控制操作返回同步状态,touch 是 oneway。插件正常释放需清理确认,声明 processExitRequired 时还等待原 Binder 死亡;Desktop 的 stop 只确认 producer 释放,之后仍需 destroy 并等待助手退出,再释放主进程 Surface。输入取消、迟到请求隔离及清理失败处理遵守上方“当前公共契约”。

虚线 CarLink 节点只表示计划中的插件实现,不表示已取得专有 SDK 或已有 ICCOA 协议。图中展开视频、输入及跨进程主链路;已有 CarPlay/AA 本机音频、CarPlay 浏览器音频及麦克风路径按各源能力保留,尚不存在所有源共用的完整音频/麦克风 broker,也不表示插件已支持音频。

当前依赖和所有权:

  • 公共运行时:内置源与宿主插件适配层都实现协议无关的 provider/session。运行时负责接纳、切源、取消和资源占用;协议 I/O、设备状态机及媒体生产者由适配器/后端负责。浏览器不直接绑定插件 Service 或 Shizuku,也不认识 ICCOA SDK 类型。
  • 插件 API 模块:projection-plugin-api 是独立 Android Library,宿主与插件共享 AIDL、基础能力字段和 SDK 契约;它不依赖 common/shared、CarPlay、AAP、浏览器或厂商 SDK。projection-plugin-sample 是独立 APK。第三方 SDK、native 库及其服务由插件 APK 持有,宿主不加载插件 dex/native。宿主侧签名/启用授权与 IPC 的 UID/token 检查约束连接,不授予插件额外 Android 权限。
  • 跨进程画面:宿主创建并管理视频 consumer,通过 Binder 传递 Surface 句柄;插件或 Shizuku 助手持有 producer。像素不作为逐帧 Binder 参数传递。停止确认、必要的进程退出及本地清理共同决定何时释放资源;不能将宿主关闭 consumer 当成远端 SDK 已停止。
  • 两套 IPC 边界:插件 API v2 服务于普通独立 APK;Desktop AIDL v7 服务于 Shizuku 启动的特权辅助进程。二者在宿主侧适配为同一 ProjectionSession,但不是同一套协议,也不因使用 Binder 而共享权限。插件 v2 目前只开放 Surface/触控/可选原生取消/夜间模式等能力;Desktop 另有显示配置、Back、输入代次及诊断快照。
  • ICCOA 的位置:正式协议实现放在未来的 CarLink 插件 APK,接入宿主现有插件适配层。宿主内置 ICCOA 卡片仅关联插件入口;不是内置 ICCOA 协议适配器,也不意味着专有 SDK 已取得或协议已实现。
  • 输出限制:当前插件、Shizuku 桌面及 framebuffer 源走 JPEG/WebP;WebRTC 直通仅适用于兼容的编码源。Surface/framebuffer→WebRTC 编码、插件音频/麦克风与编码帧 IPC 仍未实现。

内置实现继续主要以 common/shared 中的 package 划分边界;projection-plugin-api 和示例 APK 已拆为独立 Gradle 模块。原提案中的 projection-runtime、media-pipeline-android、output-browser、各 source-* 名称仍是逻辑分层,并非都已拆成同名模块。应用组合层负责装配,公共数据模型不暴露具体源协议或厂商 SDK 类型。

4. 核心接口契约

2026-10-27 实施注记:下列 suspend / StateFlow / SourceHost / InputPort / ControlPort 保留为设计轮廓,不是 SDK API。现实现采用 CompletableFuture、ProjectionStartup、ProjectionSessionState 和运行时状态订阅;能力及命令结果也仅落地了上方“当前公共契约”所述子集。

以下是设计轮廓,不是可直接编译的实现,也不要求改写现有协议线程为协程:

interface ProjectionSourceProvider {
    val descriptor: SourceDescriptor
    suspend fun probe(host: HostCapabilities): SourceAvailability
    suspend fun open(request: OpenRequest, host: SourceHost): ProjectionSession
}

interface ProjectionSession {
    val id: SessionId
    val snapshot: StateFlow<SessionSnapshot>
    val input: InputPort
    val control: ControlPort
    suspend fun close(reason: CloseReason)
}

interface SourceHost {
    val video: VideoIngressRegistry
    val audio: AudioIngressRegistry
    val microphone: MicrophoneBroker
    val resources: ResourceLeaseScope
}

interface InputPort {
    fun submit(batch: InputBatch): InputAcceptance
    suspend fun cancelAll(): CommandResult
}

interface ControlPort {
    suspend fun execute(command: SessionCommand): CommandResult
}
  • probe 仅检查可用性,不占用无线资源或发起配对;返回 MissingRuntime、NeedsPermission、NeedsProvisioning、Unavailable 等具体原因。
  • open 在运行时授予资源后创建会话,不等待第一张画面才返回。发现、配对和协商进度由状态呈现;仅用户选中的源启动有副作用的发现流程。
  • SessionSnapshot 原子包含生命周期、当前能力、显示布局版本、可恢复错误、配对提示和各轨道状态。低频状态用 StateFlow;媒体不用 StateFlow 或无界事件总线传递。
  • SourceCapabilities 表示实现可能支持的上限;会话能力取手机、宿主权限/硬件、协议协商、输出端能力的交集,并可随连接变化。
  • CommandResult 区分 Unsupported、Unavailable、Rejected、Accepted、Confirmed、Failed;协议只有“发出”证据时不能报告已执行。
  • 通用控制覆盖请求关键帧、显示/音频焦点、夜间主题、媒体操作、退出投影等语义。原始协议消息、SDK 对象和整数命令码不穿越该边界。协议专用设置保留在适配器配置中。

5. 视频:三种入口,一个输出策略

入口 适用来源 公共管线
EncodedVideo:完整压缩访问单元 + codec config CarPlay、提取后的 AA、远程编码桌面 兼容时 WebRTC 直通;否则解码到共享画面层
SurfaceProducer:源向宿主提供的 Surface 写画面 MiAuto 当前 SDK 边界、可输出到 Surface 的虚拟桌面 SurfaceTexture/EGL → JPEG;后续增加 GPU→MediaCodec encoder→WebRTC
Framebuffer:像素/脏矩形更新 典型 VNC/RFB 顺序合成持久 framebuffer,再发布快照/纹理,接 JPEG 或编码器

不要要求所有源必须提供 H.264,也不要要求所有源必须提供 Bitmap。Surface 是输出目标句柄,不是像素数据;纯 Kotlin 核心仅描述入口能力,Android 扩展负责 Surface 的创建与租约。

压缩视频契约至少包含:sessionId、streamId、formatRevision、codec/profile/level、编码尺寸、显示区域/旋转、NAL framing、codec parameters、PTS/timebase、随机访问标记、缓冲区引用。建议中立格式归一为 Annex-B NAL access unit,CarPlay 的配置记录及 AA 的参数集由各自适配器转换。若源存在帧重排,保留解码顺序信息;不能只凭 PTS 假设无 B 帧。

时间戳同时区分源媒体时钟和本地单调接收时间;重连、跳变需重建映射。缺源 PTS 时允许使用合成时间并明确标记,不能用于宣称端到端延迟或精确音画同步。

输出策略由源格式、宿主解码/编码能力、浏览器 SDP 能力决定:

  1. 编码格式/profile/packetization 都兼容时直接转封装,不解码。H.265 不能假设所有浏览器都支持。
  2. 不兼容时先保留现有解码→JPEG 回退;转码是后续可选能力,会增加耗电、延迟与编码器占用。
  3. Surface 源先证明离屏纹理可被消费,再评估重编码。不能把 SDK startCast() 返回成功当作首帧到达或浏览器已显示。
  4. VNC 必须先应用全部有序增量更新(包括 CopyRect),再对合成后的完整画面做 latest-frame 覆盖。不能丢掉中间脏矩形更新,否则最终画面会错误。

背压与所有权必须成为接口契约:

  • 回调内帧是借用引用;异步消费者 retain,发送/丢弃/关闭均 close;支持多消费者各自持有引用。禁止回调返回后继续读取会被协议层复用的数组。
  • 每条媒体分支有按时长/字节/帧数限制的独立队列。保留现有 WebRTC 50 ms / 6 帧 / 2 MiB 作为当前路径基线,不把该预算硬编码为所有源限制。
  • 压缩预测帧超时丢弃后须恢复到可解码边界,请求关键帧并等待;不可采用 JPEG 的任意 latest-frame 策略。
  • 协议 ACK 留在适配器,浏览器 ACK 留在输出端;入队、协议消费、网络发送、远端显示是不同状态。协议允许主动丢弃时仍按其消费契约推进 ACK,防止窗口死锁。
  • Surface 的创建、解绑、释放有明确完成顺序;旧 producer 停止写入后才能释放。不能只将一份 Surface 长期挂在 Activity 字段中。

6. 触控与控制:保留语义,不暴露协议

2026-10-27 实施注记:Touch、Pointer、Scroll、Key、独立 Back 及 cancelInput 已落地,Text/IME/Rotary 尚未完整实现。浏览器实际校验 sessionId/videoEpoch;下文 layoutRevision/v2 是原提案术语。取消与普通抬手已经分离,并明确 NATIVE/RELEASE_ONLY,不能要求所有源实现相同的原生取消效果。

输入分为 Touch、Pointer、Key、Text、Scroll、Rotary;按能力开放,不要求每个源全部实现。触控至少包含稳定 pointerId、DOWN/MOVE/UP/CANCEL、完整触点快照、变化触点 ID、序号、本地单调时间和 displayId。

浏览器传输使用实际内容区域的归一化坐标,并带 sessionId 与 layoutRevision。输入路由验证控制租约后统一处理 letterbox、旋转、crop 和显示区域映射;协议适配器最后转换成设备坐标及消息。显示布局变化时取消旧手势,拒绝旧版本输入,避免画面切换后误点。

  • CarPlay:适配器将稳定 ID 映射到其两个 HID 槽位;双指上限属于 CarPlay 能力,不能成为框架全局上限。
  • AA:生成明确的 action/actionIndex/pointer 集合,不能机械发送每次心跳快照作为 DOWN;转换时保证 index 与本次集合一致。
  • VNC:优先 pointer/button mask/keysym;单指映射左键、滚动映射滚轮是显式交互策略。标准 RFB 不保证多点触控、音频、麦克风或文本提交,扩展须协商。
  • Android 桌面:按实际输入后端声明能力。MediaProjection 的画面授权不等于跨应用注入触控权限;创建 VirtualDisplay 也不自动获得任意应用启动和输入注入能力。自有应用桌面、Shizuku/系统权限桌面、远端桌面应明确区分。

继承现有租约超时释放、失焦释放与边界事件保序。只允许合并相邻 MOVE,不丢 DOWN/UP/CANCEL;队列溢出时取消手势并撤销租约。源切换必须向旧会话取消输入,而不是重新查找“当前源”后把抬手发送给新源。

浏览器协议建议升级 v2,增加源描述、能力、布局版本和会话标识;保留 v1→中立触控转换。v1 无法携带会话代次,切源时应关闭其控制连接并要求重新握手,避免旧消息误投新源。

7. 音频、麦克风与扩展数据

虽然首要目标是视频、触控和控制,接口从第一版就应给音频与麦克风独立端口,否则 AA 焦点、语音助手和 CarPlay 现有浏览器音频会再次耦合回主会话。

  • 下行音频以 PCM 为首个通用接缝,包含 streamId、用途(media/navigation/voice/system)、sample rate、channels、sample format、时钟/样本位置。CarPlay RTP、AA channel 和压缩音频解包留在适配器侧,共享 codec utility 可复用。
  • 上行麦克风由 broker 选择 Android/浏览器/禁用,按源要求重采样;协议编码与封包留在源内。禁用意味着不采集、不回退,并在可协商时不声明输入能力。
  • 音频焦点由公共策略协调,再映射回协议;本机音频焦点、手机协议焦点、浏览器就绪是三个不同状态。
  • SDK 自己占用录放音但未提供数据回调时,明确声明 SourceManaged,不能假装能转发浏览器音频或注入麦克风。
  • Now Playing、导航、通话与传感器各是可选端口;用中立快照表达,不暴露 iAP2 protobuf/ICCOA 类型。手机向宿主发送导航与宿主向手机提供定位是不同方向。

8. 生命周期、资源与切源

2026-10-27 实施注记:实际阶段/错误/清理状态、启动与就绪分离、协作取消及超时机制见上方当前公共契约。下文的完整状态图和资源仲裁范围仍是目标设计;目前公共资源仅有 USB/WIRELESS/MICROPHONE/DISPLAY,未实现完整 BLE/VPN、音频焦点与 codec 预算仲裁。

会话主状态建议为 Idle → Preparing → AwaitingUser/Connecting → Negotiating → Connected → Stopping → Closed;失败含原因、是否可恢复和重试预算。媒体各自有 Waiting/Active/Paused/Recovering/Ended 状态。连接存在不等于视频活动,静止画面不等于断线;浏览器重新连接也不应自动重建手机协议会话。

资源仲裁器管理 USB 设备句柄、P2P/SoftAP 控制、BLE 广播、VPN、麦克风、音频焦点与 codec 实例预算。租约是宿主内部协调手段,不保证系统或外部应用不会抢占。对 ICCOA 这类内部自行管理网络的 SDK,需在启动前预留粗粒度独占租约,并证明 stop 后资源确实释放。

首版只启动一个具有网络副作用的源。无线源上游链路与车机浏览器下游 LAN 必须一起规划:许多设备不能同时维持所需的 STA/P2P/热点组合;改热点可能把浏览器断开。连接前做可用性检查,绑定正确网络/接口,并在手机界面提供重新连接浏览器的入口。不能承诺切换输入源时浏览器 TCP 一定不断。

切源事务:

  1. 无副作用地检查目标源依赖、权限和资源要求。
  2. 封闭旧会话输入入口,取消所有触点/按键,停止麦克风和新媒体提交。
  3. 将旧会话标为 retiring,使迟到回调失效;关闭旧帧队列和 RTC,清除旧画面/音频/元数据。
  4. 等待旧源停止完成后释放资源,再启动新源;使用新 sessionId/epoch。不要在超时后直接假装资源已释放。
  5. 新源协商成功后发布布局/能力,配置输出并等待首帧,再开放控制。失败返回可恢复的空闲/错误状态;恢复旧源应新建会话,不能复活旧回调。

数据、命令、定时器、Surface 和输出连接都绑定 sessionId/epoch。控制与状态在串行执行上下文中处理,媒体走独立有界工作队列;网络 I/O 不在状态锁内等待。close 幂等并完成未结束命令。Activity 仅承载授权与 UI,服务/运行时持有会话与媒体资源。

9. 分阶段实施与验收

阶段 工作 完成条件
P0:接缝与基线 定义中立模型、会话/输入/视频端口;记录现有 CarPlay 延迟与恢复基线 能明确每个回调、队列和资源的所有者
P1:CarPlay 适配 包装原控制器/后台快照;WebSession 改依赖活动会话;认证门控下沉;保留原媒体引擎 CarPlay 有线/无线、双指、浏览器音频、麦克风禁用、重连、RTC→JPEG 回退不退化;无 MFi 资源可启动宿主页面并显示 CarPlay 不可用
P2:验证抽象 增加 synthetic source:编码帧、Surface 图案和 framebuffer 增量;做快速切源、重配置、慢消费者 旧帧/旧触控不能污染新会话;无丢引用、卡手指、队列无界增长;输出模块没有具体协议依赖
P3:Android Auto 提取 DiAuto 协议相关部分,替换解码器直连与 Activity 焦点假设;先实现一种传输,再加无线 目标 Android 服务设备上完成画面→浏览器、触控→手机、音频/语音闭环;协议 ACK 不随浏览器拥塞停止
P4:ICCOA 可行性与适配 先完成下列 SDK 验证,再包装 manager/bridge;Surface→JPEG 为首条输出 目标硬件完成可交互离屏投影;所有不可接管能力准确标示;切源能释放网络/麦克风
P5:桌面/VNC 按需求加独立适配器,复用 Surface/framebuffer 与 Pointer/Key 端口 证明无视频压缩流、无音频、无多点能力的源仍能正常工作

ICCOA 小规模验证可以提前进行,避免架构落地后才发现 SDK 边界不可用;正式集成仍建议晚于 CarPlay 与 AA。

必要的契约测试:配置先于帧、格式变更后旧帧拒收、压缩队列溢出恢复、Surface 重建/迟到回调、buffer retain/close 平衡、触控边界与布局变更、旧命令跨切源、资源释放失败、无音频/只读源、VNC 增量合成后限帧。复用现有媒体、TouchLease、WebRTC 恢复测试作为回归;测试协议契约而非只验证方法被调用。

验收指标记录源接收、入队、丢弃、输出发送、浏览器呈现分段耗时与速率,外加内存、CPU、耗电和重连时间。真实性能改善须在相同设备/网络场景下比较,不从架构图推断。

10. 接入前仍需回答的问题

  1. ICCOA 是否能合法获得适合 WheelPlay 分发方式的运行 SDK?MiAuto 的 AGPL 源码许可不覆盖该专有运行库。DiAuto/MiAuto 均标注 AGPL-3.0;WheelPlay 根许可为 GPLv3 且已有 AGPL UI 来源声明,复用时应保留来源并核对最终组合分发义务。模块或进程隔离本身不等于豁免。
  2. ICCOA 的 Surface 是否接受 SurfaceTexture 离屏目标?是否有受保护内容或 SDK 生命周期限制?能否可靠暂停、替换 Surface、停止后台服务?公开包装层尚不足以证明。
  3. 实际 SDK 的触控、按键、麦克风注入、音频导出与关键帧请求接口是什么?没有接口或验证证据的能力保持 Unknown/Unsupported。
  4. AA/ICCOA 在普通 Android 服务手机上能否维持上游手机连接与下游车机浏览器网络?当前外部项目的运行位置和测试硬件与 WheelPlay 不同。
  5. 已明确([RFC] Shizuku/ADB 本机 Android 虚拟桌面输入源方案 #40 / feat: add Shizuku desktop and shared projection lifecycle contracts #52):Android 虚拟桌面指宿主同一 Android 系统中由 Shizuku UserService 创建独立显示屏并启动所选本机应用,不是模拟器或远程 Android 实例,也不提供独立用户/应用数据隔离。当前仅启动前设置尺寸/DPI,会话内布局变化要求重新连接;应用、副屏、输入和清理兼容性仍待 [RFC] Shizuku/ADB 本机 Android 虚拟桌面输入源方案 #40 真机验收。

11. 外部证据入口

链接固定至本次研究提交,避免后续默认分支变化造成结论漂移。

Activity

  1. fython commented on Oct 23, 2026

    @fython
    OwnerAuthor

    已按最新主线 a4e3a1f 同步本 Issue 正文与实施清单:

    正文已区分代码完成与设备验收,保留原始设计供追溯。#28 继续开放,后续优先完成 AA 真机验收和框架/CarPlay 的设备性能记录。

  2. pinned this issue on Oct 23, 2026
  3. fython commented on Oct 26, 2026

    @fython
    OwnerAuthor

    同步主线进展(2026-10-26),核对提交:54a636a。本次以追加评论记录,正文仅调整实施 checklist;正文中 2026-10-23 的状态表保留为历史记录,以下更新补充其后变化。

    已合入主线

    验证结果

    • feat: 支持独立 APK 输入源插件,为 CarLink 独立接入提供 SDK #50 最终合并前版本:SDK 8、shared 478、common 422,共 908 项单元测试全部通过;SDK AAR、宿主/示例 APK、instrumentation APK 构建及 mobile/sample lint 均通过。
    • CI #130 已全部成功,包括构建及 Android 28 / 36 运行验证;插件跨进程回归覆盖独立 APK 连续会话、Surface 输出、输入、停止确认与进程退出。
    • 新增真实 SDK 基类与宿主会话的联动回归,覆盖主动断开、清理挂起超时、握手拒绝、启动失败及手动停止。这些自动化结果不替代目标手机/车机上的协议与性能验收。

    剩余工作

    #28 继续保持开放;本次仅将已交付的插件基础设施和 VNC 代码任务标记完成,设备验收任务不提前勾选。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions