blob: 38c2d5ac7ddad0b64f74a179507d4402d8c9d6d6 [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
Yves Gerey988cc082018-10-23 12:03:01 +020013#include <string.h>
Johannes Kronad1d9f02018-11-09 11:12:36 +010014#include <cmath>
Johannes Kronc13f4be2018-12-12 09:52:53 +010015#include <limits>
Yves Gerey988cc082018-10-23 12:03:01 +020016
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020017#include "modules/rtp_rtcp/include/rtp_cvo.h"
18#include "modules/rtp_rtcp/source/byte_io.h"
Yves Gerey988cc082018-10-23 12:03:01 +020019// TODO(bug:9855) Move kNoSpatialIdx from vp9_globals.h to common_constants
20#include "modules/video_coding/codecs/interface/common_constants.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020021#include "rtc_base/checks.h"
danilchap1edb7ab2016-04-20 05:25:10 -070022
23namespace webrtc {
24// Absolute send time in RTP streams.
25//
26// The absolute send time is signaled to the receiver in-band using the
Johannes Kron07ba2b92018-09-26 13:33:35 +020027// general mechanism for RTP header extensions [RFC8285]. The payload
danilchap1edb7ab2016-04-20 05:25:10 -070028// of this extension (the transmitted value) is a 24-bit unsigned integer
29// containing the sender's current time in seconds as a fixed point number
30// with 18 bits fractional part.
31//
32// The form of the absolute send time extension block:
33//
34// 0 1 2 3
35// 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
36// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
37// | ID | len=2 | absolute send time |
38// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
danilchape2a01772016-10-28 07:08:58 -070039constexpr RTPExtensionType AbsoluteSendTime::kId;
40constexpr uint8_t AbsoluteSendTime::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -070041constexpr const char AbsoluteSendTime::kUri[];
danilchap1edb7ab2016-04-20 05:25:10 -070042
danilchap978504e2017-04-06 01:03:53 -070043bool AbsoluteSendTime::Parse(rtc::ArrayView<const uint8_t> data,
44 uint32_t* time_24bits) {
45 if (data.size() != 3)
46 return false;
47 *time_24bits = ByteReader<uint32_t, 3>::ReadBigEndian(data.data());
danilchap1edb7ab2016-04-20 05:25:10 -070048 return true;
49}
50
Danil Chapovalov9bf31582018-06-18 13:48:20 +020051bool AbsoluteSendTime::Write(rtc::ArrayView<uint8_t> data,
52 uint32_t time_24bits) {
53 RTC_DCHECK_EQ(data.size(), 3);
Danil Chapovalovf3ba6482017-06-12 15:43:55 +020054 RTC_DCHECK_LE(time_24bits, 0x00FFFFFF);
Danil Chapovalov9bf31582018-06-18 13:48:20 +020055 ByteWriter<uint32_t, 3>::WriteBigEndian(data.data(), time_24bits);
danilchap1edb7ab2016-04-20 05:25:10 -070056 return true;
57}
58
59// An RTP Header Extension for Client-to-Mixer Audio Level Indication
60//
61// https://datatracker.ietf.org/doc/draft-lennox-avt-rtp-audio-level-exthdr/
62//
63// The form of the audio level extension block:
64//
65// 0 1
66// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
67// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
68// | ID | len=0 |V| level |
69// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
70//
danilchape2a01772016-10-28 07:08:58 -070071constexpr RTPExtensionType AudioLevel::kId;
72constexpr uint8_t AudioLevel::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -070073constexpr const char AudioLevel::kUri[];
danilchap1edb7ab2016-04-20 05:25:10 -070074
danilchap978504e2017-04-06 01:03:53 -070075bool AudioLevel::Parse(rtc::ArrayView<const uint8_t> data,
danilchap1edb7ab2016-04-20 05:25:10 -070076 bool* voice_activity,
77 uint8_t* audio_level) {
danilchap978504e2017-04-06 01:03:53 -070078 if (data.size() != 1)
79 return false;
danilchap1edb7ab2016-04-20 05:25:10 -070080 *voice_activity = (data[0] & 0x80) != 0;
81 *audio_level = data[0] & 0x7F;
82 return true;
83}
84
Danil Chapovalov9bf31582018-06-18 13:48:20 +020085bool AudioLevel::Write(rtc::ArrayView<uint8_t> data,
danilchap1edb7ab2016-04-20 05:25:10 -070086 bool voice_activity,
87 uint8_t audio_level) {
Danil Chapovalov9bf31582018-06-18 13:48:20 +020088 RTC_DCHECK_EQ(data.size(), 1);
danilchap1edb7ab2016-04-20 05:25:10 -070089 RTC_CHECK_LE(audio_level, 0x7f);
90 data[0] = (voice_activity ? 0x80 : 0x00) | audio_level;
91 return true;
92}
93
94// From RFC 5450: Transmission Time Offsets in RTP Streams.
95//
96// The transmission time is signaled to the receiver in-band using the
Johannes Kron07ba2b92018-09-26 13:33:35 +020097// general mechanism for RTP header extensions [RFC8285]. The payload
danilchap1edb7ab2016-04-20 05:25:10 -070098// of this extension (the transmitted value) is a 24-bit signed integer.
99// When added to the RTP timestamp of the packet, it represents the
100// "effective" RTP transmission time of the packet, on the RTP
101// timescale.
102//
103// The form of the transmission offset extension block:
104//
105// 0 1 2 3
106// 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
107// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
108// | ID | len=2 | transmission offset |
109// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
danilchape2a01772016-10-28 07:08:58 -0700110constexpr RTPExtensionType TransmissionOffset::kId;
111constexpr uint8_t TransmissionOffset::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -0700112constexpr const char TransmissionOffset::kUri[];
danilchap1edb7ab2016-04-20 05:25:10 -0700113
danilchap978504e2017-04-06 01:03:53 -0700114bool TransmissionOffset::Parse(rtc::ArrayView<const uint8_t> data,
115 int32_t* rtp_time) {
116 if (data.size() != 3)
117 return false;
118 *rtp_time = ByteReader<int32_t, 3>::ReadBigEndian(data.data());
danilchap1edb7ab2016-04-20 05:25:10 -0700119 return true;
120}
121
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200122bool TransmissionOffset::Write(rtc::ArrayView<uint8_t> data, int32_t rtp_time) {
123 RTC_DCHECK_EQ(data.size(), 3);
Danil Chapovalov31e4e802016-08-03 18:27:40 +0200124 RTC_DCHECK_LE(rtp_time, 0x00ffffff);
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200125 ByteWriter<int32_t, 3>::WriteBigEndian(data.data(), rtp_time);
danilchap1edb7ab2016-04-20 05:25:10 -0700126 return true;
127}
128
129// 0 1 2
130// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
131// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
132// | ID | L=1 |transport wide sequence number |
133// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
danilchape2a01772016-10-28 07:08:58 -0700134constexpr RTPExtensionType TransportSequenceNumber::kId;
135constexpr uint8_t TransportSequenceNumber::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -0700136constexpr const char TransportSequenceNumber::kUri[];
danilchap1edb7ab2016-04-20 05:25:10 -0700137
danilchap978504e2017-04-06 01:03:53 -0700138bool TransportSequenceNumber::Parse(rtc::ArrayView<const uint8_t> data,
139 uint16_t* value) {
140 if (data.size() != 2)
141 return false;
142 *value = ByteReader<uint16_t>::ReadBigEndian(data.data());
danilchap1edb7ab2016-04-20 05:25:10 -0700143 return true;
144}
145
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200146bool TransportSequenceNumber::Write(rtc::ArrayView<uint8_t> data,
147 uint16_t value) {
148 RTC_DCHECK_EQ(data.size(), 2);
149 ByteWriter<uint16_t>::WriteBigEndian(data.data(), value);
danilchap1edb7ab2016-04-20 05:25:10 -0700150 return true;
151}
152
153// Coordination of Video Orientation in RTP streams.
154//
155// Coordination of Video Orientation consists in signaling of the current
156// orientation of the image captured on the sender side to the receiver for
157// appropriate rendering and displaying.
158//
159// 0 1
160// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
161// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
162// | ID | len=0 |0 0 0 0 C F R R|
163// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
danilchape2a01772016-10-28 07:08:58 -0700164constexpr RTPExtensionType VideoOrientation::kId;
165constexpr uint8_t VideoOrientation::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -0700166constexpr const char VideoOrientation::kUri[];
danilchap1edb7ab2016-04-20 05:25:10 -0700167
danilchap978504e2017-04-06 01:03:53 -0700168bool VideoOrientation::Parse(rtc::ArrayView<const uint8_t> data,
169 VideoRotation* rotation) {
170 if (data.size() != 1)
171 return false;
magjed71eb61c2016-09-08 03:24:58 -0700172 *rotation = ConvertCVOByteToVideoRotation(data[0]);
danilchap1edb7ab2016-04-20 05:25:10 -0700173 return true;
174}
175
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200176bool VideoOrientation::Write(rtc::ArrayView<uint8_t> data,
177 VideoRotation rotation) {
178 RTC_DCHECK_EQ(data.size(), 1);
danilchap1edb7ab2016-04-20 05:25:10 -0700179 data[0] = ConvertVideoRotationToCVOByte(rotation);
180 return true;
181}
182
danilchap978504e2017-04-06 01:03:53 -0700183bool VideoOrientation::Parse(rtc::ArrayView<const uint8_t> data,
184 uint8_t* value) {
185 if (data.size() != 1)
186 return false;
danilchap1edb7ab2016-04-20 05:25:10 -0700187 *value = data[0];
188 return true;
189}
190
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200191bool VideoOrientation::Write(rtc::ArrayView<uint8_t> data, uint8_t value) {
192 RTC_DCHECK_EQ(data.size(), 1);
danilchap1edb7ab2016-04-20 05:25:10 -0700193 data[0] = value;
194 return true;
195}
Danil Chapovalov08b03512016-09-07 15:08:13 +0200196
197// 0 1 2 3
198// 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
199// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
200// | ID | len=2 | MIN delay | MAX delay |
201// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
202constexpr RTPExtensionType PlayoutDelayLimits::kId;
203constexpr uint8_t PlayoutDelayLimits::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -0700204constexpr const char PlayoutDelayLimits::kUri[];
Danil Chapovalov08b03512016-09-07 15:08:13 +0200205
danilchap978504e2017-04-06 01:03:53 -0700206bool PlayoutDelayLimits::Parse(rtc::ArrayView<const uint8_t> data,
Danil Chapovalov08b03512016-09-07 15:08:13 +0200207 PlayoutDelay* playout_delay) {
208 RTC_DCHECK(playout_delay);
danilchap978504e2017-04-06 01:03:53 -0700209 if (data.size() != 3)
210 return false;
211 uint32_t raw = ByteReader<uint32_t, 3>::ReadBigEndian(data.data());
Danil Chapovalov08b03512016-09-07 15:08:13 +0200212 uint16_t min_raw = (raw >> 12);
213 uint16_t max_raw = (raw & 0xfff);
214 if (min_raw > max_raw)
215 return false;
216 playout_delay->min_ms = min_raw * kGranularityMs;
217 playout_delay->max_ms = max_raw * kGranularityMs;
218 return true;
219}
220
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200221bool PlayoutDelayLimits::Write(rtc::ArrayView<uint8_t> data,
Danil Chapovalov08b03512016-09-07 15:08:13 +0200222 const PlayoutDelay& playout_delay) {
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200223 RTC_DCHECK_EQ(data.size(), 3);
Danil Chapovalov08b03512016-09-07 15:08:13 +0200224 RTC_DCHECK_LE(0, playout_delay.min_ms);
225 RTC_DCHECK_LE(playout_delay.min_ms, playout_delay.max_ms);
226 RTC_DCHECK_LE(playout_delay.max_ms, kMaxMs);
227 // Convert MS to value to be sent on extension header.
228 uint32_t min_delay = playout_delay.min_ms / kGranularityMs;
229 uint32_t max_delay = playout_delay.max_ms / kGranularityMs;
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200230 ByteWriter<uint32_t, 3>::WriteBigEndian(data.data(),
231 (min_delay << 12) | max_delay);
Danil Chapovalov08b03512016-09-07 15:08:13 +0200232 return true;
233}
234
ilnik00d802b2017-04-11 10:34:31 -0700235// Video Content Type.
236//
237// E.g. default video or screenshare.
238//
239// 0 1
240// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
241// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
242// | ID | len=0 | Content type |
243// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
244constexpr RTPExtensionType VideoContentTypeExtension::kId;
245constexpr uint8_t VideoContentTypeExtension::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -0700246constexpr const char VideoContentTypeExtension::kUri[];
ilnik00d802b2017-04-11 10:34:31 -0700247
248bool VideoContentTypeExtension::Parse(rtc::ArrayView<const uint8_t> data,
249 VideoContentType* content_type) {
250 if (data.size() == 1 &&
ilnik6d5b4d62017-08-30 03:32:14 -0700251 videocontenttypehelpers::IsValidContentType(data[0])) {
ilnik00d802b2017-04-11 10:34:31 -0700252 *content_type = static_cast<VideoContentType>(data[0]);
253 return true;
254 }
255 return false;
256}
257
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200258bool VideoContentTypeExtension::Write(rtc::ArrayView<uint8_t> data,
ilnik00d802b2017-04-11 10:34:31 -0700259 VideoContentType content_type) {
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200260 RTC_DCHECK_EQ(data.size(), 1);
ilnik00d802b2017-04-11 10:34:31 -0700261 data[0] = static_cast<uint8_t>(content_type);
262 return true;
263}
264
ilnik04f4d122017-06-19 07:18:55 -0700265// Video Timing.
266// 6 timestamps in milliseconds counted from capture time stored in rtp header:
267// encode start/finish, packetization complete, pacer exit and reserved for
sprangba050a62017-08-18 02:51:12 -0700268// modification by the network modification. |flags| is a bitmask and has the
269// following allowed values:
270// 0 = Valid data, but no flags available (backwards compatibility)
271// 1 = Frame marked as timing frame due to cyclic timer.
272// 2 = Frame marked as timing frame due to size being outside limit.
273// 255 = Invalid. The whole timing frame extension should be ignored.
274//
ilnik04f4d122017-06-19 07:18:55 -0700275// 0 1 2 3
Johannes Krond0b69a82018-12-03 14:18:53 +0100276// 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
277// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
278// | ID | len=12| flags | encode start ms delta |
279// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
280// | encode finish ms delta | packetizer finish ms delta |
281// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
282// | pacer exit ms delta | network timestamp ms delta |
283// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
sprangba050a62017-08-18 02:51:12 -0700284// | network2 timestamp ms delta |
285// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
ilnik04f4d122017-06-19 07:18:55 -0700286
287constexpr RTPExtensionType VideoTimingExtension::kId;
288constexpr uint8_t VideoTimingExtension::kValueSizeBytes;
Steve Antond14d9f72017-07-21 10:59:39 -0700289constexpr const char VideoTimingExtension::kUri[];
ilnik04f4d122017-06-19 07:18:55 -0700290
291bool VideoTimingExtension::Parse(rtc::ArrayView<const uint8_t> data,
ilnik2edc6842017-07-06 03:06:50 -0700292 VideoSendTiming* timing) {
ilnik04f4d122017-06-19 07:18:55 -0700293 RTC_DCHECK(timing);
sprangba050a62017-08-18 02:51:12 -0700294 // TODO(sprang): Deprecate support for old wire format.
295 ptrdiff_t off = 0;
296 switch (data.size()) {
297 case kValueSizeBytes - 1:
298 timing->flags = 0;
299 off = 1; // Old wire format without the flags field.
300 break;
301 case kValueSizeBytes:
302 timing->flags = ByteReader<uint8_t>::ReadBigEndian(data.data());
303 break;
304 default:
305 return false;
306 }
307
308 timing->encode_start_delta_ms = ByteReader<uint16_t>::ReadBigEndian(
309 data.data() + VideoSendTiming::kEncodeStartDeltaOffset - off);
ilnik04f4d122017-06-19 07:18:55 -0700310 timing->encode_finish_delta_ms = ByteReader<uint16_t>::ReadBigEndian(
sprangba050a62017-08-18 02:51:12 -0700311 data.data() + VideoSendTiming::kEncodeFinishDeltaOffset - off);
ilnik04f4d122017-06-19 07:18:55 -0700312 timing->packetization_finish_delta_ms = ByteReader<uint16_t>::ReadBigEndian(
sprangba050a62017-08-18 02:51:12 -0700313 data.data() + VideoSendTiming::kPacketizationFinishDeltaOffset - off);
ilnik04f4d122017-06-19 07:18:55 -0700314 timing->pacer_exit_delta_ms = ByteReader<uint16_t>::ReadBigEndian(
sprangba050a62017-08-18 02:51:12 -0700315 data.data() + VideoSendTiming::kPacerExitDeltaOffset - off);
Danil Chapovalov996eb9e2017-10-30 17:14:41 +0100316 timing->network_timestamp_delta_ms = ByteReader<uint16_t>::ReadBigEndian(
sprangba050a62017-08-18 02:51:12 -0700317 data.data() + VideoSendTiming::kNetworkTimestampDeltaOffset - off);
Danil Chapovalov996eb9e2017-10-30 17:14:41 +0100318 timing->network2_timestamp_delta_ms = ByteReader<uint16_t>::ReadBigEndian(
sprangba050a62017-08-18 02:51:12 -0700319 data.data() + VideoSendTiming::kNetwork2TimestampDeltaOffset - off);
ilnik04f4d122017-06-19 07:18:55 -0700320 return true;
321}
322
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200323bool VideoTimingExtension::Write(rtc::ArrayView<uint8_t> data,
324 const VideoSendTiming& timing) {
325 RTC_DCHECK_EQ(data.size(), 1 + 2 * 6);
326 ByteWriter<uint8_t>::WriteBigEndian(
327 data.data() + VideoSendTiming::kFlagsOffset, timing.flags);
ilnik04f4d122017-06-19 07:18:55 -0700328 ByteWriter<uint16_t>::WriteBigEndian(
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200329 data.data() + VideoSendTiming::kEncodeStartDeltaOffset,
sprangba050a62017-08-18 02:51:12 -0700330 timing.encode_start_delta_ms);
331 ByteWriter<uint16_t>::WriteBigEndian(
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200332 data.data() + VideoSendTiming::kEncodeFinishDeltaOffset,
ilnik04f4d122017-06-19 07:18:55 -0700333 timing.encode_finish_delta_ms);
334 ByteWriter<uint16_t>::WriteBigEndian(
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200335 data.data() + VideoSendTiming::kPacketizationFinishDeltaOffset,
ilnik04f4d122017-06-19 07:18:55 -0700336 timing.packetization_finish_delta_ms);
337 ByteWriter<uint16_t>::WriteBigEndian(
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200338 data.data() + VideoSendTiming::kPacerExitDeltaOffset,
ilnik2edc6842017-07-06 03:06:50 -0700339 timing.pacer_exit_delta_ms);
ilnik04f4d122017-06-19 07:18:55 -0700340 ByteWriter<uint16_t>::WriteBigEndian(
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200341 data.data() + VideoSendTiming::kNetworkTimestampDeltaOffset,
Danil Chapovalovf0cc8142017-10-31 17:59:39 +0100342 timing.network_timestamp_delta_ms);
ilnik04f4d122017-06-19 07:18:55 -0700343 ByteWriter<uint16_t>::WriteBigEndian(
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200344 data.data() + VideoSendTiming::kNetwork2TimestampDeltaOffset,
Danil Chapovalovf0cc8142017-10-31 17:59:39 +0100345 timing.network2_timestamp_delta_ms);
ilnik04f4d122017-06-19 07:18:55 -0700346 return true;
347}
348
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200349bool VideoTimingExtension::Write(rtc::ArrayView<uint8_t> data,
ilnik04f4d122017-06-19 07:18:55 -0700350 uint16_t time_delta_ms,
sprangba050a62017-08-18 02:51:12 -0700351 uint8_t offset) {
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200352 RTC_DCHECK_GE(data.size(), offset + 2);
Danil Chapovalovf0cc8142017-10-31 17:59:39 +0100353 RTC_DCHECK_LE(offset, kValueSizeBytes - sizeof(uint16_t));
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200354 ByteWriter<uint16_t>::WriteBigEndian(data.data() + offset, time_delta_ms);
ilnik04f4d122017-06-19 07:18:55 -0700355 return true;
356}
357
Johnny Leee0c8b232018-09-11 16:50:49 -0400358// Frame Marking.
359//
360// Meta-information about an RTP stream outside the encrypted media payload,
361// useful for an RTP switch to do codec-agnostic selective forwarding
362// without decrypting the payload.
363//
364// For non-scalable streams:
365// 0 1
366// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
367// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
368// | ID | L = 0 |S|E|I|D|0 0 0 0|
369// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
370//
371// For scalable streams:
372// 0 1 2 3
373// 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
374// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
375// | ID | L = 2 |S|E|I|D|B| TID | LID | TL0PICIDX |
376// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
377
378constexpr RTPExtensionType FrameMarkingExtension::kId;
379constexpr const char FrameMarkingExtension::kUri[];
380
381bool FrameMarkingExtension::IsScalable(uint8_t temporal_id, uint8_t layer_id) {
382 return temporal_id != kNoTemporalIdx || layer_id != kNoSpatialIdx;
383}
384
385bool FrameMarkingExtension::Parse(rtc::ArrayView<const uint8_t> data,
386 FrameMarking* frame_marking) {
387 RTC_DCHECK(frame_marking);
388
389 if (data.size() != 1 && data.size() != 3)
390 return false;
391
392 frame_marking->start_of_frame = (data[0] & 0x80) != 0;
393 frame_marking->end_of_frame = (data[0] & 0x40) != 0;
394 frame_marking->independent_frame = (data[0] & 0x20) != 0;
395 frame_marking->discardable_frame = (data[0] & 0x10) != 0;
396
397 if (data.size() == 3) {
398 frame_marking->base_layer_sync = (data[0] & 0x08) != 0;
399 frame_marking->temporal_id = data[0] & 0x7;
400 frame_marking->layer_id = data[1];
401 frame_marking->tl0_pic_idx = data[2];
402 } else {
403 // non-scalable
404 frame_marking->base_layer_sync = false;
405 frame_marking->temporal_id = kNoTemporalIdx;
406 frame_marking->layer_id = kNoSpatialIdx;
407 frame_marking->tl0_pic_idx = 0;
408 }
409 return true;
410}
411
412size_t FrameMarkingExtension::ValueSize(const FrameMarking& frame_marking) {
413 if (IsScalable(frame_marking.temporal_id, frame_marking.layer_id))
414 return 3;
415 else
416 return 1;
417}
418
419bool FrameMarkingExtension::Write(rtc::ArrayView<uint8_t> data,
420 const FrameMarking& frame_marking) {
421 RTC_DCHECK_GE(data.size(), 1);
422 RTC_CHECK_LE(frame_marking.temporal_id, 0x07);
423 data[0] = frame_marking.start_of_frame ? 0x80 : 0x00;
424 data[0] |= frame_marking.end_of_frame ? 0x40 : 0x00;
425 data[0] |= frame_marking.independent_frame ? 0x20 : 0x00;
426 data[0] |= frame_marking.discardable_frame ? 0x10 : 0x00;
427
428 if (IsScalable(frame_marking.temporal_id, frame_marking.layer_id)) {
429 RTC_DCHECK_EQ(data.size(), 3);
430 data[0] |= frame_marking.base_layer_sync ? 0x08 : 0x00;
431 data[0] |= frame_marking.temporal_id & 0x07;
432 data[1] = frame_marking.layer_id;
433 data[2] = frame_marking.tl0_pic_idx;
434 }
435 return true;
436}
437
Johannes Kron09d65882018-11-27 14:36:41 +0100438// Color space including HDR metadata as an optional field.
Johannes Kronad1d9f02018-11-09 11:12:36 +0100439//
Johannes Krond0b69a82018-12-03 14:18:53 +0100440// RTP header extension to carry color space information and optionally HDR
441// metadata. The float values in the HDR metadata struct are upscaled by a
442// static factor and transmitted as unsigned integers.
Johannes Kronad1d9f02018-11-09 11:12:36 +0100443//
Johannes Krond0b69a82018-12-03 14:18:53 +0100444// Data layout of color space with HDR metadata (two-byte RTP header extension)
Johannes Kronad1d9f02018-11-09 11:12:36 +0100445// 0 1 2 3
Johannes Krond0b69a82018-12-03 14:18:53 +0100446// 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
447// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Johannes Kronc13f4be2018-12-12 09:52:53 +0100448// | ID | length=28 | primaries | transfer |
Johannes Krond0b69a82018-12-03 14:18:53 +0100449// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Johannes Kronc13f4be2018-12-12 09:52:53 +0100450// | matrix |range+chr.sit. | luminance_max |
Johannes Krond0b69a82018-12-03 14:18:53 +0100451// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Johannes Kronc13f4be2018-12-12 09:52:53 +0100452// | luminance_min | mastering_metadata.|
Johannes Krond0b69a82018-12-03 14:18:53 +0100453// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Johannes Kronc13f4be2018-12-12 09:52:53 +0100454// |primary_r.x and .y | mastering_metadata.|
Johannes Krond0b69a82018-12-03 14:18:53 +0100455// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Johannes Kronc13f4be2018-12-12 09:52:53 +0100456// |primary_g.x and .y | mastering_metadata.|
Johannes Krond0b69a82018-12-03 14:18:53 +0100457// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Johannes Kronc13f4be2018-12-12 09:52:53 +0100458// |primary_b.x and .y | mastering_metadata.|
Johannes Krond0b69a82018-12-03 14:18:53 +0100459// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Johannes Kronc13f4be2018-12-12 09:52:53 +0100460// |white.x and .y | max_content_light_level |
Johannes Krond0b69a82018-12-03 14:18:53 +0100461// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Johannes Kronc13f4be2018-12-12 09:52:53 +0100462// | max_frame_average_light_level |
463// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Johannes Kron09d65882018-11-27 14:36:41 +0100464//
Johannes Krond0b69a82018-12-03 14:18:53 +0100465// Data layout of color space w/o HDR metadata (one-byte RTP header extension)
Johannes Kron09d65882018-11-27 14:36:41 +0100466// 0 1 2 3
Johannes Krond0b69a82018-12-03 14:18:53 +0100467// 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
468// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Johannes Kronc13f4be2018-12-12 09:52:53 +0100469// | ID | L = 3 | primaries | transfer | matrix |
Johannes Krond0b69a82018-12-03 14:18:53 +0100470// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Johannes Kronc13f4be2018-12-12 09:52:53 +0100471// |range+chr.sit. |
Johannes Krond0b69a82018-12-03 14:18:53 +0100472// +-+-+-+-+-+-+-+-+
Johannes Kronad1d9f02018-11-09 11:12:36 +0100473
Johannes Kron09d65882018-11-27 14:36:41 +0100474constexpr RTPExtensionType ColorSpaceExtension::kId;
475constexpr uint8_t ColorSpaceExtension::kValueSizeBytes;
476constexpr const char ColorSpaceExtension::kUri[];
477
478bool ColorSpaceExtension::Parse(rtc::ArrayView<const uint8_t> data,
479 ColorSpace* color_space) {
480 RTC_DCHECK(color_space);
481 if (data.size() != kValueSizeBytes &&
482 data.size() != kValueSizeBytesWithoutHdrMetadata)
Johannes Kronad1d9f02018-11-09 11:12:36 +0100483 return false;
484
485 size_t offset = 0;
Johannes Kron09d65882018-11-27 14:36:41 +0100486 // Read color space information.
Johannes Kronc13f4be2018-12-12 09:52:53 +0100487 if (!color_space->set_primaries_from_uint8(data[offset++]))
Johannes Kron09d65882018-11-27 14:36:41 +0100488 return false;
Johannes Kronc13f4be2018-12-12 09:52:53 +0100489 if (!color_space->set_transfer_from_uint8(data[offset++]))
Johannes Kron09d65882018-11-27 14:36:41 +0100490 return false;
Johannes Kronc13f4be2018-12-12 09:52:53 +0100491 if (!color_space->set_matrix_from_uint8(data[offset++]))
Johannes Kron09d65882018-11-27 14:36:41 +0100492 return false;
Johannes Kronc13f4be2018-12-12 09:52:53 +0100493
494 uint8_t range_and_chroma_siting = data[offset++];
495 if (!color_space->set_range_from_uint8((range_and_chroma_siting >> 4) & 0x03))
496 return false;
497 if (!color_space->set_chroma_siting_horizontal_from_uint8(
498 (range_and_chroma_siting >> 2) & 0x03))
499 return false;
500 if (!color_space->set_chroma_siting_vertical_from_uint8(
501 range_and_chroma_siting & 0x03))
Johannes Kron09d65882018-11-27 14:36:41 +0100502 return false;
503
504 // Read HDR metadata if it exists, otherwise clear it.
505 if (data.size() == kValueSizeBytesWithoutHdrMetadata) {
506 color_space->set_hdr_metadata(nullptr);
507 } else {
508 HdrMetadata hdr_metadata;
Johannes Kronc13f4be2018-12-12 09:52:53 +0100509 offset += ParseHdrMetadata(data.subview(offset), &hdr_metadata);
510 if (!hdr_metadata.Validate())
511 return false;
Johannes Kron09d65882018-11-27 14:36:41 +0100512 color_space->set_hdr_metadata(&hdr_metadata);
513 }
514 RTC_DCHECK_EQ(ValueSize(*color_space), offset);
Johannes Kronad1d9f02018-11-09 11:12:36 +0100515 return true;
516}
517
Johannes Kron09d65882018-11-27 14:36:41 +0100518bool ColorSpaceExtension::Write(rtc::ArrayView<uint8_t> data,
519 const ColorSpace& color_space) {
Johannes Krond0b69a82018-12-03 14:18:53 +0100520 RTC_DCHECK_EQ(data.size(), ValueSize(color_space));
Johannes Kronad1d9f02018-11-09 11:12:36 +0100521 size_t offset = 0;
Johannes Kron09d65882018-11-27 14:36:41 +0100522 // Write color space information.
Johannes Kronc13f4be2018-12-12 09:52:53 +0100523 data[offset++] = static_cast<uint8_t>(color_space.primaries());
524 data[offset++] = static_cast<uint8_t>(color_space.transfer());
525 data[offset++] = static_cast<uint8_t>(color_space.matrix());
526 data[offset++] = CombineRangeAndChromaSiting(
527 color_space.range(), color_space.chroma_siting_horizontal(),
528 color_space.chroma_siting_vertical());
Johannes Kronad1d9f02018-11-09 11:12:36 +0100529
Johannes Kron09d65882018-11-27 14:36:41 +0100530 // Write HDR metadata if it exists.
531 if (color_space.hdr_metadata()) {
Johannes Kronc13f4be2018-12-12 09:52:53 +0100532 offset +=
533 WriteHdrMetadata(data.subview(offset), *color_space.hdr_metadata());
Johannes Kron09d65882018-11-27 14:36:41 +0100534 }
535 RTC_DCHECK_EQ(ValueSize(color_space), offset);
Johannes Kronad1d9f02018-11-09 11:12:36 +0100536 return true;
537}
538
Johannes Kronc13f4be2018-12-12 09:52:53 +0100539// Combines range and chroma siting into one byte with the following bit layout:
540// bits 0-1 Chroma siting vertical.
541// 2-3 Chroma siting horizontal.
542// 4-5 Range.
543// 6-7 Unused.
544uint8_t ColorSpaceExtension::CombineRangeAndChromaSiting(
545 ColorSpace::RangeID range,
546 ColorSpace::ChromaSiting chroma_siting_horizontal,
547 ColorSpace::ChromaSiting chroma_siting_vertical) {
548 RTC_DCHECK_LE(static_cast<uint8_t>(range), 3);
549 RTC_DCHECK_LE(static_cast<uint8_t>(chroma_siting_horizontal), 3);
550 RTC_DCHECK_LE(static_cast<uint8_t>(chroma_siting_vertical), 3);
551 return (static_cast<uint8_t>(range) << 4) |
552 (static_cast<uint8_t>(chroma_siting_horizontal) << 2) |
553 static_cast<uint8_t>(chroma_siting_vertical);
554}
555
556size_t ColorSpaceExtension::ParseHdrMetadata(rtc::ArrayView<const uint8_t> data,
557 HdrMetadata* hdr_metadata) {
558 RTC_DCHECK_EQ(data.size(),
559 kValueSizeBytes - kValueSizeBytesWithoutHdrMetadata);
560 size_t offset = 0;
561 offset += ParseLuminance(data.data() + offset,
562 &hdr_metadata->mastering_metadata.luminance_max,
563 kLuminanceMaxDenominator);
564 offset += ParseLuminance(data.data() + offset,
565 &hdr_metadata->mastering_metadata.luminance_min,
566 kLuminanceMinDenominator);
567 offset += ParseChromaticity(data.data() + offset,
568 &hdr_metadata->mastering_metadata.primary_r);
569 offset += ParseChromaticity(data.data() + offset,
570 &hdr_metadata->mastering_metadata.primary_g);
571 offset += ParseChromaticity(data.data() + offset,
572 &hdr_metadata->mastering_metadata.primary_b);
573 offset += ParseChromaticity(data.data() + offset,
574 &hdr_metadata->mastering_metadata.white_point);
575 hdr_metadata->max_content_light_level =
576 ByteReader<uint16_t>::ReadBigEndian(data.data() + offset);
577 offset += 2;
578 hdr_metadata->max_frame_average_light_level =
579 ByteReader<uint16_t>::ReadBigEndian(data.data() + offset);
580 offset += 2;
581 return offset;
582}
583
Johannes Kron09d65882018-11-27 14:36:41 +0100584size_t ColorSpaceExtension::ParseChromaticity(
Johannes Kronad1d9f02018-11-09 11:12:36 +0100585 const uint8_t* data,
586 HdrMasteringMetadata::Chromaticity* p) {
587 uint16_t chromaticity_x_scaled = ByteReader<uint16_t>::ReadBigEndian(data);
588 uint16_t chromaticity_y_scaled =
589 ByteReader<uint16_t>::ReadBigEndian(data + 2);
590 p->x = static_cast<float>(chromaticity_x_scaled) / kChromaticityDenominator;
591 p->y = static_cast<float>(chromaticity_y_scaled) / kChromaticityDenominator;
592 return 4; // Return number of bytes read.
593}
594
Johannes Kron09d65882018-11-27 14:36:41 +0100595size_t ColorSpaceExtension::ParseLuminance(const uint8_t* data,
596 float* f,
597 int denominator) {
Johannes Kronc13f4be2018-12-12 09:52:53 +0100598 uint16_t luminance_scaled = ByteReader<uint16_t>::ReadBigEndian(data);
Johannes Kronad1d9f02018-11-09 11:12:36 +0100599 *f = static_cast<float>(luminance_scaled) / denominator;
Johannes Kronc13f4be2018-12-12 09:52:53 +0100600 return 2; // Return number of bytes read.
601}
602
603size_t ColorSpaceExtension::WriteHdrMetadata(rtc::ArrayView<uint8_t> data,
604 const HdrMetadata& hdr_metadata) {
605 RTC_DCHECK_EQ(data.size(),
606 kValueSizeBytes - kValueSizeBytesWithoutHdrMetadata);
607 RTC_DCHECK(hdr_metadata.Validate());
608 size_t offset = 0;
609 offset += WriteLuminance(data.data() + offset,
610 hdr_metadata.mastering_metadata.luminance_max,
611 kLuminanceMaxDenominator);
612 offset += WriteLuminance(data.data() + offset,
613 hdr_metadata.mastering_metadata.luminance_min,
614 kLuminanceMinDenominator);
615 offset += WriteChromaticity(data.data() + offset,
616 hdr_metadata.mastering_metadata.primary_r);
617 offset += WriteChromaticity(data.data() + offset,
618 hdr_metadata.mastering_metadata.primary_g);
619 offset += WriteChromaticity(data.data() + offset,
620 hdr_metadata.mastering_metadata.primary_b);
621 offset += WriteChromaticity(data.data() + offset,
622 hdr_metadata.mastering_metadata.white_point);
623
624 ByteWriter<uint16_t>::WriteBigEndian(data.data() + offset,
625 hdr_metadata.max_content_light_level);
626 offset += 2;
627 ByteWriter<uint16_t>::WriteBigEndian(
628 data.data() + offset, hdr_metadata.max_frame_average_light_level);
629 offset += 2;
630 return offset;
Johannes Kronad1d9f02018-11-09 11:12:36 +0100631}
632
Johannes Kron09d65882018-11-27 14:36:41 +0100633size_t ColorSpaceExtension::WriteChromaticity(
Johannes Kronad1d9f02018-11-09 11:12:36 +0100634 uint8_t* data,
635 const HdrMasteringMetadata::Chromaticity& p) {
636 RTC_DCHECK_GE(p.x, 0.0f);
Johannes Kronc13f4be2018-12-12 09:52:53 +0100637 RTC_DCHECK_LE(p.x, 1.0f);
Johannes Kronad1d9f02018-11-09 11:12:36 +0100638 RTC_DCHECK_GE(p.y, 0.0f);
Johannes Kronc13f4be2018-12-12 09:52:53 +0100639 RTC_DCHECK_LE(p.y, 1.0f);
Johannes Kronad1d9f02018-11-09 11:12:36 +0100640 ByteWriter<uint16_t>::WriteBigEndian(
641 data, std::round(p.x * kChromaticityDenominator));
642 ByteWriter<uint16_t>::WriteBigEndian(
643 data + 2, std::round(p.y * kChromaticityDenominator));
644 return 4; // Return number of bytes written.
645}
646
Johannes Kron09d65882018-11-27 14:36:41 +0100647size_t ColorSpaceExtension::WriteLuminance(uint8_t* data,
648 float f,
649 int denominator) {
Johannes Kronad1d9f02018-11-09 11:12:36 +0100650 RTC_DCHECK_GE(f, 0.0f);
Johannes Kronc13f4be2018-12-12 09:52:53 +0100651 float upscaled_value = f * denominator;
652 RTC_DCHECK_LE(upscaled_value, std::numeric_limits<uint16_t>::max());
653 ByteWriter<uint16_t>::WriteBigEndian(data, std::round(upscaled_value));
654 return 2; // Return number of bytes written.
Johannes Kronad1d9f02018-11-09 11:12:36 +0100655}
656
Steve Antona3251dd2017-07-21 09:58:31 -0700657bool BaseRtpStringExtension::Parse(rtc::ArrayView<const uint8_t> data,
658 StringRtpHeaderExtension* str) {
659 if (data.empty() || data[0] == 0) // Valid string extension can't be empty.
660 return false;
661 str->Set(data);
662 RTC_DCHECK(!str->empty());
663 return true;
664}
665
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200666bool BaseRtpStringExtension::Write(rtc::ArrayView<uint8_t> data,
Steve Antona3251dd2017-07-21 09:58:31 -0700667 const StringRtpHeaderExtension& str) {
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200668 RTC_DCHECK_EQ(data.size(), str.size());
Steve Antona3251dd2017-07-21 09:58:31 -0700669 RTC_DCHECK_GE(str.size(), 1);
670 RTC_DCHECK_LE(str.size(), StringRtpHeaderExtension::kMaxSize);
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200671 memcpy(data.data(), str.data(), str.size());
Steve Antona3251dd2017-07-21 09:58:31 -0700672 return true;
673}
674
675bool BaseRtpStringExtension::Parse(rtc::ArrayView<const uint8_t> data,
676 std::string* str) {
677 if (data.empty() || data[0] == 0) // Valid string extension can't be empty.
678 return false;
679 const char* cstr = reinterpret_cast<const char*>(data.data());
680 // If there is a \0 character in the middle of the |data|, treat it as end
681 // of the string. Well-formed string extensions shouldn't contain it.
682 str->assign(cstr, strnlen(cstr, data.size()));
683 RTC_DCHECK(!str->empty());
684 return true;
685}
686
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200687bool BaseRtpStringExtension::Write(rtc::ArrayView<uint8_t> data,
688 const std::string& str) {
Johannes Kron78cdde32018-10-05 10:00:46 +0200689 if (str.size() > StringRtpHeaderExtension::kMaxSize) {
690 return false;
691 }
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200692 RTC_DCHECK_EQ(data.size(), str.size());
Steve Antona3251dd2017-07-21 09:58:31 -0700693 RTC_DCHECK_GE(str.size(), 1);
Danil Chapovalov9bf31582018-06-18 13:48:20 +0200694 memcpy(data.data(), str.data(), str.size());
Steve Antona3251dd2017-07-21 09:58:31 -0700695 return true;
696}
697
698// Constant declarations for string RTP header extension types.
699
danilchapef8d7732017-04-19 02:59:48 -0700700constexpr RTPExtensionType RtpStreamId::kId;
Steve Antond14d9f72017-07-21 10:59:39 -0700701constexpr const char RtpStreamId::kUri[];
danilchapef8d7732017-04-19 02:59:48 -0700702
danilchapef8d7732017-04-19 02:59:48 -0700703constexpr RTPExtensionType RepairedRtpStreamId::kId;
Steve Antond14d9f72017-07-21 10:59:39 -0700704constexpr const char RepairedRtpStreamId::kUri[];
danilchapef8d7732017-04-19 02:59:48 -0700705
Steve Antona3251dd2017-07-21 09:58:31 -0700706constexpr RTPExtensionType RtpMid::kId;
Steve Antond14d9f72017-07-21 10:59:39 -0700707constexpr const char RtpMid::kUri[];
erikvargae6b16192017-05-11 02:36:32 -0700708
danilchap1edb7ab2016-04-20 05:25:10 -0700709} // namespace webrtc