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.
5 #include "content/renderer/media/webrtc_local_audio_renderer.h"
7 #include "base/debug/trace_event.h"
8 #include "base/logging.h"
9 #include "base/message_loop/message_loop_proxy.h"
10 #include "base/metrics/histogram.h"
11 #include "base/synchronization/lock.h"
12 #include "content/renderer/media/audio_device_factory.h"
13 #include "content/renderer/media/media_stream_dispatcher.h"
14 #include "content/renderer/media/webrtc_audio_capturer.h"
15 #include "content/renderer/media/webrtc_audio_renderer.h"
16 #include "content/renderer/render_frame_impl.h"
17 #include "media/audio/audio_output_device.h"
18 #include "media/base/audio_block_fifo.h"
19 #include "media/base/audio_bus.h"
25 enum LocalRendererSinkStates {
28 kSinkStatesMax // Must always be last!
33 // media::AudioRendererSink::RenderCallback implementation
34 int WebRtcLocalAudioRenderer::Render(
35 media::AudioBus* audio_bus, int audio_delay_milliseconds) {
36 TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::Render");
37 base::AutoLock auto_lock(thread_lock_);
39 if (!playing_ || !volume_ || !loopback_fifo_) {
44 // Provide data by reading from the FIFO if the FIFO contains enough
45 // to fulfill the request.
46 if (loopback_fifo_->available_blocks()) {
47 const media::AudioBus* audio_data = loopback_fifo_->Consume();
48 DCHECK_EQ(audio_data->frames(), audio_bus->frames());
49 audio_data->CopyTo(audio_bus);
52 // This warning is perfectly safe if it happens for the first audio
53 // frames. It should not happen in a steady-state mode.
54 DVLOG(2) << "loopback FIFO is empty";
57 return audio_bus->frames();
60 void WebRtcLocalAudioRenderer::OnRenderError() {
64 // content::MediaStreamAudioSink implementation
65 void WebRtcLocalAudioRenderer::OnData(const int16* audio_data,
67 int number_of_channels,
68 int number_of_frames) {
69 DCHECK(capture_thread_checker_.CalledOnValidThread());
70 TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::CaptureData");
71 base::AutoLock auto_lock(thread_lock_);
72 if (!playing_ || !volume_ || !loopback_fifo_)
75 // Push captured audio to FIFO so it can be read by a local sink.
76 if (loopback_fifo_->GetUnfilledFrames() >= number_of_frames) {
77 loopback_fifo_->Push(audio_data, number_of_frames, sizeof(audio_data[0]));
79 const base::TimeTicks now = base::TimeTicks::Now();
80 total_render_time_ += now - last_render_time_;
81 last_render_time_ = now;
83 DVLOG(1) << "FIFO is full";
87 void WebRtcLocalAudioRenderer::OnSetFormat(
88 const media::AudioParameters& params) {
89 DVLOG(1) << "WebRtcLocalAudioRenderer::OnSetFormat()";
90 // If the source is restarted, we might have changed to another capture
92 capture_thread_checker_.DetachFromThread();
93 DCHECK(capture_thread_checker_.CalledOnValidThread());
95 // Post a task on the main render thread to reconfigure the |sink_| with the
97 message_loop_->PostTask(
99 base::Bind(&WebRtcLocalAudioRenderer::ReconfigureSink, this,
103 // WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer implementation.
104 WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer(
105 const blink::WebMediaStreamTrack& audio_track,
106 int source_render_view_id,
107 int source_render_frame_id,
109 int frames_per_buffer)
110 : audio_track_(audio_track),
111 source_render_view_id_(source_render_view_id),
112 source_render_frame_id_(source_render_frame_id),
113 session_id_(session_id),
114 message_loop_(base::MessageLoopProxy::current()),
116 frames_per_buffer_(frames_per_buffer),
118 sink_started_(false) {
119 DVLOG(1) << "WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer()";
122 WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer() {
123 DCHECK(message_loop_->BelongsToCurrentThread());
124 DCHECK(!sink_.get());
125 DVLOG(1) << "WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer()";
128 void WebRtcLocalAudioRenderer::Start() {
129 DVLOG(1) << "WebRtcLocalAudioRenderer::Start()";
130 DCHECK(message_loop_->BelongsToCurrentThread());
132 // We get audio data from |audio_track_|...
133 MediaStreamAudioSink::AddToAudioTrack(this, audio_track_);
134 // ...and |sink_| will get audio data from us.
135 DCHECK(!sink_.get());
136 sink_ = AudioDeviceFactory::NewOutputDevice(source_render_view_id_,
137 source_render_frame_id_);
139 base::AutoLock auto_lock(thread_lock_);
140 last_render_time_ = base::TimeTicks::Now();
144 void WebRtcLocalAudioRenderer::Stop() {
145 DVLOG(1) << "WebRtcLocalAudioRenderer::Stop()";
146 DCHECK(message_loop_->BelongsToCurrentThread());
149 base::AutoLock auto_lock(thread_lock_);
151 loopback_fifo_.reset();
154 // Stop the output audio stream, i.e, stop asking for data to render.
155 // It is safer to call Stop() on the |sink_| to clean up the resources even
156 // when the |sink_| is never started.
162 if (!sink_started_) {
163 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates",
164 kSinkNeverStarted, kSinkStatesMax);
166 sink_started_ = false;
168 // Ensure that the capturer stops feeding us with captured audio.
169 MediaStreamAudioSink::RemoveFromAudioTrack(this, audio_track_);
172 void WebRtcLocalAudioRenderer::Play() {
173 DVLOG(1) << "WebRtcLocalAudioRenderer::Play()";
174 DCHECK(message_loop_->BelongsToCurrentThread());
180 base::AutoLock auto_lock(thread_lock_);
181 // Resumes rendering by ensuring that WebRtcLocalAudioRenderer::Render()
182 // now reads data from the local FIFO.
184 last_render_time_ = base::TimeTicks::Now();
187 // Note: If volume_ is currently muted, the |sink_| will not be started yet.
191 void WebRtcLocalAudioRenderer::Pause() {
192 DVLOG(1) << "WebRtcLocalAudioRenderer::Pause()";
193 DCHECK(message_loop_->BelongsToCurrentThread());
198 base::AutoLock auto_lock(thread_lock_);
199 // Temporarily suspends rendering audio.
200 // WebRtcLocalAudioRenderer::Render() will return early during this state
201 // and only zeros will be provided to the active sink.
205 void WebRtcLocalAudioRenderer::SetVolume(float volume) {
206 DVLOG(1) << "WebRtcLocalAudioRenderer::SetVolume(" << volume << ")";
207 DCHECK(message_loop_->BelongsToCurrentThread());
210 base::AutoLock auto_lock(thread_lock_);
215 // Lazily start the |sink_| when the local renderer is unmuted during
220 sink_->SetVolume(volume);
223 base::TimeDelta WebRtcLocalAudioRenderer::GetCurrentRenderTime() const {
224 DCHECK(message_loop_->BelongsToCurrentThread());
225 base::AutoLock auto_lock(thread_lock_);
227 return base::TimeDelta();
228 return total_render_time();
231 bool WebRtcLocalAudioRenderer::IsLocalRenderer() const {
235 void WebRtcLocalAudioRenderer::MaybeStartSink() {
236 DCHECK(message_loop_->BelongsToCurrentThread());
237 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink()";
239 if (!sink_.get() || !source_params_.IsValid())
243 // Clear up the old data in the FIFO.
244 base::AutoLock auto_lock(thread_lock_);
245 loopback_fifo_->Clear();
248 if (!sink_params_.IsValid() || !playing_ || !volume_ || sink_started_)
251 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink() -- Starting sink_.";
252 sink_->InitializeWithSessionId(sink_params_, this, session_id_);
254 sink_started_ = true;
255 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates",
256 kSinkStarted, kSinkStatesMax);
259 void WebRtcLocalAudioRenderer::ReconfigureSink(
260 const media::AudioParameters& params) {
261 DCHECK(message_loop_->BelongsToCurrentThread());
263 DVLOG(1) << "WebRtcLocalAudioRenderer::ReconfigureSink()";
265 int implicit_ducking_effect = 0;
266 RenderFrameImpl* const frame =
267 RenderFrameImpl::FromRoutingID(source_render_frame_id_);
268 MediaStreamDispatcher* const dispatcher = frame ?
269 frame->GetMediaStreamDispatcher() : NULL;
270 if (dispatcher && dispatcher->IsAudioDuckingActive()) {
271 DVLOG(1) << "Forcing DUCKING to be ON for output";
272 implicit_ducking_effect = media::AudioParameters::DUCKING;
274 DVLOG(1) << "DUCKING not forced ON for output";
277 if (source_params_.Equals(params))
280 // Reset the |source_params_|, |sink_params_| and |loopback_fifo_| to match
283 source_params_ = params;
285 sink_params_ = media::AudioParameters(source_params_.format(),
286 source_params_.channel_layout(), source_params_.sample_rate(),
287 source_params_.bits_per_sample(),
288 WebRtcAudioRenderer::GetOptimalBufferSize(source_params_.sample_rate(),
290 // If DUCKING is enabled on the source, it needs to be enabled on the
292 source_params_.effects() | implicit_ducking_effect);
295 // TODO(henrika): we could add a more dynamic solution here but I prefer
296 // a fixed size combined with bad audio at overflow. The alternative is
297 // that we start to build up latency and that can be more difficult to
298 // detect. Tests have shown that the FIFO never contains more than 2 or 3
299 // audio frames but I have selected a max size of ten buffers just
300 // in case since these tests were performed on a 16 core, 64GB Win 7
301 // machine. We could also add some sort of error notifier in this area if
302 // the FIFO overflows.
303 const int blocks_of_buffers =
304 10 * params.frames_per_buffer() / sink_params_.frames_per_buffer() + 1;
305 media::AudioBlockFifo* new_fifo = new media::AudioBlockFifo(
306 params.channels(), sink_params_.frames_per_buffer(), blocks_of_buffers);
308 base::AutoLock auto_lock(thread_lock_);
309 loopback_fifo_.reset(new_fifo);
313 return; // WebRtcLocalAudioRenderer has not yet been started.
315 // Stop |sink_| and re-create a new one to be initialized with different audio
316 // parameters. Then, invoke MaybeStartSink() to restart everything again.
319 sink_started_ = false;
322 sink_ = AudioDeviceFactory::NewOutputDevice(source_render_view_id_,
323 source_render_frame_id_);
327 } // namespace content