// Copyright (c) 2011 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 #include #include "base/memory/singleton.h" #include "base/string_number_conversions.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "base/win/scoped_comptr.h" #include "chrome/browser/extensions/extension_tts_api_controller.h" #include "chrome/browser/extensions/extension_tts_api_platform.h" class ExtensionTtsPlatformImplWin : public ExtensionTtsPlatformImpl { public: virtual bool PlatformImplAvailable() { return true; } virtual bool Speak( int utterance_id, const std::string& utterance, const std::string& lang, const UtteranceContinuousParameters& params); virtual bool StopSpeaking(); virtual bool IsSpeaking(); virtual bool SendsEvent(TtsEventType event_type); // Get the single instance of this class. static ExtensionTtsPlatformImplWin* GetInstance(); static void __stdcall SpeechEventCallback(WPARAM w_param, LPARAM l_param); private: ExtensionTtsPlatformImplWin(); virtual ~ExtensionTtsPlatformImplWin() {} void OnSpeechEvent(); base::win::ScopedComPtr speech_synthesizer_; // These apply to the current utterance only. std::wstring utterance_; int utterance_id_; int prefix_len_; ULONG stream_number_; int char_position_; friend struct DefaultSingletonTraits; DISALLOW_COPY_AND_ASSIGN(ExtensionTtsPlatformImplWin); }; // static ExtensionTtsPlatformImpl* ExtensionTtsPlatformImpl::GetInstance() { return ExtensionTtsPlatformImplWin::GetInstance(); } bool ExtensionTtsPlatformImplWin::Speak( int utterance_id, const std::string& src_utterance, const std::string& lang, const UtteranceContinuousParameters& params) { std::wstring prefix; std::wstring suffix; if (!speech_synthesizer_) return false; // TODO(dmazzoni): support languages other than the default: crbug.com/88059 if (params.rate >= 0.0) { // Map our multiplicative range of 0.1x to 10.0x onto Microsoft's // linear range of -10 to 10: // 0.1 -> -10 // 1.0 -> 0 // 10.0 -> 10 speech_synthesizer_->SetRate(static_cast(10 * log10(params.rate))); } if (params.pitch >= 0.0) { // The TTS api allows a range of -10 to 10 for speech pitch. // TODO(dtseng): cleanup if we ever use any other properties that // require xml. std::wstring pitch_value = base::IntToString16(static_cast(params.pitch * 10 - 10)); prefix = L""; suffix = L""; } if (params.volume >= 0.0) { // The TTS api allows a range of 0 to 100 for speech volume. speech_synthesizer_->SetVolume(static_cast(params.volume * 100)); } // TODO(dmazzoni): convert SSML to SAPI xml. http://crbug.com/88072 utterance_ = UTF8ToWide(src_utterance); utterance_id_ = utterance_id; char_position_ = 0; std::wstring merged_utterance = prefix + utterance_ + suffix; prefix_len_ = prefix.size(); HRESULT result = speech_synthesizer_->Speak( merged_utterance.c_str(), SPF_ASYNC, &stream_number_); return (result == S_OK); } bool ExtensionTtsPlatformImplWin::StopSpeaking() { if (speech_synthesizer_) { // Clear the stream number so that any further events relating to this // utterance are ignored. stream_number_ = 0; if (IsSpeaking()) { // Stop speech by speaking the empty string with the purge flag. speech_synthesizer_->Speak(L"", SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL); } } return true; } bool ExtensionTtsPlatformImplWin::IsSpeaking() { if (speech_synthesizer_) { SPVOICESTATUS status; HRESULT result = speech_synthesizer_->GetStatus(&status, NULL); if (result == S_OK) { if (status.dwRunningState == 0 || // 0 == waiting to speak status.dwRunningState == SPRS_IS_SPEAKING) { return true; } } } return false; } bool ExtensionTtsPlatformImplWin::SendsEvent(TtsEventType event_type) { return (event_type == TTS_EVENT_START || event_type == TTS_EVENT_END || event_type == TTS_EVENT_MARKER || event_type == TTS_EVENT_WORD || event_type == TTS_EVENT_SENTENCE); } void ExtensionTtsPlatformImplWin::OnSpeechEvent() { ExtensionTtsController* controller = ExtensionTtsController::GetInstance(); SPEVENT event; while (S_OK == speech_synthesizer_->GetEvents(1, &event, NULL)) { if (event.ulStreamNum != stream_number_) continue; switch (event.eEventId) { case SPEI_START_INPUT_STREAM: controller->OnTtsEvent( utterance_id_, TTS_EVENT_START, 0, std::string()); break; case SPEI_END_INPUT_STREAM: char_position_ = utterance_.size(); controller->OnTtsEvent( utterance_id_, TTS_EVENT_END, char_position_, std::string()); break; case SPEI_TTS_BOOKMARK: controller->OnTtsEvent( utterance_id_, TTS_EVENT_MARKER, char_position_, std::string()); break; case SPEI_WORD_BOUNDARY: char_position_ = static_cast(event.lParam) - prefix_len_; controller->OnTtsEvent( utterance_id_, TTS_EVENT_WORD, char_position_, std::string()); break; case SPEI_SENTENCE_BOUNDARY: char_position_ = static_cast(event.lParam) - prefix_len_; controller->OnTtsEvent( utterance_id_, TTS_EVENT_SENTENCE, char_position_, std::string()); break; } } } ExtensionTtsPlatformImplWin::ExtensionTtsPlatformImplWin() : speech_synthesizer_(NULL) { CoCreateInstance( CLSID_SpVoice, NULL, CLSCTX_SERVER, IID_ISpVoice, reinterpret_cast(&speech_synthesizer_)); if (speech_synthesizer_) { ULONGLONG event_mask = SPFEI(SPEI_START_INPUT_STREAM) | SPFEI(SPEI_TTS_BOOKMARK) | SPFEI(SPEI_WORD_BOUNDARY) | SPFEI(SPEI_SENTENCE_BOUNDARY) | SPFEI(SPEI_END_INPUT_STREAM); speech_synthesizer_->SetInterest(event_mask, event_mask); speech_synthesizer_->SetNotifyCallbackFunction( ExtensionTtsPlatformImplWin::SpeechEventCallback, 0, 0); } } // static ExtensionTtsPlatformImplWin* ExtensionTtsPlatformImplWin::GetInstance() { return Singleton::get(); } // static void ExtensionTtsPlatformImplWin::SpeechEventCallback( WPARAM w_param, LPARAM l_param) { GetInstance()->OnSpeechEvent(); }