| // Copyright (c) 2012 The Chromium 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 <math.h> |
| #include <stddef.h> |
| |
| #include <map> |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/debug/leak_annotations.h" |
| #include "base/macros.h" |
| #include "base/memory/singleton.h" |
| #include "base/synchronization/lock.h" |
| #include "base/task/post_task.h" |
| #include "content/browser/speech/tts_platform_impl.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/common/content_switches.h" |
| #include "library_loaders/libspeechd.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| const char kNotSupportedError[] = |
| "Native speech synthesis not supported on this platform."; |
| |
| struct SPDChromeVoice { |
| std::string name; |
| std::string module; |
| }; |
| |
| } // namespace |
| |
| class TtsPlatformImplLinux : public TtsPlatformImpl { |
| public: |
| bool PlatformImplAvailable() override; |
| void Speak(int utterance_id, |
| const std::string& utterance, |
| const std::string& lang, |
| const VoiceData& voice, |
| const UtteranceContinuousParameters& params, |
| base::OnceCallback<void(bool)> on_speak_finished) override; |
| bool StopSpeaking() override; |
| void Pause() override; |
| void Resume() override; |
| bool IsSpeaking() override; |
| void GetVoices(std::vector<VoiceData>* out_voices) override; |
| |
| void OnSpeechEvent(SPDNotificationType type); |
| |
| // Get the single instance of this class. |
| static TtsPlatformImplLinux* GetInstance(); |
| |
| private: |
| TtsPlatformImplLinux(); |
| ~TtsPlatformImplLinux() override; |
| |
| // Initiate the connection with the speech dispatcher. |
| void Initialize(); |
| |
| // Resets the connection with speech dispatcher. |
| void Reset(); |
| |
| static void NotificationCallback(size_t msg_id, |
| size_t client_id, |
| SPDNotificationType type); |
| |
| static void IndexMarkCallback(size_t msg_id, |
| size_t client_id, |
| SPDNotificationType state, |
| char* index_mark); |
| |
| void ProcessSpeech(int utterance_id, |
| const std::string& lang, |
| const VoiceData& voice, |
| const UtteranceContinuousParameters& params, |
| base::OnceCallback<void(bool)> on_speak_finished, |
| const std::string& parsed_utterance); |
| |
| static SPDNotificationType current_notification_; |
| |
| base::Lock initialization_lock_; |
| LibSpeechdLoader libspeechd_loader_; |
| SPDConnection* conn_; |
| |
| // These apply to the current utterance only. |
| std::string utterance_; |
| int utterance_id_; |
| |
| // Map a string composed of a voicename and module to the voicename. Used to |
| // uniquely identify a voice across all available modules. |
| std::unique_ptr<std::map<std::string, SPDChromeVoice>> all_native_voices_; |
| |
| friend struct base::DefaultSingletonTraits<TtsPlatformImplLinux>; |
| |
| base::WeakPtrFactory<TtsPlatformImplLinux> weak_factory_{this}; |
| |
| DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplLinux); |
| }; |
| |
| // static |
| SPDNotificationType TtsPlatformImplLinux::current_notification_ = SPD_EVENT_END; |
| |
| TtsPlatformImplLinux::TtsPlatformImplLinux() : utterance_id_(0) { |
| const base::CommandLine& command_line = |
| *base::CommandLine::ForCurrentProcess(); |
| if (!command_line.HasSwitch(switches::kEnableSpeechDispatcher)) |
| return; |
| |
| base::PostTask( |
| FROM_HERE, |
| {base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT}, |
| base::BindOnce(&TtsPlatformImplLinux::Initialize, |
| base::Unretained(this))); |
| } |
| |
| void TtsPlatformImplLinux::Initialize() { |
| base::AutoLock lock(initialization_lock_); |
| |
| if (!libspeechd_loader_.Load("libspeechd.so.2")) |
| return; |
| |
| { |
| // spd_open has memory leaks which are hard to suppress. |
| // http://crbug.com/317360 |
| ANNOTATE_SCOPED_MEMORY_LEAK; |
| conn_ = libspeechd_loader_.spd_open("chrome", "extension_api", NULL, |
| SPD_MODE_THREADED); |
| } |
| if (!conn_) |
| return; |
| |
| // Register callbacks for all events. |
| conn_->callback_begin = conn_->callback_end = conn_->callback_cancel = |
| conn_->callback_pause = conn_->callback_resume = &NotificationCallback; |
| |
| conn_->callback_im = &IndexMarkCallback; |
| |
| libspeechd_loader_.spd_set_notification_on(conn_, SPD_BEGIN); |
| libspeechd_loader_.spd_set_notification_on(conn_, SPD_END); |
| libspeechd_loader_.spd_set_notification_on(conn_, SPD_CANCEL); |
| libspeechd_loader_.spd_set_notification_on(conn_, SPD_PAUSE); |
| libspeechd_loader_.spd_set_notification_on(conn_, SPD_RESUME); |
| } |
| |
| TtsPlatformImplLinux::~TtsPlatformImplLinux() { |
| base::AutoLock lock(initialization_lock_); |
| if (conn_) { |
| libspeechd_loader_.spd_close(conn_); |
| conn_ = NULL; |
| } |
| } |
| |
| void TtsPlatformImplLinux::Reset() { |
| base::AutoLock lock(initialization_lock_); |
| if (conn_) |
| libspeechd_loader_.spd_close(conn_); |
| conn_ = libspeechd_loader_.spd_open("chrome", "extension_api", NULL, |
| SPD_MODE_THREADED); |
| } |
| |
| bool TtsPlatformImplLinux::PlatformImplAvailable() { |
| if (!initialization_lock_.Try()) |
| return false; |
| bool result = libspeechd_loader_.loaded() && (conn_ != NULL); |
| initialization_lock_.Release(); |
| return result; |
| } |
| |
| void TtsPlatformImplLinux::Speak( |
| int utterance_id, |
| const std::string& utterance, |
| const std::string& lang, |
| const VoiceData& voice, |
| const UtteranceContinuousParameters& params, |
| base::OnceCallback<void(bool)> on_speak_finished) { |
| if (!PlatformImplAvailable()) { |
| error_ = kNotSupportedError; |
| std::move(on_speak_finished).Run(false); |
| return; |
| } |
| |
| // Parse SSML and process speech. |
| TtsController::GetInstance()->StripSSML( |
| utterance, base::BindOnce(&TtsPlatformImplLinux::ProcessSpeech, |
| weak_factory_.GetWeakPtr(), utterance_id, lang, |
| voice, params, std::move(on_speak_finished))); |
| } |
| |
| void TtsPlatformImplLinux::ProcessSpeech( |
| int utterance_id, |
| const std::string& lang, |
| const VoiceData& voice, |
| const UtteranceContinuousParameters& params, |
| base::OnceCallback<void(bool)> on_speak_finished, |
| const std::string& parsed_utterance) { |
| // Speech dispatcher's speech params are around 3x at either limit. |
| float rate = params.rate > 3 ? 3 : params.rate; |
| rate = params.rate < 0.334 ? 0.334 : rate; |
| float pitch = params.pitch > 3 ? 3 : params.pitch; |
| pitch = params.pitch < 0.334 ? 0.334 : pitch; |
| |
| auto it = all_native_voices_->find(voice.name); |
| if (it != all_native_voices_->end()) { |
| libspeechd_loader_.spd_set_output_module(conn_, it->second.module.c_str()); |
| libspeechd_loader_.spd_set_synthesis_voice(conn_, it->second.name.c_str()); |
| } |
| |
| // Map our multiplicative range to Speech Dispatcher's linear range. |
| // .334 = -100. |
| // 3 = 100. |
| libspeechd_loader_.spd_set_voice_rate(conn_, 100 * log10(rate) / log10(3)); |
| libspeechd_loader_.spd_set_voice_pitch(conn_, 100 * log10(pitch) / log10(3)); |
| |
| // Support languages other than the default |
| if (!lang.empty()) |
| libspeechd_loader_.spd_set_language(conn_, lang.c_str()); |
| |
| utterance_ = parsed_utterance; |
| utterance_id_ = utterance_id; |
| |
| if (libspeechd_loader_.spd_say(conn_, SPD_TEXT, parsed_utterance.c_str()) == |
| -1) { |
| Reset(); |
| std::move(on_speak_finished).Run(false); |
| return; |
| } |
| std::move(on_speak_finished).Run(true); |
| } |
| |
| bool TtsPlatformImplLinux::StopSpeaking() { |
| if (!PlatformImplAvailable()) |
| return false; |
| if (libspeechd_loader_.spd_stop(conn_) == -1) { |
| Reset(); |
| return false; |
| } |
| return true; |
| } |
| |
| void TtsPlatformImplLinux::Pause() { |
| if (!PlatformImplAvailable()) |
| return; |
| libspeechd_loader_.spd_pause(conn_); |
| } |
| |
| void TtsPlatformImplLinux::Resume() { |
| if (!PlatformImplAvailable()) |
| return; |
| libspeechd_loader_.spd_resume(conn_); |
| } |
| |
| bool TtsPlatformImplLinux::IsSpeaking() { |
| return current_notification_ == SPD_EVENT_BEGIN; |
| } |
| |
| void TtsPlatformImplLinux::GetVoices(std::vector<VoiceData>* out_voices) { |
| if (!all_native_voices_.get()) { |
| all_native_voices_.reset(new std::map<std::string, SPDChromeVoice>()); |
| char** modules = libspeechd_loader_.spd_list_modules(conn_); |
| if (!modules) |
| return; |
| for (int i = 0; modules[i]; i++) { |
| char* module = modules[i]; |
| libspeechd_loader_.spd_set_output_module(conn_, module); |
| SPDVoice** native_voices = |
| libspeechd_loader_.spd_list_synthesis_voices(conn_); |
| if (!native_voices) { |
| free(module); |
| continue; |
| } |
| for (int j = 0; native_voices[j]; j++) { |
| SPDVoice* native_voice = native_voices[j]; |
| SPDChromeVoice native_data; |
| native_data.name = native_voice->name; |
| native_data.module = module; |
| std::string key; |
| key.append(native_data.name); |
| key.append(" "); |
| key.append(native_data.module); |
| all_native_voices_->insert( |
| std::pair<std::string, SPDChromeVoice>(key, native_data)); |
| free(native_voices[j]); |
| } |
| free(modules[i]); |
| } |
| } |
| |
| for (auto it = all_native_voices_->begin(); it != all_native_voices_->end(); |
| ++it) { |
| out_voices->push_back(VoiceData()); |
| VoiceData& voice = out_voices->back(); |
| voice.native = true; |
| voice.name = it->first; |
| voice.events.insert(TTS_EVENT_START); |
| voice.events.insert(TTS_EVENT_END); |
| voice.events.insert(TTS_EVENT_CANCELLED); |
| voice.events.insert(TTS_EVENT_MARKER); |
| voice.events.insert(TTS_EVENT_PAUSE); |
| voice.events.insert(TTS_EVENT_RESUME); |
| } |
| } |
| |
| void TtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type) { |
| // hummmmmm |
| TtsController* controller = TtsController::GetInstance(); |
| switch (type) { |
| case SPD_EVENT_BEGIN: |
| controller->OnTtsEvent(utterance_id_, TTS_EVENT_START, 0, |
| utterance_.size(), std::string()); |
| break; |
| case SPD_EVENT_RESUME: |
| controller->OnTtsEvent(utterance_id_, TTS_EVENT_RESUME, 0, -1, |
| std::string()); |
| break; |
| case SPD_EVENT_END: |
| controller->OnTtsEvent(utterance_id_, TTS_EVENT_END, utterance_.size(), 0, |
| std::string()); |
| break; |
| case SPD_EVENT_PAUSE: |
| controller->OnTtsEvent(utterance_id_, TTS_EVENT_PAUSE, utterance_.size(), |
| -1, std::string()); |
| break; |
| case SPD_EVENT_CANCEL: |
| controller->OnTtsEvent(utterance_id_, TTS_EVENT_CANCELLED, 0, -1, |
| std::string()); |
| break; |
| case SPD_EVENT_INDEX_MARK: |
| // TODO: Can we get length from linux? |
| controller->OnTtsEvent(utterance_id_, TTS_EVENT_MARKER, 0, -1, |
| std::string()); |
| break; |
| } |
| } |
| |
| // static |
| void TtsPlatformImplLinux::NotificationCallback(size_t msg_id, |
| size_t client_id, |
| SPDNotificationType type) { |
| // We run Speech Dispatcher in threaded mode, so these callbacks should always |
| // be in a separate thread. |
| if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
| current_notification_ = type; |
| base::PostTask( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&TtsPlatformImplLinux::OnSpeechEvent, |
| base::Unretained(TtsPlatformImplLinux::GetInstance()), |
| type)); |
| } |
| } |
| |
| // static |
| void TtsPlatformImplLinux::IndexMarkCallback(size_t msg_id, |
| size_t client_id, |
| SPDNotificationType state, |
| char* index_mark) { |
| // TODO(dtseng): index_mark appears to specify an index type supplied by a |
| // client. Need to explore how this is used before hooking it up with existing |
| // word, sentence events. |
| // We run Speech Dispatcher in threaded mode, so these callbacks should always |
| // be in a separate thread. |
| if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
| current_notification_ = state; |
| base::PostTask( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&TtsPlatformImplLinux::OnSpeechEvent, |
| base::Unretained(TtsPlatformImplLinux::GetInstance()), |
| state)); |
| } |
| } |
| |
| // static |
| TtsPlatformImplLinux* TtsPlatformImplLinux::GetInstance() { |
| return base::Singleton< |
| TtsPlatformImplLinux, |
| base::LeakySingletonTraits<TtsPlatformImplLinux>>::get(); |
| } |
| |
| // static |
| TtsPlatformImpl* TtsPlatformImpl::GetInstance() { |
| return TtsPlatformImplLinux::GetInstance(); |
| } |
| |
| } // namespace content |