blob: 9b82ec95927f2d980d69876f9fc5f0a53da2019e [file] [log] [blame]
niklase@google.com470e71d2011-07-07 08:21:25 +00001/*
xians@webrtc.org79af7342012-01-31 12:22:14 +00002 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
niklase@google.com470e71d2011-07-07 08:21:25 +00003 *
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.org956aa7e2013-05-21 13:52:32 +000011#include "webrtc/voice_engine/voe_base_impl.h"
niklase@google.com470e71d2011-07-07 08:21:25 +000012
kwiberg087bd342017-02-10 08:15:44 -080013#include "webrtc/api/audio_codecs/builtin_audio_decoder_factory.h"
pbos@webrtc.org956aa7e2013-05-21 13:52:32 +000014#include "webrtc/common_audio/signal_processing/include/signal_processing_library.h"
kjellander3e6db232015-11-26 04:44:54 -080015#include "webrtc/modules/audio_coding/include/audio_coding_module.h"
pbos@webrtc.org956aa7e2013-05-21 13:52:32 +000016#include "webrtc/modules/audio_device/audio_device_impl.h"
17#include "webrtc/modules/audio_processing/include/audio_processing.h"
Edward Lemurc20978e2017-07-06 19:44:34 +020018#include "webrtc/rtc_base/format_macros.h"
19#include "webrtc/rtc_base/location.h"
20#include "webrtc/rtc_base/logging.h"
Henrik Kjellander98f53512015-10-28 18:17:40 +010021#include "webrtc/system_wrappers/include/file_wrapper.h"
pbos@webrtc.org956aa7e2013-05-21 13:52:32 +000022#include "webrtc/voice_engine/channel.h"
23#include "webrtc/voice_engine/include/voe_errors.h"
24#include "webrtc/voice_engine/output_mixer.h"
25#include "webrtc/voice_engine/transmit_mixer.h"
26#include "webrtc/voice_engine/utility.h"
27#include "webrtc/voice_engine/voice_engine_impl.h"
niklase@google.com470e71d2011-07-07 08:21:25 +000028
Jelena Marusic2dd6a272015-04-14 09:47:00 +020029namespace webrtc {
niklase@google.com470e71d2011-07-07 08:21:25 +000030
Jelena Marusic2dd6a272015-04-14 09:47:00 +020031VoEBase* VoEBase::GetInterface(VoiceEngine* voiceEngine) {
32 if (nullptr == voiceEngine) {
33 return nullptr;
34 }
35 VoiceEngineImpl* s = static_cast<VoiceEngineImpl*>(voiceEngine);
36 s->AddRef();
37 return s;
niklase@google.com470e71d2011-07-07 08:21:25 +000038}
39
Jelena Marusic2dd6a272015-04-14 09:47:00 +020040VoEBaseImpl::VoEBaseImpl(voe::SharedData* shared)
41 : voiceEngineObserverPtr_(nullptr),
Jelena Marusic2dd6a272015-04-14 09:47:00 +020042 shared_(shared) {}
43
44VoEBaseImpl::~VoEBaseImpl() {
45 TerminateInternal();
niklase@google.com470e71d2011-07-07 08:21:25 +000046}
47
solenberg13725082015-11-25 08:16:52 -080048void VoEBaseImpl::OnErrorIsReported(const ErrorCode error) {
tommi31fc21f2016-01-21 10:37:37 -080049 rtc::CritScope cs(&callbackCritSect_);
Jelena Marusic2dd6a272015-04-14 09:47:00 +020050 int errCode = 0;
51 if (error == AudioDeviceObserver::kRecordingError) {
52 errCode = VE_RUNTIME_REC_ERROR;
53 LOG_F(LS_ERROR) << "VE_RUNTIME_REC_ERROR";
54 } else if (error == AudioDeviceObserver::kPlayoutError) {
55 errCode = VE_RUNTIME_PLAY_ERROR;
56 LOG_F(LS_ERROR) << "VE_RUNTIME_PLAY_ERROR";
57 }
58 if (voiceEngineObserverPtr_) {
59 // Deliver callback (-1 <=> no channel dependency)
60 voiceEngineObserverPtr_->CallbackOnError(-1, errCode);
61 }
niklase@google.com470e71d2011-07-07 08:21:25 +000062}
63
solenberg13725082015-11-25 08:16:52 -080064void VoEBaseImpl::OnWarningIsReported(const WarningCode warning) {
tommi31fc21f2016-01-21 10:37:37 -080065 rtc::CritScope cs(&callbackCritSect_);
Jelena Marusic2dd6a272015-04-14 09:47:00 +020066 int warningCode = 0;
67 if (warning == AudioDeviceObserver::kRecordingWarning) {
68 warningCode = VE_RUNTIME_REC_WARNING;
69 LOG_F(LS_WARNING) << "VE_RUNTIME_REC_WARNING";
70 } else if (warning == AudioDeviceObserver::kPlayoutWarning) {
71 warningCode = VE_RUNTIME_PLAY_WARNING;
72 LOG_F(LS_WARNING) << "VE_RUNTIME_PLAY_WARNING";
73 }
74 if (voiceEngineObserverPtr_) {
75 // Deliver callback (-1 <=> no channel dependency)
76 voiceEngineObserverPtr_->CallbackOnError(-1, warningCode);
77 }
niklase@google.com470e71d2011-07-07 08:21:25 +000078}
79
henrikaec6fbd22017-03-31 05:43:36 -070080int32_t VoEBaseImpl::RecordedDataIsAvailable(
81 const void* audio_data,
82 const size_t number_of_frames,
83 const size_t bytes_per_sample,
84 const size_t number_of_channels,
85 const uint32_t sample_rate,
86 const uint32_t audio_delay_milliseconds,
87 const int32_t clock_drift,
88 const uint32_t volume,
89 const bool key_pressed,
90 uint32_t& new_mic_volume) {
91 RTC_DCHECK_EQ(2 * number_of_channels, bytes_per_sample);
92 RTC_DCHECK(shared_->transmit_mixer() != nullptr);
93 RTC_DCHECK(shared_->audio_device() != nullptr);
94
95 uint32_t max_volume = 0;
96 uint16_t voe_mic_level = 0;
97 // Check for zero to skip this calculation; the consumer may use this to
98 // indicate no volume is available.
99 if (volume != 0) {
100 // Scale from ADM to VoE level range
101 if (shared_->audio_device()->MaxMicrophoneVolume(&max_volume) == 0) {
102 if (max_volume) {
103 voe_mic_level = static_cast<uint16_t>(
104 (volume * kMaxVolumeLevel + static_cast<int>(max_volume / 2)) /
105 max_volume);
106 }
107 }
108 // We learned that on certain systems (e.g Linux) the voe_mic_level
109 // can be greater than the maxVolumeLevel therefore
110 // we are going to cap the voe_mic_level to the maxVolumeLevel
111 // and change the maxVolume to volume if it turns out that
112 // the voe_mic_level is indeed greater than the maxVolumeLevel.
113 if (voe_mic_level > kMaxVolumeLevel) {
114 voe_mic_level = kMaxVolumeLevel;
115 max_volume = volume;
116 }
117 }
118
119 // Perform channel-independent operations
120 // (APM, mix with file, record to file, mute, etc.)
121 shared_->transmit_mixer()->PrepareDemux(
122 audio_data, number_of_frames, number_of_channels, sample_rate,
123 static_cast<uint16_t>(audio_delay_milliseconds), clock_drift,
124 voe_mic_level, key_pressed);
125
126 // Copy the audio frame to each sending channel and perform
127 // channel-dependent operations (file mixing, mute, etc.), encode and
128 // packetize+transmit the RTP packet.
129 shared_->transmit_mixer()->ProcessAndEncodeAudio();
130
131 // Scale from VoE to ADM level range.
132 uint32_t new_voe_mic_level = shared_->transmit_mixer()->CaptureLevel();
133 if (new_voe_mic_level != voe_mic_level) {
134 // Return the new volume if AGC has changed the volume.
135 return static_cast<int>((new_voe_mic_level * max_volume +
136 static_cast<int>(kMaxVolumeLevel / 2)) /
137 kMaxVolumeLevel);
138 }
139
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200140 return 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000141}
142
solenberg13725082015-11-25 08:16:52 -0800143int32_t VoEBaseImpl::NeedMorePlayData(const size_t nSamples,
144 const size_t nBytesPerSample,
Peter Kasting69558702016-01-12 16:26:35 -0800145 const size_t nChannels,
solenberg13725082015-11-25 08:16:52 -0800146 const uint32_t samplesPerSec,
147 void* audioSamples,
148 size_t& nSamplesOut,
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200149 int64_t* elapsed_time_ms,
150 int64_t* ntp_time_ms) {
Peter Kasting69558702016-01-12 16:26:35 -0800151 GetPlayoutData(static_cast<int>(samplesPerSec), nChannels, nSamples, true,
152 audioSamples, elapsed_time_ms, ntp_time_ms);
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200153 nSamplesOut = audioFrame_.samples_per_channel_;
xians@webrtc.org56925312014-04-14 10:50:37 +0000154 return 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000155}
156
xians@webrtc.org56925312014-04-14 10:50:37 +0000157void VoEBaseImpl::PushCaptureData(int voe_channel, const void* audio_data,
158 int bits_per_sample, int sample_rate,
Peter Kasting69558702016-01-12 16:26:35 -0800159 size_t number_of_channels,
Peter Kastingdce40cf2015-08-24 14:52:23 -0700160 size_t number_of_frames) {
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200161 voe::ChannelOwner ch = shared_->channel_manager().GetChannel(voe_channel);
henrikaec6fbd22017-03-31 05:43:36 -0700162 voe::Channel* channel = ch.channel();
163 if (!channel)
164 return;
165 if (channel->Sending()) {
166 // Send the audio to each channel directly without using the APM in the
167 // transmit mixer.
168 channel->ProcessAndEncodeAudio(static_cast<const int16_t*>(audio_data),
169 sample_rate, number_of_frames,
170 number_of_channels);
xians@webrtc.org07e51962014-01-29 13:54:02 +0000171 }
172}
173
Peter Kastingdce40cf2015-08-24 14:52:23 -0700174void VoEBaseImpl::PullRenderData(int bits_per_sample,
175 int sample_rate,
Peter Kasting69558702016-01-12 16:26:35 -0800176 size_t number_of_channels,
Peter Kastingdce40cf2015-08-24 14:52:23 -0700177 size_t number_of_frames,
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200178 void* audio_data, int64_t* elapsed_time_ms,
wu@webrtc.orgcb711f72014-05-19 17:39:11 +0000179 int64_t* ntp_time_ms) {
Jelena Marusic6fc2d2f2015-04-13 14:07:04 +0200180 assert(bits_per_sample == 16);
Peter Kastingdce40cf2015-08-24 14:52:23 -0700181 assert(number_of_frames == static_cast<size_t>(sample_rate / 100));
xians@webrtc.org56925312014-04-14 10:50:37 +0000182
183 GetPlayoutData(sample_rate, number_of_channels, number_of_frames, false,
wu@webrtc.org94454b72014-06-05 20:34:08 +0000184 audio_data, elapsed_time_ms, ntp_time_ms);
xians@webrtc.org56925312014-04-14 10:50:37 +0000185}
186
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200187int VoEBaseImpl::RegisterVoiceEngineObserver(VoiceEngineObserver& observer) {
tommi31fc21f2016-01-21 10:37:37 -0800188 rtc::CritScope cs(&callbackCritSect_);
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200189 if (voiceEngineObserverPtr_) {
190 shared_->SetLastError(
191 VE_INVALID_OPERATION, kTraceError,
192 "RegisterVoiceEngineObserver() observer already enabled");
193 return -1;
194 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000195
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200196 // Register the observer in all active channels
197 for (voe::ChannelManager::Iterator it(&shared_->channel_manager());
198 it.IsValid(); it.Increment()) {
199 it.GetChannel()->RegisterVoiceEngineObserver(observer);
200 }
pbos@webrtc.org676ff1e2013-08-07 17:57:36 +0000201
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200202 shared_->transmit_mixer()->RegisterVoiceEngineObserver(observer);
203 voiceEngineObserverPtr_ = &observer;
204 return 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000205}
206
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200207int VoEBaseImpl::DeRegisterVoiceEngineObserver() {
tommi31fc21f2016-01-21 10:37:37 -0800208 rtc::CritScope cs(&callbackCritSect_);
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200209 if (!voiceEngineObserverPtr_) {
210 shared_->SetLastError(
211 VE_INVALID_OPERATION, kTraceError,
212 "DeRegisterVoiceEngineObserver() observer already disabled");
niklase@google.com470e71d2011-07-07 08:21:25 +0000213 return 0;
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200214 }
215 voiceEngineObserverPtr_ = nullptr;
216
217 // Deregister the observer in all active channels
218 for (voe::ChannelManager::Iterator it(&shared_->channel_manager());
219 it.IsValid(); it.Increment()) {
220 it.GetChannel()->DeRegisterVoiceEngineObserver();
221 }
222
223 return 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000224}
225
ossu5f7cfa52016-05-30 08:11:28 -0700226int VoEBaseImpl::Init(
227 AudioDeviceModule* external_adm,
peaha9cc40b2017-06-29 08:32:09 -0700228 AudioProcessing* external_apm,
ossu5f7cfa52016-05-30 08:11:28 -0700229 const rtc::scoped_refptr<AudioDecoderFactory>& decoder_factory) {
peaha9cc40b2017-06-29 08:32:09 -0700230 // TODO(peah): Add a DCHECK for external_apm when downstream dependencies
231 // have properly been resolved.
tommi31fc21f2016-01-21 10:37:37 -0800232 rtc::CritScope cs(shared_->crit_sec());
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200233 WebRtcSpl_Init();
234 if (shared_->statistics().Initialized()) {
235 return 0;
236 }
237 if (shared_->process_thread()) {
238 shared_->process_thread()->Start();
239 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000240
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200241 // Create an internal ADM if the user has not added an external
242 // ADM implementation as input to Init().
243 if (external_adm == nullptr) {
Tommi931e6582015-05-20 09:44:38 +0200244#if !defined(WEBRTC_INCLUDE_INTERNAL_AUDIO_DEVICE)
245 return -1;
246#else
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200247 // Create the internal ADM implementation.
Peter Boström4adbbcf2016-05-03 15:51:26 -0400248 shared_->set_audio_device(AudioDeviceModule::Create(
solenberg5b3e49a2017-03-15 08:08:07 -0700249 VoEId(shared_->instance_id(), -1),
250 AudioDeviceModule::kPlatformDefaultAudio));
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200251 if (shared_->audio_device() == nullptr) {
252 shared_->SetLastError(VE_NO_MEMORY, kTraceCritical,
253 "Init() failed to create the ADM");
254 return -1;
niklase@google.com470e71d2011-07-07 08:21:25 +0000255 }
Tommi931e6582015-05-20 09:44:38 +0200256#endif // WEBRTC_INCLUDE_INTERNAL_AUDIO_DEVICE
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200257 } else {
258 // Use the already existing external ADM implementation.
259 shared_->set_audio_device(external_adm);
260 LOG_F(LS_INFO)
261 << "An external ADM implementation will be used in VoiceEngine";
262 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000263
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200264 // Register the ADM to the process thread, which will drive the error
265 // callback mechanism
266 if (shared_->process_thread()) {
tommidea489f2017-03-03 03:20:24 -0800267 shared_->process_thread()->RegisterModule(shared_->audio_device(),
268 RTC_FROM_HERE);
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200269 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000270
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200271 bool available = false;
andrew@webrtc.orgf4589162011-10-03 15:22:28 +0000272
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200273 // --------------------
274 // Reinitialize the ADM
henrika@google.com73d65512011-09-07 15:11:18 +0000275
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200276 // Register the AudioObserver implementation
277 if (shared_->audio_device()->RegisterEventObserver(this) != 0) {
278 shared_->SetLastError(
279 VE_AUDIO_DEVICE_MODULE_ERROR, kTraceWarning,
280 "Init() failed to register event observer for the ADM");
281 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000282
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200283 // Register the AudioTransport implementation
284 if (shared_->audio_device()->RegisterAudioCallback(this) != 0) {
285 shared_->SetLastError(
286 VE_AUDIO_DEVICE_MODULE_ERROR, kTraceWarning,
287 "Init() failed to register audio callback for the ADM");
288 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000289
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200290 // ADM initialization
291 if (shared_->audio_device()->Init() != 0) {
292 shared_->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceError,
293 "Init() failed to initialize the ADM");
294 return -1;
295 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000296
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200297 // Initialize the default speaker
298 if (shared_->audio_device()->SetPlayoutDevice(
299 WEBRTC_VOICE_ENGINE_DEFAULT_DEVICE) != 0) {
300 shared_->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceInfo,
301 "Init() failed to set the default output device");
302 }
303 if (shared_->audio_device()->InitSpeaker() != 0) {
304 shared_->SetLastError(VE_CANNOT_ACCESS_SPEAKER_VOL, kTraceInfo,
305 "Init() failed to initialize the speaker");
306 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000307
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200308 // Initialize the default microphone
309 if (shared_->audio_device()->SetRecordingDevice(
310 WEBRTC_VOICE_ENGINE_DEFAULT_DEVICE) != 0) {
311 shared_->SetLastError(VE_SOUNDCARD_ERROR, kTraceInfo,
312 "Init() failed to set the default input device");
313 }
314 if (shared_->audio_device()->InitMicrophone() != 0) {
315 shared_->SetLastError(VE_CANNOT_ACCESS_MIC_VOL, kTraceInfo,
316 "Init() failed to initialize the microphone");
317 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000318
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200319 // Set number of channels
320 if (shared_->audio_device()->StereoPlayoutIsAvailable(&available) != 0) {
321 shared_->SetLastError(VE_SOUNDCARD_ERROR, kTraceWarning,
322 "Init() failed to query stereo playout mode");
323 }
324 if (shared_->audio_device()->SetStereoPlayout(available) != 0) {
325 shared_->SetLastError(VE_SOUNDCARD_ERROR, kTraceWarning,
326 "Init() failed to set mono/stereo playout mode");
327 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000328
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200329 // TODO(andrew): These functions don't tell us whether stereo recording
330 // is truly available. We simply set the AudioProcessing input to stereo
331 // here, because we have to wait until receiving the first frame to
332 // determine the actual number of channels anyway.
333 //
334 // These functions may be changed; tracked here:
335 // http://code.google.com/p/webrtc/issues/detail?id=204
336 shared_->audio_device()->StereoRecordingIsAvailable(&available);
337 if (shared_->audio_device()->SetStereoRecording(available) != 0) {
338 shared_->SetLastError(VE_SOUNDCARD_ERROR, kTraceWarning,
339 "Init() failed to set mono/stereo recording mode");
340 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000341
peaha9cc40b2017-06-29 08:32:09 -0700342 // TODO(peah): Remove this when upstream dependencies have properly been
343 // resolved.
344 AudioProcessing* apm = nullptr;
345 if (!external_apm) {
346 audio_processing_ = AudioProcessing::Create();
347 if (!audio_processing_) {
348 // This can only happen if there are problems allocating the dynamic
349 // memory in the Create() call.
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200350 LOG(LS_ERROR) << "Failed to create AudioProcessing.";
351 shared_->SetLastError(VE_NO_MEMORY);
352 return -1;
andrew@webrtc.orgf0a90c32013-03-05 01:12:49 +0000353 }
peaha9cc40b2017-06-29 08:32:09 -0700354 apm = audio_processing_.get();
355 } else {
356 apm = external_apm;
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200357 }
peaha9cc40b2017-06-29 08:32:09 -0700358
359 shared_->set_audio_processing(apm);
niklas.enbom@webrtc.orge33a1022011-11-16 10:33:53 +0000360
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200361 // Set the error state for any failures in this block.
362 shared_->SetLastError(VE_APM_ERROR);
363 // Configure AudioProcessing components.
peaha9cc40b2017-06-29 08:32:09 -0700364 // TODO(peah): Move this initialization to webrtcvoiceengine.cc.
365 if (apm->high_pass_filter()->Enable(true) != 0) {
Jelena Marusicf353dd52015-05-06 15:04:22 +0200366 LOG_F(LS_ERROR) << "Failed to enable high pass filter.";
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200367 return -1;
368 }
peaha9cc40b2017-06-29 08:32:09 -0700369 if (apm->echo_cancellation()->enable_drift_compensation(false) != 0) {
Jelena Marusicf353dd52015-05-06 15:04:22 +0200370 LOG_F(LS_ERROR) << "Failed to disable drift compensation.";
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200371 return -1;
372 }
peaha9cc40b2017-06-29 08:32:09 -0700373 if (apm->noise_suppression()->set_level(kDefaultNsMode) != 0) {
Jelena Marusicf353dd52015-05-06 15:04:22 +0200374 LOG_F(LS_ERROR) << "Failed to set noise suppression level: "
375 << kDefaultNsMode;
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200376 return -1;
377 }
peaha9cc40b2017-06-29 08:32:09 -0700378 GainControl* agc = apm->gain_control();
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200379 if (agc->set_analog_level_limits(kMinVolumeLevel, kMaxVolumeLevel) != 0) {
Jelena Marusicf353dd52015-05-06 15:04:22 +0200380 LOG_F(LS_ERROR) << "Failed to set analog level limits with minimum: "
381 << kMinVolumeLevel << " and maximum: " << kMaxVolumeLevel;
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200382 return -1;
383 }
384 if (agc->set_mode(kDefaultAgcMode) != 0) {
Jelena Marusicf353dd52015-05-06 15:04:22 +0200385 LOG_F(LS_ERROR) << "Failed to set mode: " << kDefaultAgcMode;
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200386 return -1;
387 }
388 if (agc->Enable(kDefaultAgcState) != 0) {
Jelena Marusicf353dd52015-05-06 15:04:22 +0200389 LOG_F(LS_ERROR) << "Failed to set agc state: " << kDefaultAgcState;
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200390 return -1;
391 }
392 shared_->SetLastError(0); // Clear error state.
andrew@webrtc.orgf0a90c32013-03-05 01:12:49 +0000393
niklase@google.com470e71d2011-07-07 08:21:25 +0000394#ifdef WEBRTC_VOICE_ENGINE_AGC
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200395 bool agc_enabled =
396 agc->mode() == GainControl::kAdaptiveAnalog && agc->is_enabled();
397 if (shared_->audio_device()->SetAGC(agc_enabled) != 0) {
Jelena Marusicf353dd52015-05-06 15:04:22 +0200398 LOG_F(LS_ERROR) << "Failed to set agc to enabled: " << agc_enabled;
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200399 shared_->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR);
400 // TODO(ajm): No error return here due to
401 // https://code.google.com/p/webrtc/issues/detail?id=1464
402 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000403#endif
niklase@google.com470e71d2011-07-07 08:21:25 +0000404
ossu5f7cfa52016-05-30 08:11:28 -0700405 if (decoder_factory)
406 decoder_factory_ = decoder_factory;
407 else
408 decoder_factory_ = CreateBuiltinAudioDecoderFactory();
409
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200410 return shared_->statistics().SetInitialized();
niklase@google.com470e71d2011-07-07 08:21:25 +0000411}
412
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200413int VoEBaseImpl::Terminate() {
tommi31fc21f2016-01-21 10:37:37 -0800414 rtc::CritScope cs(shared_->crit_sec());
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200415 return TerminateInternal();
niklase@google.com470e71d2011-07-07 08:21:25 +0000416}
417
turaj@webrtc.org03f33702013-11-13 00:02:48 +0000418int VoEBaseImpl::CreateChannel() {
solenberg88499ec2016-09-07 07:34:41 -0700419 return CreateChannel(ChannelConfig());
turaj@webrtc.org03f33702013-11-13 00:02:48 +0000420}
421
solenberg88499ec2016-09-07 07:34:41 -0700422int VoEBaseImpl::CreateChannel(const ChannelConfig& config) {
tommi31fc21f2016-01-21 10:37:37 -0800423 rtc::CritScope cs(shared_->crit_sec());
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200424 if (!shared_->statistics().Initialized()) {
425 shared_->SetLastError(VE_NOT_INITED, kTraceError);
426 return -1;
turaj@webrtc.org03f33702013-11-13 00:02:48 +0000427 }
solenberg88499ec2016-09-07 07:34:41 -0700428
429 ChannelConfig config_copy(config);
430 config_copy.acm_config.decoder_factory = decoder_factory_;
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200431 voe::ChannelOwner channel_owner =
solenberg88499ec2016-09-07 07:34:41 -0700432 shared_->channel_manager().CreateChannel(config_copy);
turaj@webrtc.org03f33702013-11-13 00:02:48 +0000433 return InitializeChannel(&channel_owner);
434}
435
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200436int VoEBaseImpl::InitializeChannel(voe::ChannelOwner* channel_owner) {
437 if (channel_owner->channel()->SetEngineInformation(
438 shared_->statistics(), *shared_->output_mixer(),
solenberg796b8f92017-03-01 17:02:23 -0800439 *shared_->process_thread(), *shared_->audio_device(),
henrikaec6fbd22017-03-31 05:43:36 -0700440 voiceEngineObserverPtr_, &callbackCritSect_,
441 shared_->encoder_queue()) != 0) {
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200442 shared_->SetLastError(
443 VE_CHANNEL_NOT_CREATED, kTraceError,
444 "CreateChannel() failed to associate engine and channel."
445 " Destroying channel.");
446 shared_->channel_manager().DestroyChannel(
447 channel_owner->channel()->ChannelId());
448 return -1;
449 } else if (channel_owner->channel()->Init() != 0) {
450 shared_->SetLastError(
451 VE_CHANNEL_NOT_CREATED, kTraceError,
452 "CreateChannel() failed to initialize channel. Destroying"
453 " channel.");
454 shared_->channel_manager().DestroyChannel(
455 channel_owner->channel()->ChannelId());
456 return -1;
457 }
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200458 return channel_owner->channel()->ChannelId();
niklase@google.com470e71d2011-07-07 08:21:25 +0000459}
460
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200461int VoEBaseImpl::DeleteChannel(int channel) {
tommi31fc21f2016-01-21 10:37:37 -0800462 rtc::CritScope cs(shared_->crit_sec());
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200463 if (!shared_->statistics().Initialized()) {
464 shared_->SetLastError(VE_NOT_INITED, kTraceError);
465 return -1;
466 }
467
468 {
469 voe::ChannelOwner ch = shared_->channel_manager().GetChannel(channel);
470 voe::Channel* channelPtr = ch.channel();
471 if (channelPtr == nullptr) {
472 shared_->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
473 "DeleteChannel() failed to locate channel");
474 return -1;
niklase@google.com470e71d2011-07-07 08:21:25 +0000475 }
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200476 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000477
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200478 shared_->channel_manager().DestroyChannel(channel);
479 if (StopSend() != 0) {
480 return -1;
481 }
482 if (StopPlayout() != 0) {
483 return -1;
484 }
485 return 0;
486}
niklase@google.com470e71d2011-07-07 08:21:25 +0000487
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200488int VoEBaseImpl::StartReceive(int channel) {
tommi31fc21f2016-01-21 10:37:37 -0800489 rtc::CritScope cs(shared_->crit_sec());
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200490 if (!shared_->statistics().Initialized()) {
491 shared_->SetLastError(VE_NOT_INITED, kTraceError);
492 return -1;
493 }
494 voe::ChannelOwner ch = shared_->channel_manager().GetChannel(channel);
495 voe::Channel* channelPtr = ch.channel();
496 if (channelPtr == nullptr) {
497 shared_->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
498 "StartReceive() failed to locate channel");
499 return -1;
500 }
solenberge566ac72016-10-31 12:52:33 -0700501 return 0;
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200502}
niklase@google.com470e71d2011-07-07 08:21:25 +0000503
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200504int VoEBaseImpl::StartPlayout(int channel) {
tommi31fc21f2016-01-21 10:37:37 -0800505 rtc::CritScope cs(shared_->crit_sec());
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200506 if (!shared_->statistics().Initialized()) {
507 shared_->SetLastError(VE_NOT_INITED, kTraceError);
508 return -1;
509 }
510 voe::ChannelOwner ch = shared_->channel_manager().GetChannel(channel);
511 voe::Channel* channelPtr = ch.channel();
512 if (channelPtr == nullptr) {
513 shared_->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
514 "StartPlayout() failed to locate channel");
515 return -1;
516 }
517 if (channelPtr->Playing()) {
niklase@google.com470e71d2011-07-07 08:21:25 +0000518 return 0;
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200519 }
520 if (StartPlayout() != 0) {
521 shared_->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceError,
522 "StartPlayout() failed to start playout");
523 return -1;
524 }
525 return channelPtr->StartPlayout();
niklase@google.com470e71d2011-07-07 08:21:25 +0000526}
527
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200528int VoEBaseImpl::StopPlayout(int channel) {
tommi31fc21f2016-01-21 10:37:37 -0800529 rtc::CritScope cs(shared_->crit_sec());
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200530 if (!shared_->statistics().Initialized()) {
531 shared_->SetLastError(VE_NOT_INITED, kTraceError);
532 return -1;
533 }
534 voe::ChannelOwner ch = shared_->channel_manager().GetChannel(channel);
535 voe::Channel* channelPtr = ch.channel();
536 if (channelPtr == nullptr) {
537 shared_->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
538 "StopPlayout() failed to locate channel");
539 return -1;
540 }
541 if (channelPtr->StopPlayout() != 0) {
542 LOG_F(LS_WARNING) << "StopPlayout() failed to stop playout for channel "
543 << channel;
544 }
545 return StopPlayout();
niklase@google.com470e71d2011-07-07 08:21:25 +0000546}
547
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200548int VoEBaseImpl::StartSend(int channel) {
tommi31fc21f2016-01-21 10:37:37 -0800549 rtc::CritScope cs(shared_->crit_sec());
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200550 if (!shared_->statistics().Initialized()) {
551 shared_->SetLastError(VE_NOT_INITED, kTraceError);
552 return -1;
553 }
554 voe::ChannelOwner ch = shared_->channel_manager().GetChannel(channel);
555 voe::Channel* channelPtr = ch.channel();
556 if (channelPtr == nullptr) {
557 shared_->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
558 "StartSend() failed to locate channel");
559 return -1;
560 }
561 if (channelPtr->Sending()) {
562 return 0;
563 }
564 if (StartSend() != 0) {
565 shared_->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceError,
566 "StartSend() failed to start recording");
567 return -1;
568 }
569 return channelPtr->StartSend();
niklase@google.com470e71d2011-07-07 08:21:25 +0000570}
571
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200572int VoEBaseImpl::StopSend(int channel) {
tommi31fc21f2016-01-21 10:37:37 -0800573 rtc::CritScope cs(shared_->crit_sec());
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200574 if (!shared_->statistics().Initialized()) {
575 shared_->SetLastError(VE_NOT_INITED, kTraceError);
576 return -1;
577 }
578 voe::ChannelOwner ch = shared_->channel_manager().GetChannel(channel);
579 voe::Channel* channelPtr = ch.channel();
580 if (channelPtr == nullptr) {
581 shared_->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
582 "StopSend() failed to locate channel");
583 return -1;
584 }
henrikaec6fbd22017-03-31 05:43:36 -0700585 channelPtr->StopSend();
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200586 return StopSend();
niklase@google.com470e71d2011-07-07 08:21:25 +0000587}
588
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200589int VoEBaseImpl::GetVersion(char version[1024]) {
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200590 if (version == nullptr) {
591 shared_->SetLastError(VE_INVALID_ARGUMENT, kTraceError);
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200592 return -1;
593 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000594
solenberg2515af22015-12-02 06:19:36 -0800595 std::string versionString = VoiceEngine::GetVersionString();
kwibergaf476c72016-11-28 15:21:39 -0800596 RTC_DCHECK_GT(1024, versionString.size() + 1);
solenberg2515af22015-12-02 06:19:36 -0800597 char* end = std::copy(versionString.cbegin(), versionString.cend(), version);
598 end[0] = '\n';
599 end[1] = '\0';
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200600 return 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000601}
602
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200603int VoEBaseImpl::LastError() { return (shared_->statistics().LastError()); }
niklase@google.com470e71d2011-07-07 08:21:25 +0000604
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200605int32_t VoEBaseImpl::StartPlayout() {
solenberge313e022015-09-08 02:16:04 -0700606 if (!shared_->audio_device()->Playing()) {
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200607 if (shared_->audio_device()->InitPlayout() != 0) {
608 LOG_F(LS_ERROR) << "Failed to initialize playout";
609 return -1;
610 }
611 if (shared_->audio_device()->StartPlayout() != 0) {
612 LOG_F(LS_ERROR) << "Failed to start playout";
613 return -1;
614 }
615 }
616 return 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000617}
618
pbos@webrtc.org676ff1e2013-08-07 17:57:36 +0000619int32_t VoEBaseImpl::StopPlayout() {
pbos@webrtc.org676ff1e2013-08-07 17:57:36 +0000620 // Stop audio-device playing if no channel is playing out
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200621 if (shared_->NumOfPlayingChannels() == 0) {
622 if (shared_->audio_device()->StopPlayout() != 0) {
623 shared_->SetLastError(VE_CANNOT_STOP_PLAYOUT, kTraceError,
pbos@webrtc.org676ff1e2013-08-07 17:57:36 +0000624 "StopPlayout() failed to stop playout");
625 return -1;
niklase@google.com470e71d2011-07-07 08:21:25 +0000626 }
pbos@webrtc.org676ff1e2013-08-07 17:57:36 +0000627 }
628 return 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000629}
630
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200631int32_t VoEBaseImpl::StartSend() {
solenbergd53a3f92016-04-14 13:56:37 -0700632 if (!shared_->audio_device()->RecordingIsInitialized() &&
633 !shared_->audio_device()->Recording()) {
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200634 if (shared_->audio_device()->InitRecording() != 0) {
635 LOG_F(LS_ERROR) << "Failed to initialize recording";
636 return -1;
637 }
solenbergd53a3f92016-04-14 13:56:37 -0700638 }
639 if (!shared_->audio_device()->Recording()) {
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200640 if (shared_->audio_device()->StartRecording() != 0) {
641 LOG_F(LS_ERROR) << "Failed to start recording";
642 return -1;
643 }
644 }
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200645 return 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000646}
647
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200648int32_t VoEBaseImpl::StopSend() {
649 if (shared_->NumOfSendingChannels() == 0 &&
650 !shared_->transmit_mixer()->IsRecordingMic()) {
651 // Stop audio-device recording if no channel is recording
652 if (shared_->audio_device()->StopRecording() != 0) {
653 shared_->SetLastError(VE_CANNOT_STOP_RECORDING, kTraceError,
654 "StopSend() failed to stop recording");
655 return -1;
niklase@google.com470e71d2011-07-07 08:21:25 +0000656 }
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200657 shared_->transmit_mixer()->StopSend();
658 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000659
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200660 return 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000661}
662
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200663int32_t VoEBaseImpl::TerminateInternal() {
664 // Delete any remaining channel objects
665 shared_->channel_manager().DestroyAllChannels();
niklase@google.com470e71d2011-07-07 08:21:25 +0000666
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200667 if (shared_->process_thread()) {
668 if (shared_->audio_device()) {
669 shared_->process_thread()->DeRegisterModule(shared_->audio_device());
niklase@google.com470e71d2011-07-07 08:21:25 +0000670 }
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200671 shared_->process_thread()->Stop();
672 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000673
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200674 if (shared_->audio_device()) {
675 if (shared_->audio_device()->StopPlayout() != 0) {
676 shared_->SetLastError(VE_SOUNDCARD_ERROR, kTraceWarning,
677 "TerminateInternal() failed to stop playout");
niklase@google.com470e71d2011-07-07 08:21:25 +0000678 }
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200679 if (shared_->audio_device()->StopRecording() != 0) {
680 shared_->SetLastError(VE_SOUNDCARD_ERROR, kTraceWarning,
681 "TerminateInternal() failed to stop recording");
niklase@google.com470e71d2011-07-07 08:21:25 +0000682 }
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200683 if (shared_->audio_device()->RegisterEventObserver(nullptr) != 0) {
684 shared_->SetLastError(
685 VE_AUDIO_DEVICE_MODULE_ERROR, kTraceWarning,
686 "TerminateInternal() failed to de-register event observer "
687 "for the ADM");
688 }
689 if (shared_->audio_device()->RegisterAudioCallback(nullptr) != 0) {
690 shared_->SetLastError(
691 VE_AUDIO_DEVICE_MODULE_ERROR, kTraceWarning,
692 "TerminateInternal() failed to de-register audio callback "
693 "for the ADM");
694 }
695 if (shared_->audio_device()->Terminate() != 0) {
696 shared_->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceError,
697 "TerminateInternal() failed to terminate the ADM");
698 }
699 shared_->set_audio_device(nullptr);
700 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000701
peaha9cc40b2017-06-29 08:32:09 -0700702 shared_->set_audio_processing(nullptr);
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200703
704 return shared_->statistics().SetUnInitialized();
niklase@google.com470e71d2011-07-07 08:21:25 +0000705}
706
Peter Kasting69558702016-01-12 16:26:35 -0800707void VoEBaseImpl::GetPlayoutData(int sample_rate, size_t number_of_channels,
Peter Kastingdce40cf2015-08-24 14:52:23 -0700708 size_t number_of_frames, bool feed_data_to_apm,
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200709 void* audio_data, int64_t* elapsed_time_ms,
wu@webrtc.orgcb711f72014-05-19 17:39:11 +0000710 int64_t* ntp_time_ms) {
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200711 assert(shared_->output_mixer() != nullptr);
xians@webrtc.org56925312014-04-14 10:50:37 +0000712
713 // TODO(andrew): if the device is running in mono, we should tell the mixer
714 // here so that it will only request mono from AudioCodingModule.
715 // Perform mixing of all active participants (channel-based mixing)
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200716 shared_->output_mixer()->MixActiveChannels();
xians@webrtc.org56925312014-04-14 10:50:37 +0000717
718 // Additional operations on the combined signal
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200719 shared_->output_mixer()->DoOperationsOnCombinedSignal(feed_data_to_apm);
xians@webrtc.org56925312014-04-14 10:50:37 +0000720
721 // Retrieve the final output mix (resampled to match the ADM)
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200722 shared_->output_mixer()->GetMixedAudio(sample_rate, number_of_channels,
723 &audioFrame_);
xians@webrtc.org56925312014-04-14 10:50:37 +0000724
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200725 assert(number_of_frames == audioFrame_.samples_per_channel_);
726 assert(sample_rate == audioFrame_.sample_rate_hz_);
xians@webrtc.org56925312014-04-14 10:50:37 +0000727
728 // Deliver audio (PCM) samples to the ADM
yujo36b1a5f2017-06-12 12:45:32 -0700729 memcpy(audio_data, audioFrame_.data(),
xians@webrtc.org56925312014-04-14 10:50:37 +0000730 sizeof(int16_t) * number_of_frames * number_of_channels);
wu@webrtc.orgcb711f72014-05-19 17:39:11 +0000731
Jelena Marusic2dd6a272015-04-14 09:47:00 +0200732 *elapsed_time_ms = audioFrame_.elapsed_time_ms_;
733 *ntp_time_ms = audioFrame_.ntp_time_ms_;
xians@webrtc.org56925312014-04-14 10:50:37 +0000734}
735
Minyue2013aec2015-05-13 14:14:42 +0200736int VoEBaseImpl::AssociateSendChannel(int channel,
737 int accociate_send_channel) {
tommi31fc21f2016-01-21 10:37:37 -0800738 rtc::CritScope cs(shared_->crit_sec());
Minyue2013aec2015-05-13 14:14:42 +0200739
740 if (!shared_->statistics().Initialized()) {
741 shared_->SetLastError(VE_NOT_INITED, kTraceError);
742 return -1;
743 }
744
745 voe::ChannelOwner ch = shared_->channel_manager().GetChannel(channel);
746 voe::Channel* channel_ptr = ch.channel();
747 if (channel_ptr == NULL) {
748 shared_->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
749 "AssociateSendChannel() failed to locate channel");
750 return -1;
751 }
752
753 ch = shared_->channel_manager().GetChannel(accociate_send_channel);
754 voe::Channel* accociate_send_channel_ptr = ch.channel();
755 if (accociate_send_channel_ptr == NULL) {
756 shared_->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
757 "AssociateSendChannel() failed to locate accociate_send_channel");
758 return -1;
759 }
760
761 channel_ptr->set_associate_send_channel(ch);
762 return 0;
763}
764
pbos@webrtc.orgd900e8b2013-07-03 15:12:26 +0000765} // namespace webrtc