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

Side by Side Diff: content/renderer/media/webrtc_audio_capturer.cc

Issue 11783059: Ensures that WebRTC works for device selection using a different sample rate than default (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Improved comments Created 7 years, 11 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "content/renderer/media/webrtc_audio_capturer.h" 5 #include "content/renderer/media/webrtc_audio_capturer.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/logging.h" 8 #include "base/logging.h"
9 #include "base/metrics/histogram.h" 9 #include "base/metrics/histogram.h"
10 #include "base/string_util.h" 10 #include "base/string_util.h"
(...skipping 26 matching lines...) Expand all
37 int buffer_size = 0; 37 int buffer_size = 0;
38 #if defined(OS_WIN) || defined(OS_MACOSX) 38 #if defined(OS_WIN) || defined(OS_MACOSX)
39 // Use different buffer sizes depending on the current hardware sample rate. 39 // Use different buffer sizes depending on the current hardware sample rate.
40 if (sample_rate == 44100) { 40 if (sample_rate == 44100) {
41 // We do run at 44.1kHz at the actual audio layer, but ask for frames 41 // We do run at 44.1kHz at the actual audio layer, but ask for frames
42 // at 44.0kHz to ensure that we can feed them to the webrtc::VoiceEngine. 42 // at 44.0kHz to ensure that we can feed them to the webrtc::VoiceEngine.
43 buffer_size = 440; 43 buffer_size = 440;
44 } else { 44 } else {
45 buffer_size = (sample_rate / 100); 45 buffer_size = (sample_rate / 100);
46 DCHECK_EQ(buffer_size * 100, sample_rate) << 46 DCHECK_EQ(buffer_size * 100, sample_rate) <<
47 "Sample rate not supported. Should have been caught in Init()."; 47 "Sample rate not supported";
48 } 48 }
49 #elif defined(OS_LINUX) || defined(OS_OPENBSD) 49 #elif defined(OS_LINUX) || defined(OS_OPENBSD)
50 // Based on tests using the current ALSA implementation in Chrome, we have 50 // Based on tests using the current ALSA implementation in Chrome, we have
51 // found that the best combination is 20ms on the input side and 10ms on the 51 // found that the best combination is 20ms on the input side and 10ms on the
52 // output side. 52 // output side.
53 // TODO(henrika): It might be possible to reduce the input buffer 53 // TODO(henrika): It might be possible to reduce the input buffer
54 // size and reduce the delay even more. 54 // size and reduce the delay even more.
55 buffer_size = 2 * sample_rate / 100; 55 buffer_size = 2 * sample_rate / 100;
56 #endif 56 #endif
57 57
58 return buffer_size; 58 return buffer_size;
59 } 59 }
60 60
61 // static 61 // static
62 scoped_refptr<WebRtcAudioCapturer> WebRtcAudioCapturer::CreateCapturer() { 62 scoped_refptr<WebRtcAudioCapturer> WebRtcAudioCapturer::CreateCapturer() {
63 scoped_refptr<WebRtcAudioCapturer> capturer = new WebRtcAudioCapturer(); 63 scoped_refptr<WebRtcAudioCapturer> capturer = new WebRtcAudioCapturer();
64 if (capturer->Initialize()) 64 return capturer;
65 return capturer; 65 }
66 66
67 return NULL; 67 bool WebRtcAudioCapturer::Initialize(media::ChannelLayout channel_layout,
68 int sample_rate) {
69 DCHECK(thread_checker_.CalledOnValidThread());
70 DCHECK(!sinks_.empty());
71 DVLOG(1) << "WebRtcAudioCapturer::Initialize()";
72
73 media::AudioParameters::Format format =
74 media::AudioParameters::AUDIO_PCM_LOW_LATENCY;
75
76 DVLOG(1) << "Audio input hardware channel layout: " << channel_layout;
77 UMA_HISTOGRAM_ENUMERATION("WebRTC.AudioInputChannelLayout",
78 channel_layout, media::CHANNEL_LAYOUT_MAX);
79
80 DVLOG(1) << "Audio input hardware sample rate: " << sample_rate;
81 UMA_HISTOGRAM_ENUMERATION("WebRTC.AudioInputSampleRate",
82 sample_rate, media::kUnexpectedAudioSampleRate);
83
84 // Verify that the reported input hardware sample rate is supported
85 // on the current platform.
86 if (std::find(&kValidInputRates[0],
87 &kValidInputRates[0] + arraysize(kValidInputRates),
88 sample_rate) ==
89 &kValidInputRates[arraysize(kValidInputRates)]) {
90 DLOG(ERROR) << sample_rate << " is not a supported input rate.";
91 return false;
Chris Evans 2013/01/23 09:39:04 I see that sample_rate is carefully validated in t
henrika (OOO until Aug 14) 2013/01/23 10:25:40 Good point. Will improve before committing. Thanks
92 }
93
94 int buffer_size = GetBufferSizeForSampleRate(sample_rate);
95
96 // Configure audio parameters for the default source.
97 params_.Reset(format, channel_layout, sample_rate, 16, buffer_size);
98
99 // Tell all sinks which format we use.
100 for (SinkList::const_iterator it = sinks_.begin();
101 it != sinks_.end(); ++it) {
102 (*it)->SetCaptureFormat(params_);
103 }
104
105 buffer_.reset(new int16[params_.frames_per_buffer() * params_.channels()]);
106
107 // Create and configure the default audio capturing source. The |source_|
108 // will be overwritten if an external client later calls SetCapturerSource()
109 // providing an alternaive media::AudioCapturerSource.
110 SetCapturerSource(AudioDeviceFactory::NewInputDevice(),
111 channel_layout,
112 static_cast<float>(sample_rate));
113
114 return true;
68 } 115 }
69 116
70 WebRtcAudioCapturer::WebRtcAudioCapturer() 117 WebRtcAudioCapturer::WebRtcAudioCapturer()
71 : source_(NULL), 118 : source_(NULL),
72 running_(false), 119 running_(false),
73 buffering_(false) { 120 buffering_(false) {
121 DVLOG(1) << "WebRtcAudioCapturer::WebRtcAudioCapturer()";
74 } 122 }
75 123
76 WebRtcAudioCapturer::~WebRtcAudioCapturer() { 124 WebRtcAudioCapturer::~WebRtcAudioCapturer() {
125 DCHECK(thread_checker_.CalledOnValidThread());
77 DCHECK(sinks_.empty()); 126 DCHECK(sinks_.empty());
78 DCHECK(!loopback_fifo_); 127 DCHECK(!loopback_fifo_);
128 DVLOG(1) << "WebRtcAudioCapturer::~WebRtcAudioCapturer()";
79 } 129 }
80 130
81 void WebRtcAudioCapturer::AddCapturerSink(WebRtcAudioCapturerSink* sink) { 131 void WebRtcAudioCapturer::AddCapturerSink(WebRtcAudioCapturerSink* sink) {
82 { 132 DCHECK(thread_checker_.CalledOnValidThread());
83 base::AutoLock auto_lock(lock_); 133 DVLOG(1) << "WebRtcAudioCapturer::AddCapturerSink()";
84 DCHECK(std::find(sinks_.begin(), sinks_.end(), sink) == sinks_.end()); 134 base::AutoLock auto_lock(lock_);
85 sinks_.push_back(sink); 135 DCHECK(std::find(sinks_.begin(), sinks_.end(), sink) == sinks_.end());
86 } 136 sinks_.push_back(sink);
87
88 // Tell the |sink| which format we use.
89 sink->SetCaptureFormat(params_);
90 } 137 }
91 138
92 void WebRtcAudioCapturer::RemoveCapturerSink(WebRtcAudioCapturerSink* sink) { 139 void WebRtcAudioCapturer::RemoveCapturerSink(WebRtcAudioCapturerSink* sink) {
140 DCHECK(thread_checker_.CalledOnValidThread());
141 DVLOG(1) << "WebRtcAudioCapturer::RemoveCapturerSink()";
93 base::AutoLock auto_lock(lock_); 142 base::AutoLock auto_lock(lock_);
94 for (SinkList::iterator it = sinks_.begin(); it != sinks_.end(); ++it) { 143 for (SinkList::iterator it = sinks_.begin(); it != sinks_.end(); ++it) {
95 if (sink == *it) { 144 if (sink == *it) {
96 sinks_.erase(it); 145 sinks_.erase(it);
97 break; 146 break;
98 } 147 }
99 } 148 }
100 } 149 }
101 150
102 void WebRtcAudioCapturer::SetCapturerSource( 151 void WebRtcAudioCapturer::SetCapturerSource(
103 const scoped_refptr<media::AudioCapturerSource>& source, 152 const scoped_refptr<media::AudioCapturerSource>& source,
104 media::ChannelLayout channel_layout, 153 media::ChannelLayout channel_layout,
105 float sample_rate) { 154 float sample_rate) {
106 DVLOG(1) << "SetCapturerSource()"; 155 DVLOG(1) << "SetCapturerSource(channel_layout=" << channel_layout << ","
156 << "sample_rate=" << sample_rate << ")";
107 scoped_refptr<media::AudioCapturerSource> old_source; 157 scoped_refptr<media::AudioCapturerSource> old_source;
108 { 158 {
109 base::AutoLock auto_lock(lock_); 159 base::AutoLock auto_lock(lock_);
110 if (source_ == source) 160 if (source_ == source)
111 return; 161 return;
112 162
113 source_.swap(old_source); 163 source_.swap(old_source);
114 source_ = source; 164 source_ = source;
115 } 165 }
116 166
117 // Detach the old source from normal recording. 167 const bool no_default_audio_source_exists = !buffer_.get();
118 if (old_source) { 168
119 old_source->Stop(); 169 // Detach the old source from normal recording or perform first-time
170 // initialization if Initialize() has never been called. For the second
171 // case, the caller is not "taking over an ongoing session" but instead
172 // "taking control over a new session".
173 if (old_source || no_default_audio_source_exists) {
174 DVLOG(1) << "New capture source will now be utilized.";
175 if (old_source)
176 old_source->Stop();
120 177
121 // Dispatch the new parameters both to the sink(s) and to the new source. 178 // Dispatch the new parameters both to the sink(s) and to the new source.
122 // The idea is to get rid of any dependency of the microphone parameters 179 // The idea is to get rid of any dependency of the microphone parameters
123 // which would normally be used by default. 180 // which would normally be used by default.
124 181
125 int buffer_size = GetBufferSizeForSampleRate(sample_rate); 182 int buffer_size = GetBufferSizeForSampleRate(sample_rate);
126 if (!buffer_size) { 183 if (!buffer_size) {
127 DLOG(ERROR) << "Unsupported sample-rate: " << sample_rate; 184 DLOG(ERROR) << "Unsupported sample-rate: " << sample_rate;
128 return; 185 return;
129 } 186 }
(...skipping 11 matching lines...) Expand all
141 (*it)->SetCaptureFormat(params_); 198 (*it)->SetCaptureFormat(params_);
142 } 199 }
143 } 200 }
144 201
145 if (source) 202 if (source)
146 source->Initialize(params_, this, this); 203 source->Initialize(params_, this, this);
147 } 204 }
148 205
149 void WebRtcAudioCapturer::SetStopCallback( 206 void WebRtcAudioCapturer::SetStopCallback(
150 const base::Closure& on_device_stopped_cb) { 207 const base::Closure& on_device_stopped_cb) {
208 DCHECK(thread_checker_.CalledOnValidThread());
151 DVLOG(1) << "WebRtcAudioCapturer::SetStopCallback()"; 209 DVLOG(1) << "WebRtcAudioCapturer::SetStopCallback()";
152 base::AutoLock auto_lock(lock_); 210 base::AutoLock auto_lock(lock_);
153 on_device_stopped_cb_ = on_device_stopped_cb; 211 on_device_stopped_cb_ = on_device_stopped_cb;
154 } 212 }
155 213
156 void WebRtcAudioCapturer::PrepareLoopback() { 214 void WebRtcAudioCapturer::PrepareLoopback() {
215 DCHECK(thread_checker_.CalledOnValidThread());
157 DVLOG(1) << "WebRtcAudioCapturer::PrepareLoopback()"; 216 DVLOG(1) << "WebRtcAudioCapturer::PrepareLoopback()";
158 base::AutoLock auto_lock(lock_); 217 base::AutoLock auto_lock(lock_);
159 DCHECK(!loopback_fifo_); 218 DCHECK(!loopback_fifo_);
160 219
161 // TODO(henrika): we could add a more dynamic solution here but I prefer 220 // TODO(henrika): we could add a more dynamic solution here but I prefer
162 // a fixed size combined with bad audio at overflow. The alternative is 221 // a fixed size combined with bad audio at overflow. The alternative is
163 // that we start to build up latency and that can be more difficult to 222 // that we start to build up latency and that can be more difficult to
164 // detect. Tests have shown that the FIFO never contains more than 2 or 3 223 // detect. Tests have shown that the FIFO never contains more than 2 or 3
165 // audio frames but I have selected a max size of ten buffers just 224 // audio frames but I have selected a max size of ten buffers just
166 // in case since these tests were performed on a 16 core, 64GB Win 7 225 // in case since these tests were performed on a 16 core, 64GB Win 7
167 // machine. We could also add some sort of error notifier in this area if 226 // machine. We could also add some sort of error notifier in this area if
168 // the FIFO overflows. 227 // the FIFO overflows.
169 loopback_fifo_.reset(new media::AudioFifo(params_.channels(), 228 loopback_fifo_.reset(new media::AudioFifo(params_.channels(),
170 10 * params_.frames_per_buffer())); 229 10 * params_.frames_per_buffer()));
171 buffering_ = true; 230 buffering_ = true;
172 } 231 }
173 232
174 void WebRtcAudioCapturer::CancelLoopback() { 233 void WebRtcAudioCapturer::CancelLoopback() {
234 DCHECK(thread_checker_.CalledOnValidThread());
175 DVLOG(1) << "WebRtcAudioCapturer::CancelLoopback()"; 235 DVLOG(1) << "WebRtcAudioCapturer::CancelLoopback()";
176 base::AutoLock auto_lock(lock_); 236 base::AutoLock auto_lock(lock_);
177 buffering_ = false; 237 buffering_ = false;
178 if (loopback_fifo_.get() != NULL) { 238 if (loopback_fifo_.get() != NULL) {
179 loopback_fifo_->Clear(); 239 loopback_fifo_->Clear();
180 loopback_fifo_.reset(); 240 loopback_fifo_.reset();
181 } 241 }
182 } 242 }
183 243
184 void WebRtcAudioCapturer::PauseBuffering() { 244 void WebRtcAudioCapturer::PauseBuffering() {
245 DCHECK(thread_checker_.CalledOnValidThread());
185 DVLOG(1) << "WebRtcAudioCapturer::PauseBuffering()"; 246 DVLOG(1) << "WebRtcAudioCapturer::PauseBuffering()";
186 base::AutoLock auto_lock(lock_); 247 base::AutoLock auto_lock(lock_);
187 buffering_ = false; 248 buffering_ = false;
188 } 249 }
189 250
190 void WebRtcAudioCapturer::ResumeBuffering() { 251 void WebRtcAudioCapturer::ResumeBuffering() {
252 DCHECK(thread_checker_.CalledOnValidThread());
191 DVLOG(1) << "WebRtcAudioCapturer::ResumeBuffering()"; 253 DVLOG(1) << "WebRtcAudioCapturer::ResumeBuffering()";
192 base::AutoLock auto_lock(lock_); 254 base::AutoLock auto_lock(lock_);
193 if (buffering_) 255 if (buffering_)
194 return; 256 return;
195 if (loopback_fifo_.get() != NULL) 257 if (loopback_fifo_.get() != NULL)
196 loopback_fifo_->Clear(); 258 loopback_fifo_->Clear();
197 buffering_ = true; 259 buffering_ = true;
198 } 260 }
199 261
200 bool WebRtcAudioCapturer::Initialize() {
201 DVLOG(1) << "WebRtcAudioCapturer::Initialize()";
202 // Ask the browser for the default audio input hardware sample-rate.
203 // This request is based on a synchronous IPC message.
204 // TODO(xians): we should ask for the native sample rate of a specific device.
205 int sample_rate = GetAudioInputSampleRate();
206 DVLOG(1) << "Audio input hardware sample rate: " << sample_rate;
207 UMA_HISTOGRAM_ENUMERATION("WebRTC.AudioInputSampleRate",
208 sample_rate, media::kUnexpectedAudioSampleRate);
209
210 // Verify that the reported input hardware sample rate is supported
211 // on the current platform.
212 if (std::find(&kValidInputRates[0],
213 &kValidInputRates[0] + arraysize(kValidInputRates),
214 sample_rate) ==
215 &kValidInputRates[arraysize(kValidInputRates)]) {
216 DLOG(ERROR) << sample_rate << " is not a supported input rate.";
217 return false;
218 }
219
220 // Ask the browser for the default number of audio input channels.
221 // This request is based on a synchronous IPC message.
222 // TODO(xians): we should ask for the layout of a specific device.
223 media::ChannelLayout channel_layout = GetAudioInputChannelLayout();
224 DVLOG(1) << "Audio input hardware channels: " << channel_layout;
225
226 media::AudioParameters::Format format =
227 media::AudioParameters::AUDIO_PCM_LOW_LATENCY;
228 int buffer_size = GetBufferSizeForSampleRate(sample_rate);
229 if (!buffer_size) {
230 DLOG(ERROR) << "Unsupported platform";
231 return false;
232 }
233
234 params_.Reset(format, channel_layout, sample_rate, 16, buffer_size);
235
236 buffer_.reset(new int16[params_.frames_per_buffer() * params_.channels()]);
237
238 // Create and configure the default audio capturing source. The |source_|
239 // will be overwritten if the client call the source calls
240 // SetCapturerSource().
241 SetCapturerSource(
242 AudioDeviceFactory::NewInputDevice(), channel_layout, sample_rate);
243
244 UMA_HISTOGRAM_ENUMERATION("WebRTC.AudioInputChannelLayout",
245 channel_layout, media::CHANNEL_LAYOUT_MAX);
246
247 return true;
248 }
249
250 void WebRtcAudioCapturer::ProvideInput(media::AudioBus* dest) { 262 void WebRtcAudioCapturer::ProvideInput(media::AudioBus* dest) {
251 base::AutoLock auto_lock(lock_); 263 base::AutoLock auto_lock(lock_);
252 DCHECK(loopback_fifo_.get() != NULL); 264 DCHECK(loopback_fifo_.get() != NULL);
253 265
254 if (!running_) { 266 if (!running_) {
255 dest->Zero(); 267 dest->Zero();
256 return; 268 return;
257 } 269 }
258 270
259 // Provide data by reading from the FIFO if the FIFO contains enough 271 // Provide data by reading from the FIFO if the FIFO contains enough
260 // to fulfill the request. 272 // to fulfill the request.
261 if (loopback_fifo_->frames() >= dest->frames()) { 273 if (loopback_fifo_->frames() >= dest->frames()) {
262 loopback_fifo_->Consume(dest, 0, dest->frames()); 274 loopback_fifo_->Consume(dest, 0, dest->frames());
263 } else { 275 } else {
264 dest->Zero(); 276 dest->Zero();
265 // This warning is perfectly safe if it happens for the first audio 277 // This warning is perfectly safe if it happens for the first audio
266 // frames. It should not happen in a steady-state mode. 278 // frames. It should not happen in a steady-state mode.
267 DLOG(WARNING) << "WARNING: loopback FIFO is empty."; 279 DVLOG(2) << "WARNING: loopback FIFO is empty.";
268 } 280 }
269 } 281 }
270 282
271 void WebRtcAudioCapturer::Start() { 283 void WebRtcAudioCapturer::Start() {
272 DVLOG(1) << "WebRtcAudioCapturer::Start()"; 284 DVLOG(1) << "WebRtcAudioCapturer::Start()";
273 base::AutoLock auto_lock(lock_); 285 base::AutoLock auto_lock(lock_);
274 if (running_) 286 if (running_)
275 return; 287 return;
276 288
277 // What Start() does is supposed to be very light, for example, posting a 289 // What Start() does is supposed to be very light, for example, posting a
(...skipping 23 matching lines...) Expand all
301 running_ = false; 313 running_ = false;
302 } 314 }
303 315
304 if (source) 316 if (source)
305 source->Stop(); 317 source->Stop();
306 } 318 }
307 319
308 void WebRtcAudioCapturer::SetVolume(double volume) { 320 void WebRtcAudioCapturer::SetVolume(double volume) {
309 DVLOG(1) << "WebRtcAudioCapturer::SetVolume()"; 321 DVLOG(1) << "WebRtcAudioCapturer::SetVolume()";
310 base::AutoLock auto_lock(lock_); 322 base::AutoLock auto_lock(lock_);
311
312 if (source_) 323 if (source_)
313 source_->SetVolume(volume); 324 source_->SetVolume(volume);
314 } 325 }
315 326
316 void WebRtcAudioCapturer::SetDevice(int session_id) { 327 void WebRtcAudioCapturer::SetDevice(int session_id) {
328 DCHECK(thread_checker_.CalledOnValidThread());
317 DVLOG(1) << "WebRtcAudioCapturer::SetDevice(" << session_id << ")"; 329 DVLOG(1) << "WebRtcAudioCapturer::SetDevice(" << session_id << ")";
318 base::AutoLock auto_lock(lock_); 330 base::AutoLock auto_lock(lock_);
319 if (source_) 331 if (source_)
320 source_->SetDevice(session_id); 332 source_->SetDevice(session_id);
321 } 333 }
322 334
323 void WebRtcAudioCapturer::SetAutomaticGainControl(bool enable) { 335 void WebRtcAudioCapturer::SetAutomaticGainControl(bool enable) {
324 base::AutoLock auto_lock(lock_); 336 base::AutoLock auto_lock(lock_);
325 if (source_) 337 if (source_)
326 source_->SetAutomaticGainControl(enable); 338 source_->SetAutomaticGainControl(enable);
327 } 339 }
328 340
329 bool WebRtcAudioCapturer::IsInLoopbackMode() { 341 bool WebRtcAudioCapturer::IsInLoopbackMode() {
342 DCHECK(thread_checker_.CalledOnValidThread());
330 base::AutoLock auto_lock(lock_); 343 base::AutoLock auto_lock(lock_);
331 return (loopback_fifo_ != NULL); 344 return (loopback_fifo_ != NULL);
332 } 345 }
333 346
334 void WebRtcAudioCapturer::Capture(media::AudioBus* audio_source, 347 void WebRtcAudioCapturer::Capture(media::AudioBus* audio_source,
335 int audio_delay_milliseconds, 348 int audio_delay_milliseconds,
336 double volume) { 349 double volume) {
337 // This callback is driven by AudioInputDevice::AudioThreadCallback if 350 // This callback is driven by AudioInputDevice::AudioThreadCallback if
338 // |source_| is AudioInputDevice, otherwise it is driven by client's 351 // |source_| is AudioInputDevice, otherwise it is driven by client's
339 // CaptureCallback. 352 // CaptureCallback.
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
397 // Inform the local renderer about the stopped device. 410 // Inform the local renderer about the stopped device.
398 // The renderer can then save resources by not asking for more data from 411 // The renderer can then save resources by not asking for more data from
399 // the stopped source. We are on the IO thread but the callback task will 412 // the stopped source. We are on the IO thread but the callback task will
400 // be posted on the message loop of the main render thread thanks to 413 // be posted on the message loop of the main render thread thanks to
401 // usage of BindToLoop() when the callback was initialized. 414 // usage of BindToLoop() when the callback was initialized.
402 if (!on_device_stopped_cb_.is_null()) 415 if (!on_device_stopped_cb_.is_null())
403 on_device_stopped_cb_.Run(); 416 on_device_stopped_cb_.Run();
404 } 417 }
405 418
406 } // namespace content 419 } // namespace content
OLDNEW
« no previous file with comments | « content/renderer/media/webrtc_audio_capturer.h ('k') | content/renderer/media/webrtc_audio_device_impl.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698