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_.Reset(source_params_.format(), 106 source_params_.channel_layout(), 107 source_params_.channels(), 108 source_params_.input_channels(), 109 source_params_.sample_rate(), 110 source_params_.bits_per_sample(), 111 #if defined(OS_ANDROID) 112 // On Android, input and output are using same sampling rate. In order to 113 // achieve low latency mode, we need use buffer size suggested by 114 // AudioManager for the sink paramters which will be used to decide 115 // buffer size for shared memory buffer. 116 frames_per_buffer_ 117 #else 118 2 * source_params_.frames_per_buffer() 119 #endif 120 ); 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 session_id, 147 int frames_per_buffer) 148 : audio_track_(audio_track), 149 source_render_view_id_(source_render_view_id), 150 session_id_(session_id), 151 message_loop_(base::MessageLoopProxy::current()), 152 playing_(false), 153 frames_per_buffer_(frames_per_buffer), 154 volume_(0.0), 155 sink_started_(false) { 156 DVLOG(1) << "WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer()"; 157 } 158 159 WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer() { 160 DCHECK(message_loop_->BelongsToCurrentThread()); 161 DCHECK(!sink_.get()); 162 DVLOG(1) << "WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer()"; 163 } 164 165 void WebRtcLocalAudioRenderer::Start() { 166 DVLOG(1) << "WebRtcLocalAudioRenderer::Start()"; 167 DCHECK(message_loop_->BelongsToCurrentThread()); 168 169 // We get audio data from |audio_track_|... 170 MediaStreamAudioSink::AddToAudioTrack(this, audio_track_); 171 // ...and |sink_| will get audio data from us. 172 DCHECK(!sink_.get()); 173 sink_ = AudioDeviceFactory::NewOutputDevice(source_render_view_id_); 174 175 base::AutoLock auto_lock(thread_lock_); 176 last_render_time_ = base::TimeTicks::Now(); 177 playing_ = false; 178 } 179 180 void WebRtcLocalAudioRenderer::Stop() { 181 DVLOG(1) << "WebRtcLocalAudioRenderer::Stop()"; 182 DCHECK(message_loop_->BelongsToCurrentThread()); 183 184 { 185 base::AutoLock auto_lock(thread_lock_); 186 playing_ = false; 187 loopback_fifo_.reset(); 188 } 189 190 // Stop the output audio stream, i.e, stop asking for data to render. 191 // It is safer to call Stop() on the |sink_| to clean up the resources even 192 // when the |sink_| is never started. 193 if (sink_) { 194 sink_->Stop(); 195 sink_ = NULL; 196 } 197 198 if (!sink_started_) { 199 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", 200 kSinkNeverStarted, kSinkStatesMax); 201 } 202 sink_started_ = false; 203 204 // Ensure that the capturer stops feeding us with captured audio. 205 MediaStreamAudioSink::RemoveFromAudioTrack(this, audio_track_); 206 } 207 208 void WebRtcLocalAudioRenderer::Play() { 209 DVLOG(1) << "WebRtcLocalAudioRenderer::Play()"; 210 DCHECK(message_loop_->BelongsToCurrentThread()); 211 212 if (!sink_.get()) 213 return; 214 215 { 216 base::AutoLock auto_lock(thread_lock_); 217 // Resumes rendering by ensuring that WebRtcLocalAudioRenderer::Render() 218 // now reads data from the local FIFO. 219 playing_ = true; 220 last_render_time_ = base::TimeTicks::Now(); 221 } 222 223 // Note: If volume_ is currently muted, the |sink_| will not be started yet. 224 MaybeStartSink(); 225 } 226 227 void WebRtcLocalAudioRenderer::Pause() { 228 DVLOG(1) << "WebRtcLocalAudioRenderer::Pause()"; 229 DCHECK(message_loop_->BelongsToCurrentThread()); 230 231 if (!sink_.get()) 232 return; 233 234 base::AutoLock auto_lock(thread_lock_); 235 // Temporarily suspends rendering audio. 236 // WebRtcLocalAudioRenderer::Render() will return early during this state 237 // and only zeros will be provided to the active sink. 238 playing_ = false; 239 } 240 241 void WebRtcLocalAudioRenderer::SetVolume(float volume) { 242 DVLOG(1) << "WebRtcLocalAudioRenderer::SetVolume(" << volume << ")"; 243 DCHECK(message_loop_->BelongsToCurrentThread()); 244 245 { 246 base::AutoLock auto_lock(thread_lock_); 247 // Cache the volume. 248 volume_ = volume; 249 } 250 251 // Lazily start the |sink_| when the local renderer is unmuted during 252 // playing. 253 MaybeStartSink(); 254 255 if (sink_.get()) 256 sink_->SetVolume(volume); 257 } 258 259 base::TimeDelta WebRtcLocalAudioRenderer::GetCurrentRenderTime() const { 260 DCHECK(message_loop_->BelongsToCurrentThread()); 261 base::AutoLock auto_lock(thread_lock_); 262 if (!sink_.get()) 263 return base::TimeDelta(); 264 return total_render_time(); 265 } 266 267 bool WebRtcLocalAudioRenderer::IsLocalRenderer() const { 268 return true; 269 } 270 271 void WebRtcLocalAudioRenderer::MaybeStartSink() { 272 DCHECK(message_loop_->BelongsToCurrentThread()); 273 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink()"; 274 275 if (!sink_.get() || !source_params_.IsValid()) 276 return; 277 278 base::AutoLock auto_lock(thread_lock_); 279 280 // Clear up the old data in the FIFO. 281 loopback_fifo_->Clear(); 282 283 if (!sink_params_.IsValid() || !playing_ || !volume_ || sink_started_) 284 return; 285 286 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink() -- Starting sink_."; 287 sink_->InitializeUnifiedStream(sink_params_, this, session_id_); 288 sink_->Start(); 289 sink_started_ = true; 290 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", 291 kSinkStarted, kSinkStatesMax); 292 } 293 294 void WebRtcLocalAudioRenderer::ReconfigureSink( 295 const media::AudioParameters& params) { 296 DCHECK(message_loop_->BelongsToCurrentThread()); 297 298 DVLOG(1) << "WebRtcLocalAudioRenderer::ReconfigureSink()"; 299 300 if (!sink_) 301 return; // WebRtcLocalAudioRenderer has not yet been started. 302 303 // Stop |sink_| and re-create a new one to be initialized with different audio 304 // parameters. Then, invoke MaybeStartSink() to restart everything again. 305 if (sink_started_) { 306 sink_->Stop(); 307 sink_started_ = false; 308 } 309 sink_ = AudioDeviceFactory::NewOutputDevice(source_render_view_id_); 310 MaybeStartSink(); 311 } 312 313 } // namespace content 314