summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-11 18:51:43 +0000
committerdmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-11 18:51:43 +0000
commit56f6f9dc5522435534cebcf0a1a9a239dbda2bea (patch)
treeba37024edb6d2d6f06273f6fdfef976f00507719
parent71395b17a1da1100fe57e8ad141c2ea5da338416 (diff)
downloadchromium_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
-rw-r--r--chrome/browser/speech/extension_api/tts_engine_extension_api.cc135
-rw-r--r--chrome/browser/speech/extension_api/tts_engine_extension_api.h3
-rw-r--r--chrome/browser/speech/extension_api/tts_extension_api.cc136
-rw-r--r--chrome/browser/speech/extension_api/tts_extension_api.h3
-rw-r--r--chrome/browser/speech/extension_api/tts_extension_api_constants.cc2
-rw-r--r--chrome/browser/speech/extension_api/tts_extension_api_constants.h2
-rw-r--r--chrome/browser/speech/extension_api/tts_extension_apitest.cc55
-rw-r--r--chrome/browser/speech/tts_chromeos.cc4
-rw-r--r--chrome/browser/speech/tts_controller.cc186
-rw-r--r--chrome/browser/speech/tts_controller.h38
-rw-r--r--chrome/browser/speech/tts_controller_unittest.cc4
-rw-r--r--chrome/browser/speech/tts_linux.cc22
-rw-r--r--chrome/browser/speech/tts_mac.mm78
-rw-r--r--chrome/browser/speech/tts_platform.cc4
-rw-r--r--chrome/browser/speech/tts_platform.h12
-rw-r--r--chrome/browser/speech/tts_win.cc23
-rw-r--r--chrome/test/data/extensions/api_test/tts_engine/register_engine/test.js12
17 files changed, 403 insertions, 316 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() {
diff --git a/chrome/test/data/extensions/api_test/tts_engine/register_engine/test.js b/chrome/test/data/extensions/api_test/tts_engine/register_engine/test.js
index 03fb0345..99f1bd9 100644
--- a/chrome/test/data/extensions/api_test/tts_engine/register_engine/test.js
+++ b/chrome/test/data/extensions/api_test/tts_engine/register_engine/test.js
@@ -146,16 +146,14 @@ chrome.test.runTests([
chrome.ttsEngine.onStop.addListener(stopListener);
chrome.tts.getVoices(function(voices) {
- chrome.test.assertEq(3, voices.length);
+ chrome.test.assertEq(2, voices.length);
- chrome.test.assertEq('native', voices[0].voiceName);
+ chrome.test.assertEq('Alice', voices[0].voiceName);
+ chrome.test.assertEq('en-US', voices[0].lang);
+ chrome.test.assertEq('female', voices[0].gender);
- chrome.test.assertEq('Alice', voices[1].voiceName);
+ chrome.test.assertEq('Pat', voices[1].voiceName);
chrome.test.assertEq('en-US', voices[1].lang);
- chrome.test.assertEq('female', voices[1].gender);
-
- chrome.test.assertEq('Pat', voices[2].voiceName);
- chrome.test.assertEq('en-US', voices[2].lang);
chrome.test.succeed();
});
}