OLD | NEW |
---|---|
1 /* | 1 /* |
2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. | 2 * Copyright (c) 2012 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 #include "webrtc/modules/video_coding/main/source/jitter_buffer.h" | 10 #include "webrtc/modules/video_coding/main/source/jitter_buffer.h" |
(...skipping 15 matching lines...) Expand all Loading... | |
26 #include "webrtc/system_wrappers/interface/event_wrapper.h" | 26 #include "webrtc/system_wrappers/interface/event_wrapper.h" |
27 #include "webrtc/system_wrappers/interface/logging.h" | 27 #include "webrtc/system_wrappers/interface/logging.h" |
28 #include "webrtc/system_wrappers/interface/metrics.h" | 28 #include "webrtc/system_wrappers/interface/metrics.h" |
29 #include "webrtc/system_wrappers/interface/trace_event.h" | 29 #include "webrtc/system_wrappers/interface/trace_event.h" |
30 | 30 |
31 namespace webrtc { | 31 namespace webrtc { |
32 | 32 |
33 // Use this rtt if no value has been reported. | 33 // Use this rtt if no value has been reported. |
34 static const int64_t kDefaultRtt = 200; | 34 static const int64_t kDefaultRtt = 200; |
35 | 35 |
36 // Request a keyframe if no continuous frame has been received for this | |
37 // number of milliseconds and NACKs are disabled. | |
38 static const int64_t kMaxDiscontinuousFramesTime = 10000; | |
39 | |
36 typedef std::pair<uint32_t, VCMFrameBuffer*> FrameListPair; | 40 typedef std::pair<uint32_t, VCMFrameBuffer*> FrameListPair; |
37 | 41 |
38 bool IsKeyFrame(FrameListPair pair) { | 42 bool IsKeyFrame(FrameListPair pair) { |
39 return pair.second->FrameType() == kVideoFrameKey; | 43 return pair.second->FrameType() == kVideoFrameKey; |
40 } | 44 } |
41 | 45 |
42 bool HasNonEmptyState(FrameListPair pair) { | 46 bool HasNonEmptyState(FrameListPair pair) { |
43 return pair.second->GetState() != kStateEmpty; | 47 return pair.second->GetState() != kStateEmpty; |
44 } | 48 } |
45 | 49 |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
130 stats_callback_(NULL), | 134 stats_callback_(NULL), |
131 incoming_frame_rate_(0), | 135 incoming_frame_rate_(0), |
132 incoming_frame_count_(0), | 136 incoming_frame_count_(0), |
133 time_last_incoming_frame_count_(0), | 137 time_last_incoming_frame_count_(0), |
134 incoming_bit_count_(0), | 138 incoming_bit_count_(0), |
135 incoming_bit_rate_(0), | 139 incoming_bit_rate_(0), |
136 num_consecutive_old_packets_(0), | 140 num_consecutive_old_packets_(0), |
137 num_packets_(0), | 141 num_packets_(0), |
138 num_duplicated_packets_(0), | 142 num_duplicated_packets_(0), |
139 num_discarded_packets_(0), | 143 num_discarded_packets_(0), |
144 time_last_decodable_frame_(-1), | |
140 time_first_packet_ms_(0), | 145 time_first_packet_ms_(0), |
141 jitter_estimate_(clock), | 146 jitter_estimate_(clock), |
142 inter_frame_delay_(clock_->TimeInMilliseconds()), | 147 inter_frame_delay_(clock_->TimeInMilliseconds()), |
143 rtt_ms_(kDefaultRtt), | 148 rtt_ms_(kDefaultRtt), |
144 nack_mode_(kNoNack), | 149 nack_mode_(kNoNack), |
145 low_rtt_nack_threshold_ms_(-1), | 150 low_rtt_nack_threshold_ms_(-1), |
146 high_rtt_nack_threshold_ms_(-1), | 151 high_rtt_nack_threshold_ms_(-1), |
147 missing_sequence_numbers_(SequenceNumberLessThan()), | 152 missing_sequence_numbers_(SequenceNumberLessThan()), |
148 max_nack_list_size_(0), | 153 max_nack_list_size_(0), |
149 max_packet_age_to_nack_(0), | 154 max_packet_age_to_nack_(0), |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
206 incoming_frame_rate_ = 0; | 211 incoming_frame_rate_ = 0; |
207 incoming_bit_count_ = 0; | 212 incoming_bit_count_ = 0; |
208 incoming_bit_rate_ = 0; | 213 incoming_bit_rate_ = 0; |
209 time_last_incoming_frame_count_ = clock_->TimeInMilliseconds(); | 214 time_last_incoming_frame_count_ = clock_->TimeInMilliseconds(); |
210 receive_statistics_ = FrameCounts(); | 215 receive_statistics_ = FrameCounts(); |
211 | 216 |
212 num_consecutive_old_packets_ = 0; | 217 num_consecutive_old_packets_ = 0; |
213 num_packets_ = 0; | 218 num_packets_ = 0; |
214 num_duplicated_packets_ = 0; | 219 num_duplicated_packets_ = 0; |
215 num_discarded_packets_ = 0; | 220 num_discarded_packets_ = 0; |
221 time_last_decodable_frame_ = -1; | |
216 time_first_packet_ms_ = 0; | 222 time_first_packet_ms_ = 0; |
217 | 223 |
218 // Start in a non-signaled state. | 224 // Start in a non-signaled state. |
219 waiting_for_completion_.frame_size = 0; | 225 waiting_for_completion_.frame_size = 0; |
220 waiting_for_completion_.timestamp = 0; | 226 waiting_for_completion_.timestamp = 0; |
221 waiting_for_completion_.latest_packet_time = -1; | 227 waiting_for_completion_.latest_packet_time = -1; |
222 first_packet_since_reset_ = true; | 228 first_packet_since_reset_ = true; |
223 rtt_ms_ = kDefaultRtt; | 229 rtt_ms_ = kDefaultRtt; |
224 last_decoded_state_.Reset(); | 230 last_decoded_state_.Reset(); |
225 last_gof_valid_ = false; | 231 last_gof_valid_ = false; |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
257 return running_; | 263 return running_; |
258 } | 264 } |
259 | 265 |
260 void VCMJitterBuffer::Flush() { | 266 void VCMJitterBuffer::Flush() { |
261 CriticalSectionScoped cs(crit_sect_); | 267 CriticalSectionScoped cs(crit_sect_); |
262 decodable_frames_.Reset(&free_frames_); | 268 decodable_frames_.Reset(&free_frames_); |
263 incomplete_frames_.Reset(&free_frames_); | 269 incomplete_frames_.Reset(&free_frames_); |
264 last_decoded_state_.Reset(); // TODO(mikhal): sync reset. | 270 last_decoded_state_.Reset(); // TODO(mikhal): sync reset. |
265 last_gof_valid_ = false; | 271 last_gof_valid_ = false; |
266 num_consecutive_old_packets_ = 0; | 272 num_consecutive_old_packets_ = 0; |
273 time_last_decodable_frame_ = -1; | |
267 // Also reset the jitter and delay estimates | 274 // Also reset the jitter and delay estimates |
268 jitter_estimate_.Reset(); | 275 jitter_estimate_.Reset(); |
269 inter_frame_delay_.Reset(clock_->TimeInMilliseconds()); | 276 inter_frame_delay_.Reset(clock_->TimeInMilliseconds()); |
270 waiting_for_completion_.frame_size = 0; | 277 waiting_for_completion_.frame_size = 0; |
271 waiting_for_completion_.timestamp = 0; | 278 waiting_for_completion_.timestamp = 0; |
272 waiting_for_completion_.latest_packet_time = -1; | 279 waiting_for_completion_.latest_packet_time = -1; |
273 first_packet_since_reset_ = true; | 280 first_packet_since_reset_ = true; |
274 missing_sequence_numbers_.clear(); | 281 missing_sequence_numbers_.clear(); |
275 } | 282 } |
276 | 283 |
(...skipping 408 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
685 packet.frameType != kVideoFrameKey) { | 692 packet.frameType != kVideoFrameKey) { |
686 buffer_state = kFlushIndicator; | 693 buffer_state = kFlushIndicator; |
687 } | 694 } |
688 | 695 |
689 latest_received_sequence_number_ = LatestSequenceNumber( | 696 latest_received_sequence_number_ = LatestSequenceNumber( |
690 latest_received_sequence_number_, packet.seqNum); | 697 latest_received_sequence_number_, packet.seqNum); |
691 } | 698 } |
692 } | 699 } |
693 | 700 |
694 // Is the frame already in the decodable list? | 701 // Is the frame already in the decodable list? |
695 bool continuous = IsContinuous(*frame); | 702 bool continuous = IsContinuous(*frame, false); |
703 bool continuous_for_decoding; | |
704 if (continuous && | |
stefan-webrtc
2015/09/24 07:05:48
I think I'm starting to see what you want to do he
joachim
2015/09/24 08:52:46
Thanks, however with NACKs disabled and "decode_er
stefan-webrtc
2015/09/28 13:49:24
I see. In that case, would it make sense to only p
joachim
2015/11/05 23:48:01
I updated the code as you suggested, looks a lot c
| |
705 (decode_error_mode_ != kWithErrors || nack_mode_ != kNoNack)) { | |
706 // If NACKs are enabled missing data will be handled through them so it's | |
707 // fine to always assume frames that are continuous for decoding below. | |
708 continuous_for_decoding = true; | |
709 time_last_decodable_frame_ = now_ms; | |
710 } else { | |
711 continuous_for_decoding = IsContinuous(*frame, true); | |
712 if (continuous_for_decoding) { | |
713 time_last_decodable_frame_ = now_ms; | |
714 } | |
715 } | |
696 switch (buffer_state) { | 716 switch (buffer_state) { |
697 case kGeneralError: | 717 case kGeneralError: |
698 case kTimeStampError: | 718 case kTimeStampError: |
699 case kSizeError: { | 719 case kSizeError: { |
700 free_frames_.push_back(frame); | 720 free_frames_.push_back(frame); |
701 break; | 721 break; |
702 } | 722 } |
703 case kCompleteSession: { | 723 case kCompleteSession: { |
704 if (previous_state != kStateDecodable && | 724 if (previous_state != kStateDecodable && |
705 previous_state != kStateComplete) { | 725 previous_state != kStateComplete) { |
706 CountFrame(*frame); | 726 CountFrame(*frame); |
707 if (continuous) { | 727 if (continuous) { |
708 // Signal that we have a complete session. | 728 // Signal that we have a complete session. |
709 frame_event_->Set(); | 729 frame_event_->Set(); |
710 } | 730 } |
711 } | 731 } |
712 FALLTHROUGH(); | 732 FALLTHROUGH(); |
713 } | 733 } |
714 // Note: There is no break here - continuing to kDecodableSession. | 734 // Note: There is no break here - continuing to kDecodableSession. |
715 case kDecodableSession: { | 735 case kDecodableSession: { |
716 *retransmitted = (frame->GetNackCount() > 0); | 736 *retransmitted = (frame->GetNackCount() > 0); |
717 if (continuous) { | 737 if (continuous) { |
718 decodable_frames_.InsertFrame(frame); | 738 decodable_frames_.InsertFrame(frame); |
719 FindAndInsertContinuousFrames(*frame); | 739 FindAndInsertContinuousFrames(*frame); |
720 } else { | 740 } else { |
721 incomplete_frames_.InsertFrame(frame); | 741 incomplete_frames_.InsertFrame(frame); |
722 } | 742 } |
743 if (continuous && | |
744 !continuous_for_decoding && | |
745 TooManyDiscontinuousFrames(now_ms)) { | |
746 return kFlushIndicator; | |
747 } | |
723 break; | 748 break; |
724 } | 749 } |
725 case kIncomplete: { | 750 case kIncomplete: { |
726 if (frame->GetState() == kStateEmpty && | 751 if (frame->GetState() == kStateEmpty && |
727 last_decoded_state_.UpdateEmptyFrame(frame)) { | 752 last_decoded_state_.UpdateEmptyFrame(frame)) { |
728 free_frames_.push_back(frame); | 753 free_frames_.push_back(frame); |
729 return kNoError; | 754 return kNoError; |
730 } else { | 755 } else { |
731 incomplete_frames_.InsertFrame(frame); | 756 incomplete_frames_.InsertFrame(frame); |
732 } | 757 } |
758 if (TooManyDiscontinuousFrames(now_ms)) { | |
759 return kFlushIndicator; | |
760 } | |
733 break; | 761 break; |
734 } | 762 } |
735 case kNoError: | 763 case kNoError: |
736 case kOutOfBoundsPacket: | 764 case kOutOfBoundsPacket: |
737 case kDuplicatePacket: { | 765 case kDuplicatePacket: { |
738 // Put back the frame where it came from. | 766 // Put back the frame where it came from. |
739 if (frame_list != NULL) { | 767 if (frame_list != NULL) { |
740 frame_list->InsertFrame(frame); | 768 frame_list->InsertFrame(frame); |
741 } else { | 769 } else { |
742 free_frames_.push_back(frame); | 770 free_frames_.push_back(frame); |
743 } | 771 } |
744 ++num_duplicated_packets_; | 772 ++num_duplicated_packets_; |
745 break; | 773 break; |
746 } | 774 } |
747 case kFlushIndicator: | 775 case kFlushIndicator: |
748 free_frames_.push_back(frame); | 776 free_frames_.push_back(frame); |
749 return kFlushIndicator; | 777 return kFlushIndicator; |
750 default: assert(false); | 778 default: assert(false); |
751 } | 779 } |
752 return buffer_state; | 780 return buffer_state; |
753 } | 781 } |
754 | 782 |
755 bool VCMJitterBuffer::IsContinuousInState(const VCMFrameBuffer& frame, | 783 bool VCMJitterBuffer::IsContinuousInState(const VCMFrameBuffer& frame, |
756 const VCMDecodingState& decoding_state) const { | 784 const VCMDecodingState& decoding_state, bool ignore_error_mode) const { |
757 if (decode_error_mode_ == kWithErrors) | 785 if (!ignore_error_mode && decode_error_mode_ == kWithErrors) { |
758 return true; | 786 return true; |
787 } | |
788 | |
759 // Is this frame (complete or decodable) and continuous? | 789 // Is this frame (complete or decodable) and continuous? |
760 // kStateDecodable will never be set when decode_error_mode_ is false | 790 // kStateDecodable will never be set when decode_error_mode_ is false |
761 // as SessionInfo determines this state based on the error mode (and frame | 791 // as SessionInfo determines this state based on the error mode (and frame |
762 // completeness). | 792 // completeness). |
763 return (frame.GetState() == kStateComplete || | 793 return (frame.GetState() == kStateComplete || |
764 frame.GetState() == kStateDecodable) && | 794 frame.GetState() == kStateDecodable) && |
765 decoding_state.ContinuousFrame(&frame); | 795 decoding_state.ContinuousFrame(&frame); |
766 } | 796 } |
767 | 797 |
768 bool VCMJitterBuffer::IsContinuous(const VCMFrameBuffer& frame) const { | 798 bool VCMJitterBuffer::IsContinuous(const VCMFrameBuffer& frame, |
769 if (IsContinuousInState(frame, last_decoded_state_)) { | 799 bool ignore_error_mode) const { |
800 if (IsContinuousInState(frame, last_decoded_state_, ignore_error_mode)) { | |
770 return true; | 801 return true; |
771 } | 802 } |
772 VCMDecodingState decoding_state; | 803 VCMDecodingState decoding_state; |
773 decoding_state.CopyFrom(last_decoded_state_); | 804 decoding_state.CopyFrom(last_decoded_state_); |
774 for (FrameList::const_iterator it = decodable_frames_.begin(); | 805 for (FrameList::const_iterator it = decodable_frames_.begin(); |
775 it != decodable_frames_.end(); ++it) { | 806 it != decodable_frames_.end(); ++it) { |
776 VCMFrameBuffer* decodable_frame = it->second; | 807 VCMFrameBuffer* decodable_frame = it->second; |
777 if (IsNewerTimestamp(decodable_frame->TimeStamp(), frame.TimeStamp())) { | 808 if (IsNewerTimestamp(decodable_frame->TimeStamp(), frame.TimeStamp())) { |
778 break; | 809 break; |
779 } | 810 } |
780 decoding_state.SetState(decodable_frame); | 811 decoding_state.SetState(decodable_frame); |
781 if (IsContinuousInState(frame, decoding_state)) { | 812 if (IsContinuousInState(frame, decoding_state, ignore_error_mode)) { |
782 return true; | 813 return true; |
783 } | 814 } |
784 } | 815 } |
785 return false; | 816 return false; |
786 } | 817 } |
787 | 818 |
788 void VCMJitterBuffer::FindAndInsertContinuousFrames( | 819 void VCMJitterBuffer::FindAndInsertContinuousFrames( |
789 const VCMFrameBuffer& new_frame) { | 820 const VCMFrameBuffer& new_frame) { |
790 VCMDecodingState decoding_state; | 821 VCMDecodingState decoding_state; |
791 decoding_state.CopyFrom(last_decoded_state_); | 822 decoding_state.CopyFrom(last_decoded_state_); |
(...skipping 13 matching lines...) Expand all Loading... | |
805 // 1. Continuous base or sync layer. | 836 // 1. Continuous base or sync layer. |
806 // 2. The end of the list was reached. | 837 // 2. The end of the list was reached. |
807 for (FrameList::iterator it = incomplete_frames_.begin(); | 838 for (FrameList::iterator it = incomplete_frames_.begin(); |
808 it != incomplete_frames_.end();) { | 839 it != incomplete_frames_.end();) { |
809 VCMFrameBuffer* frame = it->second; | 840 VCMFrameBuffer* frame = it->second; |
810 if (IsNewerTimestamp(original_decoded_state.time_stamp(), | 841 if (IsNewerTimestamp(original_decoded_state.time_stamp(), |
811 frame->TimeStamp())) { | 842 frame->TimeStamp())) { |
812 ++it; | 843 ++it; |
813 continue; | 844 continue; |
814 } | 845 } |
815 if (IsContinuousInState(*frame, decoding_state)) { | 846 if (IsContinuousInState(*frame, decoding_state, false)) { |
816 decodable_frames_.InsertFrame(frame); | 847 decodable_frames_.InsertFrame(frame); |
817 incomplete_frames_.erase(it++); | 848 incomplete_frames_.erase(it++); |
818 decoding_state.SetState(frame); | 849 decoding_state.SetState(frame); |
819 } else if (frame->TemporalId() <= 0) { | 850 } else if (frame->TemporalId() <= 0) { |
820 break; | 851 break; |
821 } else { | 852 } else { |
822 ++it; | 853 ++it; |
823 } | 854 } |
824 } | 855 } |
825 } | 856 } |
826 | 857 |
858 bool VCMJitterBuffer::TooManyDiscontinuousFrames(int64_t now_ms) { | |
859 // If we didn't receive a continuous decodable frame for too long and | |
860 // can't report through NACKs to the sender, request a keyframe. | |
861 if (nack_mode_ == kNoNack && | |
862 time_last_decodable_frame_ >= 0 && | |
863 now_ms - time_last_decodable_frame_ > kMaxDiscontinuousFramesTime) { | |
864 // Update to current time so we don't request keyframes too often. | |
865 time_last_decodable_frame_ = now_ms; | |
866 return true; | |
867 } | |
868 return false; | |
869 } | |
870 | |
827 uint32_t VCMJitterBuffer::EstimatedJitterMs() { | 871 uint32_t VCMJitterBuffer::EstimatedJitterMs() { |
828 CriticalSectionScoped cs(crit_sect_); | 872 CriticalSectionScoped cs(crit_sect_); |
829 // Compute RTT multiplier for estimation. | 873 // Compute RTT multiplier for estimation. |
830 // low_rtt_nackThresholdMs_ == -1 means no FEC. | 874 // low_rtt_nackThresholdMs_ == -1 means no FEC. |
831 double rtt_mult = 1.0f; | 875 double rtt_mult = 1.0f; |
832 if (low_rtt_nack_threshold_ms_ >= 0 && | 876 if (low_rtt_nack_threshold_ms_ >= 0 && |
833 rtt_ms_ >= low_rtt_nack_threshold_ms_) { | 877 rtt_ms_ >= low_rtt_nack_threshold_ms_) { |
834 // For RTTs above low_rtt_nack_threshold_ms_ we don't apply extra delay | 878 // For RTTs above low_rtt_nack_threshold_ms_ we don't apply extra delay |
835 // when waiting for retransmissions. | 879 // when waiting for retransmissions. |
836 rtt_mult = 0.0f; | 880 rtt_mult = 0.0f; |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
929 bool found_key_frame = RecycleFramesUntilKeyFrame(); | 973 bool found_key_frame = RecycleFramesUntilKeyFrame(); |
930 if (!found_key_frame) { | 974 if (!found_key_frame) { |
931 *request_key_frame = have_non_empty_frame; | 975 *request_key_frame = have_non_empty_frame; |
932 return std::vector<uint16_t>(); | 976 return std::vector<uint16_t>(); |
933 } | 977 } |
934 } | 978 } |
935 } | 979 } |
936 if (TooLargeNackList()) { | 980 if (TooLargeNackList()) { |
937 *request_key_frame = !HandleTooLargeNackList(); | 981 *request_key_frame = !HandleTooLargeNackList(); |
938 } | 982 } |
939 if (max_incomplete_time_ms_ > 0) { | 983 if (max_incomplete_time_ms_ > 0) { |
stefan-webrtc
2015/09/24 07:05:47
I wonder if what you really want to do is somethin
joachim
2015/09/24 08:52:46
Yes, that looks similar, see my other comment rega
| |
940 int non_continuous_incomplete_duration = | 984 int non_continuous_incomplete_duration = |
941 NonContinuousOrIncompleteDuration(); | 985 NonContinuousOrIncompleteDuration(); |
942 if (non_continuous_incomplete_duration > 90 * max_incomplete_time_ms_) { | 986 if (non_continuous_incomplete_duration > 90 * max_incomplete_time_ms_) { |
943 LOG_F(LS_WARNING) << "Too long non-decodable duration: " | 987 LOG_F(LS_WARNING) << "Too long non-decodable duration: " |
944 << non_continuous_incomplete_duration << " > " | 988 << non_continuous_incomplete_duration << " > " |
945 << 90 * max_incomplete_time_ms_; | 989 << 90 * max_incomplete_time_ms_; |
946 FrameList::reverse_iterator rit = find_if(incomplete_frames_.rbegin(), | 990 FrameList::reverse_iterator rit = find_if(incomplete_frames_.rbegin(), |
947 incomplete_frames_.rend(), IsKeyFrame); | 991 incomplete_frames_.rend(), IsKeyFrame); |
948 if (rit == incomplete_frames_.rend()) { | 992 if (rit == incomplete_frames_.rend()) { |
949 // Request a key frame if we don't have one already. | 993 // Request a key frame if we don't have one already. |
(...skipping 303 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1253 } | 1297 } |
1254 // Evaluate if the RTT is higher than |high_rtt_nack_threshold_ms_|, and in | 1298 // Evaluate if the RTT is higher than |high_rtt_nack_threshold_ms_|, and in |
1255 // that case we don't wait for retransmissions. | 1299 // that case we don't wait for retransmissions. |
1256 if (high_rtt_nack_threshold_ms_ >= 0 && | 1300 if (high_rtt_nack_threshold_ms_ >= 0 && |
1257 rtt_ms_ >= high_rtt_nack_threshold_ms_) { | 1301 rtt_ms_ >= high_rtt_nack_threshold_ms_) { |
1258 return false; | 1302 return false; |
1259 } | 1303 } |
1260 return true; | 1304 return true; |
1261 } | 1305 } |
1262 } // namespace webrtc | 1306 } // namespace webrtc |
OLD | NEW |