| // 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 |
| |