Chromium Code Reviews| Index: chrome/browser/speech/tts_win.cc |
| diff --git a/chrome/browser/speech/tts_win.cc b/chrome/browser/speech/tts_win.cc |
| index 1602fc246d166b41c9b471d43140cf202cdf1771..563599a857565b04e4821496bf141bc531d5ddff 100644 |
| --- a/chrome/browser/speech/tts_win.cc |
| +++ b/chrome/browser/speech/tts_win.cc |
| @@ -4,15 +4,27 @@ |
| #include <math.h> |
| #include <sapi.h> |
| +#include <sphelper.h> |
| #include "base/memory/singleton.h" |
| #include "base/strings/string_number_conversions.h" |
| +#include "base/strings/string_piece.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| +#include "base/win/scoped_co_mem.h" |
| #include "base/win/scoped_comptr.h" |
| #include "chrome/browser/speech/tts_controller.h" |
| #include "chrome/browser/speech/tts_platform.h" |
| +namespace { |
| + |
| +// ISpObjectToken key and value names. |
| +const wchar_t kAttributesKey[] = L"Attributes"; |
| +const wchar_t kGenderValue[] = L"Gender"; |
| +const wchar_t kLanguageValue[] = L"Language"; |
| + |
| +} // anonymous namespace. |
| + |
| class TtsPlatformImplWin : public TtsPlatformImpl { |
| public: |
| virtual bool PlatformImplAvailable() { |
| @@ -47,6 +59,8 @@ class TtsPlatformImplWin : public TtsPlatformImpl { |
| void OnSpeechEvent(); |
| + void SetVoiceFromName(const std::string& name); |
| + |
| base::win::ScopedComPtr<ISpVoice> speech_synthesizer_; |
| // These apply to the current utterance only. |
| @@ -79,7 +93,7 @@ bool TtsPlatformImplWin::Speak( |
| if (!speech_synthesizer_.get()) |
| return false; |
| - // TODO(dmazzoni): support languages other than the default: crbug.com/88059 |
| + SetVoiceFromName(voice.name); |
| if (params.rate >= 0.0) { |
| // Map our multiplicative range of 0.1x to 10.0x onto Microsoft's |
| @@ -172,19 +186,57 @@ bool TtsPlatformImplWin::IsSpeaking() { |
| void TtsPlatformImplWin::GetVoices( |
| std::vector<VoiceData>* out_voices) { |
| - // TODO: get all voices, not just default voice. |
| - // http://crbug.com/88059 |
| - out_voices->push_back(VoiceData()); |
| - VoiceData& voice = out_voices->back(); |
| - voice.native = true; |
| - voice.name = "native"; |
| - voice.events.insert(TTS_EVENT_START); |
| - voice.events.insert(TTS_EVENT_END); |
| - voice.events.insert(TTS_EVENT_MARKER); |
| - voice.events.insert(TTS_EVENT_WORD); |
| - voice.events.insert(TTS_EVENT_SENTENCE); |
| - voice.events.insert(TTS_EVENT_PAUSE); |
| - voice.events.insert(TTS_EVENT_RESUME); |
| + base::win::ScopedComPtr<IEnumSpObjectTokens> voice_tokens; |
| + unsigned long voice_count; |
| + if (S_OK != SpEnumTokens(SPCAT_VOICES, NULL, NULL, voice_tokens.Receive())) |
| + return; |
| + if (S_OK != voice_tokens->GetCount(&voice_count)) |
| + return; |
|
David Tseng
2013/12/07 00:49:39
Do we want to let the js client know there was an
dmazzoni
2013/12/09 17:12:24
We don't have a mechanism for this currently, i.e.
|
| + |
| + for (unsigned i = 0; i < voice_count; i++) { |
| + VoiceData voice; |
| + |
| + base::win::ScopedComPtr<ISpObjectToken> voice_token; |
| + if (S_OK != voice_tokens->Next(1, voice_token.Receive(), NULL)) |
| + return; |
| + |
| + base::win::ScopedCoMem<WCHAR> description; |
| + if (S_OK != SpGetDescription(voice_token, &description)) |
| + continue; |
| + voice.name = WideToUTF8(description.get()); |
| + |
| + base::win::ScopedComPtr<ISpDataKey> attributes; |
| + if (S_OK != voice_token->OpenKey(kAttributesKey, attributes.Receive())) |
| + continue; |
| + |
| + base::win::ScopedCoMem<WCHAR> gender; |
| + if (S_OK == attributes->GetStringValue(kGenderValue, &gender)) { |
| + if (0 == _wcsicmp(gender.get(), L"male")) |
| + voice.gender = TTS_GENDER_MALE; |
| + else if (0 == _wcsicmp(gender.get(), L"female")) |
| + voice.gender = TTS_GENDER_FEMALE; |
| + } |
| + |
| + base::win::ScopedCoMem<WCHAR> language; |
| + if (S_OK == attributes->GetStringValue(kLanguageValue, &language)) { |
| + int lcid_value; |
| + base::HexStringToInt(WideToUTF8(language.get()), &lcid_value); |
| + LCID lcid = MAKELCID(lcid_value, SORT_DEFAULT); |
| + WCHAR locale_name[LOCALE_NAME_MAX_LENGTH] = {0}; |
| + LCIDToLocaleName(lcid, locale_name, LOCALE_NAME_MAX_LENGTH, 0); |
| + voice.lang = WideToUTF8(locale_name); |
| + } |
| + |
| + voice.native = true; |
| + voice.events.insert(TTS_EVENT_START); |
| + voice.events.insert(TTS_EVENT_END); |
| + voice.events.insert(TTS_EVENT_MARKER); |
| + voice.events.insert(TTS_EVENT_WORD); |
| + voice.events.insert(TTS_EVENT_SENTENCE); |
| + voice.events.insert(TTS_EVENT_PAUSE); |
| + voice.events.insert(TTS_EVENT_RESUME); |
| + out_voices->push_back(voice); |
| + } |
|
David Tseng
2013/12/07 00:49:39
Does this fetch voices across multiple tts engines
dmazzoni
2013/12/09 17:12:24
SPCAT_VOICES returns voices from all engines in on
|
| } |
| void TtsPlatformImplWin::OnSpeechEvent() { |
| @@ -224,6 +276,32 @@ void TtsPlatformImplWin::OnSpeechEvent() { |
| } |
| } |
| +void TtsPlatformImplWin::SetVoiceFromName(const std::string& name) { |
| + if (name.empty()) |
| + return; |
| + |
| + base::win::ScopedComPtr<IEnumSpObjectTokens> voice_tokens; |
| + unsigned long voice_count; |
| + if (S_OK != SpEnumTokens(SPCAT_VOICES, NULL, NULL, voice_tokens.Receive())) |
| + return; |
| + if (S_OK != voice_tokens->GetCount(&voice_count)) |
| + return; |
| + |
| + for (unsigned i = 0; i < voice_count; i++) { |
| + base::win::ScopedComPtr<ISpObjectToken> voice_token; |
| + if (S_OK != voice_tokens->Next(1, voice_token.Receive(), NULL)) |
| + return; |
| + |
| + base::win::ScopedCoMem<WCHAR> description; |
| + if (S_OK != SpGetDescription(voice_token, &description)) |
| + continue; |
| + if (name == WideToUTF8(description.get())) { |
| + speech_synthesizer_->SetVoice(voice_token); |
|
David Tseng
2013/12/07 00:49:39
Did you notice any performance drawbacks with doin
dmazzoni
2013/12/09 17:12:24
I didn't notice anything, but that's a good idea.
|
| + break; |
| + } |
| + } |
| +} |
| + |
| TtsPlatformImplWin::TtsPlatformImplWin() |
| : utterance_id_(0), |
| prefix_len_(0), |