| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/renderer/media/webrtc_local_audio_renderer.h" |
| |
| #include "base/debug/trace_event.h" |
| #include "base/logging.h" |
| #include "base/message_loop/message_loop_proxy.h" |
| #include "base/synchronization/lock.h" |
| #include "content/renderer/media/audio_device_factory.h" |
| #include "content/renderer/media/webrtc_audio_capturer.h" |
| #include "content/renderer/render_thread_impl.h" |
| #include "media/audio/audio_output_device.h" |
| #include "media/base/audio_bus.h" |
| #include "media/base/audio_fifo.h" |
| #include "media/base/audio_hardware_config.h" |
| |
| namespace content { |
| |
| // media::AudioRendererSink::RenderCallback implementation |
| int WebRtcLocalAudioRenderer::Render( |
| media::AudioBus* audio_bus, int audio_delay_milliseconds) { |
| base::AutoLock auto_lock(thread_lock_); |
| |
| if (!playing_) { |
| audio_bus->Zero(); |
| return 0; |
| } |
| |
| TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::Render"); |
| |
| DCHECK(loopback_fifo_.get() != NULL); |
| |
| // Provide data by reading from the FIFO if the FIFO contains enough |
| // to fulfill the request. |
| if (loopback_fifo_->frames() >= audio_bus->frames()) { |
| loopback_fifo_->Consume(audio_bus, 0, audio_bus->frames()); |
| } else { |
| audio_bus->Zero(); |
| // This warning is perfectly safe if it happens for the first audio |
| // frames. It should not happen in a steady-state mode. |
| DVLOG(2) << "loopback FIFO is empty"; |
| } |
| |
| return audio_bus->frames(); |
| } |
| |
| void WebRtcLocalAudioRenderer::OnRenderError() { |
| NOTIMPLEMENTED(); |
| } |
| |
| // content::WebRtcAudioCapturerSink implementation |
| int WebRtcLocalAudioRenderer::CaptureData(const std::vector<int>& channels, |
| const int16* audio_data, |
| int sample_rate, |
| int number_of_channels, |
| int number_of_frames, |
| int audio_delay_milliseconds, |
| int current_volume, |
| bool need_audio_processing) { |
| TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::CaptureData"); |
| base::AutoLock auto_lock(thread_lock_); |
| |
| if (!playing_) |
| return 0; |
| |
| // Push captured audio to FIFO so it can be read by a local sink. |
| if (loopback_fifo_) { |
| if (loopback_fifo_->frames() + number_of_frames <= |
| loopback_fifo_->max_frames()) { |
| scoped_ptr<media::AudioBus> audio_source = media::AudioBus::Create( |
| number_of_channels, number_of_frames); |
| audio_source->FromInterleaved(audio_data, |
| audio_source->frames(), |
| sizeof(audio_data[0])); |
| loopback_fifo_->Push(audio_source.get()); |
| |
| base::Time now = base::Time::Now(); |
| total_render_time_ += now - last_render_time_; |
| last_render_time_ = now; |
| } else { |
| DVLOG(1) << "FIFO is full"; |
| } |
| } |
| |
| return 0; |
| } |
| |
| void WebRtcLocalAudioRenderer::SetCaptureFormat( |
| const media::AudioParameters& params) { |
| audio_params_ = params; |
| } |
| |
| // WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer implementation. |
| WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer( |
| WebRtcLocalAudioTrack* audio_track, |
| int source_render_view_id) |
| : audio_track_(audio_track), |
| source_render_view_id_(source_render_view_id), |
| playing_(false) { |
| DCHECK(audio_track); |
| DVLOG(1) << "WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer()"; |
| } |
| |
| WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(!sink_.get()); |
| DVLOG(1) << "WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer()"; |
| } |
| |
| void WebRtcLocalAudioRenderer::Start() { |
| DVLOG(1) << "WebRtcLocalAudioRenderer::Start()"; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| // Add this class as sink to the audio track to ensure that we receive |
| // WebRtcAudioCapturerSink::CaptureData() callbacks for captured audio. |
| // |audio_params_| will be updated right after the AddCapturerAudioTrack(). |
| audio_track_->AddSink(this); |
| |
| base::AutoLock auto_lock(thread_lock_); |
| DCHECK(!sink_.get()); |
| |
| // TODO(henrika): we could add a more dynamic solution here but I prefer |
| // a fixed size combined with bad audio at overflow. The alternative is |
| // that we start to build up latency and that can be more difficult to |
| // detect. Tests have shown that the FIFO never contains more than 2 or 3 |
| // audio frames but I have selected a max size of ten buffers just |
| // in case since these tests were performed on a 16 core, 64GB Win 7 |
| // machine. We could also add some sort of error notifier in this area if |
| // the FIFO overflows. |
| DCHECK(!loopback_fifo_); |
| loopback_fifo_.reset(new media::AudioFifo( |
| audio_params_.channels(), 10 * audio_params_.frames_per_buffer())); |
| |
| #if defined(OS_ANDROID) |
| media::AudioHardwareConfig* hardware_config = |
| RenderThreadImpl::current()->GetAudioHardwareConfig(); |
| #endif |
| |
| media::AudioParameters sink_params(audio_params_.format(), |
| audio_params_.channel_layout(), |
| audio_params_.sample_rate(), |
| audio_params_.bits_per_sample(), |
| #if defined(OS_ANDROID) |
| // On Android, input and output are using same sampling rate. In order to |
| // achieve low latency mode, we need use buffer size suggested by |
| // AudioManager for the sink paramters which will be used to decide |
| // buffer size for shared memory buffer. |
| hardware_config->GetOutputBufferSize() |
| #else |
| 2 * audio_params_.frames_per_buffer() |
| #endif |
| ); |
| sink_ = AudioDeviceFactory::NewOutputDevice(source_render_view_id_); |
| |
| // TODO(henrika): we could utilize the unified audio here instead and do |
| // sink_->InitializeIO(sink_params, 2, callback_.get()); |
| // It would then be possible to avoid using the WebRtcAudioCapturer. |
| sink_->Initialize(sink_params, this); |
| |
| // Start the capturer and local rendering. Note that, the capturer is owned |
| // by the WebRTC ADM and might already bee running. |
| sink_->Start(); |
| |
| last_render_time_ = base::Time::Now(); |
| playing_ = false; |
| } |
| |
| void WebRtcLocalAudioRenderer::Stop() { |
| DVLOG(1) << "WebRtcLocalAudioRenderer::Stop()"; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (!sink_.get()) |
| return; |
| |
| { |
| base::AutoLock auto_lock(thread_lock_); |
| playing_ = false; |
| |
| if (loopback_fifo_.get() != NULL) { |
| loopback_fifo_->Clear(); |
| loopback_fifo_.reset(); |
| } |
| } |
| |
| // Stop the output audio stream, i.e, stop asking for data to render. |
| sink_->Stop(); |
| sink_ = NULL; |
| |
| // Ensure that the capturer stops feeding us with captured audio. |
| // Note that, we do not stop the capturer here since it may still be used by |
| // the WebRTC ADM. |
| audio_track_->RemoveSink(this); |
| audio_track_ = NULL; |
| } |
| |
| void WebRtcLocalAudioRenderer::Play() { |
| DVLOG(1) << "WebRtcLocalAudioRenderer::Play()"; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| base::AutoLock auto_lock(thread_lock_); |
| |
| if (!sink_.get()) |
| return; |
| |
| // Resumes rendering by ensuring that WebRtcLocalAudioRenderer::Render() |
| // now reads data from the local FIFO. |
| playing_ = true; |
| last_render_time_ = base::Time::Now(); |
| |
| if (loopback_fifo_) |
| loopback_fifo_->Clear(); |
| } |
| |
| void WebRtcLocalAudioRenderer::Pause() { |
| DVLOG(1) << "WebRtcLocalAudioRenderer::Pause()"; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| base::AutoLock auto_lock(thread_lock_); |
| |
| if (!sink_.get()) |
| return; |
| |
| // Temporarily suspends rendering audio. |
| // WebRtcLocalAudioRenderer::Render() will return early during this state |
| // and only zeros will be provided to the active sink. |
| playing_ = false; |
| } |
| |
| void WebRtcLocalAudioRenderer::SetVolume(float volume) { |
| DVLOG(1) << "WebRtcLocalAudioRenderer::SetVolume(" << volume << ")"; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| base::AutoLock auto_lock(thread_lock_); |
| if (sink_.get()) |
| sink_->SetVolume(volume); |
| } |
| |
| base::TimeDelta WebRtcLocalAudioRenderer::GetCurrentRenderTime() const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| base::AutoLock auto_lock(thread_lock_); |
| if (!sink_.get()) |
| return base::TimeDelta(); |
| return total_render_time(); |
| } |
| |
| bool WebRtcLocalAudioRenderer::IsLocalRenderer() const { |
| return true; |
| } |
| |
| } // namespace content |