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/render_frame_impl.h"
16 #include "media/audio/audio_output_device.h"
17 #include "media/base/audio_block_fifo.h"
18 #include "media/base/audio_bus.h"
24 enum LocalRendererSinkStates
{
27 kSinkStatesMax
// Must always be last!
32 // media::AudioRendererSink::RenderCallback implementation
33 int WebRtcLocalAudioRenderer::Render(
34 media::AudioBus
* audio_bus
, int audio_delay_milliseconds
) {
35 TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::Render");
36 base::AutoLock
auto_lock(thread_lock_
);
38 if (!playing_
|| !volume_
|| !loopback_fifo_
) {
43 // Provide data by reading from the FIFO if the FIFO contains enough
44 // to fulfill the request.
45 if (loopback_fifo_
->available_blocks()) {
46 const media::AudioBus
* audio_data
= loopback_fifo_
->Consume();
47 DCHECK_EQ(audio_data
->frames(), audio_bus
->frames());
48 audio_data
->CopyTo(audio_bus
);
51 // This warning is perfectly safe if it happens for the first audio
52 // frames. It should not happen in a steady-state mode.
53 DVLOG(2) << "loopback FIFO is empty";
56 return audio_bus
->frames();
59 void WebRtcLocalAudioRenderer::OnRenderError() {
63 // content::MediaStreamAudioSink implementation
64 void WebRtcLocalAudioRenderer::OnData(const int16
* audio_data
,
66 int number_of_channels
,
67 int number_of_frames
) {
68 DCHECK(capture_thread_checker_
.CalledOnValidThread());
69 TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::CaptureData");
70 base::AutoLock
auto_lock(thread_lock_
);
71 if (!playing_
|| !volume_
|| !loopback_fifo_
)
74 // Push captured audio to FIFO so it can be read by a local sink.
75 if (loopback_fifo_
->GetUnfilledFrames() >= number_of_frames
) {
76 loopback_fifo_
->Push(audio_data
, number_of_frames
, sizeof(audio_data
[0]));
78 const base::TimeTicks now
= base::TimeTicks::Now();
79 total_render_time_
+= now
- last_render_time_
;
80 last_render_time_
= now
;
82 DVLOG(1) << "FIFO is full";
86 void WebRtcLocalAudioRenderer::OnSetFormat(
87 const media::AudioParameters
& params
) {
88 DVLOG(1) << "WebRtcLocalAudioRenderer::OnSetFormat()";
89 // If the source is restarted, we might have changed to another capture
91 capture_thread_checker_
.DetachFromThread();
92 DCHECK(capture_thread_checker_
.CalledOnValidThread());
94 // Post a task on the main render thread to reconfigure the |sink_| with the
96 message_loop_
->PostTask(
98 base::Bind(&WebRtcLocalAudioRenderer::ReconfigureSink
, this,
102 // WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer implementation.
103 WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer(
104 const blink::WebMediaStreamTrack
& audio_track
,
105 int source_render_view_id
,
106 int source_render_frame_id
,
108 int frames_per_buffer
)
109 : audio_track_(audio_track
),
110 source_render_view_id_(source_render_view_id
),
111 source_render_frame_id_(source_render_frame_id
),
112 session_id_(session_id
),
113 message_loop_(base::MessageLoopProxy::current()),
115 frames_per_buffer_(frames_per_buffer
),
117 sink_started_(false) {
118 DVLOG(1) << "WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer()";
121 WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer() {
122 DCHECK(message_loop_
->BelongsToCurrentThread());
123 DCHECK(!sink_
.get());
124 DVLOG(1) << "WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer()";
127 void WebRtcLocalAudioRenderer::Start() {
128 DVLOG(1) << "WebRtcLocalAudioRenderer::Start()";
129 DCHECK(message_loop_
->BelongsToCurrentThread());
131 // We get audio data from |audio_track_|...
132 MediaStreamAudioSink::AddToAudioTrack(this, audio_track_
);
133 // ...and |sink_| will get audio data from us.
134 DCHECK(!sink_
.get());
135 sink_
= AudioDeviceFactory::NewOutputDevice(source_render_view_id_
,
136 source_render_frame_id_
);
138 base::AutoLock
auto_lock(thread_lock_
);
139 last_render_time_
= base::TimeTicks::Now();
143 void WebRtcLocalAudioRenderer::Stop() {
144 DVLOG(1) << "WebRtcLocalAudioRenderer::Stop()";
145 DCHECK(message_loop_
->BelongsToCurrentThread());
148 base::AutoLock
auto_lock(thread_lock_
);
150 loopback_fifo_
.reset();
153 // Stop the output audio stream, i.e, stop asking for data to render.
154 // It is safer to call Stop() on the |sink_| to clean up the resources even
155 // when the |sink_| is never started.
161 if (!sink_started_
) {
162 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates",
163 kSinkNeverStarted
, kSinkStatesMax
);
165 sink_started_
= false;
167 // Ensure that the capturer stops feeding us with captured audio.
168 MediaStreamAudioSink::RemoveFromAudioTrack(this, audio_track_
);
171 void WebRtcLocalAudioRenderer::Play() {
172 DVLOG(1) << "WebRtcLocalAudioRenderer::Play()";
173 DCHECK(message_loop_
->BelongsToCurrentThread());
179 base::AutoLock
auto_lock(thread_lock_
);
180 // Resumes rendering by ensuring that WebRtcLocalAudioRenderer::Render()
181 // now reads data from the local FIFO.
183 last_render_time_
= base::TimeTicks::Now();
186 // Note: If volume_ is currently muted, the |sink_| will not be started yet.
190 void WebRtcLocalAudioRenderer::Pause() {
191 DVLOG(1) << "WebRtcLocalAudioRenderer::Pause()";
192 DCHECK(message_loop_
->BelongsToCurrentThread());
197 base::AutoLock
auto_lock(thread_lock_
);
198 // Temporarily suspends rendering audio.
199 // WebRtcLocalAudioRenderer::Render() will return early during this state
200 // and only zeros will be provided to the active sink.
204 void WebRtcLocalAudioRenderer::SetVolume(float volume
) {
205 DVLOG(1) << "WebRtcLocalAudioRenderer::SetVolume(" << volume
<< ")";
206 DCHECK(message_loop_
->BelongsToCurrentThread());
209 base::AutoLock
auto_lock(thread_lock_
);
214 // Lazily start the |sink_| when the local renderer is unmuted during
219 sink_
->SetVolume(volume
);
222 base::TimeDelta
WebRtcLocalAudioRenderer::GetCurrentRenderTime() const {
223 DCHECK(message_loop_
->BelongsToCurrentThread());
224 base::AutoLock
auto_lock(thread_lock_
);
226 return base::TimeDelta();
227 return total_render_time();
230 bool WebRtcLocalAudioRenderer::IsLocalRenderer() const {
234 void WebRtcLocalAudioRenderer::MaybeStartSink() {
235 DCHECK(message_loop_
->BelongsToCurrentThread());
236 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink()";
238 if (!sink_
.get() || !source_params_
.IsValid())
242 // Clear up the old data in the FIFO.
243 base::AutoLock
auto_lock(thread_lock_
);
244 loopback_fifo_
->Clear();
247 if (!sink_params_
.IsValid() || !playing_
|| !volume_
|| sink_started_
)
250 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink() -- Starting sink_.";
251 sink_
->InitializeWithSessionId(sink_params_
, this, session_id_
);
253 sink_started_
= true;
254 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates",
255 kSinkStarted
, kSinkStatesMax
);
258 void WebRtcLocalAudioRenderer::ReconfigureSink(
259 const media::AudioParameters
& params
) {
260 DCHECK(message_loop_
->BelongsToCurrentThread());
262 DVLOG(1) << "WebRtcLocalAudioRenderer::ReconfigureSink()";
264 int implicit_ducking_effect
= 0;
265 RenderFrameImpl
* const frame
=
266 RenderFrameImpl::FromRoutingID(source_render_frame_id_
);
267 MediaStreamDispatcher
* const dispatcher
= frame
?
268 frame
->GetMediaStreamDispatcher() : NULL
;
269 if (dispatcher
&& dispatcher
->IsAudioDuckingActive()) {
270 DVLOG(1) << "Forcing DUCKING to be ON for output";
271 implicit_ducking_effect
= media::AudioParameters::DUCKING
;
273 DVLOG(1) << "DUCKING not forced ON for output";
276 if (source_params_
== params
)
279 // Reset the |source_params_|, |sink_params_| and |loopback_fifo_| to match
282 source_params_
= params
;
284 sink_params_
= media::AudioParameters(source_params_
.format(),
285 source_params_
.channel_layout(), source_params_
.channels(),
286 source_params_
.input_channels(), source_params_
.sample_rate(),
287 source_params_
.bits_per_sample(),
288 #if defined(OS_ANDROID)
289 // On Android, input and output use the same sample rate. In order to
290 // use the low latency mode, we need to use the buffer size suggested by
291 // the AudioManager for the sink. It will later be used to decide
292 // the buffer size of the shared memory buffer.
295 2 * source_params_
.frames_per_buffer(),
297 // If DUCKING is enabled on the source, it needs to be enabled on the
299 source_params_
.effects() | implicit_ducking_effect
);
302 // TODO(henrika): we could add a more dynamic solution here but I prefer
303 // a fixed size combined with bad audio at overflow. The alternative is
304 // that we start to build up latency and that can be more difficult to
305 // detect. Tests have shown that the FIFO never contains more than 2 or 3
306 // audio frames but I have selected a max size of ten buffers just
307 // in case since these tests were performed on a 16 core, 64GB Win 7
308 // machine. We could also add some sort of error notifier in this area if
309 // the FIFO overflows.
310 const int blocks_of_buffers
=
311 10 * params
.frames_per_buffer() / sink_params_
.frames_per_buffer() + 1;
312 media::AudioBlockFifo
* new_fifo
= new media::AudioBlockFifo(
313 params
.channels(), sink_params_
.frames_per_buffer(), blocks_of_buffers
);
315 base::AutoLock
auto_lock(thread_lock_
);
316 loopback_fifo_
.reset(new_fifo
);
320 return; // WebRtcLocalAudioRenderer has not yet been started.
322 // Stop |sink_| and re-create a new one to be initialized with different audio
323 // parameters. Then, invoke MaybeStartSink() to restart everything again.
326 sink_started_
= false;
329 sink_
= AudioDeviceFactory::NewOutputDevice(source_render_view_id_
,
330 source_render_frame_id_
);
334 } // namespace content