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 "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" 19 20namespace content { 21 22namespace { 23 24enum LocalRendererSinkStates { 25 kSinkStarted = 0, 26 kSinkNeverStarted, 27 kSinkStatesMax // Must always be last! 28}; 29 30} // namespace 31 32// media::AudioRendererSink::RenderCallback implementation 33int WebRtcLocalAudioRenderer::Render( 34 media::AudioBus* audio_bus, int audio_delay_milliseconds) { 35 TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::Render"); 36 base::AutoLock auto_lock(thread_lock_); 37 38 if (!playing_ || !volume_ || !loopback_fifo_) { 39 audio_bus->Zero(); 40 return 0; 41 } 42 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); 49 } else { 50 audio_bus->Zero(); 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"; 54 } 55 56 return audio_bus->frames(); 57} 58 59void WebRtcLocalAudioRenderer::OnRenderError() { 60 NOTIMPLEMENTED(); 61} 62 63// content::MediaStreamAudioSink implementation 64void WebRtcLocalAudioRenderer::OnData(const int16* audio_data, 65 int sample_rate, 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_) 72 return; 73 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])); 77 78 const base::TimeTicks now = base::TimeTicks::Now(); 79 total_render_time_ += now - last_render_time_; 80 last_render_time_ = now; 81 } else { 82 DVLOG(1) << "FIFO is full"; 83 } 84} 85 86void 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 90 // thread. 91 capture_thread_checker_.DetachFromThread(); 92 DCHECK(capture_thread_checker_.CalledOnValidThread()); 93 94 // Post a task on the main render thread to reconfigure the |sink_| with the 95 // new format. 96 message_loop_->PostTask( 97 FROM_HERE, 98 base::Bind(&WebRtcLocalAudioRenderer::ReconfigureSink, this, 99 params)); 100} 101 102// WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer implementation. 103WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer( 104 const blink::WebMediaStreamTrack& audio_track, 105 int source_render_view_id, 106 int source_render_frame_id, 107 int session_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()), 114 playing_(false), 115 frames_per_buffer_(frames_per_buffer), 116 volume_(0.0), 117 sink_started_(false) { 118 DVLOG(1) << "WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer()"; 119} 120 121WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer() { 122 DCHECK(message_loop_->BelongsToCurrentThread()); 123 DCHECK(!sink_.get()); 124 DVLOG(1) << "WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer()"; 125} 126 127void WebRtcLocalAudioRenderer::Start() { 128 DVLOG(1) << "WebRtcLocalAudioRenderer::Start()"; 129 DCHECK(message_loop_->BelongsToCurrentThread()); 130 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_); 137 138 base::AutoLock auto_lock(thread_lock_); 139 last_render_time_ = base::TimeTicks::Now(); 140 playing_ = false; 141} 142 143void WebRtcLocalAudioRenderer::Stop() { 144 DVLOG(1) << "WebRtcLocalAudioRenderer::Stop()"; 145 DCHECK(message_loop_->BelongsToCurrentThread()); 146 147 { 148 base::AutoLock auto_lock(thread_lock_); 149 playing_ = false; 150 loopback_fifo_.reset(); 151 } 152 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. 156 if (sink_.get()) { 157 sink_->Stop(); 158 sink_ = NULL; 159 } 160 161 if (!sink_started_) { 162 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", 163 kSinkNeverStarted, kSinkStatesMax); 164 } 165 sink_started_ = false; 166 167 // Ensure that the capturer stops feeding us with captured audio. 168 MediaStreamAudioSink::RemoveFromAudioTrack(this, audio_track_); 169} 170 171void WebRtcLocalAudioRenderer::Play() { 172 DVLOG(1) << "WebRtcLocalAudioRenderer::Play()"; 173 DCHECK(message_loop_->BelongsToCurrentThread()); 174 175 if (!sink_.get()) 176 return; 177 178 { 179 base::AutoLock auto_lock(thread_lock_); 180 // Resumes rendering by ensuring that WebRtcLocalAudioRenderer::Render() 181 // now reads data from the local FIFO. 182 playing_ = true; 183 last_render_time_ = base::TimeTicks::Now(); 184 } 185 186 // Note: If volume_ is currently muted, the |sink_| will not be started yet. 187 MaybeStartSink(); 188} 189 190void WebRtcLocalAudioRenderer::Pause() { 191 DVLOG(1) << "WebRtcLocalAudioRenderer::Pause()"; 192 DCHECK(message_loop_->BelongsToCurrentThread()); 193 194 if (!sink_.get()) 195 return; 196 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. 201 playing_ = false; 202} 203 204void WebRtcLocalAudioRenderer::SetVolume(float volume) { 205 DVLOG(1) << "WebRtcLocalAudioRenderer::SetVolume(" << volume << ")"; 206 DCHECK(message_loop_->BelongsToCurrentThread()); 207 208 { 209 base::AutoLock auto_lock(thread_lock_); 210 // Cache the volume. 211 volume_ = volume; 212 } 213 214 // Lazily start the |sink_| when the local renderer is unmuted during 215 // playing. 216 MaybeStartSink(); 217 218 if (sink_.get()) 219 sink_->SetVolume(volume); 220} 221 222base::TimeDelta WebRtcLocalAudioRenderer::GetCurrentRenderTime() const { 223 DCHECK(message_loop_->BelongsToCurrentThread()); 224 base::AutoLock auto_lock(thread_lock_); 225 if (!sink_.get()) 226 return base::TimeDelta(); 227 return total_render_time(); 228} 229 230bool WebRtcLocalAudioRenderer::IsLocalRenderer() const { 231 return true; 232} 233 234void WebRtcLocalAudioRenderer::MaybeStartSink() { 235 DCHECK(message_loop_->BelongsToCurrentThread()); 236 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink()"; 237 238 if (!sink_.get() || !source_params_.IsValid()) 239 return; 240 241 { 242 // Clear up the old data in the FIFO. 243 base::AutoLock auto_lock(thread_lock_); 244 loopback_fifo_->Clear(); 245 } 246 247 if (!sink_params_.IsValid() || !playing_ || !volume_ || sink_started_) 248 return; 249 250 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink() -- Starting sink_."; 251 sink_->InitializeWithSessionId(sink_params_, this, session_id_); 252 sink_->Start(); 253 sink_started_ = true; 254 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", 255 kSinkStarted, kSinkStatesMax); 256} 257 258void WebRtcLocalAudioRenderer::ReconfigureSink( 259 const media::AudioParameters& params) { 260 DCHECK(message_loop_->BelongsToCurrentThread()); 261 262 DVLOG(1) << "WebRtcLocalAudioRenderer::ReconfigureSink()"; 263 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; 272 } else { 273 DVLOG(1) << "DUCKING not forced ON for output"; 274 } 275 276 if (source_params_.Equals(params)) 277 return; 278 279 // Reset the |source_params_|, |sink_params_| and |loopback_fifo_| to match 280 // the new format. 281 282 source_params_ = params; 283 284 sink_params_ = media::AudioParameters(source_params_.format(), 285 source_params_.channel_layout(), source_params_.sample_rate(), 286 source_params_.bits_per_sample(), 287#if defined(OS_ANDROID) 288 // On Android, input and output use the same sample rate. In order to 289 // use the low latency mode, we need to use the buffer size suggested by 290 // the AudioManager for the sink. It will later be used to decide 291 // the buffer size of the shared memory buffer. 292 frames_per_buffer_, 293#else 294 2 * source_params_.frames_per_buffer(), 295#endif 296 // If DUCKING is enabled on the source, it needs to be enabled on the 297 // sink as well. 298 source_params_.effects() | implicit_ducking_effect); 299 300 { 301 // TODO(henrika): we could add a more dynamic solution here but I prefer 302 // a fixed size combined with bad audio at overflow. The alternative is 303 // that we start to build up latency and that can be more difficult to 304 // detect. Tests have shown that the FIFO never contains more than 2 or 3 305 // audio frames but I have selected a max size of ten buffers just 306 // in case since these tests were performed on a 16 core, 64GB Win 7 307 // machine. We could also add some sort of error notifier in this area if 308 // the FIFO overflows. 309 const int blocks_of_buffers = 310 10 * params.frames_per_buffer() / sink_params_.frames_per_buffer() + 1; 311 media::AudioBlockFifo* new_fifo = new media::AudioBlockFifo( 312 params.channels(), sink_params_.frames_per_buffer(), blocks_of_buffers); 313 314 base::AutoLock auto_lock(thread_lock_); 315 loopback_fifo_.reset(new_fifo); 316 } 317 318 if (!sink_.get()) 319 return; // WebRtcLocalAudioRenderer has not yet been started. 320 321 // Stop |sink_| and re-create a new one to be initialized with different audio 322 // parameters. Then, invoke MaybeStartSink() to restart everything again. 323 if (sink_started_) { 324 sink_->Stop(); 325 sink_started_ = false; 326 } 327 328 sink_ = AudioDeviceFactory::NewOutputDevice(source_render_view_id_, 329 source_render_frame_id_); 330 MaybeStartSink(); 331} 332 333} // namespace content 334