blob: 9f83a2f3088b4eac1a4668052981d5babf0bb26d [file] [log] [blame]
// Copyright (c) 2010 The Chromium OS 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 <algorithm>
#include <string>
#include "audio_output.h"
#include "log.h"
#include "resampler.h"
#include "threading.h"
#include "tts_engine.h"
#include "tts_service.h"
using std::string;
namespace speech_synthesis {
TtsService::TtsService(TtsEngine *engine,
AudioOutput *audio_output,
Threading *threading)
: engine_(engine),
audio_output_(audio_output),
threading_(threading),
current_utterance_(NULL),
resampler_(NULL),
mutex_(threading->CreateMutex()),
cond_var_(threading->CreateCondVar()),
service_running_(false),
utterance_running_(false),
current_utterance_interruptible_(true) {
}
TtsService::~TtsService() {
delete mutex_;
delete cond_var_;
delete[] audio_buffer_;
}
bool TtsService::StartService() {
if (!audio_output_->Init(this)) {
LOG(ERROR) << "TTS Service unable to open audio output.";
return false;
}
audio_buffer_size_ = audio_output_->GetChunkSize();
ring_buffer_ = new RingBuffer<int16_t>(
threading_, audio_output_->GetTotalBufferSize());
audio_buffer_ = new int16_t[audio_buffer_size_];
if (engine_->Init() != TTS_SUCCESS) {
return false;
}
LOG(INFO) << "StartService";
audio_output_->StartAudio();
service_running_ = true;
thread_ = threading_->StartJoinableThread(this);
return true;
}
void TtsService::StopService() {
if (!service_running_) {
return;
}
LOG(INFO) << "Stopping audio.";
audio_output_->StopAudio();
LOG(INFO) << "Stopping main service.";
mutex_->Lock();
service_running_ = false;
cond_var_->Signal();
mutex_->Unlock();
LOG(INFO) << "Joining main thread.";
thread_->Join();
LOG(INFO) << "Joined";
}
void TtsService::Speak(string text, UtteranceOptions* options /*= NULL*/) {
if (!service_running_) {
return;
}
Utterance *utterance = new Utterance;
utterance->text = text;
if (options == NULL) {
utterance->voice_index = 0;
} else {
if ((utterance->voice_index =
engine_->GetVoiceIndex(options->voice_options)) == -1) {
utterance->voice_index = 0;
}
}
utterance->options = options;
mutex_->Lock();
if (!utterance->options->enqueue) {
// Remove from the back until a non-interruptible utterance is found.
// This means that the an utterance being non-interruptible gives a
// cascading effect on the utterances that are queued before the
// non-interruptible utterance. It can be enforced at a higher level
// that if interruptiple == false, then enqueue = false.
while (!utterances_.empty() &&
utterances_.back()->options->interruptible) {
delete utterances_.back();
utterances_.pop_back();
}
if (utterances_.empty() && current_utterance_interruptible_) {
ring_buffer_->Reset();
utterance_running_ = false;
}
}
utterances_.push_back(utterance);
cond_var_->Signal();
mutex_->Unlock();
}
void TtsService::Stop() {
if (!service_running_) {
return;
}
mutex_->Lock();
ring_buffer_->Reset();
while (!utterances_.empty()) {
delete utterances_.front();
utterances_.pop_front();
}
utterance_running_ = false;
cond_var_->Signal();
mutex_->Unlock();
}
tts_status TtsService::GetStatus() {
if (!service_running_) {
return TTS_ERROR;
}
tts_status status;
mutex_->Lock();
if (utterances_.empty() && !utterance_running_)
status = TTS_IDLE;
else
status = TTS_BUSY;
mutex_->Unlock();
return status;
}
void TtsService::WaitUntilFinished() {
if (!service_running_) {
return;
}
mutex_->Lock();
while (!utterances_.empty() || utterance_running_) {
cond_var_->Wait(mutex_);
}
mutex_->Unlock();
}
void TtsService::Run() {
if (!service_running_) {
return;
}
LOG(INFO) << "Running background thread";
for (;;) {
mutex_->Lock();
// If there are no utterances and there's no signal to stop,
// wait on our condition variable, which will allow this thread to
// sleep with no CPU usage and wake up immediately when there's
// work for us to do.
if (utterances_.empty() && service_running_ == true) {
cond_var_->Wait(mutex_);
}
if (service_running_ == false) {
LOG(INFO) << "Exiting background thread";
while (!utterances_.empty()) {
delete utterances_.front();
utterances_.pop_front();
}
mutex_->Unlock();
return;
}
if (current_utterance_ == NULL && !utterances_.empty()) {
current_utterance_ = utterances_.front();
current_utterance_interruptible_ =
current_utterance_->options->interruptible;
utterances_.pop_front();
}
utterance_running_ = true;
mutex_->Unlock();
if (!current_utterance_) {
continue;
}
if (current_utterance_->options) {
engine_->SetRate(current_utterance_->options->rate);
engine_->SetPitch(current_utterance_->options->pitch);
engine_->SetVolume(current_utterance_->options->volume);
}
// Synthesize the current utterance. The TTS engine will call our
// callback method, Receive, repeatedly while it performs synthesis.
// During that callback, we check if Stop was called and can cause
// this method to exit prematurely. Otherwise this method won't exit
// until this utterance is done synthesizing, and then current_utterance_
// will be set to NULL.
int samples_output = 0;
engine_->SetVoice(current_utterance_->voice_index);
resampler_ = NULL;
if (audio_output_->GetSampleRate() != engine_->GetSampleRate()) {
resampler_ = new Resampler(this,
engine_->GetSampleRate(),
audio_output_->GetSampleRate(),
audio_buffer_size_);
engine_->SetReceiver(resampler_);
} else {
engine_->SetReceiver(this);
}
// Save the utterance text because current_utterance_ is deleted
// by the Done() callback before the call to SynthesizeText exits.
string utterance_text = current_utterance_->text;
engine_->SynthesizeText(
utterance_text.c_str(),
audio_buffer_,
audio_buffer_size_,
&samples_output);
// TODO(chaitanyag): Make the completion callback here.
LOG(INFO) << "Done: " << utterance_text.c_str();
mutex_->Lock();
if (utterance_running_ == false) {
// The utterance was interrupted
engine_->Stop();
}
utterance_running_ = false;
cond_var_->Signal();
mutex_->Unlock();
delete current_utterance_;
current_utterance_ = NULL;
if (resampler_) {
delete resampler_;
}
}
}
tts_callback_status TtsService::Receive(int rate,
int num_channels,
const int16_t* data,
int num_samples,
tts_synth_status status) {
if (status == TTS_SYNTH_DONE) {
current_utterance_ = NULL;
}
// Check if we need to exit prematurely
mutex_->Lock();
if (service_running_ == false || utterance_running_ == false) {
mutex_->Unlock();
return TTS_CALLBACK_HALT;
}
mutex_->Unlock();
// If there's no audio data, just return success
if (num_samples == 0) {
return TTS_CALLBACK_CONTINUE;
}
// If the ring buffer is full, compute the amount of time we expect
// it to take for that many audio samples to be output, and sleep for
// that long.
while (ring_buffer_->WriteAvail() < num_samples) {
int ms_to_sleep = num_samples * 1000 / rate;
mutex_->Lock();
cond_var_->WaitWithTimeout(mutex_, ms_to_sleep);
if (service_running_ == false || utterance_running_ == false) {
mutex_->Unlock();
return TTS_CALLBACK_HALT;
}
mutex_->Unlock();
}
bool success = ring_buffer_->Write(data, num_samples);
if (!success) {
LOG(INFO) << "Unable to write to ring buffer";
exit(0);
}
if (status == TTS_SYNTH_DONE) {
ring_buffer_->MarkFinished();
}
return TTS_CALLBACK_CONTINUE;
}
bool TtsService::FillAudioBuffer(int16_t* samples, int size) {
int avail = ring_buffer_->ReadAvail();
// If the ring buffer is finished, play until the end. Otherwise,
// only play if we have a full buffer.
int copy_len;
if (ring_buffer_->IsFinished()) {
copy_len = avail < size? avail : size;
} else {
copy_len = avail >= size? size : 0;
}
ring_buffer_->Read(samples, copy_len);
for (int i = copy_len; i < size; i++) {
samples[i] = 0;
}
return !ring_buffer_->IsFinished();
}
} // namespace speech_synthesis