Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(833)

Side by Side Diff: webrtc/voice_engine/transport_feedback_packet_loss_tracker.cc

Issue 2711473003: R/PLR calculation - time-based window (Closed)
Patch Set: More compiler nits. Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. 2 * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
3 * 3 *
4 * Use of this source code is governed by a BSD-style license 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 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 6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may 7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree. 8 * be found in the AUTHORS file in the root of the source tree.
9 */ 9 */
10 10
(...skipping 10 matching lines...) Expand all
21 constexpr uint16_t kSeqNumHalf = 0x8000u; 21 constexpr uint16_t kSeqNumHalf = 0x8000u;
22 void UpdateCounter(size_t* counter, bool increment) { 22 void UpdateCounter(size_t* counter, bool increment) {
23 if (increment) { 23 if (increment) {
24 RTC_DCHECK_LT(*counter, std::numeric_limits<std::size_t>::max()); 24 RTC_DCHECK_LT(*counter, std::numeric_limits<std::size_t>::max());
25 ++(*counter); 25 ++(*counter);
26 } else { 26 } else {
27 RTC_DCHECK_GT(*counter, 0); 27 RTC_DCHECK_GT(*counter, 0);
28 --(*counter); 28 --(*counter);
29 } 29 }
30 } 30 }
31
32 } // namespace 31 } // namespace
33 32
34 namespace webrtc { 33 namespace webrtc {
35 34
36 TransportFeedbackPacketLossTracker::TransportFeedbackPacketLossTracker( 35 TransportFeedbackPacketLossTracker::TransportFeedbackPacketLossTracker(
37 size_t max_acked_packets, 36 int64_t max_window_size_ms,
38 size_t plr_min_num_acked_packets, 37 size_t plr_min_num_acked_packets,
39 size_t rplr_min_num_acked_pairs) 38 size_t rplr_min_num_acked_pairs)
40 : max_acked_packets_(max_acked_packets), 39 : max_window_size_ms_(max_window_size_ms),
41 ref_packet_status_(packet_status_window_.begin()), 40 ref_packet_status_(packet_status_window_.begin()),
42 plr_state_(plr_min_num_acked_packets), 41 plr_state_(plr_min_num_acked_packets),
43 rplr_state_(rplr_min_num_acked_pairs) { 42 rplr_state_(rplr_min_num_acked_pairs) {
43 RTC_DCHECK_GT(max_window_size_ms, 0);
44 RTC_DCHECK_GT(plr_min_num_acked_packets, 0); 44 RTC_DCHECK_GT(plr_min_num_acked_packets, 0);
45 RTC_DCHECK_GE(max_acked_packets, plr_min_num_acked_packets);
46 RTC_DCHECK_LE(max_acked_packets, kSeqNumHalf);
47 RTC_DCHECK_GT(rplr_min_num_acked_pairs, 0); 45 RTC_DCHECK_GT(rplr_min_num_acked_pairs, 0);
48 RTC_DCHECK_GT(max_acked_packets, rplr_min_num_acked_pairs);
49 Reset(); 46 Reset();
50 } 47 }
51 48
52 void TransportFeedbackPacketLossTracker::Reset() { 49 void TransportFeedbackPacketLossTracker::Reset() {
53 acked_packets_ = 0; 50 acked_packets_ = 0;
54 plr_state_.Reset(); 51 plr_state_.Reset();
55 rplr_state_.Reset(); 52 rplr_state_.Reset();
56 packet_status_window_.clear(); 53 packet_status_window_.clear();
57 ref_packet_status_ = packet_status_window_.begin(); 54 ref_packet_status_ = packet_status_window_.begin();
58 } 55 }
59 56
60 uint16_t TransportFeedbackPacketLossTracker::ReferenceSequenceNumber() const { 57 uint16_t TransportFeedbackPacketLossTracker::ReferenceSequenceNumber() const {
61 RTC_DCHECK(!packet_status_window_.empty()); 58 RTC_DCHECK(!packet_status_window_.empty());
62 return ref_packet_status_->first; 59 return ref_packet_status_->first;
63 } 60 }
64 61
65 uint16_t TransportFeedbackPacketLossTracker::NewestSequenceNumber() const { 62 uint16_t TransportFeedbackPacketLossTracker::NewestSequenceNumber() const {
66 RTC_DCHECK(!packet_status_window_.empty()); 63 RTC_DCHECK(!packet_status_window_.empty());
67 return PreviousPacketStatus(packet_status_window_.end())->first; 64 return PreviousPacketStatus(packet_status_window_.end())->first;
68 } 65 }
69 66
70 void TransportFeedbackPacketLossTracker::OnPacketAdded(uint16_t seq_num) { 67 void TransportFeedbackPacketLossTracker::OnPacketAdded(uint16_t seq_num,
68 int64_t send_time_ms) {
69 // Sanity - time can't flow backwards.
70 RTC_DCHECK(
71 packet_status_window_.empty() ||
72 PreviousPacketStatus(packet_status_window_.end())->second.send_time_ms <=
73 send_time_ms);
74
71 if (packet_status_window_.find(seq_num) != packet_status_window_.end() || 75 if (packet_status_window_.find(seq_num) != packet_status_window_.end() ||
72 (!packet_status_window_.empty() && 76 (!packet_status_window_.empty() &&
73 ForwardDiff(seq_num, NewestSequenceNumber()) <= kSeqNumHalf)) { 77 ForwardDiff(seq_num, NewestSequenceNumber()) <= kSeqNumHalf)) {
74 // The only way for these two to happen is when the stream lies dormant for 78 // The only way for these two to happen is when the stream lies dormant for
75 // long enough for the sequence numbers to wrap. Everything in the window in 79 // long enough for the sequence numbers to wrap. Everything in the window in
76 // such a case would be too old to use. 80 // such a case would be too old to use.
77 Reset(); 81 Reset();
78 } 82 }
79 83
80 // Shift older packets out of window. 84 // Maintain a window where the newest sequence number is at most 0x7fff away
85 // from the oldest, so that would could still distinguish old/new.
81 while (!packet_status_window_.empty() && 86 while (!packet_status_window_.empty() &&
82 ForwardDiff(ref_packet_status_->first, seq_num) >= kSeqNumHalf) { 87 ForwardDiff(ref_packet_status_->first, seq_num) >= kSeqNumHalf) {
83 RemoveOldestPacketStatus(); 88 RemoveOldestPacketStatus();
84 } 89 }
85 90
91 SentPacket sent_packet(send_time_ms, PacketStatus::Unacked);
86 packet_status_window_.insert(packet_status_window_.end(), 92 packet_status_window_.insert(packet_status_window_.end(),
87 std::make_pair(seq_num, PacketStatus::Unacked)); 93 std::make_pair(seq_num, sent_packet));
88 94
89 if (packet_status_window_.size() == 1) { 95 if (packet_status_window_.size() == 1) {
90 ref_packet_status_ = packet_status_window_.cbegin(); 96 ref_packet_status_ = packet_status_window_.cbegin();
91 } 97 }
92 } 98 }
93 99
94 void TransportFeedbackPacketLossTracker::OnReceivedTransportFeedback( 100 void TransportFeedbackPacketLossTracker::OnReceivedTransportFeedback(
95 const rtcp::TransportFeedback& feedback) { 101 const rtcp::TransportFeedback& feedback) {
96 const auto& fb_vector = feedback.GetStatusVector(); 102 const auto& status_vector = feedback.GetStatusVector();
97 const uint16_t base_seq_num = feedback.GetBaseSequence(); 103 const uint16_t base_seq_num = feedback.GetBaseSequence();
98 104
99 uint16_t seq_num = base_seq_num; 105 uint16_t seq_num = base_seq_num;
100 for (size_t i = 0; i < fb_vector.size(); ++i, ++seq_num) { 106 for (size_t i = 0; i < status_vector.size(); ++i, ++seq_num) {
101 const auto& it = packet_status_window_.find(seq_num); 107 const auto& it = packet_status_window_.find(seq_num);
102 108
103 // Packets which aren't at least marked as unacked either do not belong to 109 // Packets which aren't at least marked as unacked either do not belong to
104 // this media stream, or have been shifted out of window. 110 // this media stream, or have been shifted out of window.
105 if (it != packet_status_window_.end()) { 111 if (it == packet_status_window_.end())
106 const bool received = fb_vector[i] != 112 continue;
107 webrtc::rtcp::TransportFeedback::StatusSymbol::kNotReceived; 113
108 RecordFeedback(it, received); 114 const bool lost =
109 } 115 status_vector[i] ==
116 webrtc::rtcp::TransportFeedback::StatusSymbol::kNotReceived;
117 const PacketStatus packet_status =
118 lost ? PacketStatus::Lost : PacketStatus::Received;
119
120 UpdatePacketStatus(it, packet_status);
110 } 121 }
111 } 122 }
112 123
113 rtc::Optional<float> 124 rtc::Optional<float>
114 TransportFeedbackPacketLossTracker::GetPacketLossRate() const { 125 TransportFeedbackPacketLossTracker::GetPacketLossRate() const {
115 return plr_state_.GetMetric(); 126 return plr_state_.GetMetric();
116 } 127 }
117 128
118 rtc::Optional<float> 129 rtc::Optional<float>
119 TransportFeedbackPacketLossTracker::GetRecoverablePacketLossRate() const { 130 TransportFeedbackPacketLossTracker::GetRecoverablePacketLossRate() const {
120 return rplr_state_.GetMetric(); 131 return rplr_state_.GetMetric();
121 } 132 }
122 133
123 void TransportFeedbackPacketLossTracker::RecordFeedback( 134 void TransportFeedbackPacketLossTracker::UpdatePacketStatus(
124 PacketStatusMap::iterator it, 135 SentPacketStatusMap::iterator it,
125 bool received) { 136 PacketStatus new_status) {
126 if (it->second != PacketStatus::Unacked) { 137 if (it->second.status != PacketStatus::Unacked) {
127 // Normally, packets are sent (inserted into window as "unacked"), then we 138 // Normally, packets are sent (inserted into window as "unacked"), then we
128 // receive one feedback for them. 139 // receive one feedback for them.
129 // But it is possible that a packet would receive two feedbacks. Then: 140 // But it is possible that a packet would receive two feedbacks. Then:
130 if (it->second == PacketStatus::Lost && received) { 141 if (it->second.status == PacketStatus::Lost &&
142 new_status == PacketStatus::Received) {
131 // If older status said that the packet was lost but newer one says it 143 // If older status said that the packet was lost but newer one says it
132 // is received, we take the newer one. 144 // is received, we take the newer one.
133 UpdateMetrics(it, false); 145 UpdateMetrics(it, false);
134 it->second = PacketStatus::Unacked; // For clarity; overwritten shortly. 146 it->second.status =
147 PacketStatus::Unacked; // For clarity; overwritten shortly.
135 } else { 148 } else {
136 // If the value is unchanged or if older status said that the packet was 149 // If the value is unchanged or if older status said that the packet was
137 // received but the newer one says it is lost, we ignore it. 150 // received but the newer one says it is lost, we ignore it.
138 // The standard allows for previously-reported packets to carry 151 // The standard allows for previously-reported packets to carry
139 // no report when the reports overlap, which also looks like the 152 // no report when the reports overlap, which also looks like the
140 // packet is being reported as lost. 153 // packet is being reported as lost.
141 return; 154 return;
142 } 155 }
143 } 156 }
144 157
145 // Change from UNACKED to RECEIVED/LOST. 158 // Change from UNACKED to RECEIVED/LOST.
146 it->second = received ? PacketStatus::Received : PacketStatus::Lost; 159 it->second.status = new_status;
147 UpdateMetrics(it, true); 160 UpdateMetrics(it, true);
148 161
149 // Remove packets from the beginning of the window until maximum-acked 162 // Remove packets from the beginning of the window until we only hold packets,
150 // is observed again. Note that multiple sent-but-unacked packets might 163 // be they acked or unacked, which are not more than |max_window_size_ms|
151 // be removed before we reach the first acked (whether as received or as 164 // older from the newest packet. (If the packet we're now inserting into the
152 // lost) packet. 165 // window isn't the newest, it would not trigger any removals; the newest
153 while (acked_packets_ > max_acked_packets_) 166 // already removed all relevant.)
167 while (ref_packet_status_ != packet_status_window_.end() &&
168 (it->second.send_time_ms - ref_packet_status_->second.send_time_ms) >
169 max_window_size_ms_) {
154 RemoveOldestPacketStatus(); 170 RemoveOldestPacketStatus();
171 }
155 } 172 }
156 173
157 void TransportFeedbackPacketLossTracker::RemoveOldestPacketStatus() { 174 void TransportFeedbackPacketLossTracker::RemoveOldestPacketStatus() {
158 UpdateMetrics(ref_packet_status_, false); 175 UpdateMetrics(ref_packet_status_, false);
159 const auto it = ref_packet_status_; 176 const auto it = ref_packet_status_;
160 ref_packet_status_ = NextPacketStatus(it); 177 ref_packet_status_ = NextPacketStatus(it);
161 packet_status_window_.erase(it); 178 packet_status_window_.erase(it);
162 } 179 }
163 180
164 void TransportFeedbackPacketLossTracker::UpdateMetrics( 181 void TransportFeedbackPacketLossTracker::UpdateMetrics(
165 ConstPacketStatusIterator it, 182 ConstPacketStatusIterator it,
166 bool apply /* false = undo */) { 183 bool apply /* false = undo */) {
167 RTC_DCHECK(it != packet_status_window_.end()); 184 RTC_DCHECK(it != packet_status_window_.end());
168 // Metrics are dependent on feedbacks from the other side. We don't want 185 // Metrics are dependent on feedbacks from the other side. We don't want
169 // to update the metrics each time a packet is sent, except for the case 186 // to update the metrics each time a packet is sent, except for the case
170 // when it shifts old sent-but-unacked-packets out of window. 187 // when it shifts old sent-but-unacked-packets out of window.
171 RTC_DCHECK(!apply || it->second != PacketStatus::Unacked); 188 RTC_DCHECK(!apply || it->second.status != PacketStatus::Unacked);
172 189
173 if (it->second != PacketStatus::Unacked) { 190 if (it->second.status != PacketStatus::Unacked) {
174 UpdateCounter(&acked_packets_, apply); 191 UpdateCounter(&acked_packets_, apply);
175 } 192 }
176 193
177 UpdatePlr(it, apply); 194 UpdatePlr(it, apply);
178 UpdateRplr(it, apply); 195 UpdateRplr(it, apply);
179 } 196 }
180 197
181 void TransportFeedbackPacketLossTracker::UpdatePlr( 198 void TransportFeedbackPacketLossTracker::UpdatePlr(
182 ConstPacketStatusIterator it, 199 ConstPacketStatusIterator it,
183 bool apply /* false = undo */) { 200 bool apply /* false = undo */) {
184 switch (it->second) { 201 switch (it->second.status) {
185 case PacketStatus::Unacked: 202 case PacketStatus::Unacked:
186 return; 203 return;
187 case PacketStatus::Received: 204 case PacketStatus::Received:
188 UpdateCounter(&plr_state_.num_received_packets_, apply); 205 UpdateCounter(&plr_state_.num_received_packets_, apply);
189 break; 206 break;
190 case PacketStatus::Lost: 207 case PacketStatus::Lost:
191 UpdateCounter(&plr_state_.num_lost_packets_, apply); 208 UpdateCounter(&plr_state_.num_lost_packets_, apply);
192 break; 209 break;
193 default: 210 default:
194 RTC_NOTREACHED(); 211 RTC_NOTREACHED();
195 } 212 }
196 } 213 }
197 214
198 void TransportFeedbackPacketLossTracker::UpdateRplr( 215 void TransportFeedbackPacketLossTracker::UpdateRplr(
199 ConstPacketStatusIterator it, 216 ConstPacketStatusIterator it,
200 bool apply /* false = undo */) { 217 bool apply /* false = undo */) {
201 if (it->second == PacketStatus::Unacked) { 218 if (it->second.status == PacketStatus::Unacked) {
202 // Unacked packets cannot compose a pair. 219 // Unacked packets cannot compose a pair.
203 return; 220 return;
204 } 221 }
205 222
206 // Previous packet and current packet might compose a pair. 223 // Previous packet and current packet might compose a pair.
207 if (it != ref_packet_status_) { 224 if (it != ref_packet_status_) {
208 const auto& prev = PreviousPacketStatus(it); 225 const auto& prev = PreviousPacketStatus(it);
209 if (prev->second != PacketStatus::Unacked) { 226 if (prev->second.status != PacketStatus::Unacked) {
210 UpdateCounter(&rplr_state_.num_acked_pairs_, apply); 227 UpdateCounter(&rplr_state_.num_acked_pairs_, apply);
211 if (prev->second == PacketStatus::Lost && 228 if (prev->second.status == PacketStatus::Lost &&
212 it->second == PacketStatus::Received) { 229 it->second.status == PacketStatus::Received) {
213 UpdateCounter( 230 UpdateCounter(
214 &rplr_state_.num_recoverable_losses_, apply); 231 &rplr_state_.num_recoverable_losses_, apply);
215 } 232 }
216 } 233 }
217 } 234 }
218 235
219 // Current packet and next packet might compose a pair. 236 // Current packet and next packet might compose a pair.
220 const auto& next = NextPacketStatus(it); 237 const auto& next = NextPacketStatus(it);
221 if (next != packet_status_window_.end() && 238 if (next != packet_status_window_.end() &&
222 next->second != PacketStatus::Unacked) { 239 next->second.status != PacketStatus::Unacked) {
223 UpdateCounter(&rplr_state_.num_acked_pairs_, apply); 240 UpdateCounter(&rplr_state_.num_acked_pairs_, apply);
224 if (it->second == PacketStatus::Lost && 241 if (it->second.status == PacketStatus::Lost &&
225 next->second == PacketStatus::Received) { 242 next->second.status == PacketStatus::Received) {
226 UpdateCounter(&rplr_state_.num_recoverable_losses_, apply); 243 UpdateCounter(&rplr_state_.num_recoverable_losses_, apply);
227 } 244 }
228 } 245 }
229 } 246 }
230 247
231 TransportFeedbackPacketLossTracker::ConstPacketStatusIterator 248 TransportFeedbackPacketLossTracker::ConstPacketStatusIterator
232 TransportFeedbackPacketLossTracker::PreviousPacketStatus( 249 TransportFeedbackPacketLossTracker::PreviousPacketStatus(
233 ConstPacketStatusIterator it) const { 250 ConstPacketStatusIterator it) const {
234 RTC_DCHECK(it != ref_packet_status_); 251 RTC_DCHECK(it != ref_packet_status_);
235 if (it == packet_status_window_.end()) { 252 if (it == packet_status_window_.end()) {
(...skipping 30 matching lines...) Expand all
266 283
267 // TODO(minyue): This method checks the states of this class do not misbehave. 284 // TODO(minyue): This method checks the states of this class do not misbehave.
268 // The method is used both in unit tests and a fuzzer test. The fuzzer test 285 // The method is used both in unit tests and a fuzzer test. The fuzzer test
269 // is present to help finding potential errors. Once the fuzzer test shows no 286 // is present to help finding potential errors. Once the fuzzer test shows no
270 // error after long period, we can remove the fuzzer test, and move this method 287 // error after long period, we can remove the fuzzer test, and move this method
271 // to unit test. 288 // to unit test.
272 void TransportFeedbackPacketLossTracker::Validate() const { // Testing only! 289 void TransportFeedbackPacketLossTracker::Validate() const { // Testing only!
273 RTC_CHECK_EQ(plr_state_.num_received_packets_ + plr_state_.num_lost_packets_, 290 RTC_CHECK_EQ(plr_state_.num_received_packets_ + plr_state_.num_lost_packets_,
274 acked_packets_); 291 acked_packets_);
275 RTC_CHECK_LE(acked_packets_, packet_status_window_.size()); 292 RTC_CHECK_LE(acked_packets_, packet_status_window_.size());
276 RTC_CHECK_LE(acked_packets_, max_acked_packets_);
277 RTC_CHECK_LE(rplr_state_.num_recoverable_losses_, 293 RTC_CHECK_LE(rplr_state_.num_recoverable_losses_,
278 rplr_state_.num_acked_pairs_); 294 rplr_state_.num_acked_pairs_);
279 RTC_CHECK_LE(rplr_state_.num_acked_pairs_, acked_packets_ - 1); 295 RTC_CHECK_LE(rplr_state_.num_acked_pairs_, acked_packets_ - 1);
280 296
281 size_t unacked_packets = 0; 297 size_t unacked_packets = 0;
282 size_t received_packets = 0; 298 size_t received_packets = 0;
283 size_t lost_packets = 0; 299 size_t lost_packets = 0;
284 size_t acked_pairs = 0; 300 size_t acked_pairs = 0;
285 size_t recoverable_losses = 0; 301 size_t recoverable_losses = 0;
286 302
287 if (!packet_status_window_.empty()) { 303 if (!packet_status_window_.empty()) {
288 ConstPacketStatusIterator it = ref_packet_status_; 304 ConstPacketStatusIterator it = ref_packet_status_;
289 do { 305 do {
290 switch (it->second) { 306 switch (it->second.status) {
291 case PacketStatus::Unacked: 307 case PacketStatus::Unacked:
292 ++unacked_packets; 308 ++unacked_packets;
293 break; 309 break;
294 case PacketStatus::Received: 310 case PacketStatus::Received:
295 ++received_packets; 311 ++received_packets;
296 break; 312 break;
297 case PacketStatus::Lost: 313 case PacketStatus::Lost:
298 ++lost_packets; 314 ++lost_packets;
299 break; 315 break;
300 default: 316 default:
301 RTC_NOTREACHED(); 317 RTC_NOTREACHED();
302 } 318 }
303 319
304 auto next = std::next(it); 320 auto next = std::next(it);
305 if (next == packet_status_window_.end()) 321 if (next == packet_status_window_.end())
306 next = packet_status_window_.begin(); 322 next = packet_status_window_.begin();
307 323
308 if (next != ref_packet_status_ && 324 if (next != ref_packet_status_) { // If we have a next packet...
309 it->second != PacketStatus::Unacked && 325 RTC_CHECK_GE(next->second.send_time_ms, it->second.send_time_ms);
310 next->second != PacketStatus::Unacked) { 326
311 ++acked_pairs; 327 if (it->second.status != PacketStatus::Unacked &&
312 if (it->second == PacketStatus::Lost && 328 next->second.status != PacketStatus::Unacked) {
313 next->second == PacketStatus::Received) 329 ++acked_pairs;
314 ++recoverable_losses; 330 if (it->second.status == PacketStatus::Lost &&
331 next->second.status == PacketStatus::Received) {
332 ++recoverable_losses;
333 }
334 }
315 } 335 }
316 336
317 RTC_CHECK_LT(ForwardDiff(ReferenceSequenceNumber(), it->first), 337 RTC_CHECK_LT(ForwardDiff(ReferenceSequenceNumber(), it->first),
318 kSeqNumHalf); 338 kSeqNumHalf);
319 339
320 it = next; 340 it = next;
321 } while (it != ref_packet_status_); 341 } while (it != ref_packet_status_);
322 } 342 }
323 343
324 RTC_CHECK_EQ(plr_state_.num_received_packets_, received_packets); 344 RTC_CHECK_EQ(plr_state_.num_received_packets_, received_packets);
(...skipping 19 matching lines...) Expand all
344 TransportFeedbackPacketLossTracker::RplrState::GetMetric() const { 364 TransportFeedbackPacketLossTracker::RplrState::GetMetric() const {
345 if (num_acked_pairs_ < min_num_acked_pairs_) { 365 if (num_acked_pairs_ < min_num_acked_pairs_) {
346 return rtc::Optional<float>(); 366 return rtc::Optional<float>();
347 } else { 367 } else {
348 return rtc::Optional<float>( 368 return rtc::Optional<float>(
349 static_cast<float>(num_recoverable_losses_) / num_acked_pairs_); 369 static_cast<float>(num_recoverable_losses_) / num_acked_pairs_);
350 } 370 }
351 } 371 }
352 372
353 } // namespace webrtc 373 } // namespace webrtc
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698