座舱预览的双屏入口、面框绘制与声音主题边界

作者:

在

结论:座舱摄像头预览是一条展示 / 测试路径,源码里没有任何一处把「预览能画出面框、能发声」当成驾驶员监测(DMS)已就绪的证明。两个入口(大屏设备页与 mici 设备页)都只在 ui_state.is_offroad 为真时允许点击;预览把 driverStateV2 与 driverMonitoringState 拼在一起绘制,但这两条消息之间没有同帧对齐或新鲜度门;mici 预览发布的 selfdriveState 用的是 new_message() 默认的 valid=False,而声音服务只按「是否 updated」读取 alertSound,不检查有效位。固定快照基线为 c8fb906。

双屏入口:点击门与构造时写入的参数

预览按钮存在两个入口:大屏设备页(device.py 的 L60 附近)与 mici 设备页(device_layout.py 的 L183 附近)。两者都用 ui_state.is_offroad 在行车时禁用预览按钮——这是一道点击入口门,不是对话框运行期间的持续安全检查。

更值得注意的是参数写入的时机。大屏 CabinCameraDialog.__init__() 在构造时就同步写 IsDriverViewEnabled=True,并追加一个交互超时 pop_widget 回调;而不是等到 show_event()。反复构造会累积回调。mici 的 show_event() 才写 demo 参数、把交互超时延长到 300 秒、清 DriverTooDistracted 并建立 selfdriveState 发布端;hide_event() 关 demo 参数、恢复超时。

IsDriverViewEnabled 由 dmonitoringd_thread() 每 40 个 driverStateV2.frameId 读取一次。demo 下只要有有效驾驶员模型消息,就运行 DriverMonitoring.run_step(..., demo=True),使用固定的模拟速度、使能和标定输入。因此 UI 写参数不是立刻切换策略,预览中的警示等级也不是实车速度 / 档位条件下的策略结果。

入口 关键行为 契约边界
大屏设备页 / mici 设备页 以 is_offroad 禁用行车预览按钮 点击入口门,非运行期持续检查
大屏 CabinCameraDialog.__init__() 构造时即写 IsDriverViewEnabled=True,追加超时回调 反复构造累积回调
mici show_event() / hide_event() 写 / 清 demo 参数,超时 300 秒,建发布端 首次 show_event() 在发布端创建前发 _publish_alert_sound(None),不发送
mici 培训 CabinCameraSetupDialog 复用基类,_render() 不调用 _render_dm_alerts() 培训绘制路径与设备页声音路径不可混同

面框绘制:两条消息没有对齐门

大屏 _render() / _draw_face_detection() 在无帧时显示启动文案,有帧时按 driverStateV2 的左右驾驶位概率、faceProb>0.7 与姿态标准差绘制面框。绘制侧没有检查这条模型消息的年龄或 valid,面框坐标是近似公式。

mici 侧更复杂:driverMonitoringState 决定面部检测门,而面框与眼镜概率使用 driverStateV2 字段。两个消息没有同帧对齐,也没有新鲜度门。无帧时 mici 仍会发布一次无声消息。这意味着「画出了一个面框」既不能证明模型消息是新鲜的,也不能证明它与当前画面属于同一帧。对自研相机 / 计算板的接口而言,画面帧、driverStateV2 与 driverMonitoringState 应分别显示采样年龄与有效性;模型面框必须与具体相机标定 / 帧时间关联,不能只按静态坐标公式判定安装合格。

声音总线:同主题发布与非阻塞有效位

mici 设备页在画面可见时每次绘制都执行 _render_dm_alerts(),从而发布 selfdriveState。_publish_alert_sound() 把 alertLevel 映射为预警 / 提示 / 紧急声音,用 messaging.new_message('selfdriveState') 发布一条只填了 alertSound 的消息。

问题在于有效位。new_message() 默认 valid=False,此处没有覆写;其他 selfdriveState 字段仍是默认值。而 Soundd.get_audible_alert() 只检查 sm.updated['selfdriveState'],读取 alertSound 而不检查 valid。因此预览可以驱动声音;声卡是否实际播放仍取决于进程运行条件与设备。

相同主题也被控制、规划、UI 等模块读取,不能假定它只是私有的测试音轨。驻车入口虽然降低了同时行车的概率,但源码未提供预览发布者的独立主题,也没有持续的驻车发布门。建议把预览音效改为独立测试音命令 / 主题,或由声音服务显式接收带来源和驻车门的请求,正式 selfdriveState 保持单一权威发布者。

风险点 源码事实 影响
超时回调只增不减 add_interactive_timeout_callback() 只 append,注册的是无参数 gui_app.pop_widget() 多次打开预览后,下次超时可连续弹出多个当前导航页
offroad 回调未注销 两套 CameraView 构造时注册绑实例回调;close() 释放纹理 / 客户端但不注销 已关闭实例仍可能被列表引用
泄漏测试被跳过 test_widget_leaks.py 被 skip("segfaults") 不能视为通过的生命周期证据
mici hide_event() 只关子视图 清参数与超时覆盖,仅 close() 关闭子视图 再显示时底层状态需按各路径分别验证

生命周期:只追加的回调与未注销的订阅

大屏和 mici 对话框在每次构造时都调用 Device.add_interactive_timeout_callback(),该函数只 append,而 Device._update_wakefulness() 在超时边沿遍历全部历史回调。它们注册的是无参数的 gui_app.pop_widget(),不是「仅关闭此预览实例」。先后打开多个预览后,下一次超时可连续弹出多个当前导航页——这在源码层面可推导,具体导航栈 / 事件调度需 GUI 故障注入。

两套底层 CameraView 均在构造时向 UIState 注册绑定实例的 offroad 回调;close() 释放纹理 / 客户端,但未注销该回调。UIState.add_offroad_transition_callback() 也只追加,因此已关闭实例仍可能被列表引用,长期反复打开可能保留对象并在状态切换时调用已关闭视图。

把预览的「请求驾驶员模型 demo」定义为有所有者、可撤销、带超时的租约,是避免裸布尔 Params 在多个 UI 页面之间互相覆盖的方向;回调订阅、VisionIPC 客户端、纹理与超时覆盖都需要可重入的 show / hide / close 生命周期测试,至少覆盖连续打开、页面切换、超时、摄像头断流、离线转行车与 GUI 重启。

常见问题

座舱预览能画出面框,是否说明驾驶员监测已就绪?

不能。预览把 driverStateV2 与 driverMonitoringState 拼在一起绘制,但两者没有同帧对齐或新鲜度门,绘制侧也不检查模型消息的年龄或 valid;demo 模式还使用固定的模拟速度、使能和标定。预览是展示 / 测试路径,不构成 DMS 安全就绪的证明。

预览为什么会影响声音?

mici 预览用 new_message('selfdriveState') 只填 alertSound 发布消息,默认 valid=False;而 Soundd.get_audible_alert() 只检查是否 updated,读取 alertSound 而不看 valid。这就让预览能驱动同一条声音主题,该主题同时被控制、规划、UI 读取。

反复打开预览会有什么副作用?

两处回调都只追加不注销:交互超时回调追加无参数 pop_widget(),offroad 回调在 close() 后仍保留引用。源码层面可推导下次超时可能连续弹出多个导航页、已关闭视图仍可能被调用;具体调度与资源增长需故障注入验证。

预览按钮的驻车限制够不够?

不够。两个入口只用 is_offroad 在点击时禁用按钮,这是入口门而不是对话框运行期间的持续检查;源码未提供预览发布者的独立主题或持续的驻车发布门。降低同时行车概率不等于运行期安全约束。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/CABIN_PREVIEW_DM_AUDIO_LIFECYCLE_DEEP_DIVE.md(取用了双屏入口、DM 面框绘制、声音主题发布与回调生命周期分析)。

说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

更新记录

首次发布 2026-10-11 最近核对:2026-10-11

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注