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_.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