blob: 59aaa974103d40d4d8951f0f02c67260f54c904f [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/audio/clockless_audio_sink.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/simple_thread.h"
#include "media/base/audio_glitch_info.h"
namespace media {
// Internal to ClocklessAudioSink. Class is used to call Render() on a seperate
// thread, running as fast as it can read the data.
class ClocklessAudioSinkThread : public base::DelegateSimpleThread::Delegate {
public:
ClocklessAudioSinkThread(const AudioParameters& params,
AudioRendererSink::RenderCallback* callback,
bool hashing)
: callback_(callback),
audio_bus_(AudioBus::Create(params)),
stop_event_(new base::WaitableEvent(
base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED)) {
if (hashing)
audio_hash_ = std::make_unique<AudioHash>();
}
void Start() {
stop_event_->Reset();
thread_ = std::make_unique<base::DelegateSimpleThread>(
this, "ClocklessAudioSink");
thread_->Start();
}
// Generate a signal to stop calling Render().
base::TimeDelta Stop() {
stop_event_->Signal();
thread_->Join();
return playback_time_;
}
const AudioHash& GetAudioHash() const {
DCHECK(audio_hash_);
return *audio_hash_;
}
private:
// Call Render() repeatedly, keeping track of the rendering time.
void Run() override {
base::TimeTicks start;
while (!stop_event_->IsSignaled()) {
const int frames_received = callback_->Render(
base::TimeDelta(), base::TimeTicks::Now(), {}, audio_bus_.get());
DCHECK_GE(frames_received, 0);
if (audio_hash_)
audio_hash_->Update(audio_bus_.get(), frames_received);
if (!frames_received) {
// No data received, so let other threads run to provide data.
base::PlatformThread::YieldCurrentThread();
} else if (start.is_null()) {
// First time we processed some audio, so record the starting time.
start = base::TimeTicks::Now();
} else {
// Keep track of the last time data was rendered.
playback_time_ = base::TimeTicks::Now() - start;
}
}
}
raw_ptr<AudioRendererSink::RenderCallback, DanglingUntriaged> callback_;
std::unique_ptr<AudioBus> audio_bus_;
std::unique_ptr<base::WaitableEvent> stop_event_;
std::unique_ptr<base::DelegateSimpleThread> thread_;
base::TimeDelta playback_time_;
std::unique_ptr<AudioHash> audio_hash_;
};
ClocklessAudioSink::ClocklessAudioSink()
: ClocklessAudioSink(OutputDeviceInfo()) {}
ClocklessAudioSink::ClocklessAudioSink(const OutputDeviceInfo& device_info)
: device_info_(device_info),
initialized_(false),
playing_(false),
hashing_(false),
is_optimized_for_hw_params_(true) {}
ClocklessAudioSink::~ClocklessAudioSink() = default;
void ClocklessAudioSink::Initialize(const AudioParameters& params,
RenderCallback* callback) {
DCHECK(!initialized_);
thread_ =
std::make_unique<ClocklessAudioSinkThread>(params, callback, hashing_);
initialized_ = true;
}
void ClocklessAudioSink::Start() {
DCHECK(initialized_);
DCHECK(!playing_);
}
void ClocklessAudioSink::Stop() {
if (initialized_)
Pause();
}
void ClocklessAudioSink::Flush() {}
void ClocklessAudioSink::Play() {
DCHECK(initialized_);
if (playing_)
return;
playing_ = true;
thread_->Start();
}
void ClocklessAudioSink::Pause() {
DCHECK(initialized_);
if (!playing_)
return;
playing_ = false;
playback_time_ = thread_->Stop();
}
bool ClocklessAudioSink::SetVolume(double volume) {
// Audio is always muted.
return volume == 0.0;
}
OutputDeviceInfo ClocklessAudioSink::GetOutputDeviceInfo() {
return device_info_;
}
void ClocklessAudioSink::GetOutputDeviceInfoAsync(OutputDeviceInfoCB info_cb) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(info_cb), device_info_));
}
bool ClocklessAudioSink::IsOptimizedForHardwareParameters() {
return is_optimized_for_hw_params_;
}
bool ClocklessAudioSink::CurrentThreadIsRenderingThread() {
NOTIMPLEMENTED();
return false;
}
void ClocklessAudioSink::StartAudioHashForTesting() {
DCHECK(!initialized_);
hashing_ = true;
}
const AudioHash& ClocklessAudioSink::GetAudioHashForTesting() const {
return thread_->GetAudioHash();
}
void ClocklessAudioSink::SetIsOptimizedForHardwareParametersForTesting(
bool value) {
is_optimized_for_hw_params_ = value;
}
} // namespace media