// 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 "media/blink/webaudiosourceprovider_impl.h" #include #include #include #include "base/bind.h" #include "base/callback_helpers.h" #include "base/logging.h" #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/metrics/histogram_macros.h" #include "base/single_thread_task_runner.h" #include "base/thread_annotations.h" #include "base/threading/sequenced_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h" #include "media/base/audio_timestamp_helper.h" #include "media/base/bind_to_current_loop.h" #include "media/base/media_log.h" #include "third_party/blink/public/platform/web_audio_source_provider_client.h" using blink::WebVector; namespace media { namespace { // Simple helper class for Try() locks. Lock is Try()'d on construction and // must be checked via the locked() attribute. If acquisition was successful // the lock will be released upon destruction. // TODO(dalecurtis): This should probably move to base/ if others start using // this pattern. class AutoTryLock { public: explicit AutoTryLock(base::Lock& lock) : lock_(lock), acquired_(lock_.Try()) {} bool locked() const { return acquired_; } ~AutoTryLock() { if (acquired_) { lock_.AssertAcquired(); lock_.Release(); } } private: base::Lock& lock_; const bool acquired_; DISALLOW_COPY_AND_ASSIGN(AutoTryLock); }; } // namespace // TeeFilter is a RenderCallback implementation that allows for a client to get // a copy of the data being rendered by the |renderer_| on Render(). This class // also holds on to the necessary audio parameters. class WebAudioSourceProviderImpl::TeeFilter : public AudioRendererSink::RenderCallback { public: TeeFilter() : copy_required_(false) {} ~TeeFilter() override = default; void Initialize(AudioRendererSink::RenderCallback* renderer, int channels, int sample_rate) { DCHECK(renderer); renderer_ = renderer; channels_ = channels; sample_rate_ = sample_rate; } // AudioRendererSink::RenderCallback implementation. // These are forwarders to |renderer_| and are here to allow for a client to // get a copy of the rendered audio by SetCopyAudioCallback(). int Render(base::TimeDelta delay, base::TimeTicks delay_timestamp, int prior_frames_skipped, AudioBus* dest) override; void OnRenderError() override; bool initialized() const { return !!renderer_; } int channels() const { return channels_; } int sample_rate() const { return sample_rate_; } void SetCopyAudioCallback(CopyAudioCB callback) { copy_required_ = !callback.is_null(); base::AutoLock auto_lock(copy_lock_); copy_audio_bus_callback_ = std::move(callback); } private: AudioRendererSink::RenderCallback* renderer_ = nullptr; int channels_ = 0; int sample_rate_ = 0; // The vast majority of the time we're operating in passthrough mode. So only // acquire a lock to read |copy_audio_bus_callback_| when necessary. std::atomic copy_required_; base::Lock copy_lock_; CopyAudioCB copy_audio_bus_callback_ GUARDED_BY(copy_lock_); DISALLOW_COPY_AND_ASSIGN(TeeFilter); }; WebAudioSourceProviderImpl::WebAudioSourceProviderImpl( scoped_refptr sink, MediaLog* media_log) : volume_(1.0), state_(kStopped), client_(nullptr), sink_(std::move(sink)), tee_filter_(new TeeFilter()), media_log_(media_log), weak_factory_(this) {} WebAudioSourceProviderImpl::~WebAudioSourceProviderImpl() = default; void WebAudioSourceProviderImpl::SetClient( blink::WebAudioSourceProviderClient* client) { // Skip taking the lock if unnecessary. This function is the only setter for // |client_| so it's safe to check |client_| outside of the lock. if (client_ == client) return; base::AutoLock auto_lock(sink_lock_); if (client) { // Detach the audio renderer from normal playback. if (sink_) { sink_->Stop(); // It's not possible to resume an element after disconnection, so just // drop the sink entirely for now. sink_ = nullptr; } // The client will now take control by calling provideInput() periodically. client_ = client; set_format_cb_ = BindToCurrentLoop(base::Bind( &WebAudioSourceProviderImpl::OnSetFormat, weak_factory_.GetWeakPtr())); // If |tee_filter_| is Initialize()d - then run |set_format_cb_| to send // |client_| the current format info. Otherwise |set_format_cb_| will get // called when Initialize() is called. Note: Always using |set_format_cb_| // ensures we have the same locking order when calling into |client_|. if (tee_filter_->initialized()) std::move(set_format_cb_).Run(); return; } // Drop client, but normal playback can't be restored. This is okay, the only // way to disconnect a client is internally at time of destruction. client_ = nullptr; } void WebAudioSourceProviderImpl::ProvideInput( const WebVector& audio_data, size_t number_of_frames) { if (!bus_wrapper_ || static_cast(bus_wrapper_->channels()) != audio_data.size()) { bus_wrapper_ = AudioBus::CreateWrapper(static_cast(audio_data.size())); } const int incoming_number_of_frames = static_cast(number_of_frames); bus_wrapper_->set_frames(incoming_number_of_frames); for (size_t i = 0; i < audio_data.size(); ++i) bus_wrapper_->SetChannelData(static_cast(i), audio_data[i]); // Use a try lock to avoid contention in the real-time audio thread. AutoTryLock auto_try_lock(sink_lock_); if (!auto_try_lock.locked() || state_ != kPlaying) { // Provide silence if we failed to acquire the lock or the source is not // running. bus_wrapper_->Zero(); return; } DCHECK(client_); DCHECK_EQ(tee_filter_->channels(), bus_wrapper_->channels()); const int frames = tee_filter_->Render( base::TimeDelta(), base::TimeTicks::Now(), 0, bus_wrapper_.get()); if (frames < incoming_number_of_frames) bus_wrapper_->ZeroFramesPartial(frames, incoming_number_of_frames - frames); bus_wrapper_->Scale(volume_); } void WebAudioSourceProviderImpl::Initialize(const AudioParameters& params, RenderCallback* renderer) { base::AutoLock auto_lock(sink_lock_); DCHECK_EQ(state_, kStopped); tee_filter_->Initialize(renderer, params.channels(), params.sample_rate()); if (sink_) sink_->Initialize(params, tee_filter_.get()); if (set_format_cb_) std::move(set_format_cb_).Run(); } void WebAudioSourceProviderImpl::Start() { base::AutoLock auto_lock(sink_lock_); DCHECK(tee_filter_); DCHECK_EQ(state_, kStopped); state_ = kStarted; if (!client_ && sink_) sink_->Start(); } void WebAudioSourceProviderImpl::Stop() { base::AutoLock auto_lock(sink_lock_); state_ = kStopped; if (!client_ && sink_) sink_->Stop(); } void WebAudioSourceProviderImpl::Play() { base::AutoLock auto_lock(sink_lock_); DCHECK_EQ(state_, kStarted); state_ = kPlaying; if (!client_ && sink_) sink_->Play(); } void WebAudioSourceProviderImpl::Pause() { base::AutoLock auto_lock(sink_lock_); DCHECK(state_ == kPlaying || state_ == kStarted); state_ = kStarted; if (!client_ && sink_) sink_->Pause(); } bool WebAudioSourceProviderImpl::SetVolume(double volume) { base::AutoLock auto_lock(sink_lock_); volume_ = volume; if (!client_ && sink_) sink_->SetVolume(volume); return true; } OutputDeviceInfo WebAudioSourceProviderImpl::GetOutputDeviceInfo() { NOTREACHED(); // The blocking API is intentionally not supported. return OutputDeviceInfo(); } void WebAudioSourceProviderImpl::GetOutputDeviceInfoAsync( OutputDeviceInfoCB info_cb) { base::AutoLock auto_lock(sink_lock_); if (sink_) { sink_->GetOutputDeviceInfoAsync(std::move(info_cb)); return; } // Just return empty hardware parameters. When a |client_| is attached, the // underlying audio renderer will prefer the media parameters. See // IsOptimizedForHardwareParameters() for more details. base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(info_cb), OutputDeviceInfo(OUTPUT_DEVICE_STATUS_OK))); } bool WebAudioSourceProviderImpl::IsOptimizedForHardwareParameters() { base::AutoLock auto_lock(sink_lock_); return client_ ? false : true; } bool WebAudioSourceProviderImpl::CurrentThreadIsRenderingThread() { NOTIMPLEMENTED(); return false; } void WebAudioSourceProviderImpl::SwitchOutputDevice( const std::string& device_id, OutputDeviceStatusCB callback) { base::AutoLock auto_lock(sink_lock_); if (client_ || !sink_) std::move(callback).Run(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL); else sink_->SwitchOutputDevice(device_id, std::move(callback)); } void WebAudioSourceProviderImpl::SetCopyAudioCallback(CopyAudioCB callback) { DCHECK(!callback.is_null()); tee_filter_->SetCopyAudioCallback(std::move(callback)); } void WebAudioSourceProviderImpl::ClearCopyAudioCallback() { tee_filter_->SetCopyAudioCallback(CopyAudioCB()); } int WebAudioSourceProviderImpl::RenderForTesting(AudioBus* audio_bus) { return tee_filter_->Render(base::TimeDelta(), base::TimeTicks::Now(), 0, audio_bus); } void WebAudioSourceProviderImpl::OnSetFormat() { base::AutoLock auto_lock(sink_lock_); if (!client_) return; // Inform Blink about the audio stream format. client_->SetFormat(tee_filter_->channels(), tee_filter_->sample_rate()); } int WebAudioSourceProviderImpl::TeeFilter::Render( base::TimeDelta delay, base::TimeTicks delay_timestamp, int prior_frames_skipped, AudioBus* audio_bus) { DCHECK(initialized()); const int num_rendered_frames = renderer_->Render( delay, delay_timestamp, prior_frames_skipped, audio_bus); // Avoid taking the copy lock for the vast majority of cases. if (copy_required_) { base::AutoLock auto_lock(copy_lock_); if (!copy_audio_bus_callback_.is_null()) { const int64_t frames_delayed = AudioTimestampHelper::TimeToFrames(delay, sample_rate_); std::unique_ptr bus_copy = AudioBus::Create(audio_bus->channels(), audio_bus->frames()); audio_bus->CopyTo(bus_copy.get()); copy_audio_bus_callback_.Run(std::move(bus_copy), frames_delayed, sample_rate_); } } return num_rendered_frames; } void WebAudioSourceProviderImpl::TeeFilter::OnRenderError() { DCHECK(initialized()); renderer_->OnRenderError(); } } // namespace media