diff options
author | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-11 18:51:43 +0000 |
---|---|---|
committer | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-11 18:51:43 +0000 |
commit | 56f6f9dc5522435534cebcf0a1a9a239dbda2bea (patch) | |
tree | ba37024edb6d2d6f06273f6fdfef976f00507719 /chrome/browser/speech | |
parent | 71395b17a1da1100fe57e8ad141c2ea5da338416 (diff) | |
download | chromium_src-56f6f9dc5522435534cebcf0a1a9a239dbda2bea.zip chromium_src-56f6f9dc5522435534cebcf0a1a9a239dbda2bea.tar.gz chromium_src-56f6f9dc5522435534cebcf0a1a9a239dbda2bea.tar.bz2 |
Add support for native TTS to provide multiple voices.
This change refactors the text-to-speech system so that the
"platform native" text-to-speech implementation can provide
multiple voices, rather than just the system default voice.
This refactoring also further reduces the coupling between
the main TTS system in chrome/browser/speech and the
extension API in chrome/browser/speech/extension_api - the
goal is to later move all of the non-extension code to content.
This change also implements multiple voices on Mac OS X.
Windows support will be in a subsequent changelist.
BUG=88059
NOTRY=true
Review URL: https://chromiumcodereview.appspot.com/14657013
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@199615 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/speech')
-rw-r--r-- | chrome/browser/speech/extension_api/tts_engine_extension_api.cc | 135 | ||||
-rw-r--r-- | chrome/browser/speech/extension_api/tts_engine_extension_api.h | 3 | ||||
-rw-r--r-- | chrome/browser/speech/extension_api/tts_extension_api.cc | 136 | ||||
-rw-r--r-- | chrome/browser/speech/extension_api/tts_extension_api.h | 3 | ||||
-rw-r--r-- | chrome/browser/speech/extension_api/tts_extension_api_constants.cc | 2 | ||||
-rw-r--r-- | chrome/browser/speech/extension_api/tts_extension_api_constants.h | 2 | ||||
-rw-r--r-- | chrome/browser/speech/extension_api/tts_extension_apitest.cc | 55 | ||||
-rw-r--r-- | chrome/browser/speech/tts_chromeos.cc | 4 | ||||
-rw-r--r-- | chrome/browser/speech/tts_controller.cc | 186 | ||||
-rw-r--r-- | chrome/browser/speech/tts_controller.h | 38 | ||||
-rw-r--r-- | chrome/browser/speech/tts_controller_unittest.cc | 4 | ||||
-rw-r--r-- | chrome/browser/speech/tts_linux.cc | 22 | ||||
-rw-r--r-- | chrome/browser/speech/tts_mac.mm | 78 | ||||
-rw-r--r-- | chrome/browser/speech/tts_platform.cc | 4 | ||||
-rw-r--r-- | chrome/browser/speech/tts_platform.h | 12 | ||||
-rw-r--r-- | chrome/browser/speech/tts_win.cc | 23 |
16 files changed, 398 insertions, 309 deletions
diff --git a/chrome/browser/speech/extension_api/tts_engine_extension_api.cc b/chrome/browser/speech/extension_api/tts_engine_extension_api.cc index de8e24d..4c9b8d5 100644 --- a/chrome/browser/speech/extension_api/tts_engine_extension_api.cc +++ b/chrome/browser/speech/extension_api/tts_engine_extension_api.cc @@ -12,6 +12,7 @@ #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/speech/extension_api/tts_extension_api.h" #include "chrome/browser/speech/extension_api/tts_extension_api_constants.h" #include "chrome/browser/speech/tts_controller.h" #include "chrome/common/extensions/api/speech/tts_engine_manifest_handler.h" @@ -26,17 +27,6 @@ const char kOnSpeak[] = "ttsEngine.onSpeak"; const char kOnStop[] = "ttsEngine.onStop"; }; // namespace tts_engine_events -namespace { -// Given a language/region code of the form 'fr-FR', returns just the basic -// language portion, e.g. 'fr'. -std::string TrimLanguageCode(std::string lang) { - if (lang.size() >= 5 && lang[2] == '-') - return lang.substr(0, 2); - else - return lang; -} -} - void GetExtensionVoices(Profile* profile, std::vector<VoiceData>* out_voices) { ExtensionService* service = profile->GetExtensionService(); DCHECK(service); @@ -67,139 +57,40 @@ void GetExtensionVoices(Profile* profile, std::vector<VoiceData>* out_voices) { out_voices->push_back(VoiceData()); VoiceData& result_voice = out_voices->back(); + result_voice.native = false; result_voice.name = voice.voice_name; result_voice.lang = voice.lang; - result_voice.gender = voice.gender; result_voice.extension_id = extension->id(); + if (voice.gender == constants::kGenderMale) + result_voice.gender = TTS_GENDER_MALE; + else if (voice.gender == constants::kGenderFemale) + result_voice.gender = TTS_GENDER_FEMALE; + else + result_voice.gender = TTS_GENDER_NONE; for (std::set<std::string>::const_iterator iter = voice.event_types.begin(); iter != voice.event_types.end(); ++iter) { - result_voice.events.push_back(*iter); + result_voice.events.insert(TtsEventTypeFromString(*iter)); } // If the extension sends end events, the controller will handle // queueing and send interrupted and cancelled events. if (voice.event_types.find(constants::kEventTypeEnd) != voice.event_types.end()) { - result_voice.events.push_back(constants::kEventTypeCancelled); - result_voice.events.push_back(constants::kEventTypeInterrupted); - } - } - } -} - -bool GetMatchingExtensionVoice( - Utterance* utterance, - const Extension** matching_extension, - size_t* voice_index) { - // This will only happen during unit testing. Otherwise, an utterance - // will always have an associated profile. - if (!utterance->profile()) - return false; - - ExtensionService* service = utterance->profile()->GetExtensionService(); - - // If speech is generated when Chrome OS first starts up, it's possible - // the extension service isn't even available. - if (!service) - return false; - - extensions::EventRouter* event_router = - extensions::ExtensionSystem::Get(utterance->profile())->event_router(); - DCHECK(event_router); - - *matching_extension = NULL; - *voice_index = -1; - const ExtensionSet* extensions = service->extensions(); - ExtensionSet::const_iterator iter; - - // Make two passes: the first time, do strict language matching - // ('fr-FR' does not match 'fr-CA'). The second time, do prefix - // language matching ('fr-FR' matches 'fr' and 'fr-CA') - for (int pass = 0; pass < 2; ++pass) { - for (iter = extensions->begin(); iter != extensions->end(); ++iter) { - const Extension* extension = *iter; - - if (!event_router->ExtensionHasEventListener( - extension->id(), tts_engine_events::kOnSpeak) || - !event_router->ExtensionHasEventListener( - extension->id(), tts_engine_events::kOnStop)) { - continue; - } - - if (!utterance->extension_id().empty() && - utterance->extension_id() != extension->id()) { - continue; - } - - const std::vector<extensions::TtsVoice>* tts_voices = - extensions::TtsVoice::GetTtsVoices(extension); - if (!tts_voices) - continue; - - for (size_t i = 0; i < tts_voices->size(); ++i) { - const extensions::TtsVoice& voice = tts_voices->at(i); - if (!voice.voice_name.empty() && - !utterance->voice_name().empty() && - voice.voice_name != utterance->voice_name()) { - continue; - } - if (!voice.lang.empty() && !utterance->lang().empty()) { - std::string voice_lang = voice.lang; - std::string utterance_lang = utterance->lang(); - if (pass == 1) { - voice_lang = TrimLanguageCode(voice_lang); - utterance_lang = TrimLanguageCode(utterance_lang); - } - if (voice_lang != utterance_lang) - continue; - } - if (!voice.gender.empty() && - !utterance->gender().empty() && - voice.gender != utterance->gender()) { - continue; - } - if (utterance->required_event_types().size() > 0) { - bool has_all_required_event_types = true; - for (std::set<std::string>::const_iterator iter = - utterance->required_event_types().begin(); - iter != utterance->required_event_types().end(); - ++iter) { - if (voice.event_types.find(*iter) == voice.event_types.end()) { - has_all_required_event_types = false; - break; - } - } - if (!has_all_required_event_types) - continue; - } - - *matching_extension = extension; - *voice_index = i; - return true; + result_voice.events.insert(TTS_EVENT_CANCELLED); + result_voice.events.insert(TTS_EVENT_INTERRUPTED); } } } - - return false; } -void ExtensionTtsEngineSpeak(Utterance* utterance, - const Extension* extension, - size_t voice_index) { +void ExtensionTtsEngineSpeak(Utterance* utterance, const VoiceData& voice) { // See if the engine supports the "end" event; if so, we can keep the // utterance around and track it. If not, we're finished with this // utterance now. - const std::vector<extensions::TtsVoice>* tts_voices = - extensions::TtsVoice::GetTtsVoices(extension); - std::set<std::string> event_types; - if (tts_voices) - event_types = tts_voices->at(voice_index).event_types; - - bool sends_end_event = - (event_types.find(constants::kEventTypeEnd) != event_types.end()); + bool sends_end_event = voice.events.find(TTS_EVENT_END) != voice.events.end(); scoped_ptr<ListValue> args(new ListValue()); args->Set(0, Value::CreateStringValue(utterance->text())); diff --git a/chrome/browser/speech/extension_api/tts_engine_extension_api.h b/chrome/browser/speech/extension_api/tts_engine_extension_api.h index 80bb122..b87de3a 100644 --- a/chrome/browser/speech/extension_api/tts_engine_extension_api.h +++ b/chrome/browser/speech/extension_api/tts_engine_extension_api.h @@ -41,8 +41,7 @@ bool GetMatchingExtensionVoice(Utterance* utterance, // Speak the given utterance by sending an event to the given TTS engine // extension voice. void ExtensionTtsEngineSpeak(Utterance* utterance, - const extensions::Extension* extension, - size_t voice_index); + const VoiceData& voice); // Stop speaking the given utterance by sending an event to the extension // associated with this utterance. diff --git a/chrome/browser/speech/extension_api/tts_extension_api.cc b/chrome/browser/speech/extension_api/tts_extension_api.cc index 177bad6..117bf732 100644 --- a/chrome/browser/speech/extension_api/tts_extension_api.cc +++ b/chrome/browser/speech/extension_api/tts_extension_api.cc @@ -22,33 +22,51 @@ namespace events { const char kOnEvent[] = "tts.onEvent"; }; // namespace events -namespace { - const char *TtsEventTypeToString(TtsEventType event_type) { switch (event_type) { - case TTS_EVENT_START: - return constants::kEventTypeStart; - case TTS_EVENT_END: - return constants::kEventTypeEnd; - case TTS_EVENT_WORD: - return constants::kEventTypeWord; - case TTS_EVENT_SENTENCE: - return constants::kEventTypeSentence; - case TTS_EVENT_MARKER: - return constants::kEventTypeMarker; - case TTS_EVENT_INTERRUPTED: - return constants::kEventTypeInterrupted; - case TTS_EVENT_CANCELLED: - return constants::kEventTypeCancelled; - case TTS_EVENT_ERROR: - return constants::kEventTypeError; - default: - NOTREACHED(); - return ""; + case TTS_EVENT_START: + return constants::kEventTypeStart; + case TTS_EVENT_END: + return constants::kEventTypeEnd; + case TTS_EVENT_WORD: + return constants::kEventTypeWord; + case TTS_EVENT_SENTENCE: + return constants::kEventTypeSentence; + case TTS_EVENT_MARKER: + return constants::kEventTypeMarker; + case TTS_EVENT_INTERRUPTED: + return constants::kEventTypeInterrupted; + case TTS_EVENT_CANCELLED: + return constants::kEventTypeCancelled; + case TTS_EVENT_ERROR: + return constants::kEventTypeError; + default: + NOTREACHED(); + return constants::kEventTypeError; } } -} // anonymous namespace +TtsEventType TtsEventTypeFromString(const std::string& str) { + if (str == constants::kEventTypeStart) + return TTS_EVENT_START; + if (str == constants::kEventTypeEnd) + return TTS_EVENT_END; + if (str == constants::kEventTypeWord) + return TTS_EVENT_WORD; + if (str == constants::kEventTypeSentence) + return TTS_EVENT_SENTENCE; + if (str == constants::kEventTypeMarker) + return TTS_EVENT_MARKER; + if (str == constants::kEventTypeInterrupted) + return TTS_EVENT_INTERRUPTED; + if (str == constants::kEventTypeCancelled) + return TTS_EVENT_CANCELLED; + if (str == constants::kEventTypeError) + return TTS_EVENT_ERROR; + + NOTREACHED(); + return TTS_EVENT_ERROR; +} namespace extensions { @@ -69,15 +87,14 @@ void TtsExtensionEventHandler::OnTtsEvent(Utterance* utterance, if (utterance->src_id() < 0) return; - std::string event_type_string = TtsEventTypeToString(event_type); - const std::set<std::string>& desired_event_types = + const std::set<TtsEventType>& desired_event_types = utterance->desired_event_types(); if (desired_event_types.size() > 0 && - desired_event_types.find(event_type_string) == - desired_event_types.end()) { + desired_event_types.find(event_type) == desired_event_types.end()) { return; } + const char *event_type_string = TtsEventTypeToString(event_type); scoped_ptr<DictionaryValue> details(new DictionaryValue()); if (char_index >= 0) details->SetInteger(constants::kCharIndexKey, char_index); @@ -132,13 +149,18 @@ bool TtsSpeakFunction::RunImpl() { return false; } - std::string gender; + std::string gender_str; + TtsGenderType gender; if (options->HasKey(constants::kGenderKey)) EXTENSION_FUNCTION_VALIDATE( - options->GetString(constants::kGenderKey, &gender)); - if (!gender.empty() && - gender != constants::kGenderFemale && - gender != constants::kGenderMale) { + options->GetString(constants::kGenderKey, &gender_str)); + if (gender_str == constants::kGenderMale) { + gender = TTS_GENDER_MALE; + } else if (gender_str == constants::kGenderFemale) { + gender = TTS_GENDER_FEMALE; + } else if (gender_str.empty()) { + gender = TTS_GENDER_NONE; + } else { error_ = constants::kErrorInvalidGender; return false; } @@ -179,27 +201,27 @@ bool TtsSpeakFunction::RunImpl() { options->GetBoolean(constants::kEnqueueKey, &can_enqueue)); } - std::set<std::string> required_event_types; + std::set<TtsEventType> required_event_types; if (options->HasKey(constants::kRequiredEventTypesKey)) { ListValue* list; EXTENSION_FUNCTION_VALIDATE( options->GetList(constants::kRequiredEventTypesKey, &list)); for (size_t i = 0; i < list->GetSize(); ++i) { std::string event_type; - if (!list->GetString(i, &event_type)) - required_event_types.insert(event_type); + if (list->GetString(i, &event_type)) + required_event_types.insert(TtsEventTypeFromString(event_type.c_str())); } } - std::set<std::string> desired_event_types; + std::set<TtsEventType> desired_event_types; if (options->HasKey(constants::kDesiredEventTypesKey)) { ListValue* list; EXTENSION_FUNCTION_VALIDATE( options->GetList(constants::kDesiredEventTypesKey, &list)); for (size_t i = 0; i < list->GetSize(); ++i) { std::string event_type; - if (!list->GetString(i, &event_type)) - desired_event_types.insert(event_type); + if (list->GetString(i, &event_type)) + desired_event_types.insert(TtsEventTypeFromString(event_type.c_str())); } } @@ -269,14 +291,46 @@ bool TtsGetVoicesFunction::RunImpl() { result_voice->SetString(constants::kVoiceNameKey, voice.name); if (!voice.lang.empty()) result_voice->SetString(constants::kLangKey, voice.lang); - if (!voice.gender.empty()) - result_voice->SetString(constants::kGenderKey, voice.gender); + if (voice.gender == TTS_GENDER_MALE) + result_voice->SetString(constants::kGenderKey, constants::kGenderMale); + else if (voice.gender == TTS_GENDER_FEMALE) + result_voice->SetString(constants::kGenderKey, constants::kGenderFemale); if (!voice.extension_id.empty()) result_voice->SetString(constants::kExtensionIdKey, voice.extension_id); ListValue* event_types = new ListValue(); - for (size_t j = 0; j < voice.events.size(); ++j) - event_types->Append(Value::CreateStringValue(voice.events[j])); + for (std::set<TtsEventType>::iterator iter = voice.events.begin(); + iter != voice.events.end(); ++iter) { + const char* event_name_constant = NULL; + switch (*iter) { + case TTS_EVENT_START: + event_name_constant = constants::kEventTypeStart; + break; + case TTS_EVENT_END: + event_name_constant = constants::kEventTypeEnd; + break; + case TTS_EVENT_WORD: + event_name_constant = constants::kEventTypeWord; + break; + case TTS_EVENT_SENTENCE: + event_name_constant = constants::kEventTypeSentence; + break; + case TTS_EVENT_MARKER: + event_name_constant = constants::kEventTypeMarker; + break; + case TTS_EVENT_INTERRUPTED: + event_name_constant = constants::kEventTypeInterrupted; + break; + case TTS_EVENT_CANCELLED: + event_name_constant = constants::kEventTypeCancelled; + break; + case TTS_EVENT_ERROR: + event_name_constant = constants::kEventTypeError; + break; + } + if (event_name_constant) + event_types->Append(Value::CreateStringValue(event_name_constant)); + } result_voice->Set(constants::kEventTypesKey, event_types); result_voices->Append(result_voice); diff --git a/chrome/browser/speech/extension_api/tts_extension_api.h b/chrome/browser/speech/extension_api/tts_extension_api.h index 6d5b4ee..a70d95e 100644 --- a/chrome/browser/speech/extension_api/tts_extension_api.h +++ b/chrome/browser/speech/extension_api/tts_extension_api.h @@ -13,6 +13,9 @@ class Profile; +const char *TtsEventTypeToString(TtsEventType event_type); +TtsEventType TtsEventTypeFromString(const std::string& str); + namespace extensions { class TtsSpeakFunction diff --git a/chrome/browser/speech/extension_api/tts_extension_api_constants.cc b/chrome/browser/speech/extension_api/tts_extension_api_constants.cc index bf6e057..3568a8d 100644 --- a/chrome/browser/speech/extension_api/tts_extension_api_constants.cc +++ b/chrome/browser/speech/extension_api/tts_extension_api_constants.cc @@ -36,8 +36,6 @@ const char kEventTypeInterrupted[] = "interrupted"; const char kEventTypeCancelled[] = "cancelled"; const char kEventTypeError[] = "error"; -const char kNativeVoiceName[] = "native"; - const char kErrorUndeclaredEventType[] = "Cannot send an event type that is not declared in the extension manifest."; const char kErrorUtteranceTooLong[] = "Utterance length is too long."; diff --git a/chrome/browser/speech/extension_api/tts_extension_api_constants.h b/chrome/browser/speech/extension_api/tts_extension_api_constants.h index 760fcf0..3270e97 100644 --- a/chrome/browser/speech/extension_api/tts_extension_api_constants.h +++ b/chrome/browser/speech/extension_api/tts_extension_api_constants.h @@ -41,8 +41,6 @@ extern const char kEventTypeInterrupted[]; extern const char kEventTypeCancelled[]; extern const char kEventTypeError[]; -extern const char kNativeVoiceName[]; - extern const char kErrorUndeclaredEventType[]; extern const char kErrorUtteranceTooLong[]; extern const char kErrorInvalidLang[]; diff --git a/chrome/browser/speech/extension_api/tts_extension_apitest.cc b/chrome/browser/speech/extension_api/tts_extension_apitest.cc index d865190..e704f5d 100644 --- a/chrome/browser/speech/extension_api/tts_extension_apitest.cc +++ b/chrome/browser/speech/extension_api/tts_extension_apitest.cc @@ -36,21 +36,19 @@ class MockTtsPlatformImpl : public TtsPlatformImpl { return true; } - virtual bool SendsEvent(TtsEventType event_type) { - return (event_type == TTS_EVENT_END || - event_type == TTS_EVENT_WORD); - } - - - MOCK_METHOD4(Speak, + MOCK_METHOD5(Speak, bool(int utterance_id, const std::string& utterance, const std::string& lang, + const VoiceData& voice, const UtteranceContinuousParameters& params)); + MOCK_METHOD0(StopSpeaking, bool(void)); MOCK_METHOD0(IsSpeaking, bool(void)); + MOCK_METHOD1(GetVoices, void(std::vector<VoiceData>*)); + void SetErrorToEpicFail() { set_error("epic fail"); } @@ -58,6 +56,7 @@ class MockTtsPlatformImpl : public TtsPlatformImpl { void SendEndEvent(int utterance_id, const std::string& utterance, const std::string& lang, + const VoiceData& voice, const UtteranceContinuousParameters& params) { MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind( @@ -72,6 +71,7 @@ class MockTtsPlatformImpl : public TtsPlatformImpl { int utterance_id, const std::string& utterance, const std::string& lang, + const VoiceData& voice, const UtteranceContinuousParameters& params) { MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind( @@ -84,6 +84,7 @@ class MockTtsPlatformImpl : public TtsPlatformImpl { void SendWordEvents(int utterance_id, const std::string& utterance, const std::string& lang, + const VoiceData& voice, const UtteranceContinuousParameters& params) { for (int i = 0; i < static_cast<int>(utterance.size()); i++) { if (i == 0 || utterance[i - 1] == ' ') { @@ -126,6 +127,8 @@ class TtsApiTest : public ExtensionApiTest { virtual void SetUpInProcessBrowserTestFixture() { ExtensionApiTest::SetUpInProcessBrowserTestFixture(); TtsController::GetInstance()->SetPlatformImpl(&mock_platform_impl_); + EXPECT_CALL(mock_platform_impl_, GetVoices(_)) + .Times(AnyNumber()); } protected: @@ -138,23 +141,23 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakOptionalArgs) { InSequence s; EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); - EXPECT_CALL(mock_platform_impl_, Speak(_, "", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "", _, _, _)) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); - EXPECT_CALL(mock_platform_impl_, Speak(_, "Alpha", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "Alpha", _, _, _)) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); - EXPECT_CALL(mock_platform_impl_, Speak(_, "Bravo", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "Bravo", _, _, _)) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); - EXPECT_CALL(mock_platform_impl_, Speak(_, "Charlie", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "Charlie", _, _, _)) .WillOnce(Return(true)); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); - EXPECT_CALL(mock_platform_impl_, Speak(_, "Echo", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "Echo", _, _, _)) .WillOnce(Return(true)); ASSERT_TRUE(RunExtensionTest("tts/optional_args")) << message_; } @@ -164,7 +167,7 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakFinishesImmediately) { EXPECT_CALL(mock_platform_impl_, IsSpeaking()); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); - EXPECT_CALL(mock_platform_impl_, Speak(_, _, _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, _, _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), @@ -179,12 +182,12 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakInterrupt) { InSequence s; EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); - EXPECT_CALL(mock_platform_impl_, Speak(_, "text 1", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "text 1", _, _, _)) .WillOnce(Return(true)); // Expect the second utterance and allow it to finish. EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); - EXPECT_CALL(mock_platform_impl_, Speak(_, "text 2", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "text 2", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), @@ -196,18 +199,18 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakQueueInterrupt) { EXPECT_CALL(mock_platform_impl_, IsSpeaking()); // In this test, two utterances are queued, and then a third - // interrupts. Speak() never gets called on the second utterance. + // interrupts. Speak(, _) never gets called on the second utterance. InSequence s; EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); - EXPECT_CALL(mock_platform_impl_, Speak(_, "text 1", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "text 1", _, _, _)) .WillOnce(Return(true)); // Don't expect the second utterance, because it's queued up and the // first never finishes. // Expect the third utterance and allow it to finish successfully. EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); - EXPECT_CALL(mock_platform_impl_, Speak(_, "text 3", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "text 3", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), @@ -221,12 +224,12 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakEnqueue) { InSequence s; EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); - EXPECT_CALL(mock_platform_impl_, Speak(_, "text 1", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "text 1", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEventWhenQueueNotEmpty), Return(true))); - EXPECT_CALL(mock_platform_impl_, Speak(_, "text 2", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "text 2", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), @@ -241,7 +244,7 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakError) { InSequence s; EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); - EXPECT_CALL(mock_platform_impl_, Speak(_, "first try", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "first try", _, _, _)) .WillOnce(DoAll( InvokeWithoutArgs( CreateFunctor(&mock_platform_impl_, @@ -249,7 +252,7 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakError) { Return(false))); EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); - EXPECT_CALL(mock_platform_impl_, Speak(_, "second try", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "second try", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), @@ -263,7 +266,7 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformWordCallbacks) { InSequence s; EXPECT_CALL(mock_platform_impl_, StopSpeaking()) .WillOnce(Return(true)); - EXPECT_CALL(mock_platform_impl_, Speak(_, "one two three", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "one two three", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendWordEvents), @@ -281,17 +284,17 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, RegisterEngine) { { InSequence s; - EXPECT_CALL(mock_platform_impl_, Speak(_, "native speech", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "native speech", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), Return(true))); - EXPECT_CALL(mock_platform_impl_, Speak(_, "native speech 2", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "native speech 2", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), Return(true))); - EXPECT_CALL(mock_platform_impl_, Speak(_, "native speech 3", _, _)) + EXPECT_CALL(mock_platform_impl_, Speak(_, "native speech 3", _, _, _)) .WillOnce(DoAll( Invoke(&mock_platform_impl_, &MockTtsPlatformImpl::SendEndEvent), diff --git a/chrome/browser/speech/tts_chromeos.cc b/chrome/browser/speech/tts_chromeos.cc index a0877a2..57d3387 100644 --- a/chrome/browser/speech/tts_chromeos.cc +++ b/chrome/browser/speech/tts_chromeos.cc @@ -25,6 +25,7 @@ class TtsPlatformImplChromeOs int utterance_id, const std::string& utterance, const std::string& lang, + const VoiceData& voice, const UtteranceContinuousParameters& params) OVERRIDE { return false; } @@ -37,8 +38,7 @@ class TtsPlatformImplChromeOs return false; } - virtual bool SendsEvent(TtsEventType event_type) OVERRIDE { - return false; + virtual void GetVoices(std::vector<VoiceData>* out_voices) OVERRIDE { } // Get the single instance of this class. diff --git a/chrome/browser/speech/tts_controller.cc b/chrome/browser/speech/tts_controller.cc index f6bff52..d7fb66e 100644 --- a/chrome/browser/speech/tts_controller.cc +++ b/chrome/browser/speech/tts_controller.cc @@ -13,18 +13,24 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/speech/extension_api/tts_engine_extension_api.h" #include "chrome/browser/speech/extension_api/tts_extension_api.h" -#include "chrome/browser/speech/extension_api/tts_extension_api_constants.h" #include "chrome/browser/speech/tts_platform.h" #include "chrome/common/extensions/api/speech/tts_engine_manifest_handler.h" #include "chrome/common/extensions/extension.h" -namespace constants = tts_extension_api_constants; - namespace { // A value to be used to indicate that there is no char index available. const int kInvalidCharIndex = -1; -} // namespace +// Given a language/region code of the form 'fr-FR', returns just the basic +// language portion, e.g. 'fr'. +std::string TrimLanguageCode(std::string lang) { + if (lang.size() >= 5 && lang[2] == '-') + return lang.substr(0, 2); + else + return lang; +} + +} // namespace bool IsFinalTtsEventType(TtsEventType event_type) { return (event_type == TTS_EVENT_END || @@ -33,7 +39,6 @@ bool IsFinalTtsEventType(TtsEventType event_type) { event_type == TTS_EVENT_ERROR); } - // // UtteranceContinuousParameters // @@ -50,7 +55,9 @@ UtteranceContinuousParameters::UtteranceContinuousParameters() // -VoiceData::VoiceData() {} +VoiceData::VoiceData() + : gender(TTS_GENDER_NONE), + native(false) {} VoiceData::~VoiceData() {} @@ -133,50 +140,59 @@ void TtsController::SpeakOrEnqueue(Utterance* utterance) { } void TtsController::SpeakNow(Utterance* utterance) { - const extensions::Extension* extension; - size_t voice_index; - if (GetMatchingExtensionVoice(utterance, &extension, &voice_index)) { + // Get all available voices and try to find a matching voice. + std::vector<VoiceData> voices; + GetVoices(utterance->profile(), &voices); + int index = GetMatchingVoice(utterance, voices); + + // Select the matching voice, but if none was found, initialize an + // empty VoiceData with native = true, which will give the native + // speech synthesizer a chance to try to synthesize the utterance + // anyway. + VoiceData voice; + if (index >= 0 && index < static_cast<int>(voices.size())) + voice = voices[index]; + else + voice.native = true; + + if (!voice.native) { + DCHECK(!voice.extension_id.empty()); current_utterance_ = utterance; - utterance->set_extension_id(extension->id()); - - ExtensionTtsEngineSpeak(utterance, extension, voice_index); - - const std::vector<extensions::TtsVoice>* tts_voices = - extensions::TtsVoice::GetTtsVoices(extension); - std::set<std::string> event_types; - if (tts_voices) - event_types = tts_voices->at(voice_index).event_types; + utterance->set_extension_id(voice.extension_id); + ExtensionTtsEngineSpeak(utterance, voice); bool sends_end_event = - (event_types.find(constants::kEventTypeEnd) != event_types.end()); + voice.events.find(TTS_EVENT_END) != voice.events.end(); if (!sends_end_event) { utterance->Finish(); delete utterance; current_utterance_ = NULL; SpeakNextUtterance(); } - return; - } - - GetPlatformImpl()->clear_error(); - bool success = GetPlatformImpl()->Speak( - utterance->id(), - utterance->text(), - utterance->lang(), - utterance->continuous_parameters()); - - if (!success && - GetPlatformImpl()->LoadBuiltInTtsExtension(utterance->profile())) { - utterance_queue_.push(utterance); - return; - } + } else { + GetPlatformImpl()->clear_error(); + bool success = GetPlatformImpl()->Speak( + utterance->id(), + utterance->text(), + utterance->lang(), + voice, + utterance->continuous_parameters()); + + // If the native voice wasn't able to process this speech, see if + // the browser has built-in TTS that isn't loaded yet. + if (!success && + GetPlatformImpl()->LoadBuiltInTtsExtension(utterance->profile())) { + utterance_queue_.push(utterance); + return; + } - if (!success) { - utterance->OnTtsEvent(TTS_EVENT_ERROR, kInvalidCharIndex, - GetPlatformImpl()->error()); - delete utterance; - return; + if (!success) { + utterance->OnTtsEvent(TTS_EVENT_ERROR, kInvalidCharIndex, + GetPlatformImpl()->error()); + delete utterance; + return; + } + current_utterance_ = utterance; } - current_utterance_ = utterance; } void TtsController::Stop() { @@ -214,33 +230,12 @@ void TtsController::OnTtsEvent(int utterance_id, void TtsController::GetVoices(Profile* profile, std::vector<VoiceData>* out_voices) { - TtsPlatformImpl* platform_impl = GetPlatformImpl(); - if (platform_impl && platform_impl->PlatformImplAvailable()) { - out_voices->push_back(VoiceData()); - VoiceData& voice = out_voices->back(); - voice.name = constants::kNativeVoiceName; - voice.gender = platform_impl->gender(); - - // All platforms must send end events, and cancelled and interrupted - // events are generated from the controller. - DCHECK(platform_impl->SendsEvent(TTS_EVENT_END)); - voice.events.push_back(constants::kEventTypeEnd); - voice.events.push_back(constants::kEventTypeCancelled); - voice.events.push_back(constants::kEventTypeInterrupted); - - if (platform_impl->SendsEvent(TTS_EVENT_START)) - voice.events.push_back(constants::kEventTypeStart); - if (platform_impl->SendsEvent(TTS_EVENT_WORD)) - voice.events.push_back(constants::kEventTypeWord); - if (platform_impl->SendsEvent(TTS_EVENT_SENTENCE)) - voice.events.push_back(constants::kEventTypeSentence); - if (platform_impl->SendsEvent(TTS_EVENT_MARKER)) - voice.events.push_back(constants::kEventTypeMarker); - if (platform_impl->SendsEvent(TTS_EVENT_ERROR)) - voice.events.push_back(constants::kEventTypeError); - } + if (profile) + GetExtensionVoices(profile, out_voices); - GetExtensionVoices(profile, out_voices); + TtsPlatformImpl* platform_impl = GetPlatformImpl(); + if (platform_impl && platform_impl->PlatformImplAvailable()) + platform_impl->GetVoices(out_voices); } bool TtsController::IsSpeaking() { @@ -299,3 +294,62 @@ TtsPlatformImpl* TtsController::GetPlatformImpl() { platform_impl_ = TtsPlatformImpl::GetInstance(); return platform_impl_; } + +int TtsController::GetMatchingVoice( + const Utterance* utterance, std::vector<VoiceData>& voices) { + // Make two passes: the first time, do strict language matching + // ('fr-FR' does not match 'fr-CA'). The second time, do prefix + // language matching ('fr-FR' matches 'fr' and 'fr-CA') + for (int pass = 0; pass < 2; ++pass) { + for (size_t i = 0; i < voices.size(); ++i) { + const VoiceData& voice = voices[i]; + + if (!utterance->extension_id().empty() && + utterance->extension_id() != voice.extension_id) { + continue; + } + + if (!voice.name.empty() && + !utterance->voice_name().empty() && + voice.name != utterance->voice_name()) { + continue; + } + if (!voice.lang.empty() && !utterance->lang().empty()) { + std::string voice_lang = voice.lang; + std::string utterance_lang = utterance->lang(); + if (pass == 1) { + voice_lang = TrimLanguageCode(voice_lang); + utterance_lang = TrimLanguageCode(utterance_lang); + } + if (voice_lang != utterance_lang) { + continue; + } + } + if (voice.gender != TTS_GENDER_NONE && + utterance->gender() != TTS_GENDER_NONE && + voice.gender != utterance->gender()) { + continue; + } + + if (utterance->required_event_types().size() > 0) { + bool has_all_required_event_types = true; + for (std::set<TtsEventType>::const_iterator iter = + utterance->required_event_types().begin(); + iter != utterance->required_event_types().end(); + ++iter) { + if (voice.events.find(*iter) == voice.events.end()) { + has_all_required_event_types = false; + break; + } + } + if (!has_all_required_event_types) + continue; + } + + return static_cast<int>(i); + } + } + + return -1; +} + diff --git a/chrome/browser/speech/tts_controller.h b/chrome/browser/speech/tts_controller.h index 10044bb..53ca537 100644 --- a/chrome/browser/speech/tts_controller.h +++ b/chrome/browser/speech/tts_controller.h @@ -34,6 +34,12 @@ enum TtsEventType { TTS_EVENT_ERROR }; +enum TtsGenderType { + TTS_GENDER_NONE, + TTS_GENDER_MALE, + TTS_GENDER_FEMALE +}; + // Returns true if this event type is one that indicates an utterance // is finished and can be destroyed. bool IsFinalTtsEventType(TtsEventType event_type); @@ -54,9 +60,14 @@ struct VoiceData { std::string name; std::string lang; - std::string gender; + TtsGenderType gender; std::string extension_id; - std::vector<std::string> events; + std::set<TtsEventType> events; + + // If true, this is implemented by this platform's subclass of + // TtsPlatformImpl. If false, this is implemented by an extension. + bool native; + std::string native_voice_identifier; }; // Class that wants to receive events on utterances. @@ -116,10 +127,10 @@ class Utterance { } const std::string& lang() const { return lang_; } - void set_gender(const std::string& gender) { + void set_gender(TtsGenderType gender) { gender_ = gender; } - const std::string& gender() const { return gender_; } + TtsGenderType gender() const { return gender_; } void set_continuous_parameters(const UtteranceContinuousParameters& params) { continuous_parameters_ = params; @@ -131,17 +142,17 @@ class Utterance { void set_can_enqueue(bool can_enqueue) { can_enqueue_ = can_enqueue; } bool can_enqueue() const { return can_enqueue_; } - void set_required_event_types(const std::set<std::string>& types) { + void set_required_event_types(const std::set<TtsEventType>& types) { required_event_types_ = types; } - const std::set<std::string>& required_event_types() const { + const std::set<TtsEventType>& required_event_types() const { return required_event_types_; } - void set_desired_event_types(const std::set<std::string>& types) { + void set_desired_event_types(const std::set<TtsEventType>& types) { desired_event_types_ = types; } - const std::set<std::string>& desired_event_types() const { + const std::set<TtsEventType>& desired_event_types() const { return desired_event_types_; } @@ -202,11 +213,11 @@ class Utterance { // The parsed options. std::string voice_name_; std::string lang_; - std::string gender_; + TtsGenderType gender_; UtteranceContinuousParameters continuous_parameters_; bool can_enqueue_; - std::set<std::string> required_event_types_; - std::set<std::string> desired_event_types_; + std::set<TtsEventType> required_event_types_; + std::set<TtsEventType> desired_event_types_; // The index of the current char being spoken. int char_index_; @@ -278,6 +289,11 @@ class TtsController { // Start speaking the next utterance in the queue. void SpeakNextUtterance(); + // Given an utterance and a vector of voices, return the + // index of the voice that best matches the utterance. + int GetMatchingVoice(const Utterance* utterance, + std::vector<VoiceData>& voices); + friend struct DefaultSingletonTraits<TtsController>; // The current utterance being spoken. diff --git a/chrome/browser/speech/tts_controller_unittest.cc b/chrome/browser/speech/tts_controller_unittest.cc index 98e7f7b..16e912d 100644 --- a/chrome/browser/speech/tts_controller_unittest.cc +++ b/chrome/browser/speech/tts_controller_unittest.cc @@ -22,13 +22,13 @@ class DummyTtsPlatformImpl : public TtsPlatformImpl { int utterance_id, const std::string& utterance, const std::string& lang, + const VoiceData& voice, const UtteranceContinuousParameters& params) OVERRIDE { return true; } virtual bool IsSpeaking() OVERRIDE { return false; } virtual bool StopSpeaking() OVERRIDE { return true; } - virtual bool SendsEvent(TtsEventType event_type) OVERRIDE { return false; } - virtual std::string gender() OVERRIDE { return std::string(); } + virtual void GetVoices(std::vector<VoiceData>* out_voices) OVERRIDE {} virtual std::string error() OVERRIDE { return std::string(); } virtual void clear_error() OVERRIDE {} virtual void set_error(const std::string& error) OVERRIDE {} diff --git a/chrome/browser/speech/tts_linux.cc b/chrome/browser/speech/tts_linux.cc index 86905eb..9ab9fe8 100644 --- a/chrome/browser/speech/tts_linux.cc +++ b/chrome/browser/speech/tts_linux.cc @@ -27,10 +27,12 @@ class TtsPlatformImplLinux : public TtsPlatformImpl { int utterance_id, const std::string& utterance, const std::string& lang, + const VoiceData& voice, const UtteranceContinuousParameters& params) OVERRIDE; virtual bool StopSpeaking() OVERRIDE; virtual bool IsSpeaking() OVERRIDE; - virtual bool SendsEvent(TtsEventType event_type) OVERRIDE; + virtual void GetVoices(std::vector<VoiceData>* out_voices) OVERRIDE; + void OnSpeechEvent(SPDNotificationType type); // Get the single instance of this class. @@ -138,6 +140,7 @@ bool TtsPlatformImplLinux::Speak( int utterance_id, const std::string& utterance, const std::string& lang, + const VoiceData& voice, const UtteranceContinuousParameters& params) { if (!PlatformImplAvailable()) { error_ = kNotSupportedError; @@ -180,11 +183,18 @@ bool TtsPlatformImplLinux::IsSpeaking() { return current_notification_ == SPD_EVENT_BEGIN; } -bool TtsPlatformImplLinux::SendsEvent(TtsEventType event_type) { - return (event_type == TTS_EVENT_START || - event_type == TTS_EVENT_END || - event_type == TTS_EVENT_CANCELLED || - event_type == TTS_EVENT_MARKER); +void TtsPlatformImplLinux::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_CANCELLED); + voice.events.insert(TTS_EVENT_MARKER); } void TtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type) { diff --git a/chrome/browser/speech/tts_mac.mm b/chrome/browser/speech/tts_mac.mm index a1d24b3..689effc 100644 --- a/chrome/browser/speech/tts_mac.mm +++ b/chrome/browser/speech/tts_mac.mm @@ -58,13 +58,14 @@ class TtsPlatformImplMac : public TtsPlatformImpl { int utterance_id, const std::string& utterance, const std::string& lang, + const VoiceData& voice, const UtteranceContinuousParameters& params) OVERRIDE; virtual bool StopSpeaking() OVERRIDE; virtual bool IsSpeaking() OVERRIDE; - virtual bool SendsEvent(TtsEventType event_type) OVERRIDE; + virtual void GetVoices(std::vector<VoiceData>* out_voices) OVERRIDE; // Called by ChromeTtsDelegate when we get a callback from the // native speech engine. @@ -100,6 +101,7 @@ bool TtsPlatformImplMac::Speak( int utterance_id, const std::string& utterance, const std::string& lang, + const VoiceData& voice, const UtteranceContinuousParameters& params) { // TODO: convert SSML to SAPI xml. http://crbug.com/88072 utterance_ = utterance; @@ -117,6 +119,12 @@ bool TtsPlatformImplMac::Speak( initWithUtterance:utterance_nsstring]); [speech_synthesizer_ setDelegate:delegate_]; + if (!voice.native_voice_identifier.empty()) { + NSString* native_voice_identifier = + [NSString stringWithUTF8String:voice.native_voice_identifier.c_str()]; + [speech_synthesizer_ setVoice:native_voice_identifier]; + } + utterance_id_ = utterance_id; sent_start_event_ = false; @@ -130,9 +138,16 @@ bool TtsPlatformImplMac::Speak( } if (params.pitch >= 0.0) { - // The TTS api allows an approximate range of 30 to 65 for speech pitch. + // The input is a float from 0.0 to 2.0, with 1.0 being the default. + // Get the default pitch for this voice and modulate it by 50% - 150%. + NSError* errorCode; + NSNumber* defaultPitchObj = + [speech_synthesizer_ objectForProperty:NSSpeechPitchBaseProperty + error:&errorCode]; + int defaultPitch = defaultPitchObj ? [defaultPitchObj intValue] : 48; + int newPitch = static_cast<int>(defaultPitch * (0.5 * params.pitch + 0.5)); [speech_synthesizer_ - setObject: [NSNumber numberWithInt:(params.pitch * 17 + 30)] + setObject:[NSNumber numberWithInt:newPitch] forProperty:NSSpeechPitchBaseProperty error:nil]; } @@ -157,11 +172,58 @@ bool TtsPlatformImplMac::IsSpeaking() { return [NSSpeechSynthesizer isAnyApplicationSpeaking]; } -bool TtsPlatformImplMac::SendsEvent(TtsEventType event_type) { - return (event_type == TTS_EVENT_START || - event_type == TTS_EVENT_END || - event_type == TTS_EVENT_WORD || - event_type == TTS_EVENT_ERROR); +void TtsPlatformImplMac::GetVoices(std::vector<VoiceData>* outVoices) { + NSArray* voices = [NSSpeechSynthesizer availableVoices]; + + // Create a new temporary array of the available voices with + // the default voice first. + NSMutableArray* orderedVoices = + [NSMutableArray arrayWithCapacity:[voices count]]; + NSString* defaultVoice = [NSSpeechSynthesizer defaultVoice]; + [orderedVoices addObject:defaultVoice]; + for (NSString* voiceIdentifier in voices) { + if (![voiceIdentifier isEqualToString:defaultVoice]) + [orderedVoices addObject:voiceIdentifier]; + } + + for (NSString* voiceIdentifier in orderedVoices) { + outVoices->push_back(VoiceData()); + VoiceData& data = outVoices->back(); + + NSDictionary* attributes = + [NSSpeechSynthesizer attributesForVoice:voiceIdentifier]; + NSString* name = [attributes objectForKey:NSVoiceName]; + NSString* gender = [attributes objectForKey:NSVoiceGender]; + NSString* localeIdentifier = + [attributes objectForKey:NSVoiceLocaleIdentifier]; + + data.native = true; + data.native_voice_identifier = base::SysNSStringToUTF8(voiceIdentifier); + data.name = base::SysNSStringToUTF8(name); + + NSDictionary* localeComponents = + [NSLocale componentsFromLocaleIdentifier:localeIdentifier]; + NSString* language = [localeComponents objectForKey:NSLocaleLanguageCode]; + NSString* country = [localeComponents objectForKey:NSLocaleCountryCode]; + if (language && country) { + data.lang = + [[NSString stringWithFormat:@"%@-%@", language, country] UTF8String]; + } else { + data.lang = base::SysNSStringToUTF8(language); + } + if ([gender isEqualToString:NSVoiceGenderMale]) + data.gender = TTS_GENDER_MALE; + else if ([gender isEqualToString:NSVoiceGenderFemale]) + data.gender = TTS_GENDER_FEMALE; + else + data.gender = TTS_GENDER_NONE; + data.events.insert(TTS_EVENT_START); + data.events.insert(TTS_EVENT_END); + data.events.insert(TTS_EVENT_WORD); + data.events.insert(TTS_EVENT_ERROR); + data.events.insert(TTS_EVENT_CANCELLED); + data.events.insert(TTS_EVENT_INTERRUPTED); + } } void TtsPlatformImplMac::OnSpeechEvent( diff --git a/chrome/browser/speech/tts_platform.cc b/chrome/browser/speech/tts_platform.cc index 8ff1048..6b51bdc 100644 --- a/chrome/browser/speech/tts_platform.cc +++ b/chrome/browser/speech/tts_platform.cc @@ -10,10 +10,6 @@ bool TtsPlatformImpl::LoadBuiltInTtsExtension(Profile* profile) { return false; } -std::string TtsPlatformImpl::gender() { - return std::string(); -} - std::string TtsPlatformImpl::error() { return error_; } diff --git a/chrome/browser/speech/tts_platform.h b/chrome/browser/speech/tts_platform.h index 2bcd601..c6b5706 100644 --- a/chrome/browser/speech/tts_platform.h +++ b/chrome/browser/speech/tts_platform.h @@ -36,6 +36,7 @@ class TtsPlatformImpl { int utterance_id, const std::string& utterance, const std::string& lang, + const VoiceData& voice, const UtteranceContinuousParameters& params) = 0; // Stop speaking immediately and return true on success. @@ -44,14 +45,9 @@ class TtsPlatformImpl { // Returns whether any speech is on going. virtual bool IsSpeaking() = 0; - // Return true if this platform implementation will fire the given event. - // All platform implementations must fire the TTS_EVENT_END event at a - // minimum. - virtual bool SendsEvent(TtsEventType event_type) = 0; - - // Return the gender of the voice, should be either "male" or "female" - // if known, otherwise the empty string. - virtual std::string gender(); + // Append information about voices provided by this platform implementation + // to |out_voices|. + virtual void GetVoices(std::vector<VoiceData>* out_voices) = 0; virtual std::string error(); virtual void clear_error(); diff --git a/chrome/browser/speech/tts_win.cc b/chrome/browser/speech/tts_win.cc index cb3a0c2..06a16f5 100644 --- a/chrome/browser/speech/tts_win.cc +++ b/chrome/browser/speech/tts_win.cc @@ -23,13 +23,14 @@ class TtsPlatformImplWin : public TtsPlatformImpl { int utterance_id, const std::string& utterance, const std::string& lang, + const VoiceData& voice, const UtteranceContinuousParameters& params); virtual bool StopSpeaking(); virtual bool IsSpeaking(); - virtual bool SendsEvent(TtsEventType event_type); + virtual void GetVoices(std::vector<VoiceData>* out_voices) OVERRIDE; // Get the single instance of this class. static TtsPlatformImplWin* GetInstance(); @@ -65,6 +66,7 @@ bool TtsPlatformImplWin::Speak( int utterance_id, const std::string& src_utterance, const std::string& lang, + const VoiceData& voice, const UtteranceContinuousParameters& params) { std::wstring prefix; std::wstring suffix; @@ -141,12 +143,19 @@ bool TtsPlatformImplWin::IsSpeaking() { return false; } -bool TtsPlatformImplWin::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 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); } void TtsPlatformImplWin::OnSpeechEvent() { |