blob: bc6d4544109505cd92d99dcf25f27a02d00b8941 [file] [log] [blame]
// 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 <atomic>
#include <utility>
#include <vector>
#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<bool> copy_required_;
base::Lock copy_lock_;
CopyAudioCB copy_audio_bus_callback_ GUARDED_BY(copy_lock_);
DISALLOW_COPY_AND_ASSIGN(TeeFilter);
};
WebAudioSourceProviderImpl::WebAudioSourceProviderImpl(
scoped_refptr<SwitchableAudioRendererSink> 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<float*>& audio_data,
size_t number_of_frames) {
if (!bus_wrapper_ ||
static_cast<size_t>(bus_wrapper_->channels()) != audio_data.size()) {
bus_wrapper_ = AudioBus::CreateWrapper(static_cast<int>(audio_data.size()));
}
const int incoming_number_of_frames = static_cast<int>(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<int>(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<AudioBus> 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