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_local_audio_track.h" | 5 #include "content/renderer/media/webrtc_local_audio_track.h" |
6 | 6 |
7 #include "content/renderer/media/webaudio_capturer_source.h" | 7 #include "content/renderer/media/webaudio_capturer_source.h" |
8 #include "content/renderer/media/webrtc_audio_capturer.h" | 8 #include "content/renderer/media/webrtc_audio_capturer.h" |
9 #include "content/renderer/media/webrtc_audio_capturer_sink_owner.h" | 9 #include "content/renderer/media/webrtc_audio_capturer_sink_owner.h" |
| 10 #include "content/renderer/media/webrtc_audio_processor.h" |
10 #include "content/renderer/media/webrtc_local_audio_source_provider.h" | 11 #include "content/renderer/media/webrtc_local_audio_source_provider.h" |
11 #include "media/base/audio_fifo.h" | 12 #include "media/base/audio_fifo.h" |
12 #include "third_party/libjingle/source/talk/media/base/audiorenderer.h" | 13 #include "third_party/libjingle/source/talk/media/base/audiorenderer.h" |
13 | 14 |
14 namespace content { | 15 namespace content { |
15 | 16 |
16 static const size_t kMaxNumberOfBuffersInFifo = 2; | |
17 static const char kAudioTrackKind[] = "audio"; | 17 static const char kAudioTrackKind[] = "audio"; |
18 | 18 |
19 namespace { | |
20 | |
21 using webrtc::MediaConstraintsInterface; | |
22 | |
23 // This helper function checks if any audio constraints are set that require | |
24 // audio processing to be applied. Right now this is a big, single switch for | |
25 // all of the properties, but in the future they'll be handled one by one. | |
26 bool NeedsAudioProcessing( | |
27 const webrtc::MediaConstraintsInterface* constraints) { | |
28 if (!constraints) | |
29 return false; | |
30 | |
31 static const char* kAudioProcessingProperties[] = { | |
32 MediaConstraintsInterface::kEchoCancellation, | |
33 MediaConstraintsInterface::kExperimentalEchoCancellation, | |
34 MediaConstraintsInterface::kAutoGainControl, | |
35 MediaConstraintsInterface::kExperimentalAutoGainControl, | |
36 MediaConstraintsInterface::kNoiseSuppression, | |
37 MediaConstraintsInterface::kHighpassFilter, | |
38 MediaConstraintsInterface::kTypingNoiseDetection, | |
39 }; | |
40 | |
41 for (size_t i = 0; i < arraysize(kAudioProcessingProperties); ++i) { | |
42 bool value = false; | |
43 if (webrtc::FindConstraint(constraints, kAudioProcessingProperties[i], | |
44 &value, NULL) && | |
45 value) { | |
46 return true; | |
47 } | |
48 } | |
49 | |
50 return false; | |
51 } | |
52 | |
53 } // namespace. | |
54 | |
55 // This is a temporary audio buffer with parameters used to send data to | |
56 // callbacks. | |
57 class WebRtcLocalAudioTrack::ConfiguredBuffer : | |
58 public base::RefCounted<WebRtcLocalAudioTrack::ConfiguredBuffer> { | |
59 public: | |
60 ConfiguredBuffer() : sink_buffer_size_(0) {} | |
61 | |
62 void Initialize(const media::AudioParameters& params) { | |
63 DCHECK(params.IsValid()); | |
64 params_ = params; | |
65 | |
66 // Use 10ms as the sink buffer size since that is the native packet size | |
67 // WebRtc is running on. | |
68 sink_buffer_size_ = params.sample_rate() / 100; | |
69 audio_wrapper_ = | |
70 media::AudioBus::Create(params.channels(), sink_buffer_size_); | |
71 buffer_.reset(new int16[sink_buffer_size_ * params.channels()]); | |
72 | |
73 // The size of the FIFO should be at least twice of the source buffer size | |
74 // or twice of the sink buffer size. | |
75 int buffer_size = std::max( | |
76 kMaxNumberOfBuffersInFifo * params.frames_per_buffer(), | |
77 kMaxNumberOfBuffersInFifo * sink_buffer_size_); | |
78 fifo_.reset(new media::AudioFifo(params.channels(), buffer_size)); | |
79 } | |
80 | |
81 void Push(media::AudioBus* audio_source) { | |
82 DCHECK(fifo_->frames() + audio_source->frames() <= fifo_->max_frames()); | |
83 fifo_->Push(audio_source); | |
84 } | |
85 | |
86 bool Consume() { | |
87 if (fifo_->frames() < audio_wrapper_->frames()) | |
88 return false; | |
89 | |
90 fifo_->Consume(audio_wrapper_.get(), 0, audio_wrapper_->frames()); | |
91 audio_wrapper_->ToInterleaved(audio_wrapper_->frames(), | |
92 params_.bits_per_sample() / 8, | |
93 buffer()); | |
94 return true; | |
95 } | |
96 | |
97 int16* buffer() const { return buffer_.get(); } | |
98 const media::AudioParameters& params() const { return params_; } | |
99 int sink_buffer_size() const { return sink_buffer_size_; } | |
100 | |
101 private: | |
102 ~ConfiguredBuffer() {} | |
103 friend class base::RefCounted<WebRtcLocalAudioTrack::ConfiguredBuffer>; | |
104 | |
105 media::AudioParameters params_; | |
106 scoped_ptr<media::AudioBus> audio_wrapper_; | |
107 scoped_ptr<media::AudioFifo> fifo_; | |
108 scoped_ptr<int16[]> buffer_; | |
109 int sink_buffer_size_; | |
110 }; | |
111 | |
112 scoped_refptr<WebRtcLocalAudioTrack> WebRtcLocalAudioTrack::Create( | 19 scoped_refptr<WebRtcLocalAudioTrack> WebRtcLocalAudioTrack::Create( |
113 const std::string& id, | 20 const std::string& id, |
114 const scoped_refptr<WebRtcAudioCapturer>& capturer, | 21 const scoped_refptr<WebRtcAudioCapturer>& capturer, |
115 WebAudioCapturerSource* webaudio_source, | 22 WebAudioCapturerSource* webaudio_source, |
116 webrtc::AudioSourceInterface* track_source, | 23 webrtc::AudioSourceInterface* track_source, |
117 const webrtc::MediaConstraintsInterface* constraints) { | 24 const webrtc::MediaConstraintsInterface* constraints, |
| 25 WebRtcAudioDeviceImpl* audio_device) { |
118 talk_base::RefCountedObject<WebRtcLocalAudioTrack>* track = | 26 talk_base::RefCountedObject<WebRtcLocalAudioTrack>* track = |
119 new talk_base::RefCountedObject<WebRtcLocalAudioTrack>( | 27 new talk_base::RefCountedObject<WebRtcLocalAudioTrack>( |
120 id, capturer, webaudio_source, track_source, constraints); | 28 id, capturer, webaudio_source, track_source, |
| 29 constraints, audio_device); |
121 return track; | 30 return track; |
122 } | 31 } |
123 | 32 |
124 WebRtcLocalAudioTrack::WebRtcLocalAudioTrack( | 33 WebRtcLocalAudioTrack::WebRtcLocalAudioTrack( |
125 const std::string& label, | 34 const std::string& label, |
126 const scoped_refptr<WebRtcAudioCapturer>& capturer, | 35 const scoped_refptr<WebRtcAudioCapturer>& capturer, |
127 WebAudioCapturerSource* webaudio_source, | 36 WebAudioCapturerSource* webaudio_source, |
128 webrtc::AudioSourceInterface* track_source, | 37 webrtc::AudioSourceInterface* track_source, |
129 const webrtc::MediaConstraintsInterface* constraints) | 38 const webrtc::MediaConstraintsInterface* constraints, |
| 39 WebRtcAudioDeviceImpl* audio_device) |
130 : webrtc::MediaStreamTrack<webrtc::AudioTrackInterface>(label), | 40 : webrtc::MediaStreamTrack<webrtc::AudioTrackInterface>(label), |
131 capturer_(capturer), | 41 capturer_(capturer), |
132 webaudio_source_(webaudio_source), | 42 webaudio_source_(webaudio_source), |
133 track_source_(track_source), | 43 track_source_(track_source), |
134 need_audio_processing_(NeedsAudioProcessing(constraints)) { | 44 audio_processor_(new WebRtcAudioProcessor(constraints)), |
| 45 source_provider_(new WebRtcLocalAudioSourceProvider()), |
| 46 audio_device_(audio_device) { |
135 DCHECK(capturer.get() || webaudio_source); | 47 DCHECK(capturer.get() || webaudio_source); |
| 48 DCHECK(audio_processor_.get()); |
| 49 DCHECK(source_provider_.get()); |
| 50 AddSink(source_provider_.get()); |
136 DVLOG(1) << "WebRtcLocalAudioTrack::WebRtcLocalAudioTrack()"; | 51 DVLOG(1) << "WebRtcLocalAudioTrack::WebRtcLocalAudioTrack()"; |
137 } | 52 } |
138 | 53 |
139 WebRtcLocalAudioTrack::~WebRtcLocalAudioTrack() { | 54 WebRtcLocalAudioTrack::~WebRtcLocalAudioTrack() { |
140 DCHECK(thread_checker_.CalledOnValidThread()); | 55 DCHECK(thread_checker_.CalledOnValidThread()); |
141 DVLOG(1) << "WebRtcLocalAudioTrack::~WebRtcLocalAudioTrack()"; | 56 DVLOG(1) << "WebRtcLocalAudioTrack::~WebRtcLocalAudioTrack()"; |
142 // Users might not call Stop() on the track. | 57 // Users might not call Stop() on the track. |
143 Stop(); | 58 Stop(); |
144 } | 59 } |
145 | 60 |
146 void WebRtcLocalAudioTrack::Capture(media::AudioBus* audio_source, | 61 void WebRtcLocalAudioTrack::Capture(media::AudioBus* audio_source, |
147 int audio_delay_milliseconds, | 62 int audio_delay_milliseconds, |
148 int volume, | 63 int volume, |
149 bool key_pressed) { | 64 bool key_pressed) { |
150 scoped_refptr<WebRtcAudioCapturer> capturer; | 65 scoped_refptr<WebRtcAudioCapturer> capturer; |
151 std::vector<int> voe_channels; | 66 std::vector<int> voe_channels; |
152 int sample_rate = 0; | |
153 int number_of_channels = 0; | |
154 int number_of_frames = 0; | |
155 SinkList sinks; | 67 SinkList sinks; |
| 68 media::AudioParameters params; |
156 bool is_webaudio_source = false; | 69 bool is_webaudio_source = false; |
157 scoped_refptr<ConfiguredBuffer> current_buffer; | |
158 { | 70 { |
159 base::AutoLock auto_lock(lock_); | 71 base::AutoLock auto_lock(lock_); |
160 capturer = capturer_; | 72 capturer = capturer_; |
161 voe_channels = voe_channels_; | 73 voe_channels = voe_channels_; |
162 current_buffer = buffer_; | |
163 sample_rate = current_buffer->params().sample_rate(); | |
164 number_of_channels = current_buffer->params().channels(); | |
165 number_of_frames = current_buffer->sink_buffer_size(); | |
166 sinks = sinks_; | 74 sinks = sinks_; |
| 75 params = sink_params_; |
167 is_webaudio_source = (webaudio_source_.get() != NULL); | 76 is_webaudio_source = (webaudio_source_.get() != NULL); |
168 } | 77 } |
| 78 DCHECK(params.IsValid()); |
| 79 DCHECK(params.frames_per_buffer() == params.sample_rate() / 100); |
169 | 80 |
170 // Push the data to the fifo. | 81 audio_processor_->Push(audio_source); |
171 current_buffer->Push(audio_source); | |
172 | 82 |
| 83 // Turn off the audio processing in WebRtc when the audio processor in Chrome |
| 84 // is on. |
| 85 bool need_audio_processing = !audio_processor_->has_audio_processing(); |
173 // When the source is WebAudio, turn off the audio processing if the delay | 86 // When the source is WebAudio, turn off the audio processing if the delay |
174 // value is 0 even though the constraint is set to true. In such case, it | 87 // value is 0 even though the constraint is set to true. In such case, it |
175 // indicates the data is not from microphone. | 88 // indicates the data is not from microphone. |
176 // TODO(xians): remove the flag when supporting one APM per audio track. | |
177 // See crbug/264611 for details. | |
178 bool need_audio_processing = need_audio_processing_; | |
179 if (is_webaudio_source && need_audio_processing) | 89 if (is_webaudio_source && need_audio_processing) |
180 need_audio_processing = (audio_delay_milliseconds != 0); | 90 need_audio_processing = (audio_delay_milliseconds != 0); |
181 | 91 |
182 int current_volume = volume; | 92 int current_volume = volume; |
183 while (current_buffer->Consume()) { | 93 while (audio_processor_->ProcessAndConsume10MsData( |
| 94 audio_delay_milliseconds, volume, key_pressed)) { |
| 95 // TODO(xians): Get the new volume and set it to |current_volume|. |
184 // Feed the data to the sinks. | 96 // Feed the data to the sinks. |
185 // TODO (jiayl): we should not pass the real audio data down if the track is | 97 // TODO (jiayl): we should not pass the real audio data down if the track is |
186 // disabled. This is currently done so to feed input to WebRTC typing | 98 // disabled. This is currently done so to feed input to WebRTC typing |
187 // detection and should be changed when audio processing is moved from | 99 // detection and should be changed when audio processing is moved from |
188 // WebRTC to the track. | 100 // WebRTC to the track. |
189 for (SinkList::const_iterator it = sinks.begin(); it != sinks.end(); ++it) { | 101 for (SinkList::const_iterator it = sinks.begin(); it != sinks.end(); ++it) { |
190 int new_volume = (*it)->CaptureData(voe_channels, | 102 int new_volume = (*it)->CaptureData(voe_channels, |
191 current_buffer->buffer(), | 103 audio_processor_->OutputBuffer(), |
192 sample_rate, | 104 params.sample_rate(), |
193 number_of_channels, | 105 params.channels(), |
194 number_of_frames, | 106 params.frames_per_buffer(), |
195 audio_delay_milliseconds, | 107 audio_delay_milliseconds, |
196 current_volume, | 108 current_volume, |
197 need_audio_processing, | 109 need_audio_processing, |
198 key_pressed); | 110 key_pressed); |
199 if (new_volume != 0 && capturer.get()) { | 111 if (new_volume != 0 && capturer.get()) { |
200 // Feed the new volume to WebRtc while changing the volume on the | 112 // Feed the new volume to WebRtc while changing the volume on the |
201 // browser. | 113 // browser. |
202 capturer->SetVolume(new_volume); | 114 capturer->SetVolume(new_volume); |
203 current_volume = new_volume; | 115 current_volume = new_volume; |
204 } | 116 } |
205 } | 117 } |
206 } | 118 } |
207 } | 119 } |
208 | 120 |
209 void WebRtcLocalAudioTrack::SetCaptureFormat( | 121 void WebRtcLocalAudioTrack::SetCaptureFormat( |
210 const media::AudioParameters& params) { | 122 const media::AudioParameters& params) { |
211 if (!params.IsValid()) | 123 DCHECK(params.IsValid()); |
212 return; | |
213 | 124 |
214 scoped_refptr<ConfiguredBuffer> new_buffer(new ConfiguredBuffer()); | 125 audio_processor_->SetFormat(params); |
215 new_buffer->Initialize(params); | |
216 | |
217 SinkList sinks; | 126 SinkList sinks; |
218 { | 127 { |
219 base::AutoLock auto_lock(lock_); | 128 base::AutoLock auto_lock(lock_); |
220 buffer_ = new_buffer; | 129 source_params_ = params; |
| 130 sink_params_ = audio_processor_->OutputFormat(); |
221 sinks = sinks_; | 131 sinks = sinks_; |
222 } | 132 } |
223 | 133 |
224 // Update all the existing sinks with the new format. | 134 // Update all the existing sinks with the new format. |
225 for (SinkList::const_iterator it = sinks.begin(); | 135 for (SinkList::const_iterator it = sinks.begin(); |
226 it != sinks.end(); ++it) { | 136 it != sinks.end(); ++it) { |
227 (*it)->SetCaptureFormat(params); | 137 (*it)->SetCaptureFormat(sink_params_); |
228 } | 138 } |
229 } | 139 } |
230 | 140 |
231 void WebRtcLocalAudioTrack::AddChannel(int channel_id) { | 141 void WebRtcLocalAudioTrack::AddChannel(int channel_id) { |
232 DVLOG(1) << "WebRtcLocalAudioTrack::AddChannel(channel_id=" | 142 DVLOG(1) << "WebRtcLocalAudioTrack::AddChannel(channel_id=" |
233 << channel_id << ")"; | 143 << channel_id << ")"; |
234 base::AutoLock auto_lock(lock_); | 144 base::AutoLock auto_lock(lock_); |
235 if (std::find(voe_channels_.begin(), voe_channels_.end(), channel_id) != | 145 if (std::find(voe_channels_.begin(), voe_channels_.end(), channel_id) != |
236 voe_channels_.end()) { | 146 voe_channels_.end()) { |
237 // We need to handle the case when the same channel is connected to the | 147 // We need to handle the case when the same channel is connected to the |
(...skipping 24 matching lines...) Expand all Loading... |
262 } | 172 } |
263 | 173 |
264 std::string WebRtcLocalAudioTrack::kind() const { | 174 std::string WebRtcLocalAudioTrack::kind() const { |
265 return kAudioTrackKind; | 175 return kAudioTrackKind; |
266 } | 176 } |
267 | 177 |
268 void WebRtcLocalAudioTrack::AddSink(WebRtcAudioCapturerSink* sink) { | 178 void WebRtcLocalAudioTrack::AddSink(WebRtcAudioCapturerSink* sink) { |
269 DCHECK(thread_checker_.CalledOnValidThread()); | 179 DCHECK(thread_checker_.CalledOnValidThread()); |
270 DVLOG(1) << "WebRtcLocalAudioTrack::AddSink()"; | 180 DVLOG(1) << "WebRtcLocalAudioTrack::AddSink()"; |
271 base::AutoLock auto_lock(lock_); | 181 base::AutoLock auto_lock(lock_); |
272 if (buffer_.get()) | 182 |
273 sink->SetCaptureFormat(buffer_->params()); | 183 if (sink_params_.IsValid()) |
| 184 sink->SetCaptureFormat(sink_params_); |
274 | 185 |
275 // Verify that |sink| is not already added to the list. | 186 // Verify that |sink| is not already added to the list. |
276 DCHECK(std::find_if( | 187 DCHECK(std::find_if( |
277 sinks_.begin(), sinks_.end(), | 188 sinks_.begin(), sinks_.end(), |
278 WebRtcAudioCapturerSinkOwner::WrapsSink(sink)) == sinks_.end()); | 189 WebRtcAudioCapturerSinkOwner::WrapsSink(sink)) == sinks_.end()); |
279 | 190 |
280 // Create (and add to the list) a new WebRtcAudioCapturerSinkOwner which owns | 191 // Create (and add to the list) a new WebRtcAudioCapturerSinkOwner which owns |
281 // the |sink| and delagates all calls to the WebRtcAudioCapturerSink | 192 // the |sink| and delagates all calls to the WebRtcAudioCapturerSink |
282 // interface. | 193 // interface. |
283 sinks_.push_back(new WebRtcAudioCapturerSinkOwner(sink)); | 194 sinks_.push_back(new WebRtcAudioCapturerSinkOwner(sink)); |
(...skipping 11 matching lines...) Expand all Loading... |
295 WebRtcAudioCapturerSinkOwner::WrapsSink(sink)); | 206 WebRtcAudioCapturerSinkOwner::WrapsSink(sink)); |
296 if (it != sinks_.end()) { | 207 if (it != sinks_.end()) { |
297 // Clear the delegate to ensure that no more capture callbacks will | 208 // Clear the delegate to ensure that no more capture callbacks will |
298 // be sent to this sink. Also avoids a possible crash which can happen | 209 // be sent to this sink. Also avoids a possible crash which can happen |
299 // if this method is called while capturing is active. | 210 // if this method is called while capturing is active. |
300 (*it)->Reset(); | 211 (*it)->Reset(); |
301 sinks_.erase(it); | 212 sinks_.erase(it); |
302 } | 213 } |
303 } | 214 } |
304 | 215 |
| 216 void WebRtcLocalAudioTrack::OnRenderData(const int16* render_audio, |
| 217 int sample_rate, |
| 218 int number_of_channels, |
| 219 int number_of_frames, |
| 220 int render_delay_ms) { |
| 221 audio_processor_->FeedRenderDataToAudioProcessing(render_audio, |
| 222 sample_rate, |
| 223 number_of_channels, |
| 224 number_of_frames, |
| 225 render_delay_ms); |
| 226 } |
| 227 |
| 228 void WebRtcLocalAudioTrack::OnRenderClosing() { |
| 229 base::AutoLock auto_lock(lock_); |
| 230 audio_device_ = NULL; |
| 231 } |
| 232 |
305 void WebRtcLocalAudioTrack::Start() { | 233 void WebRtcLocalAudioTrack::Start() { |
306 DCHECK(thread_checker_.CalledOnValidThread()); | 234 DCHECK(thread_checker_.CalledOnValidThread()); |
307 DVLOG(1) << "WebRtcLocalAudioTrack::Start()"; | 235 DVLOG(1) << "WebRtcLocalAudioTrack::Start()"; |
308 if (webaudio_source_.get()) { | 236 if (webaudio_source_.get()) { |
309 // If the track is hooking up with WebAudio, do NOT add the track to the | 237 // If the track is hooking up with WebAudio, do NOT add the track to the |
310 // capturer as its sink otherwise two streams in different clock will be | 238 // capturer as its sink otherwise two streams in different clock will be |
311 // pushed through the same track. | 239 // pushed through the same track. |
312 WebRtcLocalAudioSourceProvider* source_provider = NULL; | 240 webaudio_source_->Start(this, capturer_.get()); |
313 if (capturer_.get()) { | |
314 source_provider = static_cast<WebRtcLocalAudioSourceProvider*>( | |
315 capturer_->audio_source_provider()); | |
316 } | |
317 webaudio_source_->Start(this, source_provider); | |
318 return; | 241 return; |
319 } | 242 } |
320 | 243 |
321 if (capturer_.get()) | 244 if (capturer_.get()) |
322 capturer_->AddTrack(this); | 245 capturer_->AddTrack(this); |
| 246 |
| 247 if (audio_device_) |
| 248 audio_device_->RemoveRenderDataObserver(this); |
323 } | 249 } |
324 | 250 |
325 void WebRtcLocalAudioTrack::Stop() { | 251 void WebRtcLocalAudioTrack::Stop() { |
326 DCHECK(thread_checker_.CalledOnValidThread()); | 252 DCHECK(thread_checker_.CalledOnValidThread()); |
327 DVLOG(1) << "WebRtcLocalAudioTrack::Stop()"; | 253 DVLOG(1) << "WebRtcLocalAudioTrack::Stop()"; |
328 if (!capturer_.get() && !webaudio_source_.get()) | 254 if (!capturer_.get() && !webaudio_source_.get()) |
329 return; | 255 return; |
330 | 256 |
331 if (webaudio_source_.get()) { | 257 if (webaudio_source_.get()) { |
332 // Called Stop() on the |webaudio_source_| explicitly so that | 258 // Called Stop() on the |webaudio_source_| explicitly so that |
333 // |webaudio_source_| won't push more data to the track anymore. | 259 // |webaudio_source_| won't push more data to the track anymore. |
334 // Also note that the track is not registered as a sink to the |capturer_| | 260 // Also note that the track is not registered as a sink to the |capturer_| |
335 // in such case and no need to call RemoveTrack(). | 261 // in such case and no need to call RemoveTrack(). |
336 webaudio_source_->Stop(); | 262 webaudio_source_->Stop(); |
337 } else { | |
338 capturer_->RemoveTrack(this); | |
339 } | 263 } |
340 | 264 |
341 // Protect the pointers using the lock when accessing |sinks_| and | 265 // Protect the pointers using the lock when accessing |sinks_| and |
342 // setting the |capturer_| to NULL. | 266 // setting the |capturer_| to NULL. |
343 SinkList sinks; | 267 SinkList sinks; |
344 { | 268 { |
345 base::AutoLock auto_lock(lock_); | 269 base::AutoLock auto_lock(lock_); |
346 sinks = sinks_; | 270 sinks = sinks_; |
347 webaudio_source_ = NULL; | 271 webaudio_source_ = NULL; |
348 capturer_ = NULL; | 272 capturer_ = NULL; |
| 273 if (audio_device_) |
| 274 audio_device_->RemoveRenderDataObserver(this); |
| 275 audio_device_ = NULL; |
349 } | 276 } |
350 | 277 |
351 for (SinkList::const_iterator it = sinks.begin(); it != sinks.end(); ++it) | 278 for (SinkList::const_iterator it = sinks.begin(); it != sinks.end(); ++it) |
352 (*it)->Reset(); | 279 (*it)->Reset(); |
353 } | 280 } |
354 | 281 |
355 } // namespace content | 282 } // namespace content |
OLD | NEW |