硅基俊宁.
← 返回文章

11-Media、WebRTC、设备与平台能力:从 Web 请求到一帧真实音视频

俊宁 · 日期未详
文章目录

从源码阅读入口、进程边界与验证条件理解Media、WebRTC、设备与平台能力:从 Web 请求到一帧真实音视频。

本文讨论通用机制和设计判断;示意不对应任何具体产品的已交付状态。源码阅读坐标沿用原稿,本次整理不构成新的运行时验收。

Chromium Media、WebRTC 与设备能力封面

阅读坐标沿用原稿:Chromium 153.0.8010.37,上游 commit b75a5a95ea1a1b55bdbfd6d9f42d47be7507fb8b。本次同步未重新进行源码与目标平台运行时验收。

Web 媒体请求如何经过授权、设备与平台后端

一句话结论

媒体播放、设备采集、桌面共享与 WebRTC 是相互连接但不同的链路;Web 约束只是请求,Browser 与操作系统共同完成授权和资源选择,最终帧能否产生还取决于 service、codec、驱动和设备状态。

网页里的 <video>、getUserMedia() 与 RTCPeerConnection 看起来像几行 JavaScript;实际一次视频通话、录音或屏幕共享跨过了 Web 语义、Renderer、Browser 授权、设备控制、媒体 service、系统 API 和网络传输。它不是“网页直接打开摄像头”,也不是“WebRTC 自动获得设备权限”。Browser 必须持续保有对 谁在请求、允许什么、设备由谁占用、页面失效后如何停止 的控制权。

阅读这一部分源码时,最实用的问题是:这是一段媒体数据、一个设备控制请求,还是一次用户授权决定?它现在在哪个进程,下一跳是否还带着 document 和用户选择的身份? 把三类事混在一起,是媒体问题最常见的误判来源。

先分清三条链:播放、采集与实时通信

“媒体”并不是一个单一路径。至少有三条常见链路,它们在 <video>、track 或 GPU 合成处相交,但其输入、权限和失败方式不同。

场景起点Browser 主要决定什么主要下游
播放远端媒体<audio> / <video> / MSE 等 Web APIframe、autoplay、URL/网络、解码资源与产品策略网络、demux/decode、audio/video render、compositor
采集本机媒体navigator.mediaDevices.getUserMedia()当前 document 是否可请求、用户是否授权、选哪台设备、何时停止摄像头/麦克风设备、capture pipeline、MediaStream track
实时通信RTCPeerConnection + local/remote trackWeb 安全上下文、设备采集授权、网络/企业策略与可观测性WebRTC peer connection、ICE、RTP、编解码、jitter buffer

屏幕共享 getDisplayMedia() 与摄像头采集相近,却不应简单继承 getUserMedia() 的授权结论:它涉及用户明确选择哪一个 screen/window/tab,以及选择结果是否仍对应当前请求。相同地,拿到 local track 也不等于网络已可通;WebRTC 的 signaling、ICE 候选、NAT、codec 协商与 RTP 媒体流仍是另一条待验证的链。

Web API / document
  -> Blink request and constraint processing (Renderer)
  -> Mojo / Browser-side dispatcher
  -> Browser permission UI and product policy
  -> MediaStreamManager / device selection and control
  -> capture backend or Media Service
  -> MediaStream track / WebRTC / <video> renderer
  -> compositor, audio output, or network transport

这是理解边界的路线图,不是保证每次都经历的严格同步调用栈。播放不必经过设备 prompt,纯 remote track 不必打开本机摄像头;但 Browser-side identity、生命周期与资源控制不能被 Renderer 或第三方 Runtime 绕过。

getUserMedia():约束只是请求,不是对硬件的命令

在 Renderer 一侧,third_party/blink/renderer/modules/mediastream/media_devices.* 暴露 MediaDevices 行为,user_media_processor.* 将音频/视频 constraints、请求状态、成功与失败结果组织进 MediaStream/track 的生成过程。源码中既有 GetUserMediaRequestSucceeded/Failed,也专门处理请求已经取消后设备才返回的情形;这说明异步顺序不能被假设为“点击允许后一定立即得到 stream”。

网页提交的约束表达的是需求和可接受范围,例如希望音频、希望某个分辨率、是否需要回声消除。它并不获得设备句柄,也不能指定“必须把物理设备 X 永久交给我”。Browser 侧仍须检查 caller、权限、可用设备、设备忙碌状态与产品 policy,再由采集链路选择满足条件的能力。

现象不能直接得出的结论应沿哪条链排查
getUserMedia() rejected“系统没有摄像头”secure context、frame/embedding、用户拒绝、content setting、enterprise policy、constraints、设备状态
prompt 显示且用户允许“Renderer 已经拿到图像”Browser 回调、设备启动、track 创建、首帧与页面生命周期
enumerateDevices() 结果有限“设备枚举坏了”设备权限前后的可见性、label 暴露规则、默认设备与 platform backend
track 创建成功却黑屏/无声“权限有问题”capture 首帧、constraints、设备驱动、track 是否 enabled/ended、video render/audio output

因此产品不应把一次成功的 prompt 当作永久、全局的“媒体授权 token”。授权至少要绑定请求类型、当前 document/frame、Profile 与可撤销状态;在 prompt 等待期间页面可能导航、Tab 可能关闭、frame 可能被销毁。后续回调必须检查请求是否仍有效,并关闭已经为失效请求启动的资源。

Browser 是设备访问的控制平面

content/browser/renderer_host/media/media_stream_dispatcher_host.* 是 Renderer 媒体请求进入 Browser 的一个重要入口;media_stream_manager.* 负责把请求、设备枚举与生成过程协调到 Browser-owned 的媒体管理中。media_devices_manager.*、video_capture_manager.*、audio_input_device_manager.* 则显示设备清单、视频与音频采集并非由一段 Web 代码直接管理。

Browser 在这里承担的不是简单转发:它要把 Renderer 所说的“我要音频/视频”还原为具体 requester,向产品层请求 RequestMediaAccessPermission,选择或启动受控的设备会话,并处理 stop、设备变化、进程退出与页面失效。media_stream_ui_proxy.* 将授权 UI 与这条链衔接,也再次说明 prompt 属于 Browser/product 的交互与 policy,而不是 Blink 自行决定的界面。

Renderer: MediaDevices / UserMediaProcessor
  -> Browser: MediaStreamDispatcherHost
     -> Browser policy/UI: permission request, user choice, cancellation
     -> Browser: MediaStreamManager + device managers
        -> capture service/backend
  <- stream descriptors, state and failure result
Renderer: build MediaStream tracks and attach to Web APIs

一个实现或 review 的最低要求,是把下列字段沿链传递并在关键边界复核:请求的 audio/video/display 类型、requesting frame/document、origin 与 embedding context、Profile/OTR、用户选择的设备或 display source、请求 id,以及取消/停止原因。只传一个 tab id 或 URL 字符串,无法可靠区分同一 tab 内的导航、iframe 或已经失效的 caller。

屏幕共享:授权的是一次可见来源选择

content/browser/media/capture/desktop_capture_device.* 及 desktop_capture_device_mac.* 表明桌面捕获有独立的 Browser/平台实现入口。屏幕、窗口、标签页等 source 的枚举与用户选择,带有比“允许摄像头”更强的可见性和产品语义:用户需要知道将共享什么,页面也不应悄悄把上一次选择重用于不相干请求。

如果要做“协助用户共享当前 Tab”之类能力,contract 应明确 source 范围、调用者、用户确认、切换/停止、Tab 关闭和 navigation 后的行为;并分别在 macOS 与 Windows 上验收。平台系统的 Screen Recording、camera、microphone 授权可能在 Chromium prompt 之外再拦一次,二者都成功才代表真能产生媒体帧。

WebRTC:本机采集只是 PeerConnection 的一部分

Blink 的 third_party/blink/renderer/modules/peerconnection/peer_connection_dependency_factory.* 建立 WebRTC peer connection 相关依赖;third_party/webrtc/ 承担实时通信实现的更深层细节。PeerConnection 将 local/remote track、candidate、transport 与 codec 协商组织成实时会话,但 signaling 通常由站点自己的 HTTP/WebSocket 服务完成,不是 Chromium 自动替应用连接的“信令服务器”。

从问题归因看,至少拆成四段:

local capture: permission -> device -> local track
signaling: offer/answer/candidate exchange through application service
connectivity: ICE/STUN/TURN, routing and enterprise network policy
media: codec negotiation -> RTP -> jitter/decode/render

所以“本地预览正常但对端没有画面”通常不能再回到摄像头 permission 一条线上死查;它可能是 signaling 没交换完成、candidate 不可达、DTLS/codec 不匹配,或 remote track 没有正确 attach。反过来,“ICE connected”也不证明本机采集质量、音频输出和页面渲染都正确。每段都需要各自的日志、stats 或 trace 证据。

Browser 侧的 peer_connection_tracker_host.* 与 Blink 的 tracker 入口可帮助把诊断关联到实际 PeerConnection;Chrome 的 WebRTC browser tests 还覆盖了 getUserMedia 允许、拒绝、display media 等交互情形。它们是定位起点,不能替代目标网络、TURN 配置和真实设备上的端到端验收。

媒体服务与平台后端:统一接口不抹平系统差异

content/browser/media/media_service.cc 与 media/mojo/services/media_service.* 表示部分媒体能力可以通过服务化边界提供;视频采集、音频输入、解码与输出在不同路径上会与 service、GPU/utility process 或平台设施配合。其进程划分和 feature 开关不宜凭目录名下结论,应以当前 target、trace 和实际启动的 process 为准。

更靠近硬件的代码必然分平台。当前基线可见 media/capture/video/apple/video_capture_device_avfoundation.* 和 media/capture/video/win/video_capture_device_win.*。它们说明上层 Web API 相同,并不代表下层枚举、格式协商、权限弹窗、设备断开、沙箱授权或驱动问题会一致。

层次希望保持稳定的东西必须允许不同的平台差异
Web/BlinkAPI 语义、promise/track 生命周期、约束与错误模型feature 支持、用户手势与权限策略的实际组合
Browser policycaller 身份、用户确认、Profile/OTR 边界、可撤销性产品 UI、企业 policy、系统授权结果
capture/media设备会话、buffer、格式与停止语义AVFoundation、Windows backend、驱动、可用硬件
presentationtrack 可供页面消费GPU/软件路径、显示器、音频设备与系统输出

跨平台开发的正确目标不是“让 macOS 和 Windows 的类名相同”,而是先冻结产品层 contract 和可观测结果,再为每个平台写可以发现其系统限制的验收。把 macOS 上已允许的 Screen Recording 结果外推到 Windows 机器,或把 CI 的 fake device 结果当成实体摄像头可用,都会漏掉最关键的失败面。

建议的追踪任务:解释一次“允许摄像头后仍无画面”

不要先改权限代码。按下面顺序收集事实,才能将故障放入正确层次:

  1. 记录 requesting URL、top-level/iframe、Profile/OTR、constraints、用户选择与页面是否在等待期间导航。
  2. 在 Renderer 观察 MediaDevices/UserMediaProcessor 的 request、result、track 状态和约束失败原因。
  3. 在 Browser 关联 MediaStreamDispatcherHost、permission UI、MediaStreamManager 与目标设备的启动/停止事件。
  4. 在平台侧验证系统 camera/microphone/screen 授权、设备枚举、驱动和首帧;macOS 与 Windows 分开记录。
  5. 若问题属于 WebRTC,再独立采集 signaling、ICE、selected candidate pair、codec 与 inbound/outbound stats,确认 remote track 是否实际渲染。
  6. 最后才检查 <video> attach、autoplay/mute、compositor/GPU 与音频输出。

这条链的价值是让“用户点了允许”只成为一个中间事实:它既不能证明硬件已启动,也不能证明实时连接成功,更不能证明用户授权仍然属于现在这个 document。

本文已核验的源码锚点

主题源码入口
Web media / user mediathird_party/blink/renderer/modules/mediastream/media_devices.*、user_media_processor.*、web_media_player_ms.*
Browser 媒体请求与设备管理content/browser/renderer_host/media/media_stream_dispatcher_host.*、media_stream_manager.*、media_devices_manager.*
音视频采集video_capture_manager.*、audio_input_device_manager.*、media/capture/、media/audio/
授权 UI 与桌面捕获media_stream_ui_proxy.*、content/browser/media/capture/desktop_capture_device.*
WebRTC 接合third_party/blink/renderer/modules/peerconnection/peer_connection_dependency_factory.*、content/browser/renderer_host/media/peer_connection_tracker_host.*、third_party/webrtc/
Media service 与平台后端content/browser/media/media_service.cc、media/mojo/services/media_service.*、media/capture/video/apple/、media/capture/video/win/

运行时验证路径

从故障反推边界:用户点了允许,为什么仍然没有画面

“允许”只完成权限阶段。之后还要确认 constraints 选择出实际设备、操作系统授权没有拒绝、capture session 能启动、首个 frame 抵达、format/codec 可被后续链路消费、track 没有 mute/ended,最后页面或 WebRTC sender 正确使用它。每一步都有不同 owner 和日志。

对屏幕共享还要额外记录用户选择的 source、系统录屏权限、窗口消失或屏幕拓扑变化,以及 stop indicator 的撤销路径。Agent 若需要读取画面,不应复用一次陈旧授权无限捕获;应把 source、调用者、会话、可见提示、停止动作和结果去向绑定为一次受审计 capability。

播放链与采集链在中间才汇合

媒体播放从网络/MediaSource/本地资源进入 demux、decode、audio/video renderer 和输出设备;采集从权限、设备枚举、source 启动进入 track,再可能送往页面预览、录制或 PeerConnection。它们会共享 codec、buffer、时钟和平台后端,却有不同的授权、故障和生命周期。

排查“视频黑屏”时,先区分资源没有数据、demux/codec 不支持、decode 失败、video frame 未到 renderer、compositor 没提交,还是 autoplay/可见性策略阻止。排查“摄像头无画面”则先看 permission、设备选择、OS 授权、capture source、track state 和首帧。用同一个 Media error 文案覆盖两条链,会让证据无法收敛。

设备授权是 Browser 与操作系统的两级裁决

Web permission 通过只说明 Browser 允许当前 origin/frame 请求;macOS TCC、Windows privacy setting、企业 policy、设备占用和 driver 仍可能拒绝。反过来,OS 已授权应用,也不意味着任意网页都可以访问设备。

一次可靠采集应记录:requesting origin、top-level frame、document、user activation、requested constraints、Browser permission result、selected device/session id、OS result、source started、first frame/audio sample。这样才能区分“用户拒绝”“系统拒绝”“设备不存在”“constraint 无解”和“启动后无数据”。

屏幕共享还多一个可见 picker 和 source 生命周期。用户选择窗口、屏幕或 Tab 后,source 可能关闭、最小化、改变尺寸或被系统撤销;产品不能把一次 picker 结果持久化为以后静默捕获的永久能力。

Constraints 与设备选择不是精确命令

getUserMedia() constraints 表达 ideal/exact/min/max 等偏好和约束,Browser/media layer 需要在可用设备与格式中求解。请求 1080p 不保证摄像头按这一格式输出,也可能由内部转换得到;过度 exact 约束则可能直接产生 overconstrained error。

调试分辨率、帧率或设备错误时,应同时记录原始 constraints、最终 settings、source format 和变换路径。只打印调用参数,会把“请求”和“实际工作模式”混为一谈。

MediaStreamTrack 有独立生命周期

页面拿到 stream 后,track 可能 mute/unmute、enabled/disabled、ended;页面隐藏、设备拔出、source 错误和用户停止共享会产生不同状态。多个 track 还可能共享同一个底层 source,停止一个 track 不一定立即关闭设备,反之 source 结束会影响所有消费者。

导航、BFCache、Renderer crash 与 Permission 撤销时,Browser 和 media service 必须释放或重新绑定资源。长期 Agent/会议场景尤其要监控“页面已离开但设备指示灯仍亮”“重连后重复打开 source”和“旧 track 回调写入新会话”。

WebRTC 还要经过传输与协商

本地预览成功只证明 capture 和本地 render 路径成立。PeerConnection 还包括 SDP/codec negotiation、ICE gathering、STUN/TURN、DTLS/SRTP、拥塞控制、jitter buffer、网络切换和远端 decode。远端黑屏可能来自本地未发送、网络丢包、协商不匹配或远端渲染。

验证时至少区分 local track produced、sender encoded、packets sent、remote packets received、decoder output 和 remote frame rendered。webrtc-internals、trace、网络统计和平台日志应围绕同一个 peer/session id 对齐。

Codec、GPU 与平台后端改变真实路径

硬件 encode/decode 是否可用受平台、GPU、driver、分辨率、codec profile、sandbox 和 feature 控制。软件 fallback 能提高兼容性,却可能显著增加 CPU、功耗和热压力;硬件路径成功初始化也不保证长期不会 reset。

一次跨平台媒体验收应记录实际 codec、profile、hardware/software implementation、分辨率/帧率、CPU/GPU、功耗、丢帧与恢复。不能用一台开发机的 codec 支持推断所有用户环境,更不能把 --disable-gpu 下的成功当成最终修复。

一套覆盖长期运行的验证矩阵

除首次允许和首帧,还应覆盖:拒绝后再次请求、OS 权限运行中撤销、设备插拔、默认设备切换、窗口/屏幕 source 关闭、页面导航、Tab 后台、Renderer crash、media service restart、网络从有线切 Wi-Fi、PeerConnection 重协商和长时间运行。

每个场景分别记录 permission、source、track、transport 和 render 状态。最终产品通过条件不仅是“再次有画面”,还包括设备正确释放、没有重复 session、用户能理解失败、恢复不会把旧流接进新 document。

音频链需要单独看时钟、设备与系统策略

音频没有“画面黑掉”这种直观信号。采集 sample、Audio Service、处理/回声消除、编码传输、解码、输出 device 和系统 mixer 任一阶段都可能产生静音、爆音、延迟或漂移。页面中的 track live 不代表输出设备真的发声。

排障应记录 input/output device、sample rate、channel layout、buffer/underrun、mute/volume、system route 和 WebRTC audio stats。蓝牙设备切换常伴随 profile、采样率与延迟变化;默认设备变化时,Browser 是否迁移现有 stream 取决于平台和产品策略,不能只在内置扬声器上验收。

音视频同步还依赖共同时间基准、jitter 和 renderer 调度。单独优化 video frame 或 audio buffer,可能改善一侧却放大 lip-sync 偏差;长期会议应同时观察 A/V drift、device change 与 network recovery。

产品还要保证采集指示与真实 source 生命周期一致:设备已停止时及时撤下提示,仍在采集时不能因页面 UI 消失而隐藏状态。隐私指示本身也是媒体验收的一部分。

工程结论

媒体能力是一条跨 Web、Browser、service、硬件和 OS 的资源生命周期。只有把“请求允许、设备启动、首帧到达、持续传输、用户停止、资源释放”分别验收,才算说明真实能力。

继续阅读

上游源码入口

文中的相对源码路径可从 Chromium 官方上游快照 定位。此入口保留原稿的上游阅读坐标,不包含任何产品定制代码,也不替代运行时验收。