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

Side by Side Diff: webrtc/modules/audio_processing/intelligibility/intelligibility_enhancer.cc

Issue 1685703004: Fix and simplify the power estimation in the IntelligibilityEnhancer (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@ie
Patch Set: Re-add the intelligibility namespace Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. 2 * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
3 * 3 *
4 * Use of this source code is governed by a BSD-style license 4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source 5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found 6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may 7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree. 8 * be found in the AUTHORS file in the root of the source tree.
9 */ 9 */
10 10
11 //
12 // Implements core class for intelligibility enhancer.
13 //
14 // Details of the model and algorithm can be found in the original paper:
15 // http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=6882788
16 //
17
18 #include "webrtc/modules/audio_processing/intelligibility/intelligibility_enhanc er.h" 11 #include "webrtc/modules/audio_processing/intelligibility/intelligibility_enhanc er.h"
19 12
20 #include <math.h> 13 #include <math.h>
21 #include <stdlib.h> 14 #include <stdlib.h>
22 #include <algorithm> 15 #include <algorithm>
23 #include <limits> 16 #include <limits>
24 #include <numeric> 17 #include <numeric>
25 18
26 #include "webrtc/base/checks.h" 19 #include "webrtc/base/checks.h"
27 #include "webrtc/common_audio/include/audio_util.h" 20 #include "webrtc/common_audio/include/audio_util.h"
28 #include "webrtc/common_audio/window_generator.h" 21 #include "webrtc/common_audio/window_generator.h"
29 22
30 namespace webrtc { 23 namespace webrtc {
31 24
32 namespace { 25 namespace {
33 26
34 const size_t kErbResolution = 2; 27 const size_t kErbResolution = 2;
35 const int kWindowSizeMs = 2; 28 const int kWindowSizeMs = 16;
36 const int kChunkSizeMs = 10; // Size provided by APM. 29 const int kChunkSizeMs = 10; // Size provided by APM.
37 const float kClipFreq = 200.0f; 30 const float kClipFreq = 200.0f;
38 const float kConfigRho = 0.02f; // Default production and interpretation SNR. 31 const float kConfigRho = 0.02f; // Default production and interpretation SNR.
39 const float kKbdAlpha = 1.5f; 32 const float kKbdAlpha = 1.5f;
40 const float kLambdaBot = -1.0f; // Extreme values in bisection 33 const float kLambdaBot = -1.0f; // Extreme values in bisection
41 const float kLambdaTop = -10e-18f; // search for lamda. 34 const float kLambdaTop = -10e-18f; // search for lamda.
42 35
43 // Returns dot product of vectors |a| and |b| with size |length|. 36 // Returns dot product of vectors |a| and |b| with size |length|.
44 float DotProduct(const float* a, const float* b, size_t length) { 37 float DotProduct(const float* a, const float* b, size_t length) {
45 float ret = 0.f; 38 float ret = 0.f;
46 for (size_t i = 0; i < length; ++i) { 39 for (size_t i = 0; i < length; ++i) {
47 ret = fmaf(a[i], b[i], ret); 40 ret = fmaf(a[i], b[i], ret);
48 } 41 }
49 return ret; 42 return ret;
50 } 43 }
51 44
52 // Computes the power across ERB filters from the power spectral density |var|. 45 // Computes the power across ERB bands from the power spectral density |pow|.
53 // Stores it in |result|. 46 // Stores it in |result|.
54 void FilterVariance(const float* var, 47 void MapToErbBands(const float* pow,
55 const std::vector<std::vector<float>>& filter_bank, 48 const std::vector<std::vector<float>>& filter_bank,
56 float* result) { 49 float* result) {
57 for (size_t i = 0; i < filter_bank.size(); ++i) { 50 for (size_t i = 0; i < filter_bank.size(); ++i) {
58 RTC_DCHECK_GT(filter_bank[i].size(), 0u); 51 RTC_DCHECK_GT(filter_bank[i].size(), 0u);
59 result[i] = DotProduct(&filter_bank[i][0], var, filter_bank[i].size()); 52 result[i] = DotProduct(&filter_bank[i][0], pow, filter_bank[i].size());
60 } 53 }
61 } 54 }
62 55
63 } // namespace 56 } // namespace
64 57
65 using std::complex;
66 using std::max;
67 using std::min;
68 using VarianceType = intelligibility::VarianceArray::StepType;
69
70 IntelligibilityEnhancer::TransformCallback::TransformCallback( 58 IntelligibilityEnhancer::TransformCallback::TransformCallback(
71 IntelligibilityEnhancer* parent) 59 IntelligibilityEnhancer* parent)
72 : parent_(parent) { 60 : parent_(parent) {
73 } 61 }
74 62
75 void IntelligibilityEnhancer::TransformCallback::ProcessAudioBlock( 63 void IntelligibilityEnhancer::TransformCallback::ProcessAudioBlock(
76 const complex<float>* const* in_block, 64 const std::complex<float>* const* in_block,
77 size_t in_channels, 65 size_t in_channels,
78 size_t frames, 66 size_t frames,
79 size_t /* out_channels */, 67 size_t /* out_channels */,
80 complex<float>* const* out_block) { 68 std::complex<float>* const* out_block) {
81 RTC_DCHECK_EQ(parent_->freqs_, frames); 69 RTC_DCHECK_EQ(parent_->freqs_, frames);
82 for (size_t i = 0; i < in_channels; ++i) { 70 for (size_t i = 0; i < in_channels; ++i) {
83 parent_->ProcessClearBlock(in_block[i], out_block[i]); 71 parent_->ProcessClearBlock(in_block[i], out_block[i]);
84 } 72 }
85 } 73 }
86 74
87 IntelligibilityEnhancer::IntelligibilityEnhancer() 75 IntelligibilityEnhancer::IntelligibilityEnhancer()
88 : IntelligibilityEnhancer(IntelligibilityEnhancer::Config()) { 76 : IntelligibilityEnhancer(IntelligibilityEnhancer::Config()) {
89 } 77 }
90 78
91 IntelligibilityEnhancer::IntelligibilityEnhancer(const Config& config) 79 IntelligibilityEnhancer::IntelligibilityEnhancer(const Config& config)
92 : freqs_(RealFourier::ComplexLength( 80 : freqs_(RealFourier::ComplexLength(
93 RealFourier::FftOrder(config.sample_rate_hz * kWindowSizeMs / 1000))), 81 RealFourier::FftOrder(config.sample_rate_hz * kWindowSizeMs / 1000))),
94 window_size_(static_cast<size_t>(1 << RealFourier::FftOrder(freqs_))), 82 window_size_(static_cast<size_t>(1 << RealFourier::FftOrder(freqs_))),
95 chunk_length_( 83 chunk_length_(
96 static_cast<size_t>(config.sample_rate_hz * kChunkSizeMs / 1000)), 84 static_cast<size_t>(config.sample_rate_hz * kChunkSizeMs / 1000)),
97 bank_size_(GetBankSize(config.sample_rate_hz, kErbResolution)), 85 bank_size_(GetBankSize(config.sample_rate_hz, kErbResolution)),
98 sample_rate_hz_(config.sample_rate_hz), 86 sample_rate_hz_(config.sample_rate_hz),
99 erb_resolution_(kErbResolution), 87 erb_resolution_(kErbResolution),
100 num_capture_channels_(config.num_capture_channels), 88 num_capture_channels_(config.num_capture_channels),
101 num_render_channels_(config.num_render_channels), 89 num_render_channels_(config.num_render_channels),
102 analysis_rate_(config.analysis_rate), 90 analysis_rate_(config.analysis_rate),
103 active_(true), 91 active_(true),
104 clear_variance_(freqs_, 92 clear_power_(freqs_, config.decay_rate),
105 config.var_type,
106 config.var_window_size,
107 config.var_decay_rate),
108 noise_power_(freqs_, 0.f), 93 noise_power_(freqs_, 0.f),
109 filtered_clear_var_(new float[bank_size_]), 94 filtered_clear_pow_(new float[bank_size_]),
110 filtered_noise_var_(new float[bank_size_]), 95 filtered_noise_pow_(new float[bank_size_]),
111 center_freqs_(new float[bank_size_]), 96 center_freqs_(new float[bank_size_]),
112 render_filter_bank_(CreateErbBank(freqs_)), 97 render_filter_bank_(CreateErbBank(freqs_)),
113 rho_(new float[bank_size_]), 98 rho_(new float[bank_size_]),
114 gains_eq_(new float[bank_size_]), 99 gains_eq_(new float[bank_size_]),
115 gain_applier_(freqs_, config.gain_change_limit), 100 gain_applier_(freqs_, config.gain_change_limit),
116 temp_render_out_buffer_(chunk_length_, num_render_channels_), 101 temp_render_out_buffer_(chunk_length_, num_render_channels_),
117 kbd_window_(new float[window_size_]), 102 kbd_window_(new float[window_size_]),
118 render_callback_(this), 103 render_callback_(this),
119 block_count_(0), 104 block_count_(0),
120 analysis_step_(0) { 105 analysis_step_(0) {
121 RTC_DCHECK_LE(config.rho, 1.0f); 106 RTC_DCHECK_LE(config.rho, 1.0f);
122 107
123 memset(filtered_clear_var_.get(), 108 memset(filtered_clear_pow_.get(),
124 0, 109 0,
125 bank_size_ * sizeof(filtered_clear_var_[0])); 110 bank_size_ * sizeof(filtered_clear_pow_[0]));
126 memset(filtered_noise_var_.get(), 111 memset(filtered_noise_pow_.get(),
127 0, 112 0,
128 bank_size_ * sizeof(filtered_noise_var_[0])); 113 bank_size_ * sizeof(filtered_noise_pow_[0]));
129 114
130 // Assumes all rho equal. 115 // Assumes all rho equal.
131 for (size_t i = 0; i < bank_size_; ++i) { 116 for (size_t i = 0; i < bank_size_; ++i) {
132 rho_[i] = config.rho * config.rho; 117 rho_[i] = config.rho * config.rho;
133 } 118 }
134 119
135 float freqs_khz = kClipFreq / 1000.0f; 120 float freqs_khz = kClipFreq / 1000.0f;
136 size_t erb_index = static_cast<size_t>(ceilf( 121 size_t erb_index = static_cast<size_t>(ceilf(
137 11.17f * logf((freqs_khz + 0.312f) / (freqs_khz + 14.6575f)) + 43.0f)); 122 11.17f * logf((freqs_khz + 0.312f) / (freqs_khz + 14.6575f)) + 43.0f));
138 start_freq_ = std::max(static_cast<size_t>(1), erb_index * erb_resolution_); 123 start_freq_ = std::max(static_cast<size_t>(1), erb_index * erb_resolution_);
(...skipping 30 matching lines...) Expand all
169 } 154 }
170 155
171 if (active_) { 156 if (active_) {
172 for (size_t i = 0; i < num_render_channels_; ++i) { 157 for (size_t i = 0; i < num_render_channels_; ++i) {
173 memcpy(audio[i], temp_render_out_buffer_.channels()[i], 158 memcpy(audio[i], temp_render_out_buffer_.channels()[i],
174 chunk_length_ * sizeof(**audio)); 159 chunk_length_ * sizeof(**audio));
175 } 160 }
176 } 161 }
177 } 162 }
178 163
179 void IntelligibilityEnhancer::ProcessClearBlock(const complex<float>* in_block, 164 void IntelligibilityEnhancer::ProcessClearBlock(
180 complex<float>* out_block) { 165 const std::complex<float>* in_block,
166 std::complex<float>* out_block) {
181 if (block_count_ < 2) { 167 if (block_count_ < 2) {
182 memset(out_block, 0, freqs_ * sizeof(*out_block)); 168 memset(out_block, 0, freqs_ * sizeof(*out_block));
183 ++block_count_; 169 ++block_count_;
184 return; 170 return;
185 } 171 }
186 172
187 // TODO(ekm): Use VAD to |Step| and |AnalyzeClearBlock| only if necessary. 173 // TODO(ekm): Use VAD to |Step| and |AnalyzeClearBlock| only if necessary.
188 if (true) { 174 if (true) {
189 clear_variance_.Step(in_block, false); 175 clear_power_.Step(in_block);
190 if (block_count_ % analysis_rate_ == analysis_rate_ - 1) { 176 if (block_count_ % analysis_rate_ == analysis_rate_ - 1) {
191 const float power_target = std::accumulate( 177 AnalyzeClearBlock();
192 clear_variance_.variance(), clear_variance_.variance() + freqs_, 0.f);
193 AnalyzeClearBlock(power_target);
194 ++analysis_step_; 178 ++analysis_step_;
195 } 179 }
196 ++block_count_; 180 ++block_count_;
197 } 181 }
198 182
199 if (active_) { 183 if (active_) {
200 gain_applier_.Apply(in_block, out_block); 184 gain_applier_.Apply(in_block, out_block);
201 } 185 }
202 } 186 }
203 187
204 void IntelligibilityEnhancer::AnalyzeClearBlock(float power_target) { 188 void IntelligibilityEnhancer::AnalyzeClearBlock() {
205 FilterVariance(clear_variance_.variance(), 189 const float* clear_power = clear_power_.Power();
206 render_filter_bank_, 190 MapToErbBands(clear_power,
207 filtered_clear_var_.get()); 191 render_filter_bank_,
208 FilterVariance(&noise_power_[0], 192 filtered_clear_pow_.get());
209 capture_filter_bank_, 193 MapToErbBands(&noise_power_[0],
210 filtered_noise_var_.get()); 194 capture_filter_bank_,
195 filtered_noise_pow_.get());
211 SolveForGainsGivenLambda(kLambdaTop, start_freq_, gains_eq_.get()); 196 SolveForGainsGivenLambda(kLambdaTop, start_freq_, gains_eq_.get());
197 const float power_target = std::accumulate(
198 clear_power, clear_power + freqs_, 0.f);
212 const float power_top = 199 const float power_top =
213 DotProduct(gains_eq_.get(), filtered_clear_var_.get(), bank_size_); 200 DotProduct(gains_eq_.get(), filtered_clear_pow_.get(), bank_size_);
214 SolveForGainsGivenLambda(kLambdaBot, start_freq_, gains_eq_.get()); 201 SolveForGainsGivenLambda(kLambdaBot, start_freq_, gains_eq_.get());
215 const float power_bot = 202 const float power_bot =
216 DotProduct(gains_eq_.get(), filtered_clear_var_.get(), bank_size_); 203 DotProduct(gains_eq_.get(), filtered_clear_pow_.get(), bank_size_);
217 if (power_target >= power_bot && power_target <= power_top) { 204 if (power_target >= power_bot && power_target <= power_top) {
218 SolveForLambda(power_target, power_bot, power_top); 205 SolveForLambda(power_target, power_bot, power_top);
219 UpdateErbGains(); 206 UpdateErbGains();
220 } // Else experiencing variance underflow, so do nothing. 207 } // Else experiencing power underflow, so do nothing.
221 } 208 }
222 209
223 void IntelligibilityEnhancer::SolveForLambda(float power_target, 210 void IntelligibilityEnhancer::SolveForLambda(float power_target,
224 float power_bot, 211 float power_bot,
225 float power_top) { 212 float power_top) {
226 const float kConvergeThresh = 0.001f; // TODO(ekmeyerson): Find best values 213 const float kConvergeThresh = 0.001f; // TODO(ekmeyerson): Find best values
227 const int kMaxIters = 100; // for these, based on experiments. 214 const int kMaxIters = 100; // for these, based on experiments.
228 215
229 const float reciprocal_power_target = 216 const float reciprocal_power_target =
230 1.f / (power_target + std::numeric_limits<float>::epsilon()); 217 1.f / (power_target + std::numeric_limits<float>::epsilon());
231 float lambda_bot = kLambdaBot; 218 float lambda_bot = kLambdaBot;
232 float lambda_top = kLambdaTop; 219 float lambda_top = kLambdaTop;
233 float power_ratio = 2.0f; // Ratio of achieved power to target power. 220 float power_ratio = 2.0f; // Ratio of achieved power to target power.
234 int iters = 0; 221 int iters = 0;
235 while (std::fabs(power_ratio - 1.0f) > kConvergeThresh && 222 while (std::fabs(power_ratio - 1.0f) > kConvergeThresh &&
236 iters <= kMaxIters) { 223 iters <= kMaxIters) {
237 const float lambda = lambda_bot + (lambda_top - lambda_bot) / 2.0f; 224 const float lambda = lambda_bot + (lambda_top - lambda_bot) / 2.0f;
238 SolveForGainsGivenLambda(lambda, start_freq_, gains_eq_.get()); 225 SolveForGainsGivenLambda(lambda, start_freq_, gains_eq_.get());
239 const float power = 226 const float power =
240 DotProduct(gains_eq_.get(), filtered_clear_var_.get(), bank_size_); 227 DotProduct(gains_eq_.get(), filtered_clear_pow_.get(), bank_size_);
241 if (power < power_target) { 228 if (power < power_target) {
242 lambda_bot = lambda; 229 lambda_bot = lambda;
243 } else { 230 } else {
244 lambda_top = lambda; 231 lambda_top = lambda;
245 } 232 }
246 power_ratio = std::fabs(power * reciprocal_power_target); 233 power_ratio = std::fabs(power * reciprocal_power_target);
247 ++iters; 234 ++iters;
248 } 235 }
249 } 236 }
250 237
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
283 } 270 }
284 271
285 for (size_t i = 0; i < bank_size_; ++i) { 272 for (size_t i = 0; i < bank_size_; ++i) {
286 filter_bank[i].resize(num_freqs); 273 filter_bank[i].resize(num_freqs);
287 } 274 }
288 275
289 for (size_t i = 1; i <= bank_size_; ++i) { 276 for (size_t i = 1; i <= bank_size_; ++i) {
290 size_t lll, ll, rr, rrr; 277 size_t lll, ll, rr, rrr;
291 static const size_t kOne = 1; // Avoids repeated static_cast<>s below. 278 static const size_t kOne = 1; // Avoids repeated static_cast<>s below.
292 lll = static_cast<size_t>(round( 279 lll = static_cast<size_t>(round(
293 center_freqs_[max(kOne, i - lf) - 1] * num_freqs / 280 center_freqs_[std::max(kOne, i - lf) - 1] * num_freqs /
294 (0.5f * sample_rate_hz_))); 281 (0.5f * sample_rate_hz_)));
295 ll = static_cast<size_t>(round( 282 ll = static_cast<size_t>(round(
296 center_freqs_[max(kOne, i) - 1] * num_freqs / 283 center_freqs_[std::max(kOne, i) - 1] * num_freqs /
297 (0.5f * sample_rate_hz_))); 284 (0.5f * sample_rate_hz_)));
298 lll = min(num_freqs, max(lll, kOne)) - 1; 285 lll = std::min(num_freqs, std::max(lll, kOne)) - 1;
299 ll = min(num_freqs, max(ll, kOne)) - 1; 286 ll = std::min(num_freqs, std::max(ll, kOne)) - 1;
300 287
301 rrr = static_cast<size_t>(round( 288 rrr = static_cast<size_t>(round(
302 center_freqs_[min(bank_size_, i + rf) - 1] * num_freqs / 289 center_freqs_[std::min(bank_size_, i + rf) - 1] * num_freqs /
303 (0.5f * sample_rate_hz_))); 290 (0.5f * sample_rate_hz_)));
304 rr = static_cast<size_t>(round( 291 rr = static_cast<size_t>(round(
305 center_freqs_[min(bank_size_, i + 1) - 1] * num_freqs / 292 center_freqs_[std::min(bank_size_, i + 1) - 1] * num_freqs /
306 (0.5f * sample_rate_hz_))); 293 (0.5f * sample_rate_hz_)));
307 rrr = min(num_freqs, max(rrr, kOne)) - 1; 294 rrr = std::min(num_freqs, std::max(rrr, kOne)) - 1;
308 rr = min(num_freqs, max(rr, kOne)) - 1; 295 rr = std::min(num_freqs, std::max(rr, kOne)) - 1;
309 296
310 float step, element; 297 float step, element;
311 298
312 step = ll == lll ? 0.f : 1.f / (ll - lll); 299 step = ll == lll ? 0.f : 1.f / (ll - lll);
313 element = 0.0f; 300 element = 0.0f;
314 for (size_t j = lll; j <= ll; ++j) { 301 for (size_t j = lll; j <= ll; ++j) {
315 filter_bank[i - 1][j] = element; 302 filter_bank[i - 1][j] = element;
316 element += step; 303 element += step;
317 } 304 }
318 step = rr == rrr ? 0.f : 1.f / (rrr - rr); 305 step = rr == rrr ? 0.f : 1.f / (rrr - rr);
(...skipping 17 matching lines...) Expand all
336 filter_bank[j][i] /= sum; 323 filter_bank[j][i] /= sum;
337 } 324 }
338 } 325 }
339 return filter_bank; 326 return filter_bank;
340 } 327 }
341 328
342 void IntelligibilityEnhancer::SolveForGainsGivenLambda(float lambda, 329 void IntelligibilityEnhancer::SolveForGainsGivenLambda(float lambda,
343 size_t start_freq, 330 size_t start_freq,
344 float* sols) { 331 float* sols) {
345 bool quadratic = (kConfigRho < 1.0f); 332 bool quadratic = (kConfigRho < 1.0f);
346 const float* var_x0 = filtered_clear_var_.get(); 333 const float* pow_x0 = filtered_clear_pow_.get();
347 const float* var_n0 = filtered_noise_var_.get(); 334 const float* pow_n0 = filtered_noise_pow_.get();
348 335
349 for (size_t n = 0; n < start_freq; ++n) { 336 for (size_t n = 0; n < start_freq; ++n) {
350 sols[n] = 1.0f; 337 sols[n] = 1.0f;
351 } 338 }
352 339
353 // Analytic solution for optimal gains. See paper for derivation. 340 // Analytic solution for optimal gains. See paper for derivation.
354 for (size_t n = start_freq - 1; n < bank_size_; ++n) { 341 for (size_t n = start_freq - 1; n < bank_size_; ++n) {
355 float alpha0, beta0, gamma0; 342 float alpha0, beta0, gamma0;
356 gamma0 = 0.5f * rho_[n] * var_x0[n] * var_n0[n] + 343 gamma0 = 0.5f * rho_[n] * pow_x0[n] * pow_n0[n] +
357 lambda * var_x0[n] * var_n0[n] * var_n0[n]; 344 lambda * pow_x0[n] * pow_n0[n] * pow_n0[n];
358 beta0 = lambda * var_x0[n] * (2 - rho_[n]) * var_x0[n] * var_n0[n]; 345 beta0 = lambda * pow_x0[n] * (2 - rho_[n]) * pow_x0[n] * pow_n0[n];
359 if (quadratic) { 346 if (quadratic) {
360 alpha0 = lambda * var_x0[n] * (1 - rho_[n]) * var_x0[n] * var_x0[n]; 347 alpha0 = lambda * pow_x0[n] * (1 - rho_[n]) * pow_x0[n] * pow_x0[n];
361 sols[n] = 348 sols[n] =
362 (-beta0 - sqrtf(beta0 * beta0 - 4 * alpha0 * gamma0)) / 349 (-beta0 - sqrtf(beta0 * beta0 - 4 * alpha0 * gamma0)) /
363 (2 * alpha0 + std::numeric_limits<float>::epsilon()); 350 (2 * alpha0 + std::numeric_limits<float>::epsilon());
364 } else { 351 } else {
365 sols[n] = -gamma0 / beta0; 352 sols[n] = -gamma0 / beta0;
366 } 353 }
367 sols[n] = fmax(0, sols[n]); 354 sols[n] = fmax(0, sols[n]);
368 } 355 }
369 } 356 }
370 357
371 bool IntelligibilityEnhancer::active() const { 358 bool IntelligibilityEnhancer::active() const {
372 return active_; 359 return active_;
373 } 360 }
374 361
375 } // namespace webrtc 362 } // namespace webrtc
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698