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 18 namespace content { 19 20 namespace { 21 22 enum LocalRendererSinkStates { 23 kSinkStarted = 0, 24 kSinkNeverStarted, 25 kSinkStatesMax // Must always be last! 26 }; 27 28 } // namespace 29 30 // media::AudioRendererSink::RenderCallback implementation 31 int 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 55 void WebRtcLocalAudioRenderer::OnRenderError() { 56 NOTIMPLEMENTED(); 57 } 58 59 // content::MediaStreamAudioSink implementation 60 void 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 88 void 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. 143 WebRtcLocalAudioRenderer::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 161 WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer() { 162 DCHECK(message_loop_->BelongsToCurrentThread()); 163 DCHECK(!sink_.get()); 164 DVLOG(1) << "WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer()"; 165 } 166 167 void 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 183 void 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 211 void 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 230 void 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 244 void 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 262 base::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 270 bool WebRtcLocalAudioRenderer::IsLocalRenderer() const { 271 return true; 272 } 273 274 void 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 297 void 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