Home | History | Annotate | Download | only in media
      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