// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chromeos/services/tts/tts_player.h"
#include "base/task/single_thread_task_runner.h"
namespace chromeos {
namespace tts {
TtsPlayer::TtsPlayer(
mojo::PendingRemote<media::mojom::AudioStreamFactory> factory,
const media::AudioParameters& params)
: output_device_(std::move(factory), params, this, std::string()),
task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) {}
TtsPlayer::~TtsPlayer() = default;
void TtsPlayer::Play(
base::OnceCallback<void(::mojo::PendingReceiver<mojom::TtsEventObserver>)>
callback) {
tts_event_observer_.reset();
auto pending_receiver = tts_event_observer_.BindNewPipeAndPassReceiver();
std::move(callback).Run(std::move(pending_receiver));
output_device_.Play();
}
void TtsPlayer::AddAudioBuffer(AudioBuffer buf) {
base::AutoLock al(state_lock_);
buffers_.emplace(std::move(buf));
}
void TtsPlayer::AddExplicitTimepoint(int char_index, base::TimeDelta delay) {
base::AutoLock al(state_lock_);
timepoints_.push({char_index, delay});
}
void TtsPlayer::Stop() {
base::AutoLock al(state_lock_);
StopLocked();
}
void TtsPlayer::SetVolume(float volume) {
output_device_.SetVolume(volume);
}
void TtsPlayer::Pause() {
base::AutoLock al(state_lock_);
StopLocked(false /* clear_buffers */);
}
void TtsPlayer::Resume() {
output_device_.Play();
}
int TtsPlayer::Render(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
const media::AudioGlitchInfo& glitch_info,
media::AudioBus* dest) {
size_t frame_count = dest->frames();
{
base::AutoLock al(state_lock_);
if (buffers_.empty())
return 0;
float* channel = dest->channel(0);
AudioBuffer* buffer = &buffers_.front();
for (size_t output_index = 0; output_index < frame_count;
output_index++, buffer->current_frame_index++) {
while (buffer->current_frame_index == buffer->frames.size()) {
// Buffer empty or exhausted, continue to next buffer.
PostTaskProcessRenderedBuffersLocked(buffer);
if (buffers_.empty()) {
return output_index;
}
buffer = &buffers_.front();
}
channel[output_index] = buffer->frames[buffer->current_frame_index];
}
CHECK(!buffer->frames.empty());
if (buffer->current_frame_index == buffer->frames.size()) {
PostTaskProcessRenderedBuffersLocked(buffer);
}
}
return frame_count;
}
void TtsPlayer::OnRenderError() {}
void TtsPlayer::StopLocked(bool clear_buffers) {
output_device_.Pause();
rendered_buffers_ = std::queue<AudioBuffer>();
if (clear_buffers) {
buffers_ = std::queue<AudioBuffer>();
timepoints_ = std::queue<Timepoint>();
}
}
void TtsPlayer::ProcessRenderedBuffers() {
base::AutoLock al(state_lock_);
process_rendered_buffers_posted_ = false;
for (; !rendered_buffers_.empty(); rendered_buffers_.pop()) {
const auto& buf = rendered_buffers_.front();
int status = buf.status;
// Done, 0, or error, -1.
if (status <= 0) {
if (status == -1)
tts_event_observer_->OnError();
else
tts_event_observer_->OnEnd();
StopLocked();
return;
}
if (buf.is_first_buffer) {
start_playback_time_ = base::Time::Now();
tts_event_observer_->OnStart();
}
// Implicit timepoint.
if (buf.char_index != -1)
tts_event_observer_->OnTimepoint(buf.char_index);
}
// Explicit timepoint(s).
base::TimeDelta start_to_now = base::Time::Now() - start_playback_time_;
while (!timepoints_.empty() && timepoints_.front().second <= start_to_now) {
tts_event_observer_->OnTimepoint(timepoints_.front().first);
timepoints_.pop();
}
}
TtsPlayer::AudioBuffer::AudioBuffer() = default;
TtsPlayer::AudioBuffer::~AudioBuffer() = default;
TtsPlayer::AudioBuffer::AudioBuffer(TtsPlayer::AudioBuffer&& other) {
frames.swap(other.frames);
status = other.status;
char_index = other.char_index;
is_first_buffer = other.is_first_buffer;
current_frame_index = other.current_frame_index;
}
void TtsPlayer::PostTaskProcessRenderedBuffersLocked(AudioBuffer* buffer) {
CHECK_EQ(buffer, &buffers_.front());
rendered_buffers_.push(std::move(*buffer));
buffers_.pop();
if (process_rendered_buffers_posted_) {
return;
}
process_rendered_buffers_posted_ = true;
task_runner_->PostTask(FROM_HERE,
base::BindOnce(&TtsPlayer::ProcessRenderedBuffers,
weak_factory_.GetWeakPtr()));
}
} // namespace tts
} // namespace chromeos