blob: 770fc96fc30fe2b23509fb1b550e8c8a48ff5f6e [file] [log] [blame]
pbos@webrtc.org29d58392013-05-16 12:08:03 +00001/*
2 * Copyright (c) 2013 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
pbos@webrtc.org16e03b72013-10-28 16:32:01 +000011#include "webrtc/video/video_receive_stream.h"
pbos@webrtc.org29d58392013-05-16 12:08:03 +000012
pbos@webrtc.org12dc1a32013-08-05 16:22:53 +000013#include <stdlib.h>
pbos@webrtc.org29d58392013-05-16 12:08:03 +000014
mflodmand1590b22015-12-09 07:07:59 -080015#include <set>
mflodman@webrtc.orgb429e512013-12-18 09:46:22 +000016#include <string>
17
pbos@webrtc.org0d852d52015-02-09 15:14:36 +000018#include "webrtc/base/checks.h"
Peter Boström415d2cd2015-10-26 11:35:17 +010019#include "webrtc/base/logging.h"
pbos@webrtc.org29d58392013-05-16 12:08:03 +000020#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
Stefan Holmer80e12072016-02-23 13:30:42 +010021#include "webrtc/modules/congestion_controller/include/congestion_controller.h"
Peter Boström1d04ac62016-02-05 11:25:46 +010022#include "webrtc/modules/utility/include/process_thread.h"
Peter Boströma4c76882016-03-03 16:29:02 +010023#include "webrtc/modules/video_coding/include/video_coding.h"
sprang3911c262016-04-15 01:24:14 -070024#include "webrtc/modules/video_coding/utility/ivf_file_writer.h"
Henrik Kjellander98f53512015-10-28 18:17:40 +010025#include "webrtc/system_wrappers/include/clock.h"
Peter Boström7623ce42015-12-09 12:13:30 +010026#include "webrtc/video/call_stats.h"
sprang@webrtc.org09315702014-02-07 12:06:29 +000027#include "webrtc/video/receive_statistics_proxy.h"
pbos@webrtc.org16e03b72013-10-28 16:32:01 +000028#include "webrtc/video_receive_stream.h"
pbos@webrtc.org29d58392013-05-16 12:08:03 +000029
30namespace webrtc {
mflodmana20de202015-10-18 22:08:19 -070031
sprang3911c262016-04-15 01:24:14 -070032static const bool kEnableFrameRecording = false;
33
Peter Boströmf751bf82016-02-05 14:00:53 +010034static bool UseSendSideBwe(const VideoReceiveStream::Config& config) {
35 if (!config.rtp.transport_cc)
36 return false;
37 for (const auto& extension : config.rtp.extensions) {
isheriff6f8d6862016-05-26 11:24:55 -070038 if (extension.uri == RtpExtension::kTransportSequenceNumberUri)
mflodmana20de202015-10-18 22:08:19 -070039 return true;
40 }
41 return false;
42}
43
pbos@webrtc.org32e85282015-01-15 10:09:39 +000044std::string VideoReceiveStream::Decoder::ToString() const {
45 std::stringstream ss;
Peter Boström74f6e9e2016-04-04 17:56:10 +020046 ss << "{decoder: " << (decoder ? "(VideoDecoder)" : "nullptr");
pbos@webrtc.org32e85282015-01-15 10:09:39 +000047 ss << ", payload_type: " << payload_type;
48 ss << ", payload_name: " << payload_name;
pbos@webrtc.org32e85282015-01-15 10:09:39 +000049 ss << '}';
50
51 return ss.str();
52}
53
54std::string VideoReceiveStream::Config::ToString() const {
55 std::stringstream ss;
56 ss << "{decoders: [";
57 for (size_t i = 0; i < decoders.size(); ++i) {
58 ss << decoders[i].ToString();
59 if (i != decoders.size() - 1)
60 ss << ", ";
61 }
62 ss << ']';
63 ss << ", rtp: " << rtp.ToString();
Peter Boström74f6e9e2016-04-04 17:56:10 +020064 ss << ", renderer: " << (renderer ? "(renderer)" : "nullptr");
pbos@webrtc.org32e85282015-01-15 10:09:39 +000065 ss << ", render_delay_ms: " << render_delay_ms;
pbos8fc7fa72015-07-15 08:02:58 -070066 if (!sync_group.empty())
67 ss << ", sync_group: " << sync_group;
pbos@webrtc.org32e85282015-01-15 10:09:39 +000068 ss << ", pre_decode_callback: "
Peter Boström74f6e9e2016-04-04 17:56:10 +020069 << (pre_decode_callback ? "(EncodedFrameObserver)" : "nullptr");
pbos@webrtc.org32e85282015-01-15 10:09:39 +000070 ss << ", pre_render_callback: "
Peter Boström74f6e9e2016-04-04 17:56:10 +020071 << (pre_render_callback ? "(I420FrameCallback)" : "nullptr");
pbos@webrtc.org32e85282015-01-15 10:09:39 +000072 ss << ", target_delay_ms: " << target_delay_ms;
73 ss << '}';
74
75 return ss.str();
76}
77
78std::string VideoReceiveStream::Config::Rtp::ToString() const {
79 std::stringstream ss;
80 ss << "{remote_ssrc: " << remote_ssrc;
81 ss << ", local_ssrc: " << local_ssrc;
pbosda903ea2015-10-02 02:36:56 -070082 ss << ", rtcp_mode: "
83 << (rtcp_mode == RtcpMode::kCompound ? "RtcpMode::kCompound"
84 : "RtcpMode::kReducedSize");
pbos@webrtc.org32e85282015-01-15 10:09:39 +000085 ss << ", rtcp_xr: ";
86 ss << "{receiver_reference_time_report: "
87 << (rtcp_xr.receiver_reference_time_report ? "on" : "off");
88 ss << '}';
89 ss << ", remb: " << (remb ? "on" : "off");
stefan43edf0f2015-11-20 18:05:48 -080090 ss << ", transport_cc: " << (transport_cc ? "on" : "off");
pbos@webrtc.org32e85282015-01-15 10:09:39 +000091 ss << ", nack: {rtp_history_ms: " << nack.rtp_history_ms << '}';
92 ss << ", fec: " << fec.ToString();
93 ss << ", rtx: {";
94 for (auto& kv : rtx) {
95 ss << kv.first << " -> ";
96 ss << "{ssrc: " << kv.second.ssrc;
97 ss << ", payload_type: " << kv.second.payload_type;
98 ss << '}';
99 }
100 ss << '}';
101 ss << ", extensions: [";
102 for (size_t i = 0; i < extensions.size(); ++i) {
103 ss << extensions[i].ToString();
104 if (i != extensions.size() - 1)
105 ss << ", ";
106 }
107 ss << ']';
108 ss << '}';
109 return ss.str();
110}
111
pbos@webrtc.org776e6f22014-10-29 15:28:39 +0000112namespace {
113VideoCodec CreateDecoderVideoCodec(const VideoReceiveStream::Decoder& decoder) {
114 VideoCodec codec;
115 memset(&codec, 0, sizeof(codec));
116
117 codec.plType = decoder.payload_type;
mflodmand1590b22015-12-09 07:07:59 -0800118 strncpy(codec.plName, decoder.payload_name.c_str(), sizeof(codec.plName));
pbos@webrtc.org776e6f22014-10-29 15:28:39 +0000119 if (decoder.payload_name == "VP8") {
120 codec.codecType = kVideoCodecVP8;
ivica05cfcd32015-09-07 06:04:16 -0700121 } else if (decoder.payload_name == "VP9") {
122 codec.codecType = kVideoCodecVP9;
pbos@webrtc.org776e6f22014-10-29 15:28:39 +0000123 } else if (decoder.payload_name == "H264") {
124 codec.codecType = kVideoCodecH264;
125 } else {
126 codec.codecType = kVideoCodecGeneric;
127 }
128
129 if (codec.codecType == kVideoCodecVP8) {
130 codec.codecSpecific.VP8 = VideoEncoder::GetDefaultVp8Settings();
ivica05cfcd32015-09-07 06:04:16 -0700131 } else if (codec.codecType == kVideoCodecVP9) {
132 codec.codecSpecific.VP9 = VideoEncoder::GetDefaultVp9Settings();
pbos@webrtc.org776e6f22014-10-29 15:28:39 +0000133 } else if (codec.codecType == kVideoCodecH264) {
134 codec.codecSpecific.H264 = VideoEncoder::GetDefaultH264Settings();
135 }
136
137 codec.width = 320;
138 codec.height = 180;
139 codec.startBitrate = codec.minBitrate = codec.maxBitrate =
140 Call::Config::kDefaultStartBitrateBps / 1000;
141
142 return codec;
143}
144} // namespace
pbos@webrtc.org29d58392013-05-16 12:08:03 +0000145
Peter Boströmca835252016-02-11 15:59:46 +0100146namespace internal {
mflodman0c478b32015-10-21 15:52:16 +0200147VideoReceiveStream::VideoReceiveStream(
148 int num_cpu_cores,
149 CongestionController* congestion_controller,
150 const VideoReceiveStream::Config& config,
151 webrtc::VoiceEngine* voice_engine,
152 ProcessThread* process_thread,
Stefan Holmer58c664c2016-02-08 14:31:30 +0100153 CallStats* call_stats,
154 VieRemb* remb)
solenberg4fbae2b2015-08-28 04:07:10 -0700155 : transport_adapter_(config.rtcp_send_transport),
sprang@webrtc.org40709352013-11-26 11:41:59 +0000156 encoded_frame_proxy_(config.pre_decode_callback),
157 config_(config),
Peter Boström1d04ac62016-02-05 11:25:46 +0100158 process_thread_(process_thread),
sprang@webrtc.org09315702014-02-07 12:06:29 +0000159 clock_(Clock::GetRealTimeClock()),
Peter Boströmca835252016-02-11 15:59:46 +0100160 decode_thread_(DecodeThreadFunction, this, "DecodingThread"),
mflodman0c478b32015-10-21 15:52:16 +0200161 congestion_controller_(congestion_controller),
Peter Boström1d04ac62016-02-05 11:25:46 +0100162 call_stats_(call_stats),
Peter Boström0b250722016-04-22 18:23:15 +0200163 video_receiver_(clock_, nullptr, this, this, this),
nisse30f118e2016-05-03 01:09:11 -0700164 incoming_video_stream_(config.disable_prerenderer_smoothing),
sprang0ab8e812016-02-24 01:35:40 -0800165 stats_proxy_(config_, clock_),
mflodmanfa666592016-04-28 23:15:33 -0700166 rtp_stream_receiver_(&video_receiver_,
167 congestion_controller_->GetRemoteBitrateEstimator(
168 UseSendSideBwe(config_)),
169 &transport_adapter_,
170 call_stats_->rtcp_rtt_stats(),
171 congestion_controller_->pacer(),
mflodmancfc8e3b2016-05-03 21:22:04 -0700172 congestion_controller_->packet_router(),
mflodmandc7d0d22016-05-06 05:32:22 -0700173 remb,
mflodmancfc8e3b2016-05-03 21:22:04 -0700174 config,
mflodmandc7d0d22016-05-06 05:32:22 -0700175 &stats_proxy_,
176 process_thread_),
mflodmancfc8e3b2016-05-03 21:22:04 -0700177 video_stream_decoder_(&video_receiver_,
178 &rtp_stream_receiver_,
179 &rtp_stream_receiver_,
mflodmandc7d0d22016-05-06 05:32:22 -0700180 rtp_stream_receiver_.IsRetransmissionsEnabled(),
181 rtp_stream_receiver_.IsFecEnabled(),
mflodmancfc8e3b2016-05-03 21:22:04 -0700182 &stats_proxy_,
183 &incoming_video_stream_,
Tommibd3380f2016-06-10 17:38:17 +0200184 config.pre_render_callback),
mflodmandc7d0d22016-05-06 05:32:22 -0700185 vie_sync_(&video_receiver_) {
pbosa2f30de2015-10-15 05:22:13 -0700186 LOG(LS_INFO) << "VideoReceiveStream: " << config_.ToString();
pbos@webrtc.org29d58392013-05-16 12:08:03 +0000187
Stefan Holmer58c664c2016-02-08 14:31:30 +0100188 RTC_DCHECK(process_thread_);
189 RTC_DCHECK(congestion_controller_);
190 RTC_DCHECK(call_stats_);
mflodmancfc8e3b2016-05-03 21:22:04 -0700191
192 // Register the channel to receive stats updates.
193 call_stats_->RegisterStatsObserver(&video_stream_decoder_);
pbos@webrtc.orgbbb07e62013-08-05 12:01:36 +0000194
henrikg91d6ede2015-09-17 00:24:34 -0700195 RTC_DCHECK(!config_.decoders.empty());
Peter Boström521af4e2015-11-27 16:35:04 +0100196 std::set<int> decoder_payload_types;
Peter Boströmb1ae3a42016-02-15 17:52:40 +0100197 for (const Decoder& decoder : config_.decoders) {
Peter Boström795dbe42015-11-27 14:09:07 +0100198 RTC_CHECK(decoder.decoder);
Peter Boström521af4e2015-11-27 16:35:04 +0100199 RTC_CHECK(decoder_payload_types.find(decoder.payload_type) ==
200 decoder_payload_types.end())
201 << "Duplicate payload type (" << decoder.payload_type
202 << ") for different decoders.";
203 decoder_payload_types.insert(decoder.payload_type);
Peter Boström0b250722016-04-22 18:23:15 +0200204 video_receiver_.RegisterExternalDecoder(decoder.decoder,
205 decoder.payload_type);
pbos@webrtc.org776e6f22014-10-29 15:28:39 +0000206
207 VideoCodec codec = CreateDecoderVideoCodec(decoder);
mflodmanfa666592016-04-28 23:15:33 -0700208 RTC_CHECK(rtp_stream_receiver_.SetReceiveCodec(codec));
Peter Boström0b250722016-04-22 18:23:15 +0200209 RTC_CHECK_EQ(VCM_OK, video_receiver_.RegisterReceiveCodec(
210 &codec, num_cpu_cores, false));
pbos@webrtc.org0181b5f2013-09-09 08:26:30 +0000211 }
212
Peter Boström0b250722016-04-22 18:23:15 +0200213 video_receiver_.SetRenderDelay(config.render_delay_ms);
Peter Boströmf751bf82016-02-05 14:00:53 +0100214 incoming_video_stream_.SetExpectedRenderDelay(config.render_delay_ms);
Peter Boströmf751bf82016-02-05 14:00:53 +0100215 incoming_video_stream_.SetExternalCallback(this);
Peter Boström1d04ac62016-02-05 11:25:46 +0100216
Peter Boström0b250722016-04-22 18:23:15 +0200217 process_thread_->RegisterModule(&video_receiver_);
Peter Boström1794b262016-02-16 14:12:02 +0100218 process_thread_->RegisterModule(&vie_sync_);
pbos@webrtc.org29d58392013-05-16 12:08:03 +0000219}
220
221VideoReceiveStream::~VideoReceiveStream() {
pbosa2f30de2015-10-15 05:22:13 -0700222 LOG(LS_INFO) << "~VideoReceiveStream: " << config_.ToString();
Peter Boströmca835252016-02-11 15:59:46 +0100223 Stop();
224
Peter Boström1794b262016-02-16 14:12:02 +0100225 process_thread_->DeRegisterModule(&vie_sync_);
Peter Boström0b250722016-04-22 18:23:15 +0200226 process_thread_->DeRegisterModule(&video_receiver_);
Peter Boströmb1ae3a42016-02-15 17:52:40 +0100227
mflodmancfc8e3b2016-05-03 21:22:04 -0700228 // Deregister external decoders so they are no longer running during
Peter Boströmb1ae3a42016-02-15 17:52:40 +0100229 // destruction. This effectively stops the VCM since the decoder thread is
230 // stopped, the VCM is deregistered and no asynchronous decoder threads are
231 // running.
232 for (const Decoder& decoder : config_.decoders)
Peter Boström0b250722016-04-22 18:23:15 +0200233 video_receiver_.RegisterExternalDecoder(nullptr, decoder.payload_type);
Peter Boströmb1ae3a42016-02-15 17:52:40 +0100234
mflodmancfc8e3b2016-05-03 21:22:04 -0700235 call_stats_->DeregisterStatsObserver(&video_stream_decoder_);
mflodmana20de202015-10-18 22:08:19 -0700236
Peter Boströmf751bf82016-02-05 14:00:53 +0100237 congestion_controller_->GetRemoteBitrateEstimator(UseSendSideBwe(config_))
mflodmanfa666592016-04-28 23:15:33 -0700238 ->RemoveStream(rtp_stream_receiver_.GetRemoteSsrc());
pbos@webrtc.org29d58392013-05-16 12:08:03 +0000239}
240
pbos1ba8d392016-05-01 20:18:34 -0700241void VideoReceiveStream::SignalNetworkState(NetworkState state) {
mflodmandc7d0d22016-05-06 05:32:22 -0700242 rtp_stream_receiver_.SignalNetworkState(state);
pbos1ba8d392016-05-01 20:18:34 -0700243}
244
245
246bool VideoReceiveStream::DeliverRtcp(const uint8_t* packet, size_t length) {
247 return rtp_stream_receiver_.DeliverRtcp(packet, length);
248}
249
250bool VideoReceiveStream::DeliverRtp(const uint8_t* packet,
251 size_t length,
252 const PacketTime& packet_time) {
253 return rtp_stream_receiver_.DeliverRtp(packet, length, packet_time);
254}
255
pbos@webrtc.orga5c8d2c2014-04-24 11:13:21 +0000256void VideoReceiveStream::Start() {
Peter Boströmca835252016-02-11 15:59:46 +0100257 if (decode_thread_.IsRunning())
258 return;
sprang@webrtc.orgd9b95602014-01-27 13:03:02 +0000259 transport_adapter_.Enable();
Peter Boströmf751bf82016-02-05 14:00:53 +0100260 incoming_video_stream_.Start();
Peter Boströmca835252016-02-11 15:59:46 +0100261 // Start the decode thread
262 decode_thread_.Start();
263 decode_thread_.SetPriority(rtc::kHighestPriority);
mflodmanfa666592016-04-28 23:15:33 -0700264 rtp_stream_receiver_.StartReceive();
pbos@webrtc.org29d58392013-05-16 12:08:03 +0000265}
266
pbos@webrtc.orga5c8d2c2014-04-24 11:13:21 +0000267void VideoReceiveStream::Stop() {
Peter Boströmf751bf82016-02-05 14:00:53 +0100268 incoming_video_stream_.Stop();
mflodmanfa666592016-04-28 23:15:33 -0700269 rtp_stream_receiver_.StopReceive();
Peter Boström0b250722016-04-22 18:23:15 +0200270 video_receiver_.TriggerDecoderShutdown();
Peter Boströmca835252016-02-11 15:59:46 +0100271 decode_thread_.Stop();
sprang@webrtc.orgd9b95602014-01-27 13:03:02 +0000272 transport_adapter_.Disable();
pbos@webrtc.org29d58392013-05-16 12:08:03 +0000273}
274
pbos8fc7fa72015-07-15 08:02:58 -0700275void VideoReceiveStream::SetSyncChannel(VoiceEngine* voice_engine,
276 int audio_channel_id) {
Peter Boström74f6e9e2016-04-04 17:56:10 +0200277 if (voice_engine && audio_channel_id != -1) {
pbos8fc7fa72015-07-15 08:02:58 -0700278 VoEVideoSync* voe_sync_interface = VoEVideoSync::GetInterface(voice_engine);
mflodmandc7d0d22016-05-06 05:32:22 -0700279 vie_sync_.ConfigureSync(audio_channel_id, voe_sync_interface,
280 rtp_stream_receiver_.rtp_rtcp(),
mflodmanfa666592016-04-28 23:15:33 -0700281 rtp_stream_receiver_.GetRtpReceiver());
pbos8fc7fa72015-07-15 08:02:58 -0700282 voe_sync_interface->Release();
mflodmandc7d0d22016-05-06 05:32:22 -0700283 } else {
284 vie_sync_.ConfigureSync(-1, nullptr, rtp_stream_receiver_.rtp_rtcp(),
285 rtp_stream_receiver_.GetRtpReceiver());
pbos8fc7fa72015-07-15 08:02:58 -0700286 }
287}
288
sprang@webrtc.org9510e532014-02-07 15:32:45 +0000289VideoReceiveStream::Stats VideoReceiveStream::GetStats() const {
Peter Boströmf751bf82016-02-05 14:00:53 +0100290 return stats_proxy_.GetStats();
sprang@webrtc.org09315702014-02-07 12:06:29 +0000291}
292
Tommibd3380f2016-06-10 17:38:17 +0200293void VideoReceiveStream::OnFrame(const VideoFrame& video_frame) {
Peter Boströmf751bf82016-02-05 14:00:53 +0100294 stats_proxy_.OnDecodedFrame();
sprang@webrtc.org09315702014-02-07 12:06:29 +0000295
asaperssonf8cdd182016-03-15 01:00:47 -0700296 int64_t sync_offset_ms;
297 if (vie_sync_.GetStreamSyncOffsetInMs(video_frame, &sync_offset_ms))
298 stats_proxy_.OnSyncOffsetUpdated(sync_offset_ms);
299
Peter Boström74f6e9e2016-04-04 17:56:10 +0200300 if (config_.renderer)
nisseeb83a1a2016-03-21 01:27:56 -0700301 config_.renderer->OnFrame(video_frame);
pbos@webrtc.org29d58392013-05-16 12:08:03 +0000302
asaperssona1862882016-04-18 00:41:05 -0700303 stats_proxy_.OnRenderedFrame(video_frame.width(), video_frame.height());
pbos@webrtc.org29d58392013-05-16 12:08:03 +0000304}
pbos@webrtc.org26c0c412014-09-03 16:17:12 +0000305
asapersson86b01602015-10-20 23:55:26 -0700306// TODO(asapersson): Consider moving callback from video_encoder.h or
307// creating a different callback.
308int32_t VideoReceiveStream::Encoded(
309 const EncodedImage& encoded_image,
310 const CodecSpecificInfo* codec_specific_info,
311 const RTPFragmentationHeader* fragmentation) {
Peter Boströmf751bf82016-02-05 14:00:53 +0100312 stats_proxy_.OnPreDecode(encoded_image, codec_specific_info);
asapersson86b01602015-10-20 23:55:26 -0700313 if (config_.pre_decode_callback) {
314 // TODO(asapersson): Remove EncodedFrameCallbackAdapter.
315 encoded_frame_proxy_.Encoded(
316 encoded_image, codec_specific_info, fragmentation);
317 }
sprang3911c262016-04-15 01:24:14 -0700318 if (kEnableFrameRecording) {
319 if (!ivf_writer_.get()) {
320 RTC_DCHECK(codec_specific_info);
sprang3911c262016-04-15 01:24:14 -0700321 std::ostringstream oss;
322 oss << "receive_bitstream_ssrc_" << config_.rtp.remote_ssrc << ".ivf";
kjellander02b3d272016-04-20 05:05:54 -0700323 ivf_writer_ =
324 IvfFileWriter::Open(oss.str(), codec_specific_info->codecType);
sprang3911c262016-04-15 01:24:14 -0700325 }
326 if (ivf_writer_.get()) {
327 bool ok = ivf_writer_->WriteFrame(encoded_image);
328 RTC_DCHECK(ok);
329 }
330 }
331
asapersson86b01602015-10-20 23:55:26 -0700332 return 0;
333}
334
Peter Boströmca835252016-02-11 15:59:46 +0100335bool VideoReceiveStream::DecodeThreadFunction(void* ptr) {
336 static_cast<VideoReceiveStream*>(ptr)->Decode();
337 return true;
338}
339
340void VideoReceiveStream::Decode() {
341 static const int kMaxDecodeWaitTimeMs = 50;
Peter Boström0b250722016-04-22 18:23:15 +0200342 video_receiver_.Decode(kMaxDecodeWaitTimeMs);
Peter Boströmca835252016-02-11 15:59:46 +0100343}
344
philipel83f831a2016-03-12 03:30:23 -0800345void VideoReceiveStream::SendNack(
346 const std::vector<uint16_t>& sequence_numbers) {
mflodmandc7d0d22016-05-06 05:32:22 -0700347 rtp_stream_receiver_.RequestPacketRetransmit(sequence_numbers);
philipel83f831a2016-03-12 03:30:23 -0800348}
349
350void VideoReceiveStream::RequestKeyFrame() {
mflodmandc7d0d22016-05-06 05:32:22 -0700351 rtp_stream_receiver_.RequestKeyFrame();
philipel83f831a2016-03-12 03:30:23 -0800352}
353
mflodman@webrtc.orgf3973e82013-12-13 09:40:45 +0000354} // namespace internal
355} // namespace webrtc