OLD | NEW |
| (Empty) |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 #include "content/renderer/media/webrtc_local_audio_renderer.h" | |
6 | |
7 #include <utility> | |
8 | |
9 #include "base/location.h" | |
10 #include "base/logging.h" | |
11 #include "base/metrics/histogram.h" | |
12 #include "base/synchronization/lock.h" | |
13 #include "base/thread_task_runner_handle.h" | |
14 #include "base/trace_event/trace_event.h" | |
15 #include "content/renderer/media/audio_device_factory.h" | |
16 #include "content/renderer/media/media_stream_dispatcher.h" | |
17 #include "content/renderer/media/webrtc_audio_capturer.h" | |
18 #include "content/renderer/media/webrtc_audio_renderer.h" | |
19 #include "content/renderer/render_frame_impl.h" | |
20 #include "media/audio/audio_output_device.h" | |
21 #include "media/base/audio_bus.h" | |
22 #include "media/base/audio_shifter.h" | |
23 | |
24 namespace content { | |
25 | |
26 namespace { | |
27 | |
28 enum LocalRendererSinkStates { | |
29 kSinkStarted = 0, | |
30 kSinkNeverStarted, | |
31 kSinkStatesMax // Must always be last! | |
32 }; | |
33 | |
34 } // namespace | |
35 | |
36 // media::AudioRendererSink::RenderCallback implementation | |
37 int WebRtcLocalAudioRenderer::Render(media::AudioBus* audio_bus, | |
38 uint32_t audio_delay_milliseconds, | |
39 uint32_t frames_skipped) { | |
40 TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::Render"); | |
41 base::AutoLock auto_lock(thread_lock_); | |
42 | |
43 if (!playing_ || !volume_ || !audio_shifter_) { | |
44 audio_bus->Zero(); | |
45 return 0; | |
46 } | |
47 | |
48 audio_shifter_->Pull( | |
49 audio_bus, | |
50 base::TimeTicks::Now() - | |
51 base::TimeDelta::FromMilliseconds(audio_delay_milliseconds)); | |
52 | |
53 return audio_bus->frames(); | |
54 } | |
55 | |
56 void WebRtcLocalAudioRenderer::OnRenderError() { | |
57 NOTIMPLEMENTED(); | |
58 } | |
59 | |
60 // content::MediaStreamAudioSink implementation | |
61 void WebRtcLocalAudioRenderer::OnData(const media::AudioBus& audio_bus, | |
62 base::TimeTicks estimated_capture_time) { | |
63 DCHECK(capture_thread_checker_.CalledOnValidThread()); | |
64 DCHECK(!estimated_capture_time.is_null()); | |
65 | |
66 TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::CaptureData"); | |
67 | |
68 base::AutoLock auto_lock(thread_lock_); | |
69 if (!playing_ || !volume_ || !audio_shifter_) | |
70 return; | |
71 | |
72 scoped_ptr<media::AudioBus> audio_data( | |
73 media::AudioBus::Create(audio_bus.channels(), audio_bus.frames())); | |
74 audio_bus.CopyTo(audio_data.get()); | |
75 audio_shifter_->Push(std::move(audio_data), estimated_capture_time); | |
76 const base::TimeTicks now = base::TimeTicks::Now(); | |
77 total_render_time_ += now - last_render_time_; | |
78 last_render_time_ = now; | |
79 } | |
80 | |
81 void WebRtcLocalAudioRenderer::OnSetFormat( | |
82 const media::AudioParameters& params) { | |
83 DVLOG(1) << "WebRtcLocalAudioRenderer::OnSetFormat()"; | |
84 // If the source is restarted, we might have changed to another capture | |
85 // thread. | |
86 capture_thread_checker_.DetachFromThread(); | |
87 DCHECK(capture_thread_checker_.CalledOnValidThread()); | |
88 | |
89 // Post a task on the main render thread to reconfigure the |sink_| with the | |
90 // new format. | |
91 task_runner_->PostTask( | |
92 FROM_HERE, | |
93 base::Bind(&WebRtcLocalAudioRenderer::ReconfigureSink, this, params)); | |
94 } | |
95 | |
96 // WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer implementation. | |
97 WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer( | |
98 const blink::WebMediaStreamTrack& audio_track, | |
99 int source_render_frame_id, | |
100 int session_id, | |
101 const std::string& device_id, | |
102 const url::Origin& security_origin) | |
103 : audio_track_(audio_track), | |
104 source_render_frame_id_(source_render_frame_id), | |
105 session_id_(session_id), | |
106 task_runner_(base::ThreadTaskRunnerHandle::Get()), | |
107 playing_(false), | |
108 output_device_id_(device_id), | |
109 security_origin_(security_origin), | |
110 volume_(0.0), | |
111 sink_started_(false) { | |
112 DVLOG(1) << "WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer()"; | |
113 } | |
114 | |
115 WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer() { | |
116 DCHECK(task_runner_->BelongsToCurrentThread()); | |
117 DCHECK(!sink_.get()); | |
118 DVLOG(1) << "WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer()"; | |
119 } | |
120 | |
121 void WebRtcLocalAudioRenderer::Start() { | |
122 DVLOG(1) << "WebRtcLocalAudioRenderer::Start()"; | |
123 DCHECK(task_runner_->BelongsToCurrentThread()); | |
124 | |
125 // We get audio data from |audio_track_|... | |
126 MediaStreamAudioSink::AddToAudioTrack(this, audio_track_); | |
127 // ...and |sink_| will get audio data from us. | |
128 DCHECK(!sink_.get()); | |
129 sink_ = | |
130 AudioDeviceFactory::NewOutputDevice(source_render_frame_id_, session_id_, | |
131 output_device_id_, security_origin_); | |
132 | |
133 base::AutoLock auto_lock(thread_lock_); | |
134 last_render_time_ = base::TimeTicks::Now(); | |
135 playing_ = false; | |
136 } | |
137 | |
138 void WebRtcLocalAudioRenderer::Stop() { | |
139 DVLOG(1) << "WebRtcLocalAudioRenderer::Stop()"; | |
140 DCHECK(task_runner_->BelongsToCurrentThread()); | |
141 | |
142 { | |
143 base::AutoLock auto_lock(thread_lock_); | |
144 playing_ = false; | |
145 audio_shifter_.reset(); | |
146 } | |
147 | |
148 // Stop the output audio stream, i.e, stop asking for data to render. | |
149 // It is safer to call Stop() on the |sink_| to clean up the resources even | |
150 // when the |sink_| is never started. | |
151 if (sink_.get()) { | |
152 sink_->Stop(); | |
153 sink_ = NULL; | |
154 } | |
155 | |
156 if (!sink_started_) { | |
157 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", | |
158 kSinkNeverStarted, kSinkStatesMax); | |
159 } | |
160 sink_started_ = false; | |
161 | |
162 // Ensure that the capturer stops feeding us with captured audio. | |
163 MediaStreamAudioSink::RemoveFromAudioTrack(this, audio_track_); | |
164 } | |
165 | |
166 void WebRtcLocalAudioRenderer::Play() { | |
167 DVLOG(1) << "WebRtcLocalAudioRenderer::Play()"; | |
168 DCHECK(task_runner_->BelongsToCurrentThread()); | |
169 | |
170 if (!sink_.get()) | |
171 return; | |
172 | |
173 { | |
174 base::AutoLock auto_lock(thread_lock_); | |
175 // Resumes rendering by ensuring that WebRtcLocalAudioRenderer::Render() | |
176 // now reads data from the local FIFO. | |
177 playing_ = true; | |
178 last_render_time_ = base::TimeTicks::Now(); | |
179 } | |
180 | |
181 // Note: If volume_ is currently muted, the |sink_| will not be started yet. | |
182 MaybeStartSink(); | |
183 } | |
184 | |
185 void WebRtcLocalAudioRenderer::Pause() { | |
186 DVLOG(1) << "WebRtcLocalAudioRenderer::Pause()"; | |
187 DCHECK(task_runner_->BelongsToCurrentThread()); | |
188 | |
189 if (!sink_.get()) | |
190 return; | |
191 | |
192 base::AutoLock auto_lock(thread_lock_); | |
193 // Temporarily suspends rendering audio. | |
194 // WebRtcLocalAudioRenderer::Render() will return early during this state | |
195 // and only zeros will be provided to the active sink. | |
196 playing_ = false; | |
197 } | |
198 | |
199 void WebRtcLocalAudioRenderer::SetVolume(float volume) { | |
200 DVLOG(1) << "WebRtcLocalAudioRenderer::SetVolume(" << volume << ")"; | |
201 DCHECK(task_runner_->BelongsToCurrentThread()); | |
202 | |
203 { | |
204 base::AutoLock auto_lock(thread_lock_); | |
205 // Cache the volume. | |
206 volume_ = volume; | |
207 } | |
208 | |
209 // Lazily start the |sink_| when the local renderer is unmuted during | |
210 // playing. | |
211 MaybeStartSink(); | |
212 | |
213 if (sink_.get()) | |
214 sink_->SetVolume(volume); | |
215 } | |
216 | |
217 media::OutputDevice* WebRtcLocalAudioRenderer::GetOutputDevice() { | |
218 DCHECK(task_runner_->BelongsToCurrentThread()); | |
219 return this; | |
220 } | |
221 | |
222 base::TimeDelta WebRtcLocalAudioRenderer::GetCurrentRenderTime() const { | |
223 DCHECK(task_runner_->BelongsToCurrentThread()); | |
224 base::AutoLock auto_lock(thread_lock_); | |
225 if (!sink_.get()) | |
226 return base::TimeDelta(); | |
227 return total_render_time(); | |
228 } | |
229 | |
230 bool WebRtcLocalAudioRenderer::IsLocalRenderer() const { | |
231 return true; | |
232 } | |
233 | |
234 void WebRtcLocalAudioRenderer::SwitchOutputDevice( | |
235 const std::string& device_id, | |
236 const url::Origin& security_origin, | |
237 const media::SwitchOutputDeviceCB& callback) { | |
238 DVLOG(1) << "WebRtcLocalAudioRenderer::SwitchOutputDevice()"; | |
239 DCHECK(task_runner_->BelongsToCurrentThread()); | |
240 | |
241 scoped_refptr<media::AudioOutputDevice> new_sink = | |
242 AudioDeviceFactory::NewOutputDevice(source_render_frame_id_, session_id_, | |
243 device_id, security_origin); | |
244 if (new_sink->GetDeviceStatus() != media::OUTPUT_DEVICE_STATUS_OK) { | |
245 callback.Run(new_sink->GetDeviceStatus()); | |
246 return; | |
247 } | |
248 | |
249 output_device_id_ = device_id; | |
250 security_origin_ = security_origin; | |
251 bool was_sink_started = sink_started_; | |
252 | |
253 if (sink_.get()) | |
254 sink_->Stop(); | |
255 | |
256 sink_started_ = false; | |
257 sink_ = new_sink; | |
258 int frames_per_buffer = sink_->GetOutputParameters().frames_per_buffer(); | |
259 sink_params_ = source_params_; | |
260 sink_params_.set_frames_per_buffer(WebRtcAudioRenderer::GetOptimalBufferSize( | |
261 source_params_.sample_rate(), frames_per_buffer)); | |
262 | |
263 if (was_sink_started) | |
264 MaybeStartSink(); | |
265 | |
266 callback.Run(media::OUTPUT_DEVICE_STATUS_OK); | |
267 } | |
268 | |
269 media::AudioParameters WebRtcLocalAudioRenderer::GetOutputParameters() { | |
270 DCHECK(task_runner_->BelongsToCurrentThread()); | |
271 if (!sink_.get()) | |
272 return media::AudioParameters(); | |
273 | |
274 return sink_->GetOutputParameters(); | |
275 } | |
276 | |
277 media::OutputDeviceStatus WebRtcLocalAudioRenderer::GetDeviceStatus() { | |
278 DCHECK(task_runner_->BelongsToCurrentThread()); | |
279 if (!sink_.get()) | |
280 return media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL; | |
281 | |
282 return sink_->GetDeviceStatus(); | |
283 } | |
284 | |
285 void WebRtcLocalAudioRenderer::MaybeStartSink() { | |
286 DCHECK(task_runner_->BelongsToCurrentThread()); | |
287 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink()"; | |
288 | |
289 if (!sink_.get() || !source_params_.IsValid()) | |
290 return; | |
291 | |
292 { | |
293 // Clear up the old data in the FIFO. | |
294 base::AutoLock auto_lock(thread_lock_); | |
295 audio_shifter_->Flush(); | |
296 } | |
297 | |
298 if (!sink_params_.IsValid() || !playing_ || !volume_ || sink_started_ || | |
299 sink_->GetDeviceStatus() != media::OUTPUT_DEVICE_STATUS_OK) | |
300 return; | |
301 | |
302 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink() -- Starting sink_."; | |
303 sink_->Initialize(sink_params_, this); | |
304 sink_->Start(); | |
305 sink_started_ = true; | |
306 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", | |
307 kSinkStarted, kSinkStatesMax); | |
308 } | |
309 | |
310 void WebRtcLocalAudioRenderer::ReconfigureSink( | |
311 const media::AudioParameters& params) { | |
312 DCHECK(task_runner_->BelongsToCurrentThread()); | |
313 | |
314 DVLOG(1) << "WebRtcLocalAudioRenderer::ReconfigureSink()"; | |
315 | |
316 if (source_params_.Equals(params)) | |
317 return; | |
318 | |
319 // Reset the |source_params_|, |sink_params_| and |loopback_fifo_| to match | |
320 // the new format. | |
321 | |
322 source_params_ = params; | |
323 { | |
324 // Note: The max buffer is fairly large, but will rarely be used. | |
325 // Cast needs the buffer to hold at least one second of audio. | |
326 // The clock accuracy is set to 20ms because clock accuracy is | |
327 // ~15ms on windows. | |
328 media::AudioShifter* const new_shifter = new media::AudioShifter( | |
329 base::TimeDelta::FromSeconds(2), | |
330 base::TimeDelta::FromMilliseconds(20), | |
331 base::TimeDelta::FromSeconds(20), | |
332 source_params_.sample_rate(), | |
333 params.channels()); | |
334 | |
335 base::AutoLock auto_lock(thread_lock_); | |
336 audio_shifter_.reset(new_shifter); | |
337 } | |
338 | |
339 if (!sink_.get()) | |
340 return; // WebRtcLocalAudioRenderer has not yet been started. | |
341 | |
342 // Stop |sink_| and re-create a new one to be initialized with different audio | |
343 // parameters. Then, invoke MaybeStartSink() to restart everything again. | |
344 sink_->Stop(); | |
345 sink_started_ = false; | |
346 sink_ = | |
347 AudioDeviceFactory::NewOutputDevice(source_render_frame_id_, session_id_, | |
348 output_device_id_, security_origin_); | |
349 int frames_per_buffer = sink_->GetOutputParameters().frames_per_buffer(); | |
350 sink_params_ = source_params_; | |
351 sink_params_.set_frames_per_buffer(WebRtcAudioRenderer::GetOptimalBufferSize( | |
352 source_params_.sample_rate(), frames_per_buffer)); | |
353 MaybeStartSink(); | |
354 } | |
355 | |
356 } // namespace content | |
OLD | NEW |