blob: 37524760d98990f204d016b194084eaf5da6c09e [file] [log] [blame]
danilchap1edb7ab2016-04-20 05:25:10 -07001/*
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
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020011#include "modules/rtp_rtcp/source/rtp_header_extensions.h"
danilchap1edb7ab2016-04-20 05:25:10 -070012
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020013#include "modules/rtp_rtcp/include/rtp_cvo.h"
14#include "modules/rtp_rtcp/source/byte_io.h"
15#include "rtc_base/checks.h"
16#include "rtc_base/logging.h"
danilchap1edb7ab2016-04-20 05:25:10 -070017
18namespace webrtc {
19// Absolute send time in RTP streams.
20//
21// The absolute send time is signaled to the receiver in-band using the
22// general mechanism for RTP header extensions [RFC5285]. The payload
23// of this extension (the transmitted value) is a 24-bit unsigned integer
24// containing the sender's current time in seconds as a fixed point number
25// with 18 bits fractional part.
26//
27// The form of the absolute send time extension block:
28//
29// 0 1 2 3
30// 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
31// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
32// | ID | len=2 | absolute send time |
33// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
danilchape2a01772016-10-28 07:08:58 -070034constexpr RTPExtensionType AbsoluteSendTime::kId;
35constexpr uint8_t AbsoluteSendTime::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -070036constexpr const char AbsoluteSendTime::kUri[];
danilchap1edb7ab2016-04-20 05:25:10 -070037
danilchap978504e2017-04-06 01:03:53 -070038bool AbsoluteSendTime::Parse(rtc::ArrayView<const uint8_t> data,
39 uint32_t* time_24bits) {
40 if (data.size() != 3)
41 return false;
42 *time_24bits = ByteReader<uint32_t, 3>::ReadBigEndian(data.data());
danilchap1edb7ab2016-04-20 05:25:10 -070043 return true;
44}
45
Danil Chapovalovf3ba6482017-06-12 15:43:55 +020046bool AbsoluteSendTime::Write(uint8_t* data, uint32_t time_24bits) {
47 RTC_DCHECK_LE(time_24bits, 0x00FFFFFF);
48 ByteWriter<uint32_t, 3>::WriteBigEndian(data, time_24bits);
danilchap1edb7ab2016-04-20 05:25:10 -070049 return true;
50}
51
52// An RTP Header Extension for Client-to-Mixer Audio Level Indication
53//
54// https://datatracker.ietf.org/doc/draft-lennox-avt-rtp-audio-level-exthdr/
55//
56// The form of the audio level extension block:
57//
58// 0 1
59// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
60// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
61// | ID | len=0 |V| level |
62// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
63//
danilchape2a01772016-10-28 07:08:58 -070064constexpr RTPExtensionType AudioLevel::kId;
65constexpr uint8_t AudioLevel::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -070066constexpr const char AudioLevel::kUri[];
danilchap1edb7ab2016-04-20 05:25:10 -070067
danilchap978504e2017-04-06 01:03:53 -070068bool AudioLevel::Parse(rtc::ArrayView<const uint8_t> data,
danilchap1edb7ab2016-04-20 05:25:10 -070069 bool* voice_activity,
70 uint8_t* audio_level) {
danilchap978504e2017-04-06 01:03:53 -070071 if (data.size() != 1)
72 return false;
danilchap1edb7ab2016-04-20 05:25:10 -070073 *voice_activity = (data[0] & 0x80) != 0;
74 *audio_level = data[0] & 0x7F;
75 return true;
76}
77
78bool AudioLevel::Write(uint8_t* data,
79 bool voice_activity,
80 uint8_t audio_level) {
81 RTC_CHECK_LE(audio_level, 0x7f);
82 data[0] = (voice_activity ? 0x80 : 0x00) | audio_level;
83 return true;
84}
85
86// From RFC 5450: Transmission Time Offsets in RTP Streams.
87//
88// The transmission time is signaled to the receiver in-band using the
89// general mechanism for RTP header extensions [RFC5285]. The payload
90// of this extension (the transmitted value) is a 24-bit signed integer.
91// When added to the RTP timestamp of the packet, it represents the
92// "effective" RTP transmission time of the packet, on the RTP
93// timescale.
94//
95// The form of the transmission offset extension block:
96//
97// 0 1 2 3
98// 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
99// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
100// | ID | len=2 | transmission offset |
101// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
danilchape2a01772016-10-28 07:08:58 -0700102constexpr RTPExtensionType TransmissionOffset::kId;
103constexpr uint8_t TransmissionOffset::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -0700104constexpr const char TransmissionOffset::kUri[];
danilchap1edb7ab2016-04-20 05:25:10 -0700105
danilchap978504e2017-04-06 01:03:53 -0700106bool TransmissionOffset::Parse(rtc::ArrayView<const uint8_t> data,
107 int32_t* rtp_time) {
108 if (data.size() != 3)
109 return false;
110 *rtp_time = ByteReader<int32_t, 3>::ReadBigEndian(data.data());
danilchap1edb7ab2016-04-20 05:25:10 -0700111 return true;
112}
113
Danil Chapovalov31e4e802016-08-03 18:27:40 +0200114bool TransmissionOffset::Write(uint8_t* data, int32_t rtp_time) {
115 RTC_DCHECK_LE(rtp_time, 0x00ffffff);
116 ByteWriter<int32_t, 3>::WriteBigEndian(data, rtp_time);
danilchap1edb7ab2016-04-20 05:25:10 -0700117 return true;
118}
119
120// 0 1 2
121// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
122// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
123// | ID | L=1 |transport wide sequence number |
124// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
danilchape2a01772016-10-28 07:08:58 -0700125constexpr RTPExtensionType TransportSequenceNumber::kId;
126constexpr uint8_t TransportSequenceNumber::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -0700127constexpr const char TransportSequenceNumber::kUri[];
danilchap1edb7ab2016-04-20 05:25:10 -0700128
danilchap978504e2017-04-06 01:03:53 -0700129bool TransportSequenceNumber::Parse(rtc::ArrayView<const uint8_t> data,
130 uint16_t* value) {
131 if (data.size() != 2)
132 return false;
133 *value = ByteReader<uint16_t>::ReadBigEndian(data.data());
danilchap1edb7ab2016-04-20 05:25:10 -0700134 return true;
135}
136
137bool TransportSequenceNumber::Write(uint8_t* data, uint16_t value) {
138 ByteWriter<uint16_t>::WriteBigEndian(data, value);
139 return true;
140}
141
142// Coordination of Video Orientation in RTP streams.
143//
144// Coordination of Video Orientation consists in signaling of the current
145// orientation of the image captured on the sender side to the receiver for
146// appropriate rendering and displaying.
147//
148// 0 1
149// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
150// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
151// | ID | len=0 |0 0 0 0 C F R R|
152// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
danilchape2a01772016-10-28 07:08:58 -0700153constexpr RTPExtensionType VideoOrientation::kId;
154constexpr uint8_t VideoOrientation::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -0700155constexpr const char VideoOrientation::kUri[];
danilchap1edb7ab2016-04-20 05:25:10 -0700156
danilchap978504e2017-04-06 01:03:53 -0700157bool VideoOrientation::Parse(rtc::ArrayView<const uint8_t> data,
158 VideoRotation* rotation) {
159 if (data.size() != 1)
160 return false;
magjed71eb61c2016-09-08 03:24:58 -0700161 *rotation = ConvertCVOByteToVideoRotation(data[0]);
danilchap1edb7ab2016-04-20 05:25:10 -0700162 return true;
163}
164
165bool VideoOrientation::Write(uint8_t* data, VideoRotation rotation) {
166 data[0] = ConvertVideoRotationToCVOByte(rotation);
167 return true;
168}
169
danilchap978504e2017-04-06 01:03:53 -0700170bool VideoOrientation::Parse(rtc::ArrayView<const uint8_t> data,
171 uint8_t* value) {
172 if (data.size() != 1)
173 return false;
danilchap1edb7ab2016-04-20 05:25:10 -0700174 *value = data[0];
175 return true;
176}
177
178bool VideoOrientation::Write(uint8_t* data, uint8_t value) {
179 data[0] = value;
180 return true;
181}
Danil Chapovalov08b03512016-09-07 15:08:13 +0200182
183// 0 1 2 3
184// 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
185// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
186// | ID | len=2 | MIN delay | MAX delay |
187// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
188constexpr RTPExtensionType PlayoutDelayLimits::kId;
189constexpr uint8_t PlayoutDelayLimits::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -0700190constexpr const char PlayoutDelayLimits::kUri[];
Danil Chapovalov08b03512016-09-07 15:08:13 +0200191
danilchap978504e2017-04-06 01:03:53 -0700192bool PlayoutDelayLimits::Parse(rtc::ArrayView<const uint8_t> data,
Danil Chapovalov08b03512016-09-07 15:08:13 +0200193 PlayoutDelay* playout_delay) {
194 RTC_DCHECK(playout_delay);
danilchap978504e2017-04-06 01:03:53 -0700195 if (data.size() != 3)
196 return false;
197 uint32_t raw = ByteReader<uint32_t, 3>::ReadBigEndian(data.data());
Danil Chapovalov08b03512016-09-07 15:08:13 +0200198 uint16_t min_raw = (raw >> 12);
199 uint16_t max_raw = (raw & 0xfff);
200 if (min_raw > max_raw)
201 return false;
202 playout_delay->min_ms = min_raw * kGranularityMs;
203 playout_delay->max_ms = max_raw * kGranularityMs;
204 return true;
205}
206
207bool PlayoutDelayLimits::Write(uint8_t* data,
208 const PlayoutDelay& playout_delay) {
209 RTC_DCHECK_LE(0, playout_delay.min_ms);
210 RTC_DCHECK_LE(playout_delay.min_ms, playout_delay.max_ms);
211 RTC_DCHECK_LE(playout_delay.max_ms, kMaxMs);
212 // Convert MS to value to be sent on extension header.
213 uint32_t min_delay = playout_delay.min_ms / kGranularityMs;
214 uint32_t max_delay = playout_delay.max_ms / kGranularityMs;
215 ByteWriter<uint32_t, 3>::WriteBigEndian(data, (min_delay << 12) | max_delay);
216 return true;
217}
218
ilnik00d802b2017-04-11 10:34:31 -0700219// Video Content Type.
220//
221// E.g. default video or screenshare.
222//
223// 0 1
224// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
225// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
226// | ID | len=0 | Content type |
227// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
228constexpr RTPExtensionType VideoContentTypeExtension::kId;
229constexpr uint8_t VideoContentTypeExtension::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -0700230constexpr const char VideoContentTypeExtension::kUri[];
ilnik00d802b2017-04-11 10:34:31 -0700231
232bool VideoContentTypeExtension::Parse(rtc::ArrayView<const uint8_t> data,
233 VideoContentType* content_type) {
234 if (data.size() == 1 &&
ilnik6d5b4d62017-08-30 03:32:14 -0700235 videocontenttypehelpers::IsValidContentType(data[0])) {
ilnik00d802b2017-04-11 10:34:31 -0700236 *content_type = static_cast<VideoContentType>(data[0]);
237 return true;
238 }
239 return false;
240}
241
242bool VideoContentTypeExtension::Write(uint8_t* data,
243 VideoContentType content_type) {
244 data[0] = static_cast<uint8_t>(content_type);
245 return true;
246}
247
ilnik04f4d122017-06-19 07:18:55 -0700248// Video Timing.
249// 6 timestamps in milliseconds counted from capture time stored in rtp header:
250// encode start/finish, packetization complete, pacer exit and reserved for
sprangba050a62017-08-18 02:51:12 -0700251// modification by the network modification. |flags| is a bitmask and has the
252// following allowed values:
253// 0 = Valid data, but no flags available (backwards compatibility)
254// 1 = Frame marked as timing frame due to cyclic timer.
255// 2 = Frame marked as timing frame due to size being outside limit.
256// 255 = Invalid. The whole timing frame extension should be ignored.
257//
ilnik04f4d122017-06-19 07:18:55 -0700258// 0 1 2 3
259// 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 2
260// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
sprangba050a62017-08-18 02:51:12 -0700261// | ID | len=12| flags | encode start ms delta |
ilnik04f4d122017-06-19 07:18:55 -0700262// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
sprangba050a62017-08-18 02:51:12 -0700263// | encode finish ms delta | packetizer finish ms delta |
ilnik04f4d122017-06-19 07:18:55 -0700264// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
sprangba050a62017-08-18 02:51:12 -0700265// | pacer exit ms delta | network timestamp ms delta |
ilnik04f4d122017-06-19 07:18:55 -0700266// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
sprangba050a62017-08-18 02:51:12 -0700267// | network2 timestamp ms delta |
268// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
ilnik04f4d122017-06-19 07:18:55 -0700269
270constexpr RTPExtensionType VideoTimingExtension::kId;
271constexpr uint8_t VideoTimingExtension::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -0700272constexpr const char VideoTimingExtension::kUri[];
ilnik04f4d122017-06-19 07:18:55 -0700273
274bool VideoTimingExtension::Parse(rtc::ArrayView<const uint8_t> data,
ilnik2edc6842017-07-06 03:06:50 -0700275 VideoSendTiming* timing) {
ilnik04f4d122017-06-19 07:18:55 -0700276 RTC_DCHECK(timing);
sprangba050a62017-08-18 02:51:12 -0700277 // TODO(sprang): Deprecate support for old wire format.
278 ptrdiff_t off = 0;
279 switch (data.size()) {
280 case kValueSizeBytes - 1:
281 timing->flags = 0;
282 off = 1; // Old wire format without the flags field.
283 break;
284 case kValueSizeBytes:
285 timing->flags = ByteReader<uint8_t>::ReadBigEndian(data.data());
286 break;
287 default:
288 return false;
289 }
290
291 timing->encode_start_delta_ms = ByteReader<uint16_t>::ReadBigEndian(
292 data.data() + VideoSendTiming::kEncodeStartDeltaOffset - off);
ilnik04f4d122017-06-19 07:18:55 -0700293 timing->encode_finish_delta_ms = ByteReader<uint16_t>::ReadBigEndian(
sprangba050a62017-08-18 02:51:12 -0700294 data.data() + VideoSendTiming::kEncodeFinishDeltaOffset - off);
ilnik04f4d122017-06-19 07:18:55 -0700295 timing->packetization_finish_delta_ms = ByteReader<uint16_t>::ReadBigEndian(
sprangba050a62017-08-18 02:51:12 -0700296 data.data() + VideoSendTiming::kPacketizationFinishDeltaOffset - off);
ilnik04f4d122017-06-19 07:18:55 -0700297 timing->pacer_exit_delta_ms = ByteReader<uint16_t>::ReadBigEndian(
sprangba050a62017-08-18 02:51:12 -0700298 data.data() + VideoSendTiming::kPacerExitDeltaOffset - off);
ilnik04f4d122017-06-19 07:18:55 -0700299 timing->network_timstamp_delta_ms = ByteReader<uint16_t>::ReadBigEndian(
sprangba050a62017-08-18 02:51:12 -0700300 data.data() + VideoSendTiming::kNetworkTimestampDeltaOffset - off);
ilnik04f4d122017-06-19 07:18:55 -0700301 timing->network2_timstamp_delta_ms = ByteReader<uint16_t>::ReadBigEndian(
sprangba050a62017-08-18 02:51:12 -0700302 data.data() + VideoSendTiming::kNetwork2TimestampDeltaOffset - off);
ilnik04f4d122017-06-19 07:18:55 -0700303 return true;
304}
305
ilnik2edc6842017-07-06 03:06:50 -0700306bool VideoTimingExtension::Write(uint8_t* data, const VideoSendTiming& timing) {
sprangba050a62017-08-18 02:51:12 -0700307 ByteWriter<uint8_t>::WriteBigEndian(data + VideoSendTiming::kFlagsOffset,
308 timing.flags);
ilnik04f4d122017-06-19 07:18:55 -0700309 ByteWriter<uint16_t>::WriteBigEndian(
sprangba050a62017-08-18 02:51:12 -0700310 data + VideoSendTiming::kEncodeStartDeltaOffset,
311 timing.encode_start_delta_ms);
312 ByteWriter<uint16_t>::WriteBigEndian(
313 data + VideoSendTiming::kEncodeFinishDeltaOffset,
ilnik04f4d122017-06-19 07:18:55 -0700314 timing.encode_finish_delta_ms);
315 ByteWriter<uint16_t>::WriteBigEndian(
sprangba050a62017-08-18 02:51:12 -0700316 data + VideoSendTiming::kPacketizationFinishDeltaOffset,
ilnik04f4d122017-06-19 07:18:55 -0700317 timing.packetization_finish_delta_ms);
318 ByteWriter<uint16_t>::WriteBigEndian(
sprangba050a62017-08-18 02:51:12 -0700319 data + VideoSendTiming::kPacerExitDeltaOffset,
ilnik2edc6842017-07-06 03:06:50 -0700320 timing.pacer_exit_delta_ms);
ilnik04f4d122017-06-19 07:18:55 -0700321 ByteWriter<uint16_t>::WriteBigEndian(
sprangba050a62017-08-18 02:51:12 -0700322 data + VideoSendTiming::kNetworkTimestampDeltaOffset, 0); // reserved
ilnik04f4d122017-06-19 07:18:55 -0700323 ByteWriter<uint16_t>::WriteBigEndian(
sprangba050a62017-08-18 02:51:12 -0700324 data + VideoSendTiming::kNetwork2TimestampDeltaOffset, 0); // reserved
ilnik04f4d122017-06-19 07:18:55 -0700325 return true;
326}
327
328bool VideoTimingExtension::Write(uint8_t* data,
329 uint16_t time_delta_ms,
sprangba050a62017-08-18 02:51:12 -0700330 uint8_t offset) {
331 RTC_DCHECK_LT(offset, kValueSizeBytes - sizeof(uint16_t));
332 ByteWriter<uint16_t>::WriteBigEndian(data + offset, time_delta_ms);
ilnik04f4d122017-06-19 07:18:55 -0700333 return true;
334}
335
Steve Antona3251dd2017-07-21 09:58:31 -0700336bool BaseRtpStringExtension::Parse(rtc::ArrayView<const uint8_t> data,
337 StringRtpHeaderExtension* str) {
338 if (data.empty() || data[0] == 0) // Valid string extension can't be empty.
339 return false;
340 str->Set(data);
341 RTC_DCHECK(!str->empty());
342 return true;
343}
344
345bool BaseRtpStringExtension::Write(uint8_t* data,
346 const StringRtpHeaderExtension& str) {
347 RTC_DCHECK_GE(str.size(), 1);
348 RTC_DCHECK_LE(str.size(), StringRtpHeaderExtension::kMaxSize);
349 memcpy(data, str.data(), str.size());
350 return true;
351}
352
353bool BaseRtpStringExtension::Parse(rtc::ArrayView<const uint8_t> data,
354 std::string* str) {
355 if (data.empty() || data[0] == 0) // Valid string extension can't be empty.
356 return false;
357 const char* cstr = reinterpret_cast<const char*>(data.data());
358 // If there is a \0 character in the middle of the |data|, treat it as end
359 // of the string. Well-formed string extensions shouldn't contain it.
360 str->assign(cstr, strnlen(cstr, data.size()));
361 RTC_DCHECK(!str->empty());
362 return true;
363}
364
365bool BaseRtpStringExtension::Write(uint8_t* data, const std::string& str) {
366 RTC_DCHECK_GE(str.size(), 1);
367 RTC_DCHECK_LE(str.size(), StringRtpHeaderExtension::kMaxSize);
368 memcpy(data, str.data(), str.size());
369 return true;
370}
371
372// Constant declarations for string RTP header extension types.
373
danilchapef8d7732017-04-19 02:59:48 -0700374constexpr RTPExtensionType RtpStreamId::kId;
Steve Antond14d9f72017-07-21 10:59:39 -0700375constexpr const char RtpStreamId::kUri[];
danilchapef8d7732017-04-19 02:59:48 -0700376
danilchapef8d7732017-04-19 02:59:48 -0700377constexpr RTPExtensionType RepairedRtpStreamId::kId;
Steve Antond14d9f72017-07-21 10:59:39 -0700378constexpr const char RepairedRtpStreamId::kUri[];
danilchapef8d7732017-04-19 02:59:48 -0700379
Steve Antona3251dd2017-07-21 09:58:31 -0700380constexpr RTPExtensionType RtpMid::kId;
Steve Antond14d9f72017-07-21 10:59:39 -0700381constexpr const char RtpMid::kUri[];
erikvargae6b16192017-05-11 02:36:32 -0700382
danilchap1edb7ab2016-04-20 05:25:10 -0700383} // namespace webrtc