从 6.49 秒到约 1.9 秒:RK3588上的WebRTC 实时流启动速度优化记录

一、引言

本文记录一次没有依赖常驻推流器的冷启动优化:我们如何拆开播放器、ICE、H.264 关键帧和 ZLMediaKit 轨道注册,沿状态机边界找到主要等待点,并用可验证的小改动把启动耗时降低约 71%。

**先说结论:**实测瓶颈不在 RK3588 的持续计算,而在启动状态机中的两个主要等待:publisher(推流端) 要在多个 ICE 路径中建立连接,以及连接完成后等待下一帧 H.264 IDR。第一轮压缩这两个等待后, 平均耗时从 6.49s 降到 1.92s。后续改动主要收敛 candidate 和失败恢复,平均速度只小幅变化。

二、项目背景:RK3588 上的双通道播放与网页实时流

我们优化的这个项目运行在 MYZR-RK3588-EK360 板子上。它的主要任务不是传统网络摄像机推流,而是按节目单 同时驱动两路 HDMI 播放和独立 DEP 音频输出。网页实时流是附加能力:Java 服务下发 streamControl (这是我们开发人员内部定义的一个应用层指令)后,设备把指定 HDMI 通道正在播放的内容发布到板端 ZLMediaKit,浏览器再从 ZLMediaKit 播放。

为了控制空闲资源,publisher 和共享编码 pipeline 不常驻。无人观看达到阈值后会被回收,下一次 streamControl 必须完整经历一次冷启动。因此,这里的"启动快"不能通过永远保持编码器和 WebRTC 连接在线来换取。

(一)GStreamer、WebRTC 和 ZLMediaKit 的数据链路

这里有两段 WebRTC:第一段是 播放器 publisher → 同机 ZLMediaKit ,第二段是 ZLMediaKit → 浏览器。本文优化的是第一段和 ZLM 媒体源注册。HTTP 返回播放地址后, 浏览器才开始第二段协商。

(二)启动控制链路

三、真实测试环境

项目 测试环境 获取方式
板卡 MYZR-RK3588-EK360,aarch64 /proc/device-tree/model
系统 Buildroot 2021.11 /etc/os-release
Linux 内核 5.10.226,PREEMPT uname -a
GStreamer 1.22.9 gst-inspect-1.0 --version
webrtcbin / Opus 插件 1.22.9 gst-inspect-1.0 webrtcbin / opus
Rockchip MPP 插件 gst-rockchip 1.14.4,提供 mpph264enc gst-inspect-1.0 rockchipmpp
ZLMediaKit master@0e9e59b,构建时间 2026-08-13 16:30:41 /index/api/version
libsrtp 2.5.0 MediaServer 启动日志
交叉编译器 GNU AArch64 Toolchain 10.3.1 aarch64-none-linux-gnu-g++ --version
测试设备 192.168.31.163,播放器 HTTP 8050,ZLM HTTP 65521 / RTC 65522 板端配置

四、WebRTC相关知识

可以把 ICE 类比为"给两块板找一条能通的网线和端口",candidate 是候选接线方案;IDR 是"可以从这里 独立开始解码"的视频同步点;track ready 则是媒体服务器确认这条视频或音频流水线已经真的进货,而不只是 管线名称存在。

五、先定义指标:什么时候才算启动完成

接口采用"回复即可以播放"语义。streamControl 不会在 SDP 交换完就立即返回,它继续查询 ZLM,直到 H.264 和 Opus 都满足 ready=true 且帧数大于 0:

复制代码
// app/WebRtcStreamManager.cpp,当前最终实现节选
if (!track.value("ready", false) ||
    !track.contains("frames") || !track["frames"].is_number()) {
    continue;
}
if (track["frames"].get<double>() <= 0) continue;

if (codec == "H264") videoReady = true;
if (codec == "OPUS") audioReady = true;

if (videoReady && audioReady) {
    ready = true;
    status = "H264 and Opus tracks are ready";
}

修改/检查位置:

我们编写的代码app/WebRtcStreamManager.cpp:718QueryZlMediaKitStreamReady():823WaitForZlMediaKitStreamReady(),以及 app/main.cpp:1471 附近的 streamControl 响应逻辑。

时间指标 起止点 能回答什么
HTTP time_total 命令发出 → 收到 200 和播放地址 用户感知的完整启动耗时,是版本比较主指标。
elapsedMs 开始轮询 ZLM → 双轨 ready 轨道注册等待,但它与 ICE/媒体传输可能重叠。
publisherAgeMs publisher 创建 → 双轨 ready publisher 内部总生命周期。
connectedToReadyMs publisher CONNECTED → 双轨 ready 定位关键帧和轨道注册。

这些内部时间不能简单相加。ICE/DTLS 建连、RTP 发送和 ZLM ready 轮询有重叠。本文所有版本间的速度对比 统一使用完整 HTTP time_total;内部时间只用于定位。更直白地说:HTTP time_totalstreamControl 请求发出算到 HTTP 200,elapsedMs 则从开始轮询 ZLM 算到 H.264 + Opus ready,两者起点和统计范围都不同。5258ms 的 elapsedMs 并不意味着还应该再加到 6.49s 上,它只是完整 HTTP 请求内部的一段时间。

六、初始 6.49 秒是怎样拆出来的

(一)基线现象

一次典型冷启动从收到命令到返回地址约 6.49s,其中日志显示 ZLM 双轨 ready 轮询耗时 5258ms:

复制代码
[14:12:44] Received streamControl message
[WebRTC-1] Shared hybrid H.264/Opus pipeline started
[14:12:47] Peer zlm-publisher-1 connection-state=2
[14:12:50] ZLMediaKit stream ready: H264 and Opus tracks are ready, elapsedMs=5258
[14:12:50] Response sent: {"code":200, ...}

(二)不要先怀疑 RK3588 性能,先看状态机在哪里等待

定位不是从"CPU 可能不够快"开始,而是按下面的顺序逐层缩小范围:

  1. 先用 HTTP time_total 测完整用户路径,得到代表值 6.49s
  2. 再按日志事件排列状态机:streamControl → pipeline → offer → CONNECTED → track ready → HTTP 200
  3. 在基线日志中先看到 CONNECTED 前有明显耗时,说明网络协商路径需要单独检查。
  4. 又看到 CONNECTED 后 H.264 仍未 ready,说明连接成功并不等于视频已经可解码。
  5. 分别用 candidate 收敛和主动 IDR 改变这两个条件,观察对应阶段是否缩短。
  6. 根据对照结果确认:ICE 路径不确定和等待自然 IDR 是两个主要等待点。

后续加入细粒度打点后,正常样本中的 pipeline PLAYING 只有 3--9ms,offer 约 0.1--0.45s,首个板内 UDP candidate 只有 1--2ms,remote description 到 CONNECTED 为 78--95ms,CONNECTED 到双轨 ready 为 107--529ms。这组数据把"执行代码很慢"和"状态机在等待"区分开来:大头不是 CPU 持续计算,而是协商、 关键帧和媒体注册条件尚未满足。

(三)为什么判断 ICE 是瓶颈

  • publisher 从创建到 connection-state=CONNECTED 约 3.29s,远高于板内 UDP 应有的毫秒级路径。
  • SDP 中存在多个 UDP/TCP candidate,播放器与同机 ZLM 仍需要做不必要的候选检查。
  • 把 ZLM answer 收敛到板内 IPv4 UDP host candidate 后,连接阶段降到约 1.22--1.47s;进一步让 offer 从源头只生成一个板内 UDP candidate 后,本地 gathering 稳定到 1--2ms。

这是一条实验支持的判断链:先从状态日志发现连接时间异常,再改变 candidate 集合作为对照实验,连接时间 随之下降。它能确认 candidate 收敛有效,但由于第一轮还同时上线了 force-IDR,不能精确拆出各自对完整 HTTP 耗时的贡献。

(四)为什么判断 IDR 是瓶颈

  • 连接成功后,Opus 可以持续出包,但 H.264 track ready 仍有约 3--6s 波动。
  • 编码器 GOP 配置为帧率大小,但加入实时流的时刻是任意的,ZLM 可能正好从 GOP 中间开始接收。
  • 在 publisher CONNECTED 后主动请求 IDR,ready 等待立即降到约 0.20--1.01s。
  • 最终分阶段日志进一步确认 CONNECTED → ready 只剩 107--529ms。

同样不是只凭经验认定:主动 IDR 上线后,H.264 ready 等待明显下降;最终日志又把 CONNECTED → ready 量化到 107--529ms。这里的严谨结论是"等待明显下降",而不是宣称所有 场景都不会再等待。

(五)为什么这次不是 RK3588 算力不足?

最终分段数据中,pipeline 进入 PLAYING 只需 3--9ms,正常 candidate gathering 只需 1--2ms, CONNECTED → ready 也已降到 107--529ms。主要耗时跟随状态转换而变化,没有表现为 CPU 上 持续数秒的计算。实时流启动慢不一定是编码性能不够,也可能是状态机在等待网络、关键帧或媒体注册条件。

(六)一个重要的反证:调 ZLM 单轨等待没有用

我们也测试了 wait_add_track_ms=1000。如果瓶颈是 ZLM 不确定"是否还有第二条轨",这个参数 应该直接缩短等待。但成功样本仍为 5.242s、5.254s、8.284s、8.489s,并出现一次 10s 超时。 这说明方向不对:SDP 已声明 H.264 和 Opus,真正缺的是可用连接和可解码 H.264 起点。

(七)从性能到确定性,再到尾部稳定性

试验 0 缩短 ZLM wait_add_track_ms

一个合理但没有命中瓶颈的参数实验

原问题

怀疑 ZLM 在只看到一条 track 时,会固定等待另一条 track,拖慢媒体源注册。

原理解释

wait_add_track_ms 用于"当前只有单轨且协议元数据没有明确轨道数"的场景。本项目的 WebRTC SDP 已明确声明 video 和 audio,因此它不是主要等待点。

修改位置

/root/ZLMediaKit/config.ini[general];测试值从 3000 改为 1000,验证后恢复为 3000。安装包模板仍为 output/ZLMediaKit/config.ini:193

核心代码/配置

复制代码
[general]
# 试验值,最终未采用
wait_add_track_ms=1000

优化收益

0。没有形成更快且稳定的 HTTP 样本,且仍出现 10 秒超时。该方案已回退。

方案 1 ZLM answer 仅保留板内 UDP candidate,并在 CONNECTED 后强制 IDR

整个优化中收益最大的一轮

原问题

  • 播放器和 ZLM 在同一块板上,却仍处理多个 UDP/TCP candidate,连接阶段约 3.29s。
  • 连接成功后只能等待编码器自然输出下一个 IDR,H.264 ready 波动约 3--6s。

原理解释

同机 publisher 不需要公网 NAT 或 TCP 回退。把 answer 收敛为板内 UDP host candidate,可以减少无效 路径检查。连接一旦建立,再强制编码器产生 IDR,ZLM 就不必等待自然 GOP 边界。

修改位置

当轮历史实现:app/WebRtcStreamManager.cpp 的 answer candidate 过滤和 OnPeerConnectionStateNotify()app/main.cppzlm_publisher_ice_ip=auto 解析。当前最终实现位于 app/WebRtcStreamManager.cpp:421-506:3190-3240app/main.cpp:634-643

核心代码

这段 answer 处理只保留一个可用的板内 UDP host candidate;后半段则在连接进入 CONNECTED 后,请求共享 H.264 编码器尽快产生 IDR。

复制代码
// answer 中只接受 UDP host candidate,其余删除;当前实现节选
if (retainedMediaCandidate || !IsUdpHostCandidate(attribute)) {
    gst_sdp_media_remove_attribute(media, j);
    ++removedCount;
    continue;
}

// 当轮为 CONNECTED 后一次 IDR;最终实现仍保留这个阶段
if (peer->zlmPublisher &&
    state == GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTED) {
    RequestPublisherIdr(peer, true);
}

优化收益

  • 8 次成功冷启动:1.43--2.38s ,平均 1.92s
  • 相比 6.49s 缩短 4.57s ,耗时降低约 70%
  • 启动速度约为原来的 3.4 倍
  • ICE/连接阶段降至约 1.22--1.47s ;ready 等待降至 0.20--1.01s

candidate 收敛和强制 IDR 在同一轮上线,因此 4.57s 是组合收益。没有单独的 A/B 样本能把这 4.57s 精确分摊给两个改动,本文不做虚构归因。

方案 2 让本地 publisher offer 从源头 UDP-only

平均收益很小,稳定性价值更大

原问题

方案 1 过滤的是 ZLM 返回的 answer,但 publisher 生成 offer 时仍可能枚举其他网卡和 TCP candidate, 本地 ICE gathering 仍存在波动,甚至偶发 10s gathering 超时。

原理解释

既然通信双方在同一块板上,最确定的拓扑就是指定网卡 IP 的 UDP host candidate。直接配置 webrtcbin 的 ICE agent,只允许 UDP 并绑定 192.168.31.163,比生成后再容忍多余 candidate 更可控。

修改位置

app/WebRtcStreamManager.cpp:337-419ConfigurePublisherIceAgent()ValidatePublisherOfferIceCandidates(); 调用点在 :1273-1280

核心代码

这段代码真正做两件事:关闭 TCP candidate,并把 ICE agent 限制到指定的板内 IP。

复制代码
GObject* iceAgent = nullptr;
g_object_get(webrtc, "ice-agent", &iceAgent, nullptr);

g_object_set(iceAgent,
             "ice-tcp", FALSE,
             "ice-udp", TRUE,
             nullptr);

gboolean addressAdded = FALSE;
g_signal_emit_by_name(iceAgent, "add-local-ip-address",
                      publisherIceIp.c_str(), &addressAdded);

优化收益

轮次 HTTP 冷启动 本地 gathering
1 1.55s 1--2ms
2 2.07s 1--2ms
3 2.03s 1--2ms
4 1.76s 1--2ms
平均 1.85s 1--2ms

相对上一版 1.92s 只缩短约 0.07s ,提升约 3%--4%。主要收益是 offer 只包含一个板内 UDP candidate,并把正常 gathering 收敛到毫秒级。

方案 3 两阶段强制 IDR、首 candidate 等待和自动重试

不追求漂亮平均值,重点修复异常尾部

原问题

  • 只在 CONNECTED 后发 IDR,仍可能错过 remote description 刚生效的更早窗口。
  • UDP-only 后,正常 candidate 只需 1--2ms,但偶发一次完全没有及时出现;旧逻辑会等待 gathering complete,最坏 10s 后失败。
  • 此前日志只有总等待,无法判断 offer、ZLM API、连接和 ready 分别消耗多少。

原理解释

第一次 IDR 放在 remote description 设置完成后,让编码器尽早准备同步帧;第二次放在真正 CONNECTED 后,确保传输路径已经可发送。UDP-only 场景只需要首个有效 candidate,不必等待完整 gathering。 如果 1s 仍没有 candidate,销毁这个异常 publisher 并重建一次,比等满 10s 更可控。

两阶段 IDR 的触发窗口

复制代码
set-remote-description
│
├── IDR #1:尽早触发编码器产生同步帧
│
└── CONNECTED
    │
    └── IDR #2:确保 WebRTC 已进入可发送阶段

第一次的目标是提前准备关键帧,第二次的目标是覆盖传输真正可用后的窗口。代码用两个独立标志保证 每个 publisher 实例在每个阶段最多请求一次,因此重复状态通知不会反复触发。这个设计不是理论上保证 一定更快,而是实测用于降低 track ready 等待波动。

修改位置

app/WebRtcStreamManager.cpp:1225-1473 的 publisher 分阶段计时、首 candidate 等待和 remote-description IDR;:1848-1861 的自动重试;:3190-3240 的两阶段 IDR;声明位于 app/WebRtcStreamManager.h

核心代码

前两段逻辑不是重新编码整条视频,而是向共享 H.264 编码器请求尽快产生下一帧 IDR;每个阶段只会请求一次。

复制代码
// 阶段一:remote description 生效后请求 IDR
peer->remoteDescriptionSetAtMs.store(SteadyNowMs());
RequestPublisherIdr(peer.get(), false);

// 阶段二:CONNECTED 后再次请求 IDR
if (state == GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTED) {
    RequestPublisherIdr(peer, true);
}

这里不再等待完整 ICE gathering,只等待第一个有效 UDP candidate;正常路径是 1--2ms,等待上限为 1000ms。

复制代码
// UDP-only:只等首个本地 candidate;常量值为 1000ms
peer->iceCondition.wait_for(
    lock,
    std::chrono::milliseconds(kPublisherLocalCandidateTimeoutMilliseconds),
    [&peer] { return !peer->localCandidates.empty(); });

下面的恢复逻辑只匹配 candidate 超时,并且只重建一次 publisher,避免把其他错误掩盖成无限重试。

复制代码
// 只对 candidate 超时自动重试一次
if (!publisherStarted &&
    errorMsg.find("Timed out gathering local UDP ICE candidate") == 0) {
    errorMsg.clear();
    publisherStarted = StartZlMediaKitPublisher(channel, errorMsg);
}

优化收益

轮次 HTTP 总耗时 CONNECTED → ready candidate
1 2.350s 107ms 首次 1s 无 candidate,自动重建成功
2 1.937s 529ms 1ms
3 2.104s 520ms 1ms
4 1.667s 116ms 1ms
  • 四轮全部计入:平均 2.015s ,相对基线降低约 69%
  • 排除发生重试的一轮:三次常规平均 1.903s ,相对基线降低约 71%
  • 相对方案 2 的 1.85s,常规平均没有显著提速,样本内反而慢约 0.05s。
  • 异常路径从"最多等 10s 后失败"变成"2.350s 内自动恢复并返回成功"。
  • 首 candidate 的单次等待上限从 10s 降到 1s,等待窗口缩短 90%。

这轮的正确评价是稳定性优化。 它没有创造更低的平均值,但消除了 一类 10 秒失败,并让 CONNECTED → ready 稳定在 107--529ms。把它宣传成新的大幅提速并不准确。

七、所有真实数据放在一起

阶段 测试数据 相对上一有效版本 相对基线
基线 代表值 6.49s;内部 ready 5258ms --- ---
wait_add_track_ms=1000 内部 ready 5.242 / 5.254 / 8.284 / 8.489s;一次 10s 超时 无提升 无提升
方案 1 8 次 1.43--2.38s,平均 1.92s 缩短 4.57s 降低约 70%
方案 2 1.55 / 2.07 / 2.03 / 1.76s,平均 1.8525s 缩短约 0.07s,约 3%--4% 降低约 71%
最终常规路径 1.937 / 2.104 / 1.667s,平均 1.9027s 无显著平均提升 降低 70.7%
最终全样本 再计入 2.350s 自动重试轮,平均 2.0145s 因包含异常恢复,慢约 0.16s 降低 69.0%

(一)最终启动时间线

这些内部阶段存在重叠,不能直接相加。尤其是 offer、ICE、RTP 发送和 ZLM ready 轮询可能并行推进;版本对比仍以端到端 HTTP time_total 为准。

目前实测观察到约 1 秒的主要固定耗时集中在 ZLM /index/api/webrtc 的 answer 阶段 (样本为 1.003--1.006s)。本文没有通过 ZLMediaKit 内部调用栈证明具体原因,因此不把这个 1 秒写成 已确认的根因。

(二)哪些是事实,哪些还只是当前推断

结论强度 本文能够支持的内容
已确认的事实 candidate 数量收敛后,连接耗时下降;主动 IDR 后,H.264 ready 等待明显下降;UDP-only 后正常 gathering 稳定在 1--2ms;单次自动重试处理了一次 candidate 异常,并在 2.350s 内成功。
实验结果 第一轮 8 次成功样本平均 1.92s;方案 2 四次平均 1.8525s;最终三次常规平均 1.9027s,计入一次重试后四次平均 2.0145s。
当前无法精确证明 方案 1 中 candidate filter 与 force-IDR 各自贡献多少;ZLM answer 约 1 秒具体来自哪个内部定时器或等待条件;4--8 次样本不能证明长期 P95/P99。

candidate 收敛 + force IDR 同时上线,因此不能把 4.57 秒精确分摊到两个改动。

八、这些优化适用于什么场景?

本文的 UDP-only 和 host candidate 收敛建立在一个非常具体的拓扑上:publisher 与 ZLMediaKit 运行在同一块 RK3588 上,板内 IP 已知,不需要公网 NAT 穿透,不依赖 TURN,也不需要 TCP fallback。 在这些前提下,限制候选集合是在减少无意义的不确定性。

可以直接参考 不能直接照搬 UDP-only
publisher 与 ZLM 同机;网络拓扑固定;板内 UDP 可达;无需 TURN 和 TCP 回退。 publisher 与 ZLM 不同机;跨 NAT 或公网 WebRTC;依赖 TURN relay;复杂多网卡;网络要求 TCP fallback。

本文优化的是这个项目的已知拓扑,而不是提出一个适用于所有 WebRTC 场景的通用 UDP-only 配置。在公网或未知网络中直接删除 TCP、STUN/TURN candidate,可能让连接能力倒退。

九、可以迁移到其他嵌入式流媒体项目的经验

  1. 先定义完成语义。"接口返回了"与"媒体能播放"不是一回事。只有先规定 track ready, 测出来的启动时间才有业务意义。
  2. **按状态机打点,不要先怀疑算力。**pipeline、offer、candidate、answer、CONNECTED、首个 IDR、track ready 每个边界都要有时间。RK3588 CPU 使用率正常不代表状态机没有等待。
  3. **同机通信应显式收敛网络拓扑。**候选越多并不等于更稳。已知双方同机时,绑定一个板内 UDP host candidate 能减少不确定路径。
  4. **关键帧要在消费者真正可接收时触发。**启动编码器时发 IDR 可能太早;只在 CONNECTED 后发又可能错过准备窗口。remote description 和 CONNECTED 两阶段覆盖更完整。
  5. **优化平均值也要优化尾部失败。**正常 1ms gathering 很漂亮,但一次 10s 超时足以破坏用户 体验。短超时、限定错误类型、只重试一次,比无限重试更容易控制。
  6. 保留无效实验。 wait_add_track_ms 没有收益,反而帮助排除错误假设。技术复盘 如果只保留成功方案,会让后来者重复走弯路。
  7. **组合上线不能伪造单项收益。**candidate filter 和 IDR 在同一轮上线,只能报告组合收益。 没有独立 A/B 数据,就不要人为分摊。
  8. **样本量决定结论强度。**4--8 次测试能确认 6.49s 到约 1.9s 的数量级变化,但不能替代长期 P95/P99 压测。
  9. **资源约束属于架构条件。**常驻 publisher 可以更快,但资源不可接受。最终的约 1.7--2.1s 是在"无人观看回收、按需冷启动"约束下取得的结果。

(一)回答全文的四个问题

问题 答案
为什么慢? 板内 publisher 处理多余 ICE 路径,加上连接后等待自然 H.264 IDR。
怎么定位? 用状态日志拆出 CONNECTED 和 track ready 边界,再用 candidate 收敛、强制 IDR 做对照实验。
怎么优化? answer 和 offer 都限制为板内 UDP host candidate;在 remote description 与 CONNECTED 两阶段强制 IDR;首 candidate 1s 超时后限定重试一次。
什么时候不能照搬? publisher 与 ZLM 非同机、跨 NAT、公网、TURN、复杂多网卡或需要 TCP fallback 时,不能直接套用本文 UDP-only 策略。

最终常规冷启动平均约 1.90s,相对 6.49s 降低 70.7%。第一轮解决了主要性能瓶颈;第二轮主要改善 candidate 的确定性;第三轮主要改善异常路径和尾部稳定性。约 1.7--2.1s 是当前"按需冷启动、不常驻 publisher"的实际结果。若继续优化,应依次调查 ZLM answer 阶段实测约 1 秒的耗时、评估 publisher 常驻/复用,或讨论 ready 前提前返回。但后两者会改变资源约束或"回复即可以播放"的语义,因此当前停止。

相关推荐
SendTomo1 天前
SendTomo 与 send.wang 全景对比评测与实战指南
网络·网络协议·tcp/ip·webrtc·p2p
java_logo1 天前
Docker 部署 SRS:轻松搭建实时音视频流媒体平台
docker·容器·webrtc·实时音视频·rtmp·http-flv·轩辕镜像
wjcroom2 天前
一个可以在线多人玩的五子棋开发与布署方法-WebRTC和WebSocket的测试方法
websocket·网络协议·webrtc
cuijiecheng20185 天前
RK3588 WebRTC 实时流卡顿优化:从视频桥接到 H.264 PTS 回退
音视频·webrtc·h.264
SendTomo7 天前
send.wang:基于浏览器WebRTC实现无客户端文件互传
网络·python·网络协议·webrtc·p2p
应用市场13 天前
无线控制与图传协议全景:从 S.BUS 到 WebRTC,一篇讲透遥控、数传与视频链路
音视频·webrtc
福大大架构师每日一题13 天前
webrtc-rs/webrtc v0.20.3更新:Android 网络切换后 ICE Restart 卡死约 10 秒的问题终于解决
android·网络·webrtc
SendTomo14 天前
P2P传输中IP交换的可控性实现
webrtc·p2p
SendTomo14 天前
基于WebRTC的P2P文件传输IP暴露是正常现象
网络·网络协议·tcp/ip·webrtc·p2p