2026/10/13 1:43:15

WebRTC.rs 实战:用 play-from-disk-h26x 从磁盘文件向浏览器推送 H264/H265 视频与 Opus 音频

WebRTC.rs 实战:用 play-from-disk-h26x 从磁盘文件向浏览器推送 H264/H265 视频与 Opus 音频 音视频即时通讯通信【免费下载链接】webrtcAsync-friendly WebRTC implementation in Rust项目地址https://gitcode.com/gh_mirrors/webr/webrtc点击查看免费下载本技术指南围绕 WebRTC.rswebrtc crateRust 异步友好的 WebRTC 实现官方示例play-from-disk-h26x展开讲解如何把保存在磁盘上的 H.264/H.265 视频文件和 Opus 音频文件通过 PeerConnection 实时推送到浏览器端播放。读完本文你将掌握 ffmpeg 转封装与转码的完整命令、示例程序的 CLI 参数与两种平台的运行方式、浏览器与 Rust 侧双向 SDP 交换的手动信令流程并能结合源码理解MediaEngine编解码器注册、TrackLocalStaticSample打包发送、payload type 协商与节拍器控流等底层原理。示例定位与适用场景在 examples/README.md 的 Media API 示例清单中play-from-disk-h26x被定义为演示如何从磁盘文件向浏览器发送 H264/H265 视频的官方示例。它与 play-from-disk-vpxVP8/VP9互为补充两者共用同一套读文件 → 逐帧写入 TrackLocalStaticSample → RTP 打包发送的模式区别仅在于视频编码标准与对应的 SDP fmtp 描述。这个示例非常适合以下场景验证 WebRTC.rs 对 H.264video/avc与 H.265video/hevc两种编码的推流能力以及浏览器端的解码播放兼容性在不接入摄像头/麦克风、不依赖真实采集设备的前提下用一段本地视频文件快速搭建 WebRTC 推流链路做联调作为开发本地媒体文件点播无人值守录制回放浏览器直播预览等功能的起步模板。示例入口文件为 play-from-disk-h26x.rs在 Cargo.toml 中以[[example]] name play-from-disk-h26x注册因此可以直接通过cargo run --example play-from-disk-h26x运行。前置条件获取代码并初始化子模块WebRTC.rs 采用了两层结构webrtccrate 是薄异步层Sans-I/O 协议核心位于rtc子模块仓库根目录下的rtc/目录由.gitmodules声明为 submodule。示例依赖的H26xSampleReader、OggReader、MediaEngine等类型即来自rtccrate。因此在编译运行前需要先初始化子模块git submodule update --init --recursive cargo build默认特性已包含runtime-tokio运行时后端与crypto-ring加密提供者直接编译即可如需切换到 smol 运行时可启用runtime-smol特性。第一步用 ffmpeg 准备媒体文件play-from-disk-h26x需要两类输入文件一个包含 H.264 或 H.265 视频轨的output.264/output.265文件以及一个包含 Opus 音频轨的output.ogg文件。原 READMEexamples/play-from-disk-h26x/README.md给出了三条 ffmpeg 命令ffmpeg -i $INPUT_FILE -an -c:v libx264 -bsf:v h264_mp4toannexb -b:v 2M -max_delay 0 -bf 0 output.264 ffmpeg -i $INPUT_FILE -an -c:v libx265 -bsf:v hevc_mp4toannexb -b:v 2M -max_delay 0 -bf 0 output.265 ffmpeg -i $INPUT_FILE -c:a libopus -page_duration 20000 -vn output.ogg逐项拆解各参数的实际作用参数作用说明-i $INPUT_FILE指定输入文件任意 ffmpeg 可解码的格式如 mp4/mkv 等。$INPUT_FILE为输入路径占位符请替换为真实文件-an丢弃输入中的音频流确保output.264/output.265只含视频轨-c:v libx264视频编码器选用 H.264 编码器 libx264-c:v libx265视频编码器选用 H.265 编码器 libx265生成output.265时使用-bsf:v h264_mp4toannexb将 H.264 码流从 MP4 封装AVCC转换为 Annex-B 格式起始码 00 00 00 01这是 RTP 打包 H.264 与 WebRTC 解码所要求的字节流形态-bsf:v hevc_mp4toannexb与上一条对应把 H.265 码流转换为 Annex-B 字节流-b:v 2M视频目标码率 2 Mbps-max_delay 0关闭复用器延迟缓冲减少起始延迟-bf 0禁用 B 帧避免 B 帧引起的乱序重排降低播放延迟并简化推流时序-c:a libopus音频编码器选用 Opus-page_duration 20000Ogg 页面时长 20000 微秒20 ms对应一页 960 个采样点48 kHz 采样率与示例源码中音频推送节拍OGG_PAGE_DURATION 20ms精确对齐-vn丢弃视频流确保output.ogg只含音频轨第二步理解手动信令流程与 CLI 参数该示例采用复制粘贴式手动信令manual signaling浏览器与 Rust 进程之间通过文本形式交换 SDPSessionDescription中间没有任何信令服务器。完整闭环为浏览器端 jsfiddle 示例页生成 Offer顶部文本框将浏览器 Offer 粘贴为play-from-disk-h26x的标准输入示例程序解析 Offer、创建 Answer 并完成 ICE 收集随后将 Answer 打印到标准输出将程序输出的 Answer 复制到浏览器页面底部文本框点击 Start Session 按钮建立连接并开始播放。示例的 CLI 参数定义在 play-from-disk-h26x.rs 第 51–74 行的Cli结构体中通过 clap 派生解析参数默认值说明-d, --debug关闭开启 env_logger 调试日志输出-l, --log-levelINFO日志级别如 DEBUG/INFO/WARN/ERROR需与--debug配合生效-i, --input-sdp-file空从文件读取浏览器 SDP 而非读取标准输入-o, --output-log-file空指定日志输出文件配合--debug使用默认输出到 stdout-v, --video无视频文件路径.264为 H264.265为 HEVC-a, --audio无音频文件路径.oggOpus--hevc关闭声明视频为 HEVC/H.265 而非 H.264第三步运行 play-from-disk-h26x原 README 要求将上一步生成的output.264/output.265与output.ogg放在示例所在目录命令中以rtc/examples/test-data/前缀路径引用即仓库根目录下的 rtc 子模块测试数据目录可按实际存放位置调整路径。随后打开示例页面在页面顶部文本框复制浏览器的 SessionDescription按平台执行对应命令。Linux / macOSecho $BROWSER_SDP | cargo run --example play-from-disk-h26x -- -v rtc/examples/test-data/output.264 -a rtc/examples/test-data/output.ogg如需推送 H.265 视频追加--hevc标志echo $BROWSER_SDP | cargo run --example play-from-disk-h26x -- -v rtc/examples/test-data/output.265 -a rtc/examples/test-data/output.ogg --hevc其中$BROWSER_SDP为浏览器 Offer 的 shell 变量直接替换为复制的文本亦可。--之后的部分才会被透传给示例程序本身cargo run自身参数需放在--之前。Windows在 Windows 上先将浏览器 SessionDescription 粘贴保存到文件如my_file再用输入重定向喂给程序cargo run --example play-from-disk-h26x -v rtc/examples/test-data/output.264 -a rtc/examples/test-data/output.ogg my_file cargo run --example play-from-disk-h26x -v rtc/examples/test-data/output.265 -a rtc/examples/test-data/output.ogg --hevc my_file收尾将play-from-disk-h26x打印到标准输出的 AnswerBase64 编码的 JSON复制到浏览器页面第二个文本框点击 Start Session。此时浏览器上方应开始播放视频程序在媒体文件全部推送完毕后自动退出——这正是 README 描述的 will exit when the file reaches the end。等待期间可按CtrlC提前终止。也可以使用--input-sdp-file替代 stdin 粘贴cargo run --example play-from-disk-h26x -- -v rtc/examples/test-data/output.264 -a rtc/examples/test-data/output.ogg -i browser_offer.txt源码解析从 Offer 到 RTP 推流的完整链路下面沿 play-from-disk-h26x.rs 的async_main主流程逐段拆解理解每一步背后的实现。1. 通过 MediaEngine 注册编解码器程序首先构造MediaEngine并手动注册 Opus 音频与 H.264/H.265 视频编解码参数源码第 164–203 行。注册与否直接取决于是否传入了对应文件audio_file.is_some()时才注册 Opusvideo_file.is_some()时才注册视频编解码器。let audio_codec RTCRtpCodecParameters { rtp_codec: RTCRtpCodec { mime_type: MIME_TYPE_OPUS.to_owned(), clock_rate: 48000, channels: 2, sdp_fmtp_line: .to_owned(), rtcp_feedback: vec![], }, payload_type: 120, ..Default::default() };音频采用 Opus48 kHz 时钟、双声道、payload type 120。视频部分由--hevc标志二选一编码MIME 类型时钟频率sdp_fmtp_linepayload typeH.264video/avcMIME_TYPE_H26490000level-asymmetry-allowed1;packetization-mode1;profile-level-id42e01f102H.265video/hevcMIME_TYPE_HEVC90000空字符串98clock_rate: 90000是 WebRTC 视频的标准 RTP 时间戳时钟频率90 kHz。H.264 的 fmtp 行中packetization-mode1表示使用非交错模式每个 NALU 独立分包profile-level-id42e01f声明 Baseline/Constrained Baseline profile与 ffmpeg 默认 libx264 输出及 Chrome 解码兼容性匹配。随后调用register_default_interceptors(Registry::new(), mut media_engine)注册默认拦截器链NACK、重传、键帧请求等。2. 构建 PeerConnection配置使用公共 STUN 服务器stun:stun.l.google.com:19302做 NAT 穿越源码第 208–213 行let config RTCConfigurationBuilder::new() .with_ice_servers(vec![RTCIceServer { urls: vec![stun:stun.l.google.com:19302.to_string()], ..Default::default() }]) .build();随后通过PeerConnectionBuilder组装连接并通过with_runtime(runtime.clone())注入运行时。runtime()来自 examples/common/mod.rs 的共享辅助模块——该模块使用OnceLock缓存单例运行时并显式注释说明每个示例都应从这里获取运行时以保证定时器辅助函数与每个PeerConnection共享同一个运行时实例。with_udp_addrs(vec![format!({}:0, signal::get_local_ip())])将 UDP 绑定到本机 IP 的随机端口。事件处理器Handler源码第 78–107 行实现了PeerConnectionEventHandlertraiton_ice_gathering_state_change在 ICE 收集完成时通知主流程on_connection_state_change在连接建立时放行推流、在连接失败/断开/关闭时触发退出。3. 添加本地轨道视频与音频轨道均使用TrackLocalStaticSample源码第 242–297 行每个轨道先随机生成 SSRCrand::random::u32()再用MediaStreamTrack::new描述流 ID、轨道 ID、标签、类型RtpCodecKind::Video/RtpCodecKind::Audio以及带 SSRC 与 codec 的RTCRtpEncodingParameters最后通过peer_connection.add_track(...)加入连接。TrackLocalStaticSample的完整实现在 src/media_stream/track_local/static_sample.rs。其模块文档明确指出它接收编码后的媒体帧VP8/VP9/H.264 视频、Opus 音频作为Sample并在内部完成 RTP 打包工作——按编解码器的 MTU常量RTP_OUTBOUND_MTU: usize 1200把一个 Sample 拆成一个或多个 RTP 包、分配序列号、根据 Sample 时长设置时间戳。write_sample内部先依据sample.duration.as_secs_f64() * clock_rate计算本帧对应的采样数再交给按 SSRC 缓存的Packetizer打包最后把每个包的payload_type改写为调用方传入的值后经TrackLocalStaticRTP::write_rtp_with_extensions写出发送。4. 交换 SDP 并等待连接主流程打印提示后从 stdin或--input-sdp-file指定的文件读取浏览器 Offer经signal::decode解码后反序列化为RTCSessionDescriptionlet desc_data signal::decode(line.as_str())?; let offer serde_json::from_str::RTCSessionDescription(desc_data)?; peer_connection.set_remote_description(offer).await?; let answer peer_connection.create_answer(None).await?; peer_connection.set_local_description(answer).await?;随后gather_complete_rx.recv().await阻塞等待 ICE 收集完成非 trickle ICE 的同步交换模式再将本地描述序列化、signal::encode为 Base64 打印到 stdout 供浏览器侧填写。连接状态变为Connected后才真正开始推流。5. 协商 payload type 与并发推流negotiated_payload_type源码第 382–391 行从sender.get_parameters()中取第一个已协商 codec 的 payload type。这是推流前必须完成的一步write_sample会为每个 RTP 包盖上该 payload type而rtc的write_rtp要求它与发送端协商出的 codec 一致否则包会被拒收。视频与音频两个推流任务通过runtime.spawn并发运行各自独立处理文件读取与节拍源码第 331–355 行runtime.spawn(Box::pin(async move { if let Err(e) stream_video(video_file_name, track, is_hevc, payload_type).await { eprintln!(video streaming error: {e}); } let _ video_done_tx.try_send(()); }));主流程随后用futures::select!同时监听三个终止条件连接失败信号done_rx、CtrlC信号、或视频与音频两个任务都结束video_done_rxaudio_done_rx——对应 README 中文件播完程序自动退出的行为。源码解析磁盘文件如何变成 RTP 流H.264/H.265 视频推流stream_video源码第 393–442 行使用H26xSampleReader逐帧读取 Annex-B 码流let file File::open(video_file_name)?; let reader BufReader::new(file); let mut video_reader H26xSampleReader::new(reader, 1_048_576, is_hevc);H26xSampleReader与OggReader均位于rtccrate 的media::io模块导入见源码第 7–8 行。构造参数中的1_048_5761 MiB是读取缓冲上限第三个布尔参数指示解析 H.265HEVC而非 H.264。循环内每取到一帧就以该帧 SSRC 与协商后的 payload type 调用sample_writer(...).write_sample(...)写入轨道sample.timed为真时H.264/H.265 帧均为定时帧duration取H26X_FRAME_DURATION 33ms约 30 fps 的帧间隔否则为 0。源码中的注释揭示了一个重要工程细节必须使用interval(H26X_FRAME_DURATION)节拍器而非简单的sleep因为 avoids accumulating skew, just calling time.Sleep didnt compensate for the time spent parsing the data——单纯 sleep 无法补偿解析数据本身消耗的时间会导致节奏累积漂移节拍器则按固定周期对齐规避了睡眠延迟问题。Opus 音频推流stream_audio源码第 444–491 行使用OggReader逐页解析 Ogg 容器let (mut ogg, _) OggReader::new(reader, true)?; let mut last_granule: u64 0; while let Ok((page_data, page_header)) ogg.parse_next_page() { let sample_count page_header.granule_position - last_granule; last_granule page_header.granule_position; let sample_duration Duration::from_millis(sample_count * 1000 / 48000); ... }这里利用 Ogg 页面头的granule_position累计采样点数计算本页实际包含的音频采样数再按 48 kHz 换算成毫秒时长——这正是前面 ffmpeg 命令中-page_duration 2000020 ms/页960 采样点得以对齐的底层依据。每写一页后同样通过interval(OGG_PAGE_DURATION)20 ms 节拍控流保证实时性。验证集成测试与同类示例对照仓库中的集成测试 tests/play_save_disk.rs 验证了与本例同构的从 IVF/Ogg 文件读样本 → 写入TrackLocalStaticSample→ 接收端收到并解析 RTP 包完整链路测试建立 Offerer/Answerer 两端的 PeerConnection用IVFReaderVP8 视频与OggReaderOpus 音频推流接收端在on_track中通过track.poll()统计收到的视频/音频 RTP 包数量断言 10 秒内双方各收到至少 30 个包。其中negotiated_payload_type的取值逻辑与 tests/play_save_disk.rs 第 341–358 行完全一致从sender.get_parameters()取首个 codec 的 payload type印证了这一环节在真实链路中的必要性。对照阅读可加深理解play-from-disk-vpx同一套架构的 VP8/VP9 版本视频 payload type 分别为 96VP8与 98VP9且不使用--hevc标志save-to-disk-h26x接收方向示例将 WebRTC 收到的 H.264/H.265 与 Opus 落盘保存与本例构成推流 ↔ 录流的完整闭环play-from-disk-playlist-controlOgg/Opus 播放方向的进阶版本通过 DataChannel 暴露播放列表并支持浏览器端切换曲目。常见问题与排查要点文件路径错误程序启动时会校验-v/-a指定的文件是否存在不存在即报错video file: ... not exist源码第 150–159 行。注意 README 命令中的rtc/examples/test-data/是仓库根目录下的相对路径需先在仓库根目录执行命令或改为实际存放路径。浏览器端无法播放 H.265HEVC 在浏览器中的解码支持取决于平台与浏览器版本。如需最大化兼容优先使用默认的 H.264 模式只有目标浏览器明确支持 HEVC 时才加--hevc。连接建立缓慢或失败示例依赖公共 STUN 服务器做 NAT 穿越可确认 UDP 出网与 3478 端口可达性局域网联调也可自行替换RTCIceServer配置。日志定位问题加-d -l DEBUG可开启详细日志-o file可将日志重定向到文件便于排查。小结从 examples/play-from-disk-h26x/README.md 的 6 步操作到 play-from-disk-h26x.rs 的 370 余行实现play-from-disk-h26x完整示范了 WebRTC.rs 推流链路的每个关键环节ffmpeg 产出 Annex-B 码流与 Opus/Ogg 音频 →MediaEngine注册编解码器并协商 payload type →TrackLocalStaticSample自动分包 → 节拍器按帧率/页面时长控流。掌握该示例你就具备了在 Rust 侧用任意本地媒体文件驱动浏览器 WebRTC 播放的基础能力可直接复用于点播、直播、录制回放等真实业务。赞分享音视频即时通讯通信【免费下载链接】webrtcAsync-friendly WebRTC implementation in Rust项目地址https://gitcode.com/gh_mirrors/webr/webrtc点击查看免费下载相关推荐Rust webrtc 实战用 play-from-disk-vpx 将磁盘上的 VP8/VP9 视频与 Opus 音频推送到浏览器Rust webrtc 实战用 play from disk vpx 将磁盘上的 VP8/VP9 视频与 Opus 音频推送到浏览器 本文以 examples音视频即时通讯通信webrtc (WebRTC.rs) save-to-disk-h26x服务端录制 H.264/H.265 视频与 Opus 音频到磁盘的实战指南webrtc WebRTC.rs save to disk h26x服务端录制 H.264/H.265 视频与 Opus 音频到磁盘的实战指南 本文以 exa音视频即时通讯通信WebRTC.rs save-to-disk-av1 实战把浏览器 AV1 视频录制为 IVF 文件WebRTC.rs save to disk av1 实战把浏览器 AV1 视频录制为 IVF 文件 本文基于 webrtcWebRTC.rsRust 实音视频即时通讯通信上一篇Freeplane 思维导图模板安装与配置指南下一篇【亲测免费】 Swagger Editor 安装与配置完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考