WebRTC 直播会话:Answer 失败后直播参数为何仍为真

作者:

在

结论:直播会话最值得记录的不是「能否连上」,而是「参数真值与会话存在」会脱钩:驻车路径先写 IsLiveStreaming=True,若本机服务在生成 Answer 时超时或抛异常,代码会移除会话并把错误交回 HTTP 线程,却不安排 5 秒清参数,全局布尔位可能保持为真。此外 HTTP 本机入口直接信任 Content-Length,没有请求体大小或读取 deadline 上限。以下基于本地 source/ 快照 c8fb906 的隔离探针与静态追踪,未运行真实 WebRTC 连接或 HTTP 压测。

唤醒与接入:athenad 到 webrtcd

驻车时 athenad.startStream() 先写 IsLiveStreaming=True,等本地 webrtcd 可用再 POST;普通汽车驻车路径由 manager 按该参数启用 webrtcd 和相关直播服务。请求侧 wait_for_webrtcd() GET /schema,仅 ConnectionError 增加重试计数并休眠;post_stream_request() POST /stream 并解析 JSON,却不检查 HTTP 成功状态,4xx/5xx 的 JSON 也能作为普通返回值到上层。

服务侧用 ThreadingHTTPServer、本机默认端口 5001;HTTP 线程用 run_coroutine_threadsafe(...).result() 等待 asyncio loop,路由仅接收三个路径。stream_lock 串行化会话替换:若已有启用会话,禁用型预热请求返回 HTTP 200 的 busy JSON;否则通知并停止旧会话,再构建新会话,Answer 生成限时 30 秒,成功后启动 run_task。

一个可追踪的失败窗口

这条结论不依赖网络是否暴露:驻车 startStream() 先写 IsLiveStreaming=True;若本机服务已启动但 handle_get_stream() 在生成 Answer 时超时或抛异常,代码会移除会话并把错误交回 HTTP 线程,不会安排 5 秒清参数。正常的清参数仅由已启动 run_task 的完成回调触发。也就是说「客户端拿到 500」与「直播参数已清除」是两件独立的事。

另一个独立边界是 wait_for_webrtcd(max_retries=10):计数只在连接异常时增加。服务持续返回非 2xx 时不会达到最大重试次数,这不是「最多十次 GET」的实现;ReadTimeout 等其他异常也不是这里专门处理的重试路径。

时限与状态转移

整条链路的关键时限散落在不同函数里,把它们放在一起看才能估计最坏等待。

阶段 时限 / 机制
Answer 生成 限时 30 秒
会话连线 限时 15 秒
普通会话 最长 300 秒
testJoystick body 会话 等待断开,不适用 300 秒上限
清参数 run_task 完成后 5 秒,若会话字典仍空才写 IsLiveStreaming=False
重试计数 仅 ConnectionError 计数,每次休眠 0.5 秒

StreamSession.run() 连接后启动数据桥和码率任务;普通会话 300 秒超时,testJoystick body 会话则等待断开。清理由锁和 _cleanup_done 避免重复执行;若清理中途抛错,完成标记已设置,后续调用会跳过剩余资源,而且清理自身也不清 IsLiveStreaming。

HTTP 本机边界:Content-Length 没有上限

内容类型必须严格等于 application/json,附 charset 的值在解析 body 前即返回 415;未知路径和错误方法在读体前分别返回 404/405;/notify 畸形 JSON 返回 400,未捕获的路由异常返回含异常类名与消息的 500 JSON。

真正的边界在读取:_read_body() 直接信任 Content-Length,没有请求体大小或读取 deadline 的本地上限;隔离探针确认 Content-Length=-1 被传给 rfile.read(-1),十亿长度也原样传入读取。默认仅监听 127.0.0.1,但本地调用方、端口转发和自定义 --host 仍是部署权限边界。

硬件与插件验收

建议把直播能力改为由受信任 supervisor 授予的有租期会话 lease,包含发起者、允许摄像头/主题、最大时长和清理责任;失败、取消、替换与服务进程崩溃均需释放 lease。IsLiveStreaming 这种全局布尔位不应作为唯一所有权记录。_shutdown() 会先停 HTTP 接入、再逐会话通知并停止、最后停事件循环,但关机路径不显式写 IsLiveStreaming=False,仍依赖 manager/参数生命周期接管。

失败路径与清理顺序

清理只在特定路径上被正确注册:remove_finished_session() 回调只在 Answer 成功、session.start() 之后才注册,所以生成 Answer 失败或入口在此之前异常,都不走这条退场门。正常清理则按固定顺序执行。

顺序 清理动作
1 写 LivestreamRequestKeyframe=False
2 停止码率任务与出站桥
3 停止轨道
4 停止媒体流

若码率任务的 stop() 抛错,_cleanup_done 已为真,出站桥、轨道和媒体流都不会再执行停止,随后重试也直接返回;连接失败时 run() 的 finally 仍会尝试清理。普通会话等待超时会发送 disconnect,而 testJoystick body 分支没有同一套超时/通知处理,显式 stop() 则会取消活动任务后再清理。

常见问题

为什么客户端拿到 500,直播参数还可能是真?

因为 Answer 失败分支会移除会话,但不调用 schedule_teardown();清参数只由已启动 run_task 的完成回调安排,正常退场门在此路径上没有被触发。

wait_for_webrtcd 真的最多试十次吗?

不是。重试计数只在 ConnectionError 时增加;服务持续返回非 2xx 时循环不递增次数也不休眠,所以这不等于「最多十次 GET」。

服务端会限制请求体大小吗?

不会。_read_body() 直接信任 Content-Length,没有请求体大小或读取 deadline 的本地上限;Content-Length=-1 会被原样传给读取。

Content-Type 带了 charset 会怎样?

内容类型必须严格等于 application/json;附 charset 的值在解析 body 之前就会返回 415。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/WEBRTC_SESSION_LIFECYCLE_HTTP_BOUNDARY_DEEP_DIVE.md(athenad→webrtcd→manager 的会话生命周期与 HTTP 边界,含源码行号索引)
  • 项目 02 —— openpilot-research/probes/test_webrtc_stream_session_replacement_contract.py(Answer 失败与会话替换的五项异步探针)
  • 项目 02 —— openpilot-research/probes/test_webrtc_http_handler_boundary_contract.py(路径、方法与 Content-Length 边界的七项探针)

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

更新记录

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

评论

发表回复

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