blob: 660ac22ac87147c04d2567d572abb2ab714a91fa [file] [log] [blame]
niklase@google.com470e71d2011-07-07 08:21:25 +00001/*
2 * Copyright (c) 2011 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
11#include "audio_processing_impl.h"
12
ajm@google.com808e0e02011-08-03 21:08:51 +000013#include <assert.h>
niklase@google.com470e71d2011-07-07 08:21:25 +000014
15#include "audio_buffer.h"
ajm@google.com808e0e02011-08-03 21:08:51 +000016#include "critical_section_wrapper.h"
niklase@google.com470e71d2011-07-07 08:21:25 +000017#include "echo_cancellation_impl.h"
18#include "echo_control_mobile_impl.h"
ajm@google.com808e0e02011-08-03 21:08:51 +000019#include "file_wrapper.h"
niklase@google.com470e71d2011-07-07 08:21:25 +000020#include "high_pass_filter_impl.h"
21#include "gain_control_impl.h"
22#include "level_estimator_impl.h"
ajm@google.com808e0e02011-08-03 21:08:51 +000023#include "module_common_types.h"
niklase@google.com470e71d2011-07-07 08:21:25 +000024#include "noise_suppression_impl.h"
25#include "processing_component.h"
26#include "splitting_filter.h"
27#include "voice_detection_impl.h"
ajm@google.com808e0e02011-08-03 21:08:51 +000028#include "webrtc/audio_processing/debug.pb.h"
niklase@google.com470e71d2011-07-07 08:21:25 +000029
30namespace webrtc {
niklase@google.com470e71d2011-07-07 08:21:25 +000031AudioProcessing* AudioProcessing::Create(int id) {
32 /*WEBRTC_TRACE(webrtc::kTraceModuleCall,
33 webrtc::kTraceAudioProcessing,
34 id,
35 "AudioProcessing::Create()");*/
36
37 AudioProcessingImpl* apm = new AudioProcessingImpl(id);
38 if (apm->Initialize() != kNoError) {
39 delete apm;
40 apm = NULL;
41 }
42
43 return apm;
44}
45
46void AudioProcessing::Destroy(AudioProcessing* apm) {
47 delete static_cast<AudioProcessingImpl*>(apm);
48}
49
50AudioProcessingImpl::AudioProcessingImpl(int id)
51 : id_(id),
52 echo_cancellation_(NULL),
53 echo_control_mobile_(NULL),
54 gain_control_(NULL),
55 high_pass_filter_(NULL),
56 level_estimator_(NULL),
57 noise_suppression_(NULL),
58 voice_detection_(NULL),
59 debug_file_(FileWrapper::Create()),
ajm@google.com808e0e02011-08-03 21:08:51 +000060 event_msg_(new audioproc::Event()),
niklase@google.com470e71d2011-07-07 08:21:25 +000061 crit_(CriticalSectionWrapper::CreateCriticalSection()),
62 render_audio_(NULL),
63 capture_audio_(NULL),
64 sample_rate_hz_(kSampleRate16kHz),
65 split_sample_rate_hz_(kSampleRate16kHz),
66 samples_per_channel_(sample_rate_hz_ / 100),
67 stream_delay_ms_(0),
68 was_stream_delay_set_(false),
ajm@google.com808e0e02011-08-03 21:08:51 +000069 num_reverse_channels_(1),
70 num_input_channels_(1),
71 num_output_channels_(1) {
niklase@google.com470e71d2011-07-07 08:21:25 +000072
73 echo_cancellation_ = new EchoCancellationImpl(this);
74 component_list_.push_back(echo_cancellation_);
75
76 echo_control_mobile_ = new EchoControlMobileImpl(this);
77 component_list_.push_back(echo_control_mobile_);
78
79 gain_control_ = new GainControlImpl(this);
80 component_list_.push_back(gain_control_);
81
82 high_pass_filter_ = new HighPassFilterImpl(this);
83 component_list_.push_back(high_pass_filter_);
84
85 level_estimator_ = new LevelEstimatorImpl(this);
86 component_list_.push_back(level_estimator_);
87
88 noise_suppression_ = new NoiseSuppressionImpl(this);
89 component_list_.push_back(noise_suppression_);
90
91 voice_detection_ = new VoiceDetectionImpl(this);
92 component_list_.push_back(voice_detection_);
93}
94
95AudioProcessingImpl::~AudioProcessingImpl() {
96 while (!component_list_.empty()) {
97 ProcessingComponent* component = component_list_.front();
98 component->Destroy();
99 delete component;
100 component_list_.pop_front();
101 }
102
103 if (debug_file_->Open()) {
104 debug_file_->CloseFile();
105 }
106 delete debug_file_;
107 debug_file_ = NULL;
108
ajm@google.com808e0e02011-08-03 21:08:51 +0000109 delete event_msg_;
110 event_msg_ = NULL;
111
niklase@google.com470e71d2011-07-07 08:21:25 +0000112 delete crit_;
113 crit_ = NULL;
114
ajm@google.com808e0e02011-08-03 21:08:51 +0000115 if (render_audio_) {
niklase@google.com470e71d2011-07-07 08:21:25 +0000116 delete render_audio_;
117 render_audio_ = NULL;
118 }
119
ajm@google.com808e0e02011-08-03 21:08:51 +0000120 if (capture_audio_) {
niklase@google.com470e71d2011-07-07 08:21:25 +0000121 delete capture_audio_;
122 capture_audio_ = NULL;
123 }
124}
125
126CriticalSectionWrapper* AudioProcessingImpl::crit() const {
127 return crit_;
128}
129
130int AudioProcessingImpl::split_sample_rate_hz() const {
131 return split_sample_rate_hz_;
132}
133
134int AudioProcessingImpl::Initialize() {
135 CriticalSectionScoped crit_scoped(*crit_);
136 return InitializeLocked();
137}
138
139int AudioProcessingImpl::InitializeLocked() {
140 if (render_audio_ != NULL) {
141 delete render_audio_;
142 render_audio_ = NULL;
143 }
144
145 if (capture_audio_ != NULL) {
146 delete capture_audio_;
147 capture_audio_ = NULL;
148 }
149
ajm@google.com808e0e02011-08-03 21:08:51 +0000150 render_audio_ = new AudioBuffer(num_reverse_channels_,
niklase@google.com470e71d2011-07-07 08:21:25 +0000151 samples_per_channel_);
ajm@google.com808e0e02011-08-03 21:08:51 +0000152 capture_audio_ = new AudioBuffer(num_input_channels_,
niklase@google.com470e71d2011-07-07 08:21:25 +0000153 samples_per_channel_);
154
155 was_stream_delay_set_ = false;
156
157 // Initialize all components.
158 std::list<ProcessingComponent*>::iterator it;
159 for (it = component_list_.begin(); it != component_list_.end(); it++) {
160 int err = (*it)->Initialize();
161 if (err != kNoError) {
162 return err;
163 }
164 }
165
ajm@google.com808e0e02011-08-03 21:08:51 +0000166 if (debug_file_->Open()) {
167 int err = WriteInitMessage();
168 if (err != kNoError) {
169 return err;
170 }
171 }
172
niklase@google.com470e71d2011-07-07 08:21:25 +0000173 return kNoError;
174}
175
176int AudioProcessingImpl::set_sample_rate_hz(int rate) {
177 CriticalSectionScoped crit_scoped(*crit_);
178 if (rate != kSampleRate8kHz &&
179 rate != kSampleRate16kHz &&
180 rate != kSampleRate32kHz) {
181 return kBadParameterError;
182 }
183
184 sample_rate_hz_ = rate;
185 samples_per_channel_ = rate / 100;
186
187 if (sample_rate_hz_ == kSampleRate32kHz) {
188 split_sample_rate_hz_ = kSampleRate16kHz;
189 } else {
190 split_sample_rate_hz_ = sample_rate_hz_;
191 }
192
193 return InitializeLocked();
194}
195
196int AudioProcessingImpl::sample_rate_hz() const {
197 return sample_rate_hz_;
198}
199
200int AudioProcessingImpl::set_num_reverse_channels(int channels) {
201 CriticalSectionScoped crit_scoped(*crit_);
202 // Only stereo supported currently.
203 if (channels > 2 || channels < 1) {
204 return kBadParameterError;
205 }
206
ajm@google.com808e0e02011-08-03 21:08:51 +0000207 num_reverse_channels_ = channels;
niklase@google.com470e71d2011-07-07 08:21:25 +0000208
209 return InitializeLocked();
210}
211
212int AudioProcessingImpl::num_reverse_channels() const {
ajm@google.com808e0e02011-08-03 21:08:51 +0000213 return num_reverse_channels_;
niklase@google.com470e71d2011-07-07 08:21:25 +0000214}
215
216int AudioProcessingImpl::set_num_channels(
217 int input_channels,
218 int output_channels) {
219 CriticalSectionScoped crit_scoped(*crit_);
220 if (output_channels > input_channels) {
221 return kBadParameterError;
222 }
223
224 // Only stereo supported currently.
225 if (input_channels > 2 || input_channels < 1) {
226 return kBadParameterError;
227 }
228
229 if (output_channels > 2 || output_channels < 1) {
230 return kBadParameterError;
231 }
232
ajm@google.com808e0e02011-08-03 21:08:51 +0000233 num_input_channels_ = input_channels;
234 num_output_channels_ = output_channels;
niklase@google.com470e71d2011-07-07 08:21:25 +0000235
236 return InitializeLocked();
237}
238
239int AudioProcessingImpl::num_input_channels() const {
ajm@google.com808e0e02011-08-03 21:08:51 +0000240 return num_input_channels_;
niklase@google.com470e71d2011-07-07 08:21:25 +0000241}
242
243int AudioProcessingImpl::num_output_channels() const {
ajm@google.com808e0e02011-08-03 21:08:51 +0000244 return num_output_channels_;
niklase@google.com470e71d2011-07-07 08:21:25 +0000245}
246
247int AudioProcessingImpl::ProcessStream(AudioFrame* frame) {
248 CriticalSectionScoped crit_scoped(*crit_);
249 int err = kNoError;
250
251 if (frame == NULL) {
252 return kNullPointerError;
253 }
254
255 if (frame->_frequencyInHz !=
256 static_cast<WebRtc_UWord32>(sample_rate_hz_)) {
257 return kBadSampleRateError;
258 }
259
ajm@google.com808e0e02011-08-03 21:08:51 +0000260 if (frame->_audioChannel != num_input_channels_) {
niklase@google.com470e71d2011-07-07 08:21:25 +0000261 return kBadNumberChannelsError;
262 }
263
264 if (frame->_payloadDataLengthInSamples != samples_per_channel_) {
265 return kBadDataLengthError;
266 }
267
268 if (debug_file_->Open()) {
ajm@google.com808e0e02011-08-03 21:08:51 +0000269 event_msg_->set_type(audioproc::Event::STREAM);
270 audioproc::Stream* msg = event_msg_->mutable_stream();
271 const size_t data_size = sizeof(WebRtc_Word16) *
272 frame->_payloadDataLengthInSamples *
273 frame->_audioChannel;
274 msg->set_input_data(frame->_payloadData, data_size);
275 msg->set_delay(stream_delay_ms_);
276 msg->set_drift(echo_cancellation_->stream_drift_samples());
277 msg->set_level(gain_control_->stream_analog_level());
niklase@google.com470e71d2011-07-07 08:21:25 +0000278 }
279
280 capture_audio_->DeinterleaveFrom(frame);
281
282 // TODO(ajm): experiment with mixing and AEC placement.
ajm@google.com808e0e02011-08-03 21:08:51 +0000283 if (num_output_channels_ < num_input_channels_) {
284 capture_audio_->Mix(num_output_channels_);
niklase@google.com470e71d2011-07-07 08:21:25 +0000285
ajm@google.com808e0e02011-08-03 21:08:51 +0000286 frame->_audioChannel = num_output_channels_;
niklase@google.com470e71d2011-07-07 08:21:25 +0000287 }
288
289 if (sample_rate_hz_ == kSampleRate32kHz) {
ajm@google.com808e0e02011-08-03 21:08:51 +0000290 for (int i = 0; i < num_input_channels_; i++) {
niklase@google.com470e71d2011-07-07 08:21:25 +0000291 // Split into a low and high band.
292 SplittingFilterAnalysis(capture_audio_->data(i),
293 capture_audio_->low_pass_split_data(i),
294 capture_audio_->high_pass_split_data(i),
295 capture_audio_->analysis_filter_state1(i),
296 capture_audio_->analysis_filter_state2(i));
297 }
298 }
299
300 err = high_pass_filter_->ProcessCaptureAudio(capture_audio_);
301 if (err != kNoError) {
302 return err;
303 }
304
305 err = gain_control_->AnalyzeCaptureAudio(capture_audio_);
306 if (err != kNoError) {
307 return err;
308 }
309
310 err = echo_cancellation_->ProcessCaptureAudio(capture_audio_);
311 if (err != kNoError) {
312 return err;
313 }
314
315 if (echo_control_mobile_->is_enabled() &&
316 noise_suppression_->is_enabled()) {
317 capture_audio_->CopyLowPassToReference();
318 }
319
320 err = noise_suppression_->ProcessCaptureAudio(capture_audio_);
321 if (err != kNoError) {
322 return err;
323 }
324
325 err = echo_control_mobile_->ProcessCaptureAudio(capture_audio_);
326 if (err != kNoError) {
327 return err;
328 }
329
330 err = voice_detection_->ProcessCaptureAudio(capture_audio_);
331 if (err != kNoError) {
332 return err;
333 }
334
335 err = gain_control_->ProcessCaptureAudio(capture_audio_);
336 if (err != kNoError) {
337 return err;
338 }
339
340 //err = level_estimator_->ProcessCaptureAudio(capture_audio_);
341 //if (err != kNoError) {
342 // return err;
343 //}
344
345 if (sample_rate_hz_ == kSampleRate32kHz) {
ajm@google.com808e0e02011-08-03 21:08:51 +0000346 for (int i = 0; i < num_output_channels_; i++) {
niklase@google.com470e71d2011-07-07 08:21:25 +0000347 // Recombine low and high bands.
348 SplittingFilterSynthesis(capture_audio_->low_pass_split_data(i),
349 capture_audio_->high_pass_split_data(i),
350 capture_audio_->data(i),
351 capture_audio_->synthesis_filter_state1(i),
352 capture_audio_->synthesis_filter_state2(i));
353 }
354 }
355
356 capture_audio_->InterleaveTo(frame);
357
ajm@google.com808e0e02011-08-03 21:08:51 +0000358 if (debug_file_->Open()) {
359 audioproc::Stream* msg = event_msg_->mutable_stream();
360 const size_t data_size = sizeof(WebRtc_Word16) *
361 frame->_payloadDataLengthInSamples *
362 frame->_audioChannel;
363 msg->set_output_data(frame->_payloadData, data_size);
364 err = WriteMessageToDebugFile();
365 if (err != kNoError) {
366 return err;
367 }
368 }
369
niklase@google.com470e71d2011-07-07 08:21:25 +0000370 return kNoError;
371}
372
373int AudioProcessingImpl::AnalyzeReverseStream(AudioFrame* frame) {
374 CriticalSectionScoped crit_scoped(*crit_);
375 int err = kNoError;
376
377 if (frame == NULL) {
378 return kNullPointerError;
379 }
380
381 if (frame->_frequencyInHz !=
382 static_cast<WebRtc_UWord32>(sample_rate_hz_)) {
383 return kBadSampleRateError;
384 }
385
ajm@google.com808e0e02011-08-03 21:08:51 +0000386 if (frame->_audioChannel != num_reverse_channels_) {
niklase@google.com470e71d2011-07-07 08:21:25 +0000387 return kBadNumberChannelsError;
388 }
389
390 if (frame->_payloadDataLengthInSamples != samples_per_channel_) {
391 return kBadDataLengthError;
392 }
393
394 if (debug_file_->Open()) {
ajm@google.com808e0e02011-08-03 21:08:51 +0000395 event_msg_->set_type(audioproc::Event::REVERSE_STREAM);
396 audioproc::ReverseStream* msg = event_msg_->mutable_reverse_stream();
397 const size_t data_size = sizeof(WebRtc_Word16) *
398 frame->_payloadDataLengthInSamples *
399 frame->_audioChannel;
400 msg->set_data(frame->_payloadData, data_size);
401 err = WriteMessageToDebugFile();
402 if (err != kNoError) {
403 return err;
niklase@google.com470e71d2011-07-07 08:21:25 +0000404 }
405 }
406
407 render_audio_->DeinterleaveFrom(frame);
408
409 // TODO(ajm): turn the splitting filter into a component?
410 if (sample_rate_hz_ == kSampleRate32kHz) {
ajm@google.com808e0e02011-08-03 21:08:51 +0000411 for (int i = 0; i < num_reverse_channels_; i++) {
niklase@google.com470e71d2011-07-07 08:21:25 +0000412 // Split into low and high band.
413 SplittingFilterAnalysis(render_audio_->data(i),
414 render_audio_->low_pass_split_data(i),
415 render_audio_->high_pass_split_data(i),
416 render_audio_->analysis_filter_state1(i),
417 render_audio_->analysis_filter_state2(i));
418 }
419 }
420
421 // TODO(ajm): warnings possible from components?
422 err = echo_cancellation_->ProcessRenderAudio(render_audio_);
423 if (err != kNoError) {
424 return err;
425 }
426
427 err = echo_control_mobile_->ProcessRenderAudio(render_audio_);
428 if (err != kNoError) {
429 return err;
430 }
431
432 err = gain_control_->ProcessRenderAudio(render_audio_);
433 if (err != kNoError) {
434 return err;
435 }
436
437 //err = level_estimator_->AnalyzeReverseStream(render_audio_);
438 //if (err != kNoError) {
439 // return err;
440 //}
441
442 was_stream_delay_set_ = false;
443 return err; // TODO(ajm): this is for returning warnings; necessary?
444}
445
446int AudioProcessingImpl::set_stream_delay_ms(int delay) {
447 was_stream_delay_set_ = true;
448 if (delay < 0) {
449 return kBadParameterError;
450 }
451
452 // TODO(ajm): the max is rather arbitrarily chosen; investigate.
453 if (delay > 500) {
454 stream_delay_ms_ = 500;
455 return kBadStreamParameterWarning;
456 }
457
458 stream_delay_ms_ = delay;
459 return kNoError;
460}
461
462int AudioProcessingImpl::stream_delay_ms() const {
463 return stream_delay_ms_;
464}
465
466bool AudioProcessingImpl::was_stream_delay_set() const {
467 return was_stream_delay_set_;
468}
469
470int AudioProcessingImpl::StartDebugRecording(
471 const char filename[AudioProcessing::kMaxFilenameSize]) {
472 CriticalSectionScoped crit_scoped(*crit_);
473 assert(kMaxFilenameSize == FileWrapper::kMaxFileNameSize);
474
475 if (filename == NULL) {
476 return kNullPointerError;
477 }
478
479 // Stop any ongoing recording.
480 if (debug_file_->Open()) {
481 if (debug_file_->CloseFile() == -1) {
482 return kFileError;
483 }
484 }
485
486 if (debug_file_->OpenFile(filename, false) == -1) {
487 debug_file_->CloseFile();
488 return kFileError;
489 }
490
ajm@google.com808e0e02011-08-03 21:08:51 +0000491 int err = WriteInitMessage();
492 if (err != kNoError) {
493 return err;
niklase@google.com470e71d2011-07-07 08:21:25 +0000494 }
495
496 return kNoError;
497}
498
499int AudioProcessingImpl::StopDebugRecording() {
500 CriticalSectionScoped crit_scoped(*crit_);
501 // We just return if recording hasn't started.
502 if (debug_file_->Open()) {
503 if (debug_file_->CloseFile() == -1) {
504 return kFileError;
505 }
506 }
507
508 return kNoError;
509}
510
511EchoCancellation* AudioProcessingImpl::echo_cancellation() const {
512 return echo_cancellation_;
513}
514
515EchoControlMobile* AudioProcessingImpl::echo_control_mobile() const {
516 return echo_control_mobile_;
517}
518
519GainControl* AudioProcessingImpl::gain_control() const {
520 return gain_control_;
521}
522
523HighPassFilter* AudioProcessingImpl::high_pass_filter() const {
524 return high_pass_filter_;
525}
526
527LevelEstimator* AudioProcessingImpl::level_estimator() const {
528 return level_estimator_;
529}
530
531NoiseSuppression* AudioProcessingImpl::noise_suppression() const {
532 return noise_suppression_;
533}
534
535VoiceDetection* AudioProcessingImpl::voice_detection() const {
536 return voice_detection_;
537}
538
539WebRtc_Word32 AudioProcessingImpl::Version(WebRtc_Word8* version,
540 WebRtc_UWord32& bytes_remaining, WebRtc_UWord32& position) const {
541 if (version == NULL) {
542 /*WEBRTC_TRACE(webrtc::kTraceError,
543 webrtc::kTraceAudioProcessing,
544 -1,
545 "Null version pointer");*/
546 return kNullPointerError;
547 }
548 memset(&version[position], 0, bytes_remaining);
549
ajm@google.com808e0e02011-08-03 21:08:51 +0000550 char my_version[] = "AudioProcessing 1.0.0";
niklase@google.com470e71d2011-07-07 08:21:25 +0000551 // Includes null termination.
552 WebRtc_UWord32 length = static_cast<WebRtc_UWord32>(strlen(my_version));
553 if (bytes_remaining < length) {
554 /*WEBRTC_TRACE(webrtc::kTraceError,
555 webrtc::kTraceAudioProcessing,
556 -1,
557 "Buffer of insufficient length");*/
558 return kBadParameterError;
559 }
560 memcpy(&version[position], my_version, length);
561 bytes_remaining -= length;
562 position += length;
563
564 std::list<ProcessingComponent*>::const_iterator it;
565 for (it = component_list_.begin(); it != component_list_.end(); it++) {
566 char component_version[256];
567 strcpy(component_version, "\n");
568 int err = (*it)->get_version(&component_version[1],
569 sizeof(component_version) - 1);
570 if (err != kNoError) {
571 return err;
572 }
573 if (strncmp(&component_version[1], "\0", 1) == 0) {
574 // Assume empty if first byte is NULL.
575 continue;
576 }
577
578 length = static_cast<WebRtc_UWord32>(strlen(component_version));
579 if (bytes_remaining < length) {
580 /*WEBRTC_TRACE(webrtc::kTraceError,
581 webrtc::kTraceAudioProcessing,
582 -1,
583 "Buffer of insufficient length");*/
584 return kBadParameterError;
585 }
586 memcpy(&version[position], component_version, length);
587 bytes_remaining -= length;
588 position += length;
589 }
590
591 return kNoError;
592}
593
594WebRtc_Word32 AudioProcessingImpl::ChangeUniqueId(const WebRtc_Word32 id) {
595 CriticalSectionScoped crit_scoped(*crit_);
596 /*WEBRTC_TRACE(webrtc::kTraceModuleCall,
597 webrtc::kTraceAudioProcessing,
598 id_,
599 "ChangeUniqueId(new id = %d)",
600 id);*/
601 id_ = id;
602
603 return kNoError;
604}
ajm@google.com808e0e02011-08-03 21:08:51 +0000605
606int AudioProcessingImpl::WriteMessageToDebugFile() {
607 int32_t size = event_msg_->ByteSize();
608 if (size <= 0) {
609 return kUnspecifiedError;
610 }
611#if defined(WEBRTC_BIG_ENDIAN)
612 // TODO(ajm): Use little-endian "on the wire". For the moment, we can be
613 // pretty safe in assuming little-endian.
614#endif
615
616 if (!event_msg_->SerializeToString(&event_str_)) {
617 return kUnspecifiedError;
618 }
619
620 // Write message preceded by its size.
621 if (!debug_file_->Write(&size, sizeof(int32_t))) {
622 return kFileError;
623 }
624 if (!debug_file_->Write(event_str_.data(), event_str_.length())) {
625 return kFileError;
626 }
627
628 event_msg_->Clear();
629
630 return 0;
631}
632
633int AudioProcessingImpl::WriteInitMessage() {
634 event_msg_->set_type(audioproc::Event::INIT);
635 audioproc::Init* msg = event_msg_->mutable_init();
636 msg->set_sample_rate(sample_rate_hz_);
637 msg->set_device_sample_rate(echo_cancellation_->device_sample_rate_hz());
638 msg->set_num_input_channels(num_input_channels_);
639 msg->set_num_output_channels(num_output_channels_);
640 msg->set_num_reverse_channels(num_reverse_channels_);
641
642 int err = WriteMessageToDebugFile();
643 if (err != kNoError) {
644 return err;
645 }
646
647 return kNoError;
648}
niklase@google.com470e71d2011-07-07 08:21:25 +0000649} // namespace webrtc