| OLD | NEW |
| 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 Loading... |
| 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; |
| 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 |
| 112 return true; |
| 68 } | 113 } |
| 69 | 114 |
| 70 WebRtcAudioCapturer::WebRtcAudioCapturer() | 115 WebRtcAudioCapturer::WebRtcAudioCapturer() |
| 71 : source_(NULL), | 116 : source_(NULL), |
| 72 running_(false), | 117 running_(false), |
| 73 buffering_(false) { | 118 buffering_(false) { |
| 119 DVLOG(1) << "WebRtcAudioCapturer::WebRtcAudioCapturer()"; |
| 74 } | 120 } |
| 75 | 121 |
| 76 WebRtcAudioCapturer::~WebRtcAudioCapturer() { | 122 WebRtcAudioCapturer::~WebRtcAudioCapturer() { |
| 123 DCHECK(thread_checker_.CalledOnValidThread()); |
| 77 DCHECK(sinks_.empty()); | 124 DCHECK(sinks_.empty()); |
| 78 DCHECK(!loopback_fifo_); | 125 DCHECK(!loopback_fifo_); |
| 126 DVLOG(1) << "WebRtcAudioCapturer::~WebRtcAudioCapturer()"; |
| 79 } | 127 } |
| 80 | 128 |
| 81 void WebRtcAudioCapturer::AddCapturerSink(WebRtcAudioCapturerSink* sink) { | 129 void WebRtcAudioCapturer::AddCapturerSink(WebRtcAudioCapturerSink* sink) { |
| 82 { | 130 DCHECK(thread_checker_.CalledOnValidThread()); |
| 83 base::AutoLock auto_lock(lock_); | 131 DVLOG(1) << "WebRtcAudioCapturer::AddCapturerSink()"; |
| 84 DCHECK(std::find(sinks_.begin(), sinks_.end(), sink) == sinks_.end()); | 132 base::AutoLock auto_lock(lock_); |
| 85 sinks_.push_back(sink); | 133 DCHECK(std::find(sinks_.begin(), sinks_.end(), sink) == sinks_.end()); |
| 86 } | 134 sinks_.push_back(sink); |
| 87 | |
| 88 // Tell the |sink| which format we use. | |
| 89 sink->SetCaptureFormat(params_); | |
| 90 } | 135 } |
| 91 | 136 |
| 92 void WebRtcAudioCapturer::RemoveCapturerSink(WebRtcAudioCapturerSink* sink) { | 137 void WebRtcAudioCapturer::RemoveCapturerSink(WebRtcAudioCapturerSink* sink) { |
| 138 DCHECK(thread_checker_.CalledOnValidThread()); |
| 139 DVLOG(1) << "WebRtcAudioCapturer::RemoveCapturerSink()"; |
| 93 base::AutoLock auto_lock(lock_); | 140 base::AutoLock auto_lock(lock_); |
| 94 for (SinkList::iterator it = sinks_.begin(); it != sinks_.end(); ++it) { | 141 for (SinkList::iterator it = sinks_.begin(); it != sinks_.end(); ++it) { |
| 95 if (sink == *it) { | 142 if (sink == *it) { |
| 96 sinks_.erase(it); | 143 sinks_.erase(it); |
| 97 break; | 144 break; |
| 98 } | 145 } |
| 99 } | 146 } |
| 100 } | 147 } |
| 101 | 148 |
| 102 void WebRtcAudioCapturer::SetCapturerSource( | 149 void WebRtcAudioCapturer::SetCapturerSource( |
| 103 const scoped_refptr<media::AudioCapturerSource>& source) { | 150 const scoped_refptr<media::AudioCapturerSource>& source) { |
| 151 DCHECK(thread_checker_.CalledOnValidThread()); |
| 104 DVLOG(1) << "SetCapturerSource()"; | 152 DVLOG(1) << "SetCapturerSource()"; |
| 105 scoped_refptr<media::AudioCapturerSource> old_source; | 153 scoped_refptr<media::AudioCapturerSource> old_source; |
| 106 { | 154 { |
| 107 base::AutoLock auto_lock(lock_); | 155 base::AutoLock auto_lock(lock_); |
| 108 if (source_ == source) | 156 if (source_ == source) |
| 109 return; | 157 return; |
| 110 | 158 |
| 111 source_.swap(old_source); | 159 source_.swap(old_source); |
| 112 source_ = source; | 160 source_ = source; |
| 113 } | 161 } |
| 114 | 162 |
| 115 // Detach the old source from normal recording. | 163 // Detach the old source from normal recording. |
| 116 if (old_source) | 164 if (old_source) |
| 117 old_source->Stop(); | 165 old_source->Stop(); |
| 118 | 166 |
| 119 if (source) | 167 if (source) |
| 120 source->Initialize(params_, this, this); | 168 source->Initialize(params_, this, this); |
| 121 } | 169 } |
| 122 | 170 |
| 123 void WebRtcAudioCapturer::SetStopCallback( | 171 void WebRtcAudioCapturer::SetStopCallback( |
| 124 const base::Closure& on_device_stopped_cb) { | 172 const base::Closure& on_device_stopped_cb) { |
| 173 DCHECK(thread_checker_.CalledOnValidThread()); |
| 125 DVLOG(1) << "WebRtcAudioCapturer::SetStopCallback()"; | 174 DVLOG(1) << "WebRtcAudioCapturer::SetStopCallback()"; |
| 126 base::AutoLock auto_lock(lock_); | 175 base::AutoLock auto_lock(lock_); |
| 127 on_device_stopped_cb_ = on_device_stopped_cb; | 176 on_device_stopped_cb_ = on_device_stopped_cb; |
| 128 } | 177 } |
| 129 | 178 |
| 130 void WebRtcAudioCapturer::PrepareLoopback() { | 179 void WebRtcAudioCapturer::PrepareLoopback() { |
| 180 DCHECK(thread_checker_.CalledOnValidThread()); |
| 131 DVLOG(1) << "WebRtcAudioCapturer::PrepareLoopback()"; | 181 DVLOG(1) << "WebRtcAudioCapturer::PrepareLoopback()"; |
| 132 base::AutoLock auto_lock(lock_); | 182 base::AutoLock auto_lock(lock_); |
| 133 DCHECK(!loopback_fifo_); | 183 DCHECK(!loopback_fifo_); |
| 134 | 184 |
| 135 // TODO(henrika): we could add a more dynamic solution here but I prefer | 185 // TODO(henrika): we could add a more dynamic solution here but I prefer |
| 136 // a fixed size combined with bad audio at overflow. The alternative is | 186 // a fixed size combined with bad audio at overflow. The alternative is |
| 137 // that we start to build up latency and that can be more difficult to | 187 // that we start to build up latency and that can be more difficult to |
| 138 // detect. Tests have shown that the FIFO never contains more than 2 or 3 | 188 // detect. Tests have shown that the FIFO never contains more than 2 or 3 |
| 139 // audio frames but I have selected a max size of ten buffers just | 189 // audio frames but I have selected a max size of ten buffers just |
| 140 // in case since these tests were performed on a 16 core, 64GB Win 7 | 190 // in case since these tests were performed on a 16 core, 64GB Win 7 |
| 141 // machine. We could also add some sort of error notifier in this area if | 191 // machine. We could also add some sort of error notifier in this area if |
| 142 // the FIFO overflows. | 192 // the FIFO overflows. |
| 143 loopback_fifo_.reset(new media::AudioFifo(params_.channels(), | 193 loopback_fifo_.reset(new media::AudioFifo(params_.channels(), |
| 144 10 * params_.frames_per_buffer())); | 194 10 * params_.frames_per_buffer())); |
| 145 buffering_ = true; | 195 buffering_ = true; |
| 146 } | 196 } |
| 147 | 197 |
| 148 void WebRtcAudioCapturer::CancelLoopback() { | 198 void WebRtcAudioCapturer::CancelLoopback() { |
| 199 DCHECK(thread_checker_.CalledOnValidThread()); |
| 149 DVLOG(1) << "WebRtcAudioCapturer::CancelLoopback()"; | 200 DVLOG(1) << "WebRtcAudioCapturer::CancelLoopback()"; |
| 150 base::AutoLock auto_lock(lock_); | 201 base::AutoLock auto_lock(lock_); |
| 151 buffering_ = false; | 202 buffering_ = false; |
| 152 if (loopback_fifo_.get() != NULL) { | 203 if (loopback_fifo_.get() != NULL) { |
| 153 loopback_fifo_->Clear(); | 204 loopback_fifo_->Clear(); |
| 154 loopback_fifo_.reset(); | 205 loopback_fifo_.reset(); |
| 155 } | 206 } |
| 156 } | 207 } |
| 157 | 208 |
| 158 void WebRtcAudioCapturer::PauseBuffering() { | 209 void WebRtcAudioCapturer::PauseBuffering() { |
| 210 DCHECK(thread_checker_.CalledOnValidThread()); |
| 159 DVLOG(1) << "WebRtcAudioCapturer::PauseBuffering()"; | 211 DVLOG(1) << "WebRtcAudioCapturer::PauseBuffering()"; |
| 160 base::AutoLock auto_lock(lock_); | 212 base::AutoLock auto_lock(lock_); |
| 161 buffering_ = false; | 213 buffering_ = false; |
| 162 } | 214 } |
| 163 | 215 |
| 164 void WebRtcAudioCapturer::ResumeBuffering() { | 216 void WebRtcAudioCapturer::ResumeBuffering() { |
| 217 DCHECK(thread_checker_.CalledOnValidThread()); |
| 165 DVLOG(1) << "WebRtcAudioCapturer::ResumeBuffering()"; | 218 DVLOG(1) << "WebRtcAudioCapturer::ResumeBuffering()"; |
| 166 base::AutoLock auto_lock(lock_); | 219 base::AutoLock auto_lock(lock_); |
| 167 if (buffering_) | 220 if (buffering_) |
| 168 return; | 221 return; |
| 169 if (loopback_fifo_.get() != NULL) | 222 if (loopback_fifo_.get() != NULL) |
| 170 loopback_fifo_->Clear(); | 223 loopback_fifo_->Clear(); |
| 171 buffering_ = true; | 224 buffering_ = true; |
| 172 } | 225 } |
| 173 | 226 |
| 174 bool WebRtcAudioCapturer::Initialize() { | |
| 175 DVLOG(1) << "WebRtcAudioCapturer::Initialize()"; | |
| 176 // Ask the browser for the default audio input hardware sample-rate. | |
| 177 // This request is based on a synchronous IPC message. | |
| 178 // TODO(xians): we should ask for the native sample rate of a specific device. | |
| 179 int sample_rate = GetAudioInputSampleRate(); | |
| 180 DVLOG(1) << "Audio input hardware sample rate: " << sample_rate; | |
| 181 UMA_HISTOGRAM_ENUMERATION("WebRTC.AudioInputSampleRate", | |
| 182 sample_rate, media::kUnexpectedAudioSampleRate); | |
| 183 | |
| 184 // Verify that the reported input hardware sample rate is supported | |
| 185 // on the current platform. | |
| 186 if (std::find(&kValidInputRates[0], | |
| 187 &kValidInputRates[0] + arraysize(kValidInputRates), | |
| 188 sample_rate) == | |
| 189 &kValidInputRates[arraysize(kValidInputRates)]) { | |
| 190 DLOG(ERROR) << sample_rate << " is not a supported input rate."; | |
| 191 return false; | |
| 192 } | |
| 193 | |
| 194 // Ask the browser for the default number of audio input channels. | |
| 195 // This request is based on a synchronous IPC message. | |
| 196 // TODO(xians): we should ask for the layout of a specific device. | |
| 197 media::ChannelLayout channel_layout = GetAudioInputChannelLayout(); | |
| 198 DVLOG(1) << "Audio input hardware channels: " << channel_layout; | |
| 199 | |
| 200 media::AudioParameters::Format format = | |
| 201 media::AudioParameters::AUDIO_PCM_LOW_LATENCY; | |
| 202 int buffer_size = GetBufferSizeForSampleRate(sample_rate); | |
| 203 if (!buffer_size) { | |
| 204 DLOG(ERROR) << "Unsupported platform"; | |
| 205 return false; | |
| 206 } | |
| 207 | |
| 208 params_.Reset(format, channel_layout, sample_rate, 16, buffer_size); | |
| 209 | |
| 210 buffer_.reset(new int16[params_.frames_per_buffer() * params_.channels()]); | |
| 211 | |
| 212 // Create and configure the default audio capturing source. The |source_| | |
| 213 // will be overwritten if the client call the source calls | |
| 214 // SetCapturerSource(). | |
| 215 SetCapturerSource(AudioDeviceFactory::NewInputDevice()); | |
| 216 | |
| 217 UMA_HISTOGRAM_ENUMERATION("WebRTC.AudioInputChannelLayout", | |
| 218 channel_layout, media::CHANNEL_LAYOUT_MAX); | |
| 219 | |
| 220 return true; | |
| 221 } | |
| 222 | |
| 223 void WebRtcAudioCapturer::ProvideInput(media::AudioBus* dest) { | 227 void WebRtcAudioCapturer::ProvideInput(media::AudioBus* dest) { |
| 224 base::AutoLock auto_lock(lock_); | 228 base::AutoLock auto_lock(lock_); |
| 225 DCHECK(loopback_fifo_.get() != NULL); | 229 DCHECK(loopback_fifo_.get() != NULL); |
| 226 | 230 |
| 227 if (!running_) { | 231 if (!running_) { |
| 228 dest->Zero(); | 232 dest->Zero(); |
| 229 return; | 233 return; |
| 230 } | 234 } |
| 231 | 235 |
| 232 // Provide data by reading from the FIFO if the FIFO contains enough | 236 // Provide data by reading from the FIFO if the FIFO contains enough |
| 233 // to fulfill the request. | 237 // to fulfill the request. |
| 234 if (loopback_fifo_->frames() >= dest->frames()) { | 238 if (loopback_fifo_->frames() >= dest->frames()) { |
| 235 loopback_fifo_->Consume(dest, 0, dest->frames()); | 239 loopback_fifo_->Consume(dest, 0, dest->frames()); |
| 236 } else { | 240 } else { |
| 237 dest->Zero(); | 241 dest->Zero(); |
| 238 // This warning is perfectly safe if it happens for the first audio | 242 // This warning is perfectly safe if it happens for the first audio |
| 239 // frames. It should not happen in a steady-state mode. | 243 // frames. It should not happen in a steady-state mode. |
| 240 DLOG(WARNING) << "WARNING: loopback FIFO is empty."; | 244 DVLOG(2) << "WARNING: loopback FIFO is empty."; |
| 241 } | 245 } |
| 242 } | 246 } |
| 243 | 247 |
| 244 void WebRtcAudioCapturer::Start() { | 248 void WebRtcAudioCapturer::Start() { |
| 245 DVLOG(1) << "WebRtcAudioCapturer::Start()"; | 249 DVLOG(1) << "WebRtcAudioCapturer::Start()"; |
| 246 base::AutoLock auto_lock(lock_); | 250 base::AutoLock auto_lock(lock_); |
| 247 if (running_) | 251 if (running_) |
| 248 return; | 252 return; |
| 249 | 253 |
| 250 // What Start() does is supposed to be very light, for example, posting a | 254 // What Start() does is supposed to be very light, for example, posting a |
| (...skipping 23 matching lines...) Expand all Loading... |
| 274 running_ = false; | 278 running_ = false; |
| 275 } | 279 } |
| 276 | 280 |
| 277 if (source) | 281 if (source) |
| 278 source->Stop(); | 282 source->Stop(); |
| 279 } | 283 } |
| 280 | 284 |
| 281 void WebRtcAudioCapturer::SetVolume(double volume) { | 285 void WebRtcAudioCapturer::SetVolume(double volume) { |
| 282 DVLOG(1) << "WebRtcAudioCapturer::SetVolume()"; | 286 DVLOG(1) << "WebRtcAudioCapturer::SetVolume()"; |
| 283 base::AutoLock auto_lock(lock_); | 287 base::AutoLock auto_lock(lock_); |
| 284 | |
| 285 if (source_) | 288 if (source_) |
| 286 source_->SetVolume(volume); | 289 source_->SetVolume(volume); |
| 287 } | 290 } |
| 288 | 291 |
| 289 void WebRtcAudioCapturer::SetDevice(int session_id) { | 292 void WebRtcAudioCapturer::SetDevice(int session_id) { |
| 293 DCHECK(thread_checker_.CalledOnValidThread()); |
| 290 DVLOG(1) << "WebRtcAudioCapturer::SetDevice(" << session_id << ")"; | 294 DVLOG(1) << "WebRtcAudioCapturer::SetDevice(" << session_id << ")"; |
| 291 base::AutoLock auto_lock(lock_); | 295 base::AutoLock auto_lock(lock_); |
| 292 if (source_) | 296 if (source_) |
| 293 source_->SetDevice(session_id); | 297 source_->SetDevice(session_id); |
| 294 } | 298 } |
| 295 | 299 |
| 296 void WebRtcAudioCapturer::SetAutomaticGainControl(bool enable) { | 300 void WebRtcAudioCapturer::SetAutomaticGainControl(bool enable) { |
| 297 base::AutoLock auto_lock(lock_); | 301 base::AutoLock auto_lock(lock_); |
| 298 if (source_) | 302 if (source_) |
| 299 source_->SetAutomaticGainControl(enable); | 303 source_->SetAutomaticGainControl(enable); |
| 300 } | 304 } |
| 301 | 305 |
| 302 bool WebRtcAudioCapturer::IsInLoopbackMode() { | 306 bool WebRtcAudioCapturer::IsInLoopbackMode() { |
| 307 DCHECK(thread_checker_.CalledOnValidThread()); |
| 303 base::AutoLock auto_lock(lock_); | 308 base::AutoLock auto_lock(lock_); |
| 304 return (loopback_fifo_ != NULL); | 309 return (loopback_fifo_ != NULL); |
| 305 } | 310 } |
| 306 | 311 |
| 307 void WebRtcAudioCapturer::Capture(media::AudioBus* audio_source, | 312 void WebRtcAudioCapturer::Capture(media::AudioBus* audio_source, |
| 308 int audio_delay_milliseconds, | 313 int audio_delay_milliseconds, |
| 309 double volume) { | 314 double volume) { |
| 310 // This callback is driven by AudioInputDevice::AudioThreadCallback if | 315 // This callback is driven by AudioInputDevice::AudioThreadCallback if |
| 311 // |source_| is AudioInputDevice, otherwise it is driven by client's | 316 // |source_| is AudioInputDevice, otherwise it is driven by client's |
| 312 // CaptureCallback. | 317 // CaptureCallback. |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 370 // Inform the local renderer about the stopped device. | 375 // Inform the local renderer about the stopped device. |
| 371 // The renderer can then save resources by not asking for more data from | 376 // The renderer can then save resources by not asking for more data from |
| 372 // the stopped source. We are on the IO thread but the callback task will | 377 // the stopped source. We are on the IO thread but the callback task will |
| 373 // be posted on the message loop of the main render thread thanks to | 378 // be posted on the message loop of the main render thread thanks to |
| 374 // usage of BindToLoop() when the callback was initialized. | 379 // usage of BindToLoop() when the callback was initialized. |
| 375 if (!on_device_stopped_cb_.is_null()) | 380 if (!on_device_stopped_cb_.is_null()) |
| 376 on_device_stopped_cb_.Run(); | 381 on_device_stopped_cb_.Run(); |
| 377 } | 382 } |
| 378 | 383 |
| 379 } // namespace content | 384 } // namespace content |
| OLD | NEW |