OLD | NEW |
---|---|
(Empty) | |
1 /* | |
2 * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. | |
3 * | |
4 * Use of this source code is governed by a BSD-style license | |
5 * that can be found in the LICENSE file in the root of the source | |
6 * tree. An additional intellectual property rights grant can be found | |
7 * in the file PATENTS. All contributing project authors may | |
8 * be found in the AUTHORS file in the root of the source tree. | |
9 */ | |
10 | |
11 #include "webrtc/modules/rtp_rtcp/source/rtp_packet.h" | |
12 | |
13 #include <cstring> | |
14 | |
15 #include "webrtc/base/checks.h" | |
16 #include "webrtc/base/logging.h" | |
17 #include "webrtc/base/random.h" | |
18 #include "webrtc/common_types.h" | |
19 #include "webrtc/modules/rtp_rtcp/source/rtp_header_extension.h" | |
20 #include "webrtc/modules/rtp_rtcp/source/rtp_header_extensions.h" | |
21 #include "webrtc/modules/rtp_rtcp/source/byte_io.h" | |
22 | |
23 namespace webrtc { | |
24 namespace rtp { | |
25 namespace { | |
26 const size_t kFixedHeaderSize = 12; | |
27 const uint8_t kRtpVersion = 2; | |
28 const uint16_t kOneByteExtensionId = 0xBEDE; | |
29 const size_t kDefaultPacketSize = 1500; | |
30 } // namespace | |
31 // 0 1 2 3 | |
32 // 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 | |
33 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
34 // |V=2|P|X| CC |M| PT | sequence number | | |
35 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
36 // | timestamp | | |
37 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
38 // | synchronization source (SSRC) identifier | | |
39 // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | |
40 // | Contributing source (CSRC) identifiers | | |
41 // | .... | | |
42 // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | |
43 // |One-byte eXtensions id = 0xbede| length in 32bits | | |
44 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
45 // | Extensions | | |
46 // | .... | | |
47 // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | |
48 // | Payload | | |
49 // | .... : padding... | | |
50 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
51 // | padding | Padding size | | |
52 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
53 Packet::Packet(const ExtensionManager* extensions) | |
54 : extensions_(extensions), buffer_(kDefaultPacketSize) { | |
55 Clear(); | |
56 } | |
57 | |
58 Packet::Packet(const ExtensionManager* extensions, size_t capacity) | |
59 : extensions_(extensions), buffer_(capacity) { | |
60 RTC_DCHECK_GE(capacity, kFixedHeaderSize); | |
61 Clear(); | |
62 } | |
63 | |
64 Packet::~Packet() {} | |
65 | |
66 void Packet::SetExtensionManager(const ExtensionManager* extensions) { | |
67 RTC_DCHECK(extensions); | |
68 extensions_ = extensions; | |
69 for (size_t i = 0; i < num_extensions_; ++i) { | |
70 uint8_t id = data()[extension_entries_[i].offset - 1] >> 4; | |
71 extension_entries_[i].type = extensions_->GetType(id); | |
72 } | |
73 } | |
74 | |
75 bool Packet::Parse(const uint8_t* buffer, size_t buffer_size) { | |
76 if (!ParseBuffer(buffer, buffer_size)) { | |
77 Clear(); | |
78 return false; | |
79 } | |
80 RTC_DCHECK_EQ(size(), buffer_size); | |
81 buffer_.SetData(buffer, buffer_size); | |
82 return true; | |
83 } | |
84 | |
85 bool Packet::Parse(rtc::Buffer buffer) { | |
86 if (!ParseBuffer(buffer.data(), buffer.size())) { | |
87 Clear(); | |
88 return false; | |
89 } | |
90 RTC_DCHECK_EQ(size(), buffer.size()); | |
91 buffer_ = std::move(buffer); | |
92 return true; | |
93 } | |
94 | |
95 bool Packet::Marker() const { | |
96 RTC_DCHECK_EQ(marker_, (data()[1] & 0x80) != 0); | |
97 return marker_; | |
98 } | |
99 | |
100 uint8_t Packet::PayloadType() const { | |
101 RTC_DCHECK_EQ(payload_type_, data()[1] & 0x7f); | |
102 return payload_type_; | |
103 } | |
104 | |
105 uint16_t Packet::SequenceNumber() const { | |
106 RTC_DCHECK_EQ(sequence_number_, | |
107 ByteReader<uint16_t>::ReadBigEndian(data() + 2)); | |
108 return sequence_number_; | |
109 } | |
110 | |
111 uint32_t Packet::Timestamp() const { | |
112 RTC_DCHECK_EQ(sequence_number_, | |
113 ByteReader<uint32_t>::ReadBigEndian(data() + 4)); | |
114 return timestamp_; | |
115 } | |
116 | |
117 uint32_t Packet::Ssrc() const { | |
118 RTC_DCHECK_EQ(sequence_number_, | |
119 ByteReader<uint32_t>::ReadBigEndian(data() + 8)); | |
120 return ssrc_; | |
121 } | |
122 | |
123 std::vector<uint32_t> Packet::Csrcs() const { | |
124 size_t num_csrc = data()[0] & 0x0F; | |
125 RTC_DCHECK_GE(capacity(), kFixedHeaderSize + num_csrc * 4); | |
126 std::vector<uint32_t> csrcs(num_csrc); | |
127 for (size_t i = 0; i < num_csrc; ++i) { | |
128 csrcs[i] = | |
129 ByteReader<uint32_t>::ReadBigEndian(&data()[kFixedHeaderSize + i * 4]); | |
130 } | |
131 return csrcs; | |
132 } | |
133 | |
134 void Packet::GetHeader(RTPHeader* header) const { | |
135 header->markerBit = Marker(); | |
136 header->payloadType = PayloadType(); | |
137 header->sequenceNumber = SequenceNumber(); | |
138 header->timestamp = Timestamp(); | |
139 header->ssrc = Ssrc(); | |
140 std::vector<uint32_t> csrcs = Csrcs(); | |
141 header->numCSRCs = csrcs.size(); | |
142 for (size_t i = 0; i < csrcs.size(); ++i) { | |
143 header->arrOfCSRCs[i] = csrcs[i]; | |
144 } | |
145 header->paddingLength = padding_size(); | |
146 header->headerLength = headers_size(); | |
147 header->payload_type_frequency = 0; | |
148 header->extension.hasTransmissionTimeOffset = | |
149 GetExtension<TransmissionOffset>( | |
150 &header->extension.transmissionTimeOffset); | |
151 header->extension.hasAbsoluteSendTime = | |
152 GetExtension<AbsoluteSendTime>(&header->extension.absoluteSendTime); | |
153 header->extension.hasTransportSequenceNumber = | |
154 GetExtension<TransportSequenceNumber>( | |
155 &header->extension.transportSequenceNumber); | |
156 header->extension.hasAudioLevel = GetExtension<AudioLevel>( | |
157 &header->extension.voiceActivity, &header->extension.audioLevel); | |
158 header->extension.hasVideoRotation = | |
159 GetExtension<VideoOrientation>(&header->extension.videoRotation); | |
160 } | |
161 | |
162 size_t Packet::headers_size() const { | |
163 return payload_offset_; | |
164 } | |
165 | |
166 size_t Packet::payload_size() const { | |
167 return payload_size_; | |
168 } | |
169 | |
170 size_t Packet::padding_size() const { | |
171 return padding_size_; | |
172 } | |
173 | |
174 const uint8_t* Packet::payload() const { | |
175 return data() + payload_offset_; | |
176 } | |
177 | |
178 size_t Packet::capacity() const { | |
179 return buffer_.capacity(); | |
180 } | |
181 | |
182 size_t Packet::size() const { | |
183 return payload_offset_ + payload_size_ + padding_size_; | |
184 } | |
185 | |
186 const uint8_t* Packet::data() const { | |
187 return buffer_.data(); | |
188 } | |
189 | |
190 size_t Packet::FreeCapacity() const { | |
191 return capacity() - size(); | |
192 } | |
193 | |
194 size_t Packet::MaxPayloadSize() const { | |
195 return capacity() - payload_offset_; | |
196 } | |
197 | |
198 void Packet::CopyHeader(const Packet& packet) { | |
199 RTC_DCHECK_GE(capacity(), packet.headers_size()); | |
200 | |
201 marker_ = packet.marker_; | |
202 payload_type_ = packet.payload_type_; | |
203 sequence_number_ = packet.sequence_number_; | |
204 timestamp_ = packet.timestamp_; | |
205 ssrc_ = packet.ssrc_; | |
206 payload_offset_ = packet.payload_offset_; | |
207 num_extensions_ = packet.num_extensions_; | |
208 for (size_t i = 0; i < num_extensions_; ++i) { | |
209 extension_entries_[i] = packet.extension_entries_[i]; | |
210 } | |
211 extensions_size_ = packet.extensions_size_; | |
212 buffer_.SetData(packet.data(), packet.headers_size()); | |
213 // Reset payload and padding. | |
214 payload_size_ = 0; | |
215 padding_size_ = 0; | |
216 } | |
217 | |
218 void Packet::SetMarker(bool marker_bit) { | |
219 marker_ = marker_bit; | |
220 if (marker_) { | |
221 WriteAt(1, data()[1] | 0x80); | |
stefan-webrtc
2016/04/14 13:00:29
Would it make sense to instead have a Serialize fu
sprang_webrtc
2016/04/14 13:41:50
I tried that once it quickly became difficult, esp
danilchap
2016/04/14 14:13:19
Packet class is all about serializing/deserializin
| |
222 } else { | |
223 WriteAt(1, data()[1] & 0x7F); | |
224 } | |
225 } | |
226 | |
227 void Packet::SetPayloadType(uint8_t payload_type) { | |
228 RTC_DCHECK_LE(payload_type, 0x7Fu); | |
229 payload_type_ = payload_type; | |
230 WriteAt(1, (data()[1] & 0x80) | payload_type); | |
231 } | |
232 | |
233 void Packet::SetSequenceNumber(uint16_t seq_no) { | |
234 sequence_number_ = seq_no; | |
235 ByteWriter<uint16_t>::WriteBigEndian(WriteAt(2), seq_no); | |
236 } | |
237 | |
238 void Packet::SetTimestamp(uint32_t timestamp) { | |
239 timestamp_ = timestamp; | |
240 ByteWriter<uint32_t>::WriteBigEndian(WriteAt(4), timestamp); | |
241 } | |
242 | |
243 void Packet::SetSsrc(uint32_t ssrc) { | |
244 ssrc_ = ssrc; | |
245 ByteWriter<uint32_t>::WriteBigEndian(WriteAt(8), ssrc); | |
246 } | |
247 | |
248 void Packet::SetCsrcs(const std::vector<uint32_t>& csrcs) { | |
249 RTC_DCHECK_EQ(num_extensions_, 0u); | |
250 RTC_DCHECK_EQ(payload_size_, 0u); | |
251 RTC_DCHECK_EQ(padding_size_, 0u); | |
252 RTC_DCHECK_LE(csrcs.size(), 0x0fu); | |
253 RTC_DCHECK_LE(kFixedHeaderSize + 4 * csrcs.size(), capacity()); | |
254 payload_offset_ = kFixedHeaderSize + 4 * csrcs.size(); | |
255 WriteAt(0, (data()[0] & 0xF0) | csrcs.size()); | |
256 size_t offset = kFixedHeaderSize; | |
257 for (uint32_t csrc : csrcs) { | |
258 ByteWriter<uint32_t>::WriteBigEndian(WriteAt(offset), csrc); | |
259 offset += 4; | |
260 } | |
261 } | |
262 | |
263 uint8_t* Packet::AllocatePayload(size_t size_bytes) { | |
264 RTC_DCHECK_EQ(padding_size_, 0u); | |
265 if (payload_offset_ + size_bytes > capacity()) { | |
266 LOG(LS_WARNING) << "Cannot set payload, not enough space in buffer."; | |
267 return nullptr; | |
268 } | |
269 payload_size_ = size_bytes; | |
270 return WriteAt(payload_offset_); | |
271 } | |
272 | |
273 void Packet::SetPayloadSize(size_t size_bytes) { | |
274 RTC_DCHECK_EQ(padding_size_, 0u); | |
275 RTC_DCHECK_LE(size_bytes, payload_size_); | |
276 payload_size_ = size_bytes; | |
277 } | |
278 | |
279 bool Packet::SetPadding(uint8_t size_bytes, Random* random) { | |
280 RTC_DCHECK(random); | |
281 if (payload_offset_ + payload_size_ + size_bytes > capacity()) { | |
282 LOG(LS_WARNING) << "Cannot set padding size " << size_bytes << ", only " | |
283 << (capacity() - payload_offset_ - payload_size_) | |
284 << " bytes left in buffer."; | |
285 return false; | |
286 } | |
287 padding_size_ = size_bytes; | |
288 if (padding_size_ > 0) { | |
289 size_t padding_offset = payload_offset_ + payload_size_; | |
290 size_t padding_end = padding_offset + padding_size_; | |
291 for (size_t offset = padding_offset; offset < padding_end - 1; ++offset) { | |
292 WriteAt(offset, random->Rand<uint8_t>()); | |
293 } | |
294 WriteAt(padding_end - 1, padding_size_); | |
295 WriteAt(0, data()[0] | 0x20); // Set padding bit. | |
296 } else { | |
297 WriteAt(0, data()[0] & ~0x20); // Clear padding bit. | |
298 } | |
299 return true; | |
300 } | |
301 | |
302 void Packet::Clear() { | |
303 marker_ = false; | |
304 payload_type_ = 0; | |
305 sequence_number_ = 0; | |
306 timestamp_ = 0; | |
307 ssrc_ = 0; | |
308 payload_offset_ = kFixedHeaderSize; | |
309 payload_size_ = 0; | |
310 padding_size_ = 0; | |
311 num_extensions_ = 0; | |
312 extensions_size_ = 0; | |
313 | |
314 memset(WriteAt(0), 0, kFixedHeaderSize); | |
315 WriteAt(0, kRtpVersion << 6); | |
316 } | |
317 | |
318 bool Packet::ParseBuffer(const uint8_t* buffer, size_t size) { | |
319 if (size < kFixedHeaderSize) { | |
320 return false; | |
321 } | |
322 const uint8_t version = buffer[0] >> 6; | |
323 if (version != kRtpVersion) { | |
324 return false; | |
325 } | |
326 const bool has_padding = (buffer[0] & 0x20) != 0; | |
327 // eXtension | |
sprang_webrtc
2016/04/14 13:41:50
remove comment
danilchap
2016/04/14 14:13:19
Done.
| |
328 const bool has_extension = (buffer[0] & 0x10) != 0; | |
329 const uint8_t number_of_crcs = buffer[0] & 0x0f; | |
330 marker_ = (buffer[1] & 0x80) != 0; | |
331 payload_type_ = buffer[1] & 0x7f; | |
332 | |
333 sequence_number_ = ByteReader<uint16_t>::ReadBigEndian(&buffer[2]); | |
334 timestamp_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[4]); | |
335 ssrc_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[8]); | |
336 if (size < kFixedHeaderSize + number_of_crcs * 4) { | |
337 return false; | |
338 } | |
339 payload_offset_ = kFixedHeaderSize + number_of_crcs * 4; | |
340 | |
341 if (has_padding) { | |
342 padding_size_ = buffer[size - 1]; | |
343 if (padding_size_ == 0) { | |
344 LOG(LS_WARNING) << "Padding was set, but padding size is zero"; | |
345 return false; | |
346 } | |
347 } else { | |
348 padding_size_ = 0; | |
349 } | |
350 | |
351 num_extensions_ = 0; | |
352 extensions_size_ = 0; | |
353 if (has_extension) { | |
354 /* RTP header extension, RFC 3550. | |
355 0 1 2 3 | |
356 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 | |
357 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
358 | defined by profile | length | | |
359 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
360 | header extension | | |
361 | .... | | |
362 */ | |
363 size_t extension_offset = payload_offset_ + 4; | |
364 if (extension_offset > size) { | |
365 return false; | |
366 } | |
367 uint16_t profile = | |
368 ByteReader<uint16_t>::ReadBigEndian(&buffer[payload_offset_]); | |
369 size_t extensions_capacity = | |
370 ByteReader<uint16_t>::ReadBigEndian(&buffer[payload_offset_ + 2]); | |
371 extensions_capacity *= 4; | |
372 if (extension_offset + extensions_capacity > size) { | |
373 return false; | |
374 } | |
375 if (profile == kOneByteExtensionId) { | |
376 const size_t kOneByteHeaderSize = 1; | |
377 const uint8_t kPaddingId = 0; | |
378 const uint8_t kReservedId = 15; | |
379 while (extensions_size_ + kOneByteHeaderSize < extensions_capacity) { | |
380 uint8_t id = buffer[extension_offset + extensions_size_] >> 4; | |
381 if (id == kReservedId) { | |
382 break; | |
383 } else if (id == kPaddingId) { | |
384 extensions_size_++; | |
385 continue; | |
386 } | |
387 uint8_t length = | |
388 1 + (buffer[extension_offset + extensions_size_] & 0xf); | |
389 extensions_size_ += kOneByteHeaderSize; | |
390 if (num_extensions_ >= kMaxExtensionHeaders) | |
philipel
2016/04/14 11:15:16
{} :)
sprang_webrtc
2016/04/14 13:41:50
Should we really silently ignore this?
maybe log w
danilchap
2016/04/14 14:13:19
Done: so many extensions means packet is invalid,
| |
391 break; | |
392 extension_entries_[num_extensions_].type = | |
393 extensions_ ? extensions_->GetType(id) | |
394 : ExtensionManager::kInvalidType; | |
395 extension_entries_[num_extensions_].length = length; | |
396 extension_entries_[num_extensions_].offset = | |
397 extension_offset + extensions_size_; | |
398 num_extensions_++; | |
399 extensions_size_ += length; | |
400 } | |
401 } | |
sprang_webrtc
2016/04/14 13:41:50
Log warning if profile isn't one byte extension he
danilchap
2016/04/14 14:13:19
Done.
| |
402 payload_offset_ = extension_offset + extensions_capacity; | |
403 } | |
404 | |
405 if (payload_offset_ + padding_size_ > size) { | |
406 return false; | |
407 } | |
408 payload_size_ = size - payload_offset_ - padding_size_; | |
409 return true; | |
410 } | |
411 | |
412 bool Packet::FindExtension(ExtensionType type, | |
413 uint8_t length, | |
414 uint16_t* offset) const { | |
415 RTC_DCHECK(offset); | |
416 for (size_t i = 0; i < num_extensions_; ++i) { | |
417 if (extension_entries_[i].type == type) { | |
418 RTC_CHECK_EQ(length, extension_entries_[i].length) | |
419 << "Length mismatch for extension '" << type << "'" | |
420 << "should be " << length << ", received " | |
421 << extension_entries_[i].length; | |
422 *offset = extension_entries_[i].offset; | |
423 return true; | |
424 } | |
425 } | |
426 return false; | |
427 } | |
428 | |
429 bool Packet::AllocateExtension(ExtensionType type, | |
430 uint8_t length, | |
431 uint16_t* offset) { | |
432 if (!extensions_) { | |
433 return false; | |
434 } | |
435 if (FindExtension(type, length, offset)) { | |
436 return true; | |
437 } | |
438 | |
439 // Can't add new extension after payload/padding was set. | |
440 if (payload_size_ > 0) { | |
441 return false; | |
442 } | |
443 if (padding_size_ > 0) { | |
444 return false; | |
445 } | |
446 | |
447 uint8_t extension_id = extensions_->GetId(type); | |
448 if (extension_id == ExtensionManager::kInvalidId) { | |
449 return false; | |
450 } | |
451 RTC_DCHECK_GT(length, 0u); | |
452 RTC_DCHECK_LE(length, 16u); | |
453 | |
454 size_t num_csrc = data()[0] & 0x0F; | |
455 size_t extensions_offset = kFixedHeaderSize + (num_csrc * 4) + 4; | |
456 const uint8_t kExtensionHeaderSize = 1; // Using one-byte extensions only. | |
457 if (extensions_offset + extensions_size_ + kExtensionHeaderSize + length > | |
458 capacity()) { | |
459 LOG(LS_WARNING) << "Extension cannot be registered: " | |
460 "Not enough space left in buffer."; | |
461 return false; | |
462 } | |
463 | |
464 uint16_t new_extensions_size = | |
465 extensions_size_ + kExtensionHeaderSize + length; | |
466 uint16_t extensions_words = | |
467 (new_extensions_size + 3) / 4; // Wrap up to 32bit. | |
468 if (extensions_words > 0xFFFF) { | |
469 LOG(LS_WARNING) << "Too much extension header data, exceeds 2^16 DWORDS."; | |
470 return false; | |
471 } | |
472 | |
473 // All checks passed, write down the extension. | |
474 if (num_extensions_ == 0) { | |
475 RTC_DCHECK_EQ(payload_offset_, kFixedHeaderSize + (num_csrc * 4)); | |
476 RTC_DCHECK_EQ(extensions_size_, 0); | |
477 WriteAt(0, data()[0] | 0x10); // Set extension bit. | |
478 // Profile specific ID always set to OneByteExtensionHeader. | |
479 ByteWriter<uint16_t>::WriteBigEndian(WriteAt(extensions_offset - 4), | |
480 kOneByteExtensionId); | |
481 } | |
482 | |
483 WriteAt(extensions_offset + extensions_size_, | |
484 (extension_id << 4) | (length - 1)); | |
485 RTC_DCHECK_LT(num_extensions_, kMaxExtensionHeaders); | |
486 extension_entries_[num_extensions_].type = type; | |
487 extension_entries_[num_extensions_].length = length; | |
488 *offset = extensions_offset + kExtensionHeaderSize + extensions_size_; | |
489 extension_entries_[num_extensions_].offset = *offset; | |
490 ++num_extensions_; | |
491 extensions_size_ = new_extensions_size; | |
492 | |
493 // Update header length field. | |
494 ByteWriter<uint16_t>::WriteBigEndian(WriteAt(extensions_offset - 2), | |
495 extensions_words); | |
496 // Fill extension padding place with zeroes. | |
497 size_t extension_padding_size = 4 * extensions_words - extensions_size_; | |
498 memset(WriteAt(extensions_offset + extensions_size_), 0, | |
499 extension_padding_size); | |
500 payload_offset_ = extensions_offset + 4 * extensions_words; | |
501 return true; | |
502 } | |
503 | |
504 uint8_t* Packet::WriteAt(size_t offset) { | |
505 return buffer_.data() + offset; | |
506 } | |
507 | |
508 void Packet::WriteAt(size_t offset, uint8_t byte) { | |
509 buffer_.data()[offset] = byte; | |
510 } | |
511 | |
512 } // namespace rtp | |
513 } // namespace webrtc | |
OLD | NEW |