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/webrtc_audio_capturer.h" 14#include "media/audio/audio_output_device.h" 15#include "media/base/audio_bus.h" 16#include "media/base/audio_fifo.h" 17 18namespace content { 19 20namespace { 21 22enum LocalRendererSinkStates { 23 kSinkStarted = 0, 24 kSinkNeverStarted, 25 kSinkStatesMax // Must always be last! 26}; 27 28} // namespace 29 30// media::AudioRendererSink::RenderCallback implementation 31int WebRtcLocalAudioRenderer::Render( 32 media::AudioBus* audio_bus, int audio_delay_milliseconds) { 33 TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::Render"); 34 base::AutoLock auto_lock(thread_lock_); 35 36 if (!playing_ || !volume_ || !loopback_fifo_) { 37 audio_bus->Zero(); 38 return 0; 39 } 40 41 // Provide data by reading from the FIFO if the FIFO contains enough 42 // to fulfill the request. 43 if (loopback_fifo_->frames() >= audio_bus->frames()) { 44 loopback_fifo_->Consume(audio_bus, 0, audio_bus->frames()); 45 } else { 46 audio_bus->Zero(); 47 // This warning is perfectly safe if it happens for the first audio 48 // frames. It should not happen in a steady-state mode. 49 DVLOG(2) << "loopback FIFO is empty"; 50 } 51 52 return audio_bus->frames(); 53} 54 55void WebRtcLocalAudioRenderer::OnRenderError() { 56 NOTIMPLEMENTED(); 57} 58 59// content::MediaStreamAudioSink implementation 60void WebRtcLocalAudioRenderer::OnData(const int16* audio_data, 61 int sample_rate, 62 int number_of_channels, 63 int number_of_frames) { 64 DCHECK(capture_thread_checker_.CalledOnValidThread()); 65 TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::CaptureData"); 66 base::AutoLock auto_lock(thread_lock_); 67 if (!playing_ || !volume_ || !loopback_fifo_) 68 return; 69 70 // Push captured audio to FIFO so it can be read by a local sink. 71 if (loopback_fifo_->frames() + number_of_frames <= 72 loopback_fifo_->max_frames()) { 73 scoped_ptr<media::AudioBus> audio_source = media::AudioBus::Create( 74 number_of_channels, number_of_frames); 75 audio_source->FromInterleaved(audio_data, 76 audio_source->frames(), 77 sizeof(audio_data[0])); 78 loopback_fifo_->Push(audio_source.get()); 79 80 const base::TimeTicks now = base::TimeTicks::Now(); 81 total_render_time_ += now - last_render_time_; 82 last_render_time_ = now; 83 } else { 84 DVLOG(1) << "FIFO is full"; 85 } 86} 87 88void WebRtcLocalAudioRenderer::OnSetFormat( 89 const media::AudioParameters& params) { 90 DVLOG(1) << "WebRtcLocalAudioRenderer::OnSetFormat()"; 91 // If the source is restarted, we might have changed to another capture 92 // thread. 93 capture_thread_checker_.DetachFromThread(); 94 DCHECK(capture_thread_checker_.CalledOnValidThread()); 95 96 // Reset the |source_params_|, |sink_params_| and |loopback_fifo_| to match 97 // the new format. 98 { 99 base::AutoLock auto_lock(thread_lock_); 100 if (source_params_ == params) 101 return; 102 103 source_params_ = params; 104 105 sink_params_ = media::AudioParameters(source_params_.format(), 106 source_params_.channel_layout(), source_params_.channels(), 107 source_params_.input_channels(), source_params_.sample_rate(), 108 source_params_.bits_per_sample(), 109#if defined(OS_ANDROID) 110 // On Android, input and output use the same sample rate. In order to 111 // use the low latency mode, we need to use the buffer size suggested by 112 // the AudioManager for the sink. It will later be used to decide 113 // the buffer size of the shared memory buffer. 114 frames_per_buffer_, 115#else 116 2 * source_params_.frames_per_buffer(), 117#endif 118 // If DUCKING is enabled on the source, it needs to be enabled on the 119 // sink as well. 120 source_params_.effects()); 121 122 // TODO(henrika): we could add a more dynamic solution here but I prefer 123 // a fixed size combined with bad audio at overflow. The alternative is 124 // that we start to build up latency and that can be more difficult to 125 // detect. Tests have shown that the FIFO never contains more than 2 or 3 126 // audio frames but I have selected a max size of ten buffers just 127 // in case since these tests were performed on a 16 core, 64GB Win 7 128 // machine. We could also add some sort of error notifier in this area if 129 // the FIFO overflows. 130 loopback_fifo_.reset(new media::AudioFifo( 131 params.channels(), 10 * params.frames_per_buffer())); 132 } 133 134 // Post a task on the main render thread to reconfigure the |sink_| with the 135 // new format. 136 message_loop_->PostTask( 137 FROM_HERE, 138 base::Bind(&WebRtcLocalAudioRenderer::ReconfigureSink, this, 139 params)); 140} 141 142// WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer implementation. 143WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer( 144 const blink::WebMediaStreamTrack& audio_track, 145 int source_render_view_id, 146 int source_render_frame_id, 147 int session_id, 148 int frames_per_buffer) 149 : audio_track_(audio_track), 150 source_render_view_id_(source_render_view_id), 151 source_render_frame_id_(source_render_frame_id), 152 session_id_(session_id), 153 message_loop_(base::MessageLoopProxy::current()), 154 playing_(false), 155 frames_per_buffer_(frames_per_buffer), 156 volume_(0.0), 157 sink_started_(false) { 158 DVLOG(1) << "WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer()"; 159} 160 161WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer() { 162 DCHECK(message_loop_->BelongsToCurrentThread()); 163 DCHECK(!sink_.get()); 164 DVLOG(1) << "WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer()"; 165} 166 167void WebRtcLocalAudioRenderer::Start() { 168 DVLOG(1) << "WebRtcLocalAudioRenderer::Start()"; 169 DCHECK(message_loop_->BelongsToCurrentThread()); 170 171 // We get audio data from |audio_track_|... 172 MediaStreamAudioSink::AddToAudioTrack(this, audio_track_); 173 // ...and |sink_| will get audio data from us. 174 DCHECK(!sink_.get()); 175 sink_ = AudioDeviceFactory::NewOutputDevice(source_render_view_id_, 176 source_render_frame_id_); 177 178 base::AutoLock auto_lock(thread_lock_); 179 last_render_time_ = base::TimeTicks::Now(); 180 playing_ = false; 181} 182 183void WebRtcLocalAudioRenderer::Stop() { 184 DVLOG(1) << "WebRtcLocalAudioRenderer::Stop()"; 185 DCHECK(message_loop_->BelongsToCurrentThread()); 186 187 { 188 base::AutoLock auto_lock(thread_lock_); 189 playing_ = false; 190 loopback_fifo_.reset(); 191 } 192 193 // Stop the output audio stream, i.e, stop asking for data to render. 194 // It is safer to call Stop() on the |sink_| to clean up the resources even 195 // when the |sink_| is never started. 196 if (sink_) { 197 sink_->Stop(); 198 sink_ = NULL; 199 } 200 201 if (!sink_started_) { 202 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", 203 kSinkNeverStarted, kSinkStatesMax); 204 } 205 sink_started_ = false; 206 207 // Ensure that the capturer stops feeding us with captured audio. 208 MediaStreamAudioSink::RemoveFromAudioTrack(this, audio_track_); 209} 210 211void WebRtcLocalAudioRenderer::Play() { 212 DVLOG(1) << "WebRtcLocalAudioRenderer::Play()"; 213 DCHECK(message_loop_->BelongsToCurrentThread()); 214 215 if (!sink_.get()) 216 return; 217 218 { 219 base::AutoLock auto_lock(thread_lock_); 220 // Resumes rendering by ensuring that WebRtcLocalAudioRenderer::Render() 221 // now reads data from the local FIFO. 222 playing_ = true; 223 last_render_time_ = base::TimeTicks::Now(); 224 } 225 226 // Note: If volume_ is currently muted, the |sink_| will not be started yet. 227 MaybeStartSink(); 228} 229 230void WebRtcLocalAudioRenderer::Pause() { 231 DVLOG(1) << "WebRtcLocalAudioRenderer::Pause()"; 232 DCHECK(message_loop_->BelongsToCurrentThread()); 233 234 if (!sink_.get()) 235 return; 236 237 base::AutoLock auto_lock(thread_lock_); 238 // Temporarily suspends rendering audio. 239 // WebRtcLocalAudioRenderer::Render() will return early during this state 240 // and only zeros will be provided to the active sink. 241 playing_ = false; 242} 243 244void WebRtcLocalAudioRenderer::SetVolume(float volume) { 245 DVLOG(1) << "WebRtcLocalAudioRenderer::SetVolume(" << volume << ")"; 246 DCHECK(message_loop_->BelongsToCurrentThread()); 247 248 { 249 base::AutoLock auto_lock(thread_lock_); 250 // Cache the volume. 251 volume_ = volume; 252 } 253 254 // Lazily start the |sink_| when the local renderer is unmuted during 255 // playing. 256 MaybeStartSink(); 257 258 if (sink_.get()) 259 sink_->SetVolume(volume); 260} 261 262base::TimeDelta WebRtcLocalAudioRenderer::GetCurrentRenderTime() const { 263 DCHECK(message_loop_->BelongsToCurrentThread()); 264 base::AutoLock auto_lock(thread_lock_); 265 if (!sink_.get()) 266 return base::TimeDelta(); 267 return total_render_time(); 268} 269 270bool WebRtcLocalAudioRenderer::IsLocalRenderer() const { 271 return true; 272} 273 274void WebRtcLocalAudioRenderer::MaybeStartSink() { 275 DCHECK(message_loop_->BelongsToCurrentThread()); 276 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink()"; 277 278 if (!sink_.get() || !source_params_.IsValid()) 279 return; 280 281 base::AutoLock auto_lock(thread_lock_); 282 283 // Clear up the old data in the FIFO. 284 loopback_fifo_->Clear(); 285 286 if (!sink_params_.IsValid() || !playing_ || !volume_ || sink_started_) 287 return; 288 289 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink() -- Starting sink_."; 290 sink_->InitializeWithSessionId(sink_params_, this, session_id_); 291 sink_->Start(); 292 sink_started_ = true; 293 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", 294 kSinkStarted, kSinkStatesMax); 295} 296 297void WebRtcLocalAudioRenderer::ReconfigureSink( 298 const media::AudioParameters& params) { 299 DCHECK(message_loop_->BelongsToCurrentThread()); 300 301 DVLOG(1) << "WebRtcLocalAudioRenderer::ReconfigureSink()"; 302 303 if (!sink_) 304 return; // WebRtcLocalAudioRenderer has not yet been started. 305 306 // Stop |sink_| and re-create a new one to be initialized with different audio 307 // parameters. Then, invoke MaybeStartSink() to restart everything again. 308 if (sink_started_) { 309 sink_->Stop(); 310 sink_started_ = false; 311 } 312 sink_ = AudioDeviceFactory::NewOutputDevice(source_render_view_id_, 313 source_render_frame_id_); 314 MaybeStartSink(); 315} 316 317} // namespace content 318