Skip to content

Commit

Permalink
Support Espeak tts on linux Chrome OS Chrome builds
Browse files Browse the repository at this point in the history
This change enables Chrome OS Chrome builds running on non-cros linux to
work with Espeak-ng.

On linux builds, dcheck always triggers an error in mojo.
Consequently, in TtsService, where the service makes mojo calls on a
Remote within the audio thread, a DCHECK was hit:

2021-01-11T22:46:32.666960Z FATAL chrome[2743839:2743847]: [sequence_checker.h(120)] Check failed: checker.CalledOnValidSequence().
#0 0x7f7ee68c681f base::debug::CollectStackTrace()
#1 0x7f7ee665fd8a base::debug::StackTrace::StackTrace()
#2 0x7f7ee665fd45 base::debug::StackTrace::StackTrace()
#3 0x7f7ee66a9057 logging::LogMessage::~LogMessage()
#4 0x7f7ee66a9769 logging::LogMessage::~LogMessage()
#5 0x7f7ee6620c1b logging::CheckError::~CheckError()
#6 0x7f7ee564e09c base::ScopedValidateSequenceChecker::ScopedValidateSequenceChecker()
#7 0x7f7ee566c068 mojo::InterfaceEndpointClient::SendMessage()
#8 0x7f7ee566c99f mojo::InterfaceEndpointClient::Accept()
#9 0x565159b53c29 chromeos::tts::mojom::TtsEventObserverProxy::OnStart()
#10 0x56515c4e3a5a chromeos::tts::TtsService::Render()
#11 0x7f7ed8648f02 media::AudioOutputDeviceThreadCallback::Process()
#12 0x7f7ed861fe9d media::AudioDeviceThread::ThreadMain()
#13 0x7f7ee68fb1ab base::(anonymous namespace)::ThreadFunc()
#14 0x7f7ea303fea7 start_thread
#15 0x7f7ea2baad8f clone

Fix this by queueing up already rendered audio buffers and processing
them on the main thread's task runner.

Test: manually on a linux chromeos build. Verify that web speech produces speech through Espeak.

Change-Id: I4b1e5b0a6e72e4e571b5a26fde731acc57936894
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2621983
Reviewed-by: Katie Dektar <katie@chromium.org>
Commit-Queue: David Tseng <dtseng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#842622}
  • Loading branch information
dtsengchromium authored and Chromium LUCI CQ committed Jan 12, 2021
1 parent 3b18fb8 commit 9e3e041
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include "base/json/json_writer.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
Expand Down Expand Up @@ -366,7 +367,12 @@ bool TtsExtensionEngine::IsBuiltInTtsEngineInitialized(
saw_espeak |=
voice.engine_id == extension_misc::kEspeakSpeechSynthesisExtensionId;
}
return saw_google_tts && saw_espeak;

// When running on a real Chrome OS environment, require both Google tts and
// Espeak to be initialized; otherwise, only check for Espeak (i.e. on a
// non-Chrome OS linux system running the CHrome OS variant of Chrome).
return base::SysInfo::IsRunningOnChromeOS() ? (saw_google_tts && saw_espeak)
: saw_espeak;
#else
// Vacuously; no built in engines on other platforms yet. TODO: network tts?
return true;
Expand Down
78 changes: 48 additions & 30 deletions chromeos/services/tts/tts_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ constexpr int kDefaultBufferSize = 512;
} // namespace

TtsService::TtsService(mojo::PendingReceiver<mojom::TtsService> receiver)
: service_receiver_(this, std::move(receiver)), tts_stream_factory_(this) {
: service_receiver_(this, std::move(receiver)),
tts_stream_factory_(this),
task_runner_(base::ThreadTaskRunnerHandle::Get()) {
if (setpriority(PRIO_PROCESS, 0, -10 /* real time audio */) != 0) {
PLOG(ERROR) << "Unable to request real time priority; performance will be "
"impacted.";
Expand Down Expand Up @@ -122,50 +124,30 @@ int TtsService::Render(base::TimeDelta delay,
int prior_frames_skipped,
media::AudioBus* dest) {
size_t frames_in_buf = 0;
int32_t status = -1;
{
base::AutoLock al(state_lock_);
if (buffers_.empty())
return 0;

const AudioBuffer& buf = buffers_.front();

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 0;
}

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();
}

frames_in_buf = buf.frames.size();
const float* frames = nullptr;
if (!buf.frames.empty())
frames = &buf.frames[0];
float* channel = dest->channel(0);
for (size_t i = 0; i < frames_in_buf; i++)
channel[i] = frames[i];

rendered_buffers_.push(std::move(buffers_.front()));
buffers_.pop();

if (!process_rendered_buffers_posted_) {
process_rendered_buffers_posted_ = true;
task_runner_->PostTask(FROM_HERE,
base::BindOnce(&TtsService::ProcessRenderedBuffers,
weak_factory_.GetWeakPtr()));
}
}

return frames_in_buf;
Expand All @@ -175,6 +157,7 @@ void TtsService::OnRenderError() {}

void TtsService::StopLocked(bool clear_buffers) {
output_device_->Pause();
rendered_buffers_ = std::queue<AudioBuffer>();
if (clear_buffers) {
buffers_ = std::queue<AudioBuffer>();
timepoints_ = std::queue<Timepoint>();
Expand All @@ -191,6 +174,41 @@ void TtsService::ProcessPendingTtsStreamFactories() {
tts_stream_factory_.Bind(std::move(factory));
}

void TtsService::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();
}
}

TtsService::AudioBuffer::AudioBuffer() = default;

TtsService::AudioBuffer::~AudioBuffer() = default;
Expand Down
13 changes: 13 additions & 0 deletions chromeos/services/tts/tts_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ class TtsService : public mojom::TtsService,
// Satisfies any pending tts stream factory receivers.
void ProcessPendingTtsStreamFactories();

// Do any processing (e.g. sending start/end events) on buffers that have just
// been rendered on the audio thread.
void ProcessRenderedBuffers();

// Connection to tts in the browser.
mojo::Receiver<mojom::TtsService> service_receiver_;

Expand All @@ -117,6 +121,7 @@ class TtsService : public mojom::TtsService,

// The queue of audio buffers to be played by the audio thread.
std::queue<AudioBuffer> buffers_ GUARDED_BY(state_lock_);
std::queue<AudioBuffer> rendered_buffers_;

// An explicit list of increasing time delta sorted timepoints to be fired
// while rendering audio at the specified |delay| from start of audio
Expand All @@ -126,6 +131,14 @@ class TtsService : public mojom::TtsService,

// The time at which playback of the current utterance started.
base::Time start_playback_time_;

// Whether a task to process rendered audio buffers has been posted.
bool process_rendered_buffers_posted_ GUARDED_BY(state_lock_) = false;

// The main thread's task runner handle.
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;

base::WeakPtrFactory<TtsService> weak_factory_{this};
};

} // namespace tts
Expand Down

0 comments on commit 9e3e041

Please sign in to comment.