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