WebRTC 之 RTX
Abstract | WebRTC RTX 笔记 |
---|---|
Authors | Walter Fan |
Category | learning note |
Status | v1.0 |
Updated | 2020-08-28 |
License | CC-BY-NC-ND 4.0 |
什么是 RTX
RTX 就是重传 Retransmission, 将丢失的包重新由发送方传给接收方。
Webrtc 默认开启 RTX (重传),它一般采用不同的 SSRC 进行传输,即原始的 RTP 包和重传的 RTP 包的 SSRC 是不同的,这样不会干扰原始 RTP 包的度量。
RTX 包的 Payload 在 RFC4588 中有详细描述,一般 NACK 导致的重传包和 Bandwidth Probe 导致的探测包也可能走 RTX 通道。
为什么用 RTX
媒体流多使用 RTP 通过 UDP 进行传输,由于是不可靠传输,丢包是不可避免,也是可以容忍的,但是对于一些关键数据是不能丢失的,这时候就需要重传(RTX)。
在 WebRTC 中常用的 QoS 策略有
- 反馈:例如 PLI , NACK
- 冗余, 例如 FEC, RTX
- 调整:例如码率,分辨率及帧率的调整
- 缓存: 例如 Receive Adaptive Jitter Buffer, Send Buffer
这些措施一般需要结合基于拥塞控制(congestion control) 及带宽估计(bandwidth estimation)技术, 不同的网络条件下需要采用不同的措施。
FEC 用作丢包恢复需要占用更多的带宽,即使 5% 的丢包需要几乎一倍的带宽,在带宽有限的情况下可能会使情况更糟。
RTX 不会占用太多的带宽,接收方发送 NACK 指明哪些包丢失了,发送方通过单独的 RTP 流(不同的 SSRC)来重传丢失的包,但是 RTX 至少需要一个 RTT 来修复丢失的包。
音频流对于延迟很敏感,而且占用带宽不多,所以用 FEC 更好。WebRTC 默认并不为 audio 开启 RTX
视频流对于延迟没那么敏感,而且占用带宽很多,所以用 RTX 更好。
RTX 相关的信令
RTX 的信令层主要是由发送方通过 SDP 告知接收方我支持 RTX 特性,并且约定原始包和重传包之间的关系由什么方式指定。
现在常用的方式有三种
- APT - Associated Payload Type 关联荷载类型 - Chrome, Edge, Firefox, Safari 都支持
- RID/RRID - RTP Stream Id 和 Repaired RTP Stream Id - - Chrome, Edge, Safari 支持, Firefox 不支持
- SSRC Group - SSRC 分组 - Firefox 支持,其他三个现在优先用 rid/rrid
SDP Extensions
1) Associated Payload Type
在SDP 中可以指定 RTP 流所关联的 RTX 流的荷载类型 Associated Payload Type, 参照 RFC 4588, 期望在 SDP 中有如下属性
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96;rtx-time=3000
for examplev=0
o=mascha 2980675221 2980675778 IN IP4 host.example.net
c=IN IP4 192.0.2.0
m=video 49170 RTP/AVPF 96 97
a=rtpmap:96 MP4V-ES/90000
a=rtcp-fb:96 nack
a=fmtp:96 profile-level-id=8;config=01010000012000884006682C2090A21F
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96;rtx-time=3000
2) RID and RRID
As RFC 8853, 约定 RTP 包中增加 rid 和 rrid 的扩展头
a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=simulcast...
a=rid:<rid-id> <direction> [pt=<fmt-list>;]<restriction>=<value>...
- direction 可以是 send 或者 recv,pt 包含相关的 payload type, restriction 是指一些编码约束, 详情参见 RFC8851
3) SSRC-Group
还有一个方法就是 SSRC Group, 将相互之间有关联关系的媒体流的 SSRC 编配成一个个小组
1. FID SSRC-group for RTX
举例如下
a=ssrc:659652645 cname:Taj3/ieCnLbsUFoH
a=ssrc:659652645 msid:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk 028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc:659652645 mslabel:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk
a=ssrc:659652645 label:028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc:98148385 cname:Taj3/ieCnLbsUFoH
a=ssrc:98148385 msid:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk 028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc:98148385 mslabel:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk
a=ssrc:98148385 label:028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc-group:FID 659652645 98148385
2. SIM SSRC-group for Simulcast
Simulcast 联播结合 RTX , 可做如下所示例中的分组
a=ssrc:659652645 cname:Taj3/ieCnLbsUFoH
a=ssrc:659652645 msid:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk 028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc:659652645 mslabel:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk
a=ssrc:659652645 label:028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc:98148385 cname:Taj3/ieCnLbsUFoH
a=ssrc:98148385 msid:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk 028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc:98148385 mslabel:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk
a=ssrc:98148385 label:028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc:1982135572 cname:Taj3/ieCnLbsUFoH
a=ssrc:1982135572 msid:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk 028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc:1982135572 mslabel:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk
a=ssrc:1982135572 label:028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc:2523084908 cname:Taj3/ieCnLbsUFoH
a=ssrc:2523084908 msid:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk 028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc:2523084908 mslabel:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk
a=ssrc:2523084908 label:028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc:3604909222 cname:Taj3/ieCnLbsUFoH
a=ssrc:3604909222 msid:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk 028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc:3604909222 mslabel:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk
a=ssrc:3604909222 label:028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc:1893605472 cname:Taj3/ieCnLbsUFoH
a=ssrc:1893605472 msid:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk 028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc:1893605472 mslabel:i1zOaprU7rZzMDaOXFdqwkq7Q6wP6f3cgUgk
a=ssrc:1893605472 label:028ab73b-cdd0-4b61-a282-ea0ed0c6a9bb
a=ssrc-group:SIM 659652645 1982135572 3604909222
a=ssrc-group:FID 659652645 98148385
a=ssrc-group:FID 1982135572 2523084908
a=ssrc-group:FID 3604909222 1893605472
RTP 头扩展
根据 RFC8852: RTP Stream Identifier Source Description (SDES) 中的定义,RID 和 RRID 的扩展头格式如下
- RtpStreamId 对每个 RTP stream 都是不同的(类似于 SSRC , 在RTP Session 中需要保持唯一性)
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|RtpStreamId=12 | length | RtpStreamId ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- RepairedRtpStreamId 只会出现在 Repair RTP Streams 中, 指明它所修复的 RTP 流的 rid
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Repaired...=13 | length | RepairRtpStreamId ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
RTX 媒体包的格式
RFC4588 - "RTP Retransmission Payload Format" 中描述了 RTX RTP 包的格式。
- RTP 头中会包含上面所述的 rrid
- RTP 荷载中会有一个 OSN ,对应原始 RTP 包中的 sequence number
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| OSN | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| Original RTP Packet Payload |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
例如
- SDP 中指定了 rid 的值 和扩展头的标识
a=rid:1 send
a=rid:2 send
a=rid:3 send
a=simulcast:send 1;2;3a=extmap:8/sendrecv http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4/sendrecv urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:5/sendrecv urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:7/sendrecv urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
- 原始的 RTP 包的格式如下
Real-Time Transport Protocol
[Stream setup by HEUR RTP (frame 62)]
10.. .... = Version: RFC 1889 Version (2)
..0. .... = Padding: False
...1 .... = Extension: True
.... 0000 = Contributing source identifiers count: 0
1... .... = Marker: True
Payload type: DynamicRTP-Type-97 (97)
Sequence number: 27303
[Extended sequence number: 92839]
Timestamp: 3417222624
Synchronization Source identifier: 0x9100cc9c (2432748700)
Defined by profile: Unknown (0xbede)
Extension length: 2
Header extensions
RFC 5285 Header Extension (One-Byte Header)
Identifier: 8
Length: 3
Extension Data: 6e8c4a
RFC 5285 Header Extension (One-Byte Header)
Identifier: 4
Length: 1
Extension Data: 30
RFC 5285 Header Extension (One-Byte Header)
Identifier: 5
Length: 1
Extension Data: 31
Payload: 9a2ba3655796f772c2c0159bd6570fb896b7f95142362c29381d926f75cf8c364f927912…
- RTX RTP 包的格式如下
Real-Time Transport Protocol
[Stream setup by HEUR RTP (frame 62)]
10.. .... = Version: RFC 1889 Version (2)
..0. .... = Padding: False
...1 .... = Extension: True
.... 0000 = Contributing source identifiers count: 0
0... .... = Marker: False
Payload type: DynamicRTP-Type-124 (124)
Sequence number: 7863
[Extended sequence number: 73399]
Timestamp: 3417198504
Synchronization Source identifier: 0x58b41246 (1488196166)
Defined by profile: Unknown (0xbede)
Extension length: 2
Header extensions
RFC 5285 Header Extension (One-Byte Header)
Identifier: 8
Length: 3
Extension Data: 6e051f
RFC 5285 Header Extension (One-Byte Header)
Identifier: 4
Length: 1
Extension Data: 30
RFC 5285 Header Extension (One-Byte Header)
Identifier: 7
Length: 1
Extension Data: 31
Payload: 9d41d0efd4d67217f916c5854544005a847a64f0936f6620873be35ba26fb2ddfe465015…
WebRTC 是怎么实现 RTX 的
在 WebRTC 中,主要实现在两个方面
1)接收端生成 NACK:检查 Sequence Number , 如果发现有丢包,并且在合理范围之内,就会生成 NACK 包给发送方,要求重传。
NACK 包格式参见 RFC4585#page-34
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P| 1 | 205 | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of packet sender |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of media source |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| PID(SN) | BLP |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
BLP: 是指位掩码,bit 位为1 表示这个包丢失了
( bitmask of following lost packets 16bits, bit_i=1: lost )
在 SDP 中可以指定RTX 所支持的时长, 如果没有,那么 WebRTC 在发送端会维持一个所发送包的默认的长度,
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96;rtx-time=3000
2) 发送端处理 NACK, 并发送 RTX 包
当收到 NACK 请求时
- OnReceivedNack
void RTPSender::OnReceivedNack(const std::vector<uint16_t>& nack_sequence_numbers,int64_t avg_rtt) {packet_history_->SetRtt(TimeDelta::Millis(5 + avg_rtt));for (uint16_t seq_no : nack_sequence_numbers) {const int32_t bytes_sent = ReSendPacket(seq_no);if (bytes_sent < 0) {// Failed to send one Sequence number. Give up the rest in this nack.RTC_LOG(LS_WARNING) << "Failed resending RTP packet " << seq_no<< ", Discard rest of packets.";break;}}
}
- 于是,从发送历史中找到 NACK 中指明的包,构建 RTX 包以重传
nt32_t RTPSender::ReSendPacket(uint16_t packet_id) {int32_t packet_size = 0;const bool rtx = (RtxStatus() & kRtxRetransmitted) > 0;std::unique_ptr<RtpPacketToSend> packet =packet_history_->GetPacketAndMarkAsPending(packet_id, [&](const RtpPacketToSend& stored_packet) {// Check if we're overusing retransmission bitrate.// TODO(sprang): Add histograms for nack success or failure// reasons.packet_size = stored_packet.size();std::unique_ptr<RtpPacketToSend> retransmit_packet;if (retransmission_rate_limiter_ &&!retransmission_rate_limiter_->TryUseRate(packet_size)) {return retransmit_packet;}if (rtx) {retransmit_packet = BuildRtxPacket(stored_packet);} else {retransmit_packet =std::make_unique<RtpPacketToSend>(stored_packet);}if (retransmit_packet) {retransmit_packet->set_retransmitted_sequence_number(stored_packet.SequenceNumber());}return retransmit_packet;});if (packet_size == 0) {// Packet not found or already queued for retransmission, ignore.RTC_DCHECK(!packet);return 0;}if (!packet) {// Packet was found, but lambda helper above chose not to create// `retransmit_packet` out of it.return -1;}packet->set_packet_type(RtpPacketMediaType::kRetransmission);packet->set_fec_protect_packet(false);std::vector<std::unique_ptr<RtpPacketToSend>> packets;packets.emplace_back(std::move(packet));paced_sender_->EnqueuePackets(std::move(packets));return packet_size;
}
- 构建 RTX 包
std::unique_ptr<RtpPacketToSend> RTPSender::BuildRtxPacket(const RtpPacketToSend& packet) {std::unique_ptr<RtpPacketToSend> rtx_packet;// Add original RTP header.{MutexLock lock(&send_mutex_);if (!sending_media_)return nullptr;RTC_DCHECK(rtx_ssrc_);// Replace payload type.auto kv = rtx_payload_type_map_.find(packet.PayloadType());if (kv == rtx_payload_type_map_.end())return nullptr;rtx_packet = std::make_unique<RtpPacketToSend>(&rtp_header_extension_map_,max_packet_size_);rtx_packet->SetPayloadType(kv->second);// Replace SSRC.rtx_packet->SetSsrc(*rtx_ssrc_);CopyHeaderAndExtensionsToRtxPacket(packet, rtx_packet.get());// RTX packets are sent on an SSRC different from the main media, so the// decision to attach MID and/or RRID header extensions is completely// separate from that of the main media SSRC.//// Note that RTX packets must used the RepairedRtpStreamId (RRID) header// extension instead of the RtpStreamId (RID) header extension even though// the payload is identical.if (always_send_mid_and_rid_ || !rtx_ssrc_has_acked_) {// These are no-ops if the corresponding header extension is not// registered.if (!mid_.empty()) {rtx_packet->SetExtension<RtpMid>(mid_);}if (!rid_.empty()) {rtx_packet->SetExtension<RepairedRtpStreamId>(rid_);}}}RTC_DCHECK(rtx_packet);uint8_t* rtx_payload =rtx_packet->AllocatePayload(packet.payload_size() + kRtxHeaderSize);if (rtx_payload == nullptr)return nullptr;// Add OSN (original sequence number).ByteWriter<uint16_t>::WriteBigEndian(rtx_payload, packet.SequenceNumber());// Add original payload data.auto payload = packet.payload();memcpy(rtx_payload + kRtxHeaderSize, payload.data(), payload.size());// Add original additional data.rtx_packet->set_additional_data(packet.additional_data());// Copy capture time so e.g. TransmissionOffset is correctly set.rtx_packet->set_capture_time(packet.capture_time());return rtx_packet;
}static void CopyHeaderAndExtensionsToRtxPacket(const RtpPacketToSend& packet,RtpPacketToSend* rtx_packet) {// Set the relevant fixed packet headers. The following are not set:// * Payload type - it is replaced in rtx packets.// * Sequence number - RTX has a separate sequence numbering.// * SSRC - RTX stream has its own SSRC.rtx_packet->SetMarker(packet.Marker());rtx_packet->SetTimestamp(packet.Timestamp());// Set the variable fields in the packet header:// * CSRCs - must be set before header extensions.// * Header extensions - replace Rid header with RepairedRid header.const std::vector<uint32_t> csrcs = packet.Csrcs();rtx_packet->SetCsrcs(csrcs);for (int extension_num = kRtpExtensionNone + 1;extension_num < kRtpExtensionNumberOfExtensions; ++extension_num) {auto extension = static_cast<RTPExtensionType>(extension_num);// Stream ID header extensions (MID, RSID) are sent per-SSRC. Since RTX// operates on a different SSRC, the presence and values of these header// extensions should be determined separately and not blindly copied.if (extension == kRtpExtensionMid ||extension == kRtpExtensionRtpStreamId) {continue;}// Empty extensions should be supported, so not checking `source.empty()`.if (!packet.HasExtension(extension)) {continue;}rtc::ArrayView<const uint8_t> source = packet.FindExtension(extension);rtc::ArrayView<uint8_t> destination =rtx_packet->AllocateExtension(extension, source.size());// Could happen if any:// 1. Extension has 0 length.// 2. Extension is not registered in destination.// 3. Allocating extension in destination failed.if (destination.empty() || source.size() != destination.size()) {continue;}std::memcpy(destination.begin(), source.begin(), destination.size());}
}
3) 接收端收 RTX packet,重新构建 media packet , 找到对应的 media stream ,放入其接收缓冲
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/call/rtx_receive_stream.cc;l=35
void RtxReceiveStream::OnRtpPacket(const RtpPacketReceived& rtx_packet) {RTC_DCHECK_RUN_ON(&packet_checker_);if (rtp_receive_statistics_) {rtp_receive_statistics_->OnRtpPacket(rtx_packet);}rtc::ArrayView<const uint8_t> payload = rtx_packet.payload();if (payload.size() < kRtxHeaderSize) {return;}auto it = associated_payload_types_.find(rtx_packet.PayloadType());if (it == associated_payload_types_.end()) {RTC_DLOG(LS_VERBOSE) << "Unknown payload type "<< static_cast<int>(rtx_packet.PayloadType())<< " on rtx ssrc " << rtx_packet.Ssrc();return;}RtpPacketReceived media_packet;media_packet.CopyHeaderFrom(rtx_packet);media_packet.SetSsrc(media_ssrc_);media_packet.SetSequenceNumber((payload[0] << 8) + payload[1]);media_packet.SetPayloadType(it->second);media_packet.set_recovered(true);media_packet.set_arrival_time(rtx_packet.arrival_time());// Skip the RTX header.rtc::ArrayView<const uint8_t> rtx_payload = payload.subview(kRtxHeaderSize);uint8_t* media_payload = media_packet.AllocatePayload(rtx_payload.size());RTC_DCHECK(media_payload != nullptr);memcpy(media_payload, rtx_payload.data(), rtx_payload.size());media_sink_->OnRtpPacket(media_packet);
}
存在问题
现在 WebRTC Library 对于 rrid(RepairedRtpStreamId) 的支持并不完善,仍然需要用 SSRC-Group 来指明 RTX stream 所使用的 SSRC , 然后才能进行丢包恢复,参见 Issue 10297: RTX does not work if SSRCs are not negotiated
参考资料
- RFC4588: RTP Retransmission Payload Format
- RFC4585: Extended RTP Profile for RTCP-Based Feedback
- RFC8851: RTP Payload Format Restrictions
- RFC8852: RTP Stream Identifier Source Description (SDES)
- RFC8853: Using Simulcast in Session Description Protocol (SDP) and RTP Sessions
- Implement RTX for WebRTC
- https://w3c.github.io/webrtc-pc/#simulcast-functionality
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可
http://www.taodudu.cc/news/show-1992660.html
相关文章:
- webrtc-sdp编码信息协商
- WebRTC:会话描述协议SDP
- 会话描述协议-SDP
- nike tiempo ylak raoh fmtp
- h264和h265视频流SDP描述详解
- SDP的fmtp部分
- gentoo中文文档
- 小程序登录(附详细文档)
- Bpmn.js 中文文档(一)
- lsi计算文档相似度
- 文档分类的实现原理
- pyhon爬虫—爬取原创力文档(全面解析)
- open62541中文文档
- BUMO 区块链开发文档
- itext7中文开发文档(二)
- 免费解析成pdf下载 -原创力文档
- python免费 爬取原创力文档并转换成pdf(部分版)
- 下载文库文档
- python爬虫-book118
- 若依接口文档
- 短信开发文档
- Xilinx官方文档检索说明
- 冰点——收费文档的下载利器
- openshift安装文档
- Python爬虫入门教程21:文档的爬取
- Vivado下使用Microblaze控制LED(vcu118,HLS级开发)
- autojs java文件_autojs官方文档
- python book118_Python3 book118.com文档下载(图片形式)
- book118可预览文档下载
- book118免费下载文档方法
WebRTC 之 RTX相关推荐
- 什么是FEC/NACK/RTX
FEC (Forward Error Correction) 中文叫前向纠错 前向纠错技术(Forward Error Correction)在确保信号的长距可靠传输方面也起着非常重要的作用,逐渐成为 ...
- WebRTC内置debug工具,详细参数解读
为了确保这篇文章所写内容尽可能的准确,我决定请来Philipp Hancke来作为此篇文章的共同作者. 当你想要找到你WebRTC产品中的问题时,webrtc-internals是一个非常棒的工具,因 ...
- 资讯|WebRTC M98 更新
WebRTC M98 目前在 Chrome 的稳定版中可用,包含 3 个新特性以及超过 15 个错误修复.增强和稳定性/性能改进. 欢迎关注网易云信公众号,我们将定期翻译 WebRTC 相关内容,帮助 ...
- 资讯|WebRTC M97 更新
WebRTC M97 目前在 Chrome 的稳定版中可用,包含 10 多个错误修复.增强和稳定性/性能改进. 欢迎关注网易云信公众号,我们将定期翻译 WebRTC 相关内容,帮助开发者获得最新资讯, ...
- 【入门】WebRTC知识点概览 | 内有技术干货免费下载
什么是WebRTC WebRTC 即Web Real-Time Communication(网页实时通信)的缩写,是一个支持网页浏览器之间进行实时数据传输(包括音频.视频.数据流)的技术.经过多年的发 ...
- WebRTC 中收集音视频编解码能力
在 WebRTC 中,交互的两端在建立连接过程中,需要通过 ICE 协议,交换各自的音视频编解码能力,如编解码器和编解码器的一些参数配置,并协商出一组配置和参数,用于后续的音视频传输过程. 对于音频, ...
- 新的Azure通信服务(ACS)如何实现WebRTC?
正文字数:3144 阅读时长:4分钟 本文来自Housepaty的软件工程师Gustavo Garcia,他对Azure通信服务(ACS)进行了全面的评估,包括从浏览器兼容性.编解码器到带宽估计算法 ...
- 不需要SFU实现WebRTC联播实践
不需要SFU而实现WebRTC联播,appear.in的WebRTC工程师Philipp Hancke实现了在Chrome和Firefox之间的联播.LiveVideoStack对原文进行了摘译. 文 ...
- video_replay如何捕获和回放WebRTC视频流
视频编码问题常常是最难解决的问题之一,video_replay工具可以帮助分析定位故障.视频协作平台pixip的工程师Stian Selnes撰文,详解了如何通过video_replay来捕获.分析视 ...
- Google Hangouts支持使用Firefox WebRTC
自去年4月Firefox 53删除NPAPI以来,该插件一直无法被正常访问.而就在去年年末,Google Hangouts(环聊)重新支持使用Firefox WebRTC.本文深度剖析了Firefox ...
最新文章
- 28和lba48命令格式区别_linux硬盘分区、格式化、挂载超详细步骤
- 对JavaScript内置对象arguments的一些见解
- 十、Linux文件系统基本操作(mount挂载,umount卸载)
- 时空图卷积神经网络(st-gcn)论文解读
- Facebook 实时聊天架构日均处理数十亿条消息!
- JAVA如何插入MySql的datetime类型
- git将本地练手的项目放置到git远端上--本地仓库和远程建立连接
- ACWING830 单调栈
- 真正的云主机到底是什么样的?转发
- 软考——论文写作基本介绍
- 元宇宙势不可挡,facebook已更名Meta,前端人又能做什么?
- 动手学 《动手学深度学习》(安装pytorch)
- 连接池原理解读,各个连接池对比
- C#高级编程——C#扩展方法+接口,定义统一的搜索接口,基于Unity(三)——图文详解加源码
- 记一次Linux 下磁盘分配和扩容操作
- 【中文分词】 FMM BMM (python)
- 第十章 国民收入的决定:收入-支出模型
- 目前服务器的操作系统有哪些?
- IAR下连仿真器可以正常运行,程序下载到flash部分功能异常
- 2016.1.6~2017.7.7,袋鼠云一岁半啦
热门文章
- c语言观察程序流程图,程序流程图的画法
- 2022考研王道计算机408pdf(王道计算机组成原理+王道操作系统+王道计算机网络+王道数据结构)无水印
- 王招治计算机财务管理,计算机财务管理——以Excel为分析工具
- 传奇修改map地图教程_传奇地图MAP编辑器
- 苹果计算机如何安装应用软件,Mac下如何安装软件?
- Halcon深度学习自定义网络模型-VGG16
- 如何使用Python玩转PDF各种骚操作?你看了就知道。
- idea 检测 重复代码_重复代码检测
- php导出excel不完整,急:php导出excel时,因数据比较多,经常导出不完整就结束了【php系统数据导出excel表格】...
- html网页url伪静态,静态、动态、伪静态三种URL表形式优缺点介绍