| // Copyright 2013 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_source_provider.h" |
| |
| #include "base/logging.h" |
| #include "content/renderer/render_thread_impl.h" |
| #include "media/audio/audio_parameters.h" |
| #include "media/base/audio_fifo.h" |
| #include "media/base/audio_hardware_config.h" |
| #include "third_party/WebKit/public/platform/WebAudioSourceProviderClient.h" |
| |
| using blink::WebVector; |
| |
| namespace content { |
| |
| static const size_t kMaxNumberOfBuffers = 10; |
| |
| // Size of the buffer that WebAudio processes each time, it is the same value |
| // as AudioNode::ProcessingSizeInFrames in WebKit. |
| // static |
| const size_t WebRtcLocalAudioSourceProvider::kWebAudioRenderBufferSize = 128; |
| |
| WebRtcLocalAudioSourceProvider::WebRtcLocalAudioSourceProvider( |
| const blink::WebMediaStreamTrack& track) |
| : is_enabled_(false), |
| track_(track), |
| track_stopped_(false) { |
| // Get the native audio output hardware sample-rate for the sink. |
| // We need to check if RenderThreadImpl is valid here since the unittests |
| // do not have one and they will inject their own |sink_params_| for testing. |
| if (RenderThreadImpl::current()) { |
| media::AudioHardwareConfig* hardware_config = |
| RenderThreadImpl::current()->GetAudioHardwareConfig(); |
| int sample_rate = hardware_config->GetOutputSampleRate(); |
| sink_params_.Reset( |
| media::AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| media::CHANNEL_LAYOUT_STEREO, 2, 0, sample_rate, 16, |
| kWebAudioRenderBufferSize); |
| } |
| |
| // Connect the source provider to the track as a sink. |
| MediaStreamAudioSink::AddToAudioTrack(this, track_); |
| } |
| |
| WebRtcLocalAudioSourceProvider::~WebRtcLocalAudioSourceProvider() { |
| if (audio_converter_.get()) |
| audio_converter_->RemoveInput(this); |
| |
| // If the track is still active, it is necessary to notify the track before |
| // the source provider goes away. |
| if (!track_stopped_) |
| MediaStreamAudioSink::RemoveFromAudioTrack(this, track_); |
| } |
| |
| void WebRtcLocalAudioSourceProvider::OnSetFormat( |
| const media::AudioParameters& params) { |
| // We need detach the thread here because it will be a new capture thread |
| // calling OnSetFormat() and OnData() if the source is restarted. |
| capture_thread_checker_.DetachFromThread(); |
| DCHECK(capture_thread_checker_.CalledOnValidThread()); |
| DCHECK(params.IsValid()); |
| DCHECK(sink_params_.IsValid()); |
| |
| base::AutoLock auto_lock(lock_); |
| source_params_ = params; |
| // Create the audio converter with |disable_fifo| as false so that the |
| // converter will request source_params.frames_per_buffer() each time. |
| // This will not increase the complexity as there is only one client to |
| // the converter. |
| audio_converter_.reset( |
| new media::AudioConverter(params, sink_params_, false)); |
| audio_converter_->AddInput(this); |
| fifo_.reset(new media::AudioFifo( |
| params.channels(), |
| kMaxNumberOfBuffers * params.frames_per_buffer())); |
| input_bus_ = media::AudioBus::Create(params.channels(), |
| params.frames_per_buffer()); |
| } |
| |
| void WebRtcLocalAudioSourceProvider::OnReadyStateChanged( |
| blink::WebMediaStreamSource::ReadyState state) { |
| if (state == blink::WebMediaStreamSource::ReadyStateEnded) |
| track_stopped_ = true; |
| } |
| |
| void WebRtcLocalAudioSourceProvider::OnData( |
| const int16* audio_data, |
| int sample_rate, |
| int number_of_channels, |
| int number_of_frames) { |
| DCHECK(capture_thread_checker_.CalledOnValidThread()); |
| base::AutoLock auto_lock(lock_); |
| if (!is_enabled_) |
| return; |
| |
| DCHECK(fifo_.get()); |
| |
| // TODO(xians): A better way to handle the interleaved and deinterleaved |
| // format switching, see issue/317710. |
| DCHECK(input_bus_->frames() == number_of_frames); |
| DCHECK(input_bus_->channels() == number_of_channels); |
| input_bus_->FromInterleaved(audio_data, number_of_frames, 2); |
| |
| if (fifo_->frames() + number_of_frames <= fifo_->max_frames()) { |
| fifo_->Push(input_bus_.get()); |
| } else { |
| // This can happen if the data in FIFO is too slowly consumed or |
| // WebAudio stops consuming data. |
| DVLOG(3) << "Local source provicer FIFO is full" << fifo_->frames(); |
| } |
| } |
| |
| void WebRtcLocalAudioSourceProvider::setClient( |
| blink::WebAudioSourceProviderClient* client) { |
| NOTREACHED(); |
| } |
| |
| void WebRtcLocalAudioSourceProvider::provideInput( |
| const WebVector<float*>& audio_data, size_t number_of_frames) { |
| DCHECK_EQ(number_of_frames, kWebAudioRenderBufferSize); |
| if (!output_wrapper_ || |
| static_cast<size_t>(output_wrapper_->channels()) != audio_data.size()) { |
| output_wrapper_ = media::AudioBus::CreateWrapper(audio_data.size()); |
| } |
| |
| output_wrapper_->set_frames(number_of_frames); |
| for (size_t i = 0; i < audio_data.size(); ++i) |
| output_wrapper_->SetChannelData(i, audio_data[i]); |
| |
| base::AutoLock auto_lock(lock_); |
| if (!audio_converter_) |
| return; |
| |
| is_enabled_ = true; |
| audio_converter_->Convert(output_wrapper_.get()); |
| } |
| |
| double WebRtcLocalAudioSourceProvider::ProvideInput( |
| media::AudioBus* audio_bus, base::TimeDelta buffer_delay) { |
| if (fifo_->frames() >= audio_bus->frames()) { |
| fifo_->Consume(audio_bus, 0, audio_bus->frames()); |
| } else { |
| audio_bus->Zero(); |
| DVLOG(1) << "WARNING: Underrun, FIFO has data " << fifo_->frames() |
| << " samples but " << audio_bus->frames() |
| << " samples are needed"; |
| } |
| |
| return 1.0; |
| } |
| |
| void WebRtcLocalAudioSourceProvider::SetSinkParamsForTesting( |
| const media::AudioParameters& sink_params) { |
| sink_params_ = sink_params; |
| } |
| |
| } // namespace content |