Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/speech/tts_controller_impl.h" | 5 #include "chrome/browser/speech/tts_controller_impl.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 | 8 |
| 9 #include <string> | 9 #include <string> |
| 10 #include <vector> | 10 #include <vector> |
| (...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 156 | 156 |
| 157 void TtsControllerImpl::SpeakNow(Utterance* utterance) { | 157 void TtsControllerImpl::SpeakNow(Utterance* utterance) { |
| 158 // Ensure we have all built-in voices loaded. This is a no-op if already | 158 // Ensure we have all built-in voices loaded. This is a no-op if already |
| 159 // loaded. | 159 // loaded. |
| 160 bool loaded_built_in = | 160 bool loaded_built_in = |
| 161 GetPlatformImpl()->LoadBuiltInTtsExtension(utterance->browser_context()); | 161 GetPlatformImpl()->LoadBuiltInTtsExtension(utterance->browser_context()); |
| 162 | 162 |
| 163 // Get all available voices and try to find a matching voice. | 163 // Get all available voices and try to find a matching voice. |
| 164 std::vector<VoiceData> voices; | 164 std::vector<VoiceData> voices; |
| 165 GetVoices(utterance->browser_context(), &voices); | 165 GetVoices(utterance->browser_context(), &voices); |
| 166 | |
| 167 // Get the best matching voice. If nothing matches, just set "native" | |
| 168 // to true because that might trigger deferred loading of native voices. | |
| 166 int index = GetMatchingVoice(utterance, voices); | 169 int index = GetMatchingVoice(utterance, voices); |
| 167 | |
| 168 VoiceData voice; | 170 VoiceData voice; |
| 169 if (index != -1) { | 171 if (index >= 0) |
| 170 // Select the matching voice. | |
| 171 voice = voices[index]; | 172 voice = voices[index]; |
| 172 } else { | 173 else |
| 173 // However, if no match was found on a platform without native tts voices, | 174 voice.native = true; // Try to let |
| 174 // attempt to get a voice based only on the current locale without respect | |
| 175 // to any supplied voice names. | |
| 176 std::vector<VoiceData> native_voices; | |
| 177 | |
| 178 if (GetPlatformImpl()->PlatformImplAvailable()) | |
| 179 GetPlatformImpl()->GetVoices(&native_voices); | |
| 180 | |
| 181 if (native_voices.empty() && !voices.empty()) { | |
| 182 // TODO(dtseng): Notify extension caller of an error. | |
| 183 utterance->set_voice_name(""); | |
| 184 // TODO(gaochun): Replace the global variable g_browser_process with | |
| 185 // GetContentClient()->browser() to eliminate the dependency of browser | |
| 186 // once TTS implementation was moved to content. | |
| 187 utterance->set_lang(g_browser_process->GetApplicationLocale()); | |
| 188 index = GetMatchingVoice(utterance, voices); | |
| 189 | |
| 190 // If even that fails, just take the first available voice. | |
| 191 if (index == -1) | |
| 192 index = 0; | |
| 193 voice = voices[index]; | |
| 194 } else { | |
| 195 // Otherwise, simply give native voices a chance to handle this utterance. | |
| 196 voice.native = true; | |
| 197 } | |
| 198 } | |
| 199 | 175 |
| 200 GetPlatformImpl()->WillSpeakUtteranceWithVoice(utterance, voice); | 176 GetPlatformImpl()->WillSpeakUtteranceWithVoice(utterance, voice); |
| 201 | 177 |
| 202 if (!voice.native) { | 178 if (!voice.native) { |
| 203 #if !defined(OS_ANDROID) | 179 #if !defined(OS_ANDROID) |
| 204 DCHECK(!voice.extension_id.empty()); | 180 DCHECK(!voice.extension_id.empty()); |
| 205 current_utterance_ = utterance; | 181 current_utterance_ = utterance; |
| 206 utterance->set_extension_id(voice.extension_id); | 182 utterance->set_extension_id(voice.extension_id); |
| 207 if (tts_engine_delegate_) | 183 if (tts_engine_delegate_) |
| 208 tts_engine_delegate_->Speak(utterance, voice); | 184 tts_engine_delegate_->Speak(utterance, voice); |
| (...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 369 } | 345 } |
| 370 | 346 |
| 371 TtsPlatformImpl* TtsControllerImpl::GetPlatformImpl() { | 347 TtsPlatformImpl* TtsControllerImpl::GetPlatformImpl() { |
| 372 if (!platform_impl_) | 348 if (!platform_impl_) |
| 373 platform_impl_ = TtsPlatformImpl::GetInstance(); | 349 platform_impl_ = TtsPlatformImpl::GetInstance(); |
| 374 return platform_impl_; | 350 return platform_impl_; |
| 375 } | 351 } |
| 376 | 352 |
| 377 int TtsControllerImpl::GetMatchingVoice( | 353 int TtsControllerImpl::GetMatchingVoice( |
| 378 const Utterance* utterance, std::vector<VoiceData>& voices) { | 354 const Utterance* utterance, std::vector<VoiceData>& voices) { |
| 379 // Make two passes: the first time, do strict language matching | 355 // Return the index of the voice that best match the utterance parameters. |
| 380 // ('fr-FR' does not match 'fr-CA'). The second time, do prefix | 356 // |
| 381 // language matching ('fr-FR' matches 'fr' and 'fr-CA') | 357 // These criteria are considered mandatory - if they're specified, any voice |
| 382 for (int pass = 0; pass < 2; ++pass) { | 358 // that doesn't match is rejected. |
| 383 for (size_t i = 0; i < voices.size(); ++i) { | 359 // |
| 384 const VoiceData& voice = voices[i]; | 360 // Extension ID |
| 361 // Voice name | |
| 362 // | |
| 363 // The other criteria are scored based on how well they match, in | |
| 364 // this order of precedence: | |
| 365 // | |
| 366 // Utterange language (exact region preferred, then general language) | |
| 367 // App/system language (exact region preferred, then general language) | |
| 368 // Required event types | |
| 369 // Gender | |
| 385 | 370 |
| 386 if (!utterance->extension_id().empty() && | 371 // TODO(gaochun): Replace the global variable g_browser_process with |
| 387 utterance->extension_id() != voice.extension_id) { | 372 // GetContentClient()->browser() to eliminate the dependency of browser |
| 388 continue; | 373 // once TTS implementation was moved to content. |
| 389 } | 374 std::string app_lang = g_browser_process->GetApplicationLocale(); |
| 390 | 375 |
| 391 if (!voice.name.empty() && | 376 // Start with a best score of -1, that way even if none of the criteria |
| 392 !utterance->voice_name().empty() && | 377 // match, something will be returned if there are any voices. |
| 393 voice.name != utterance->voice_name()) { | 378 int best_score = -1; |
| 394 continue; | 379 int best_score_index = -1; |
| 395 } | 380 for (size_t i = 0; i < voices.size(); ++i) { |
| 396 if (!voice.lang.empty() && !utterance->lang().empty()) { | 381 const VoiceData& voice = voices[i]; |
| 397 std::string voice_lang = voice.lang; | 382 int score = 0; |
| 398 std::string utterance_lang = utterance->lang(); | 383 |
| 399 if (pass == 1) { | 384 // If the extension ID is specified, check for an exact match. |
| 400 voice_lang = TrimLanguageCode(voice_lang); | 385 if (!utterance->extension_id().empty() && |
| 401 utterance_lang = TrimLanguageCode(utterance_lang); | 386 utterance->extension_id() != voice.extension_id) |
| 402 } | 387 continue; |
| 403 if (voice_lang != utterance_lang) { | 388 |
| 404 continue; | 389 // If the voice name is specified, check for an exact match. |
| 390 if (!utterance->voice_name().empty() && | |
| 391 voice.name != utterance->voice_name()) | |
| 392 continue; | |
| 393 | |
| 394 // Prefer the utterance language. | |
| 395 if (!voice.lang.empty() && !utterance->lang().empty()) { | |
| 396 // An exact language match is worth more | |
| 397 if (voice.lang == utterance->lang()) | |
| 398 score += 32; | |
| 399 | |
| 400 // A partial language match is worth less | |
| 401 if (TrimLanguageCode(voice.lang) == TrimLanguageCode(utterance->lang())) | |
|
David Tseng
2016/09/23 22:10:50
else if?
dmazzoni
2016/09/26 19:26:12
Done
| |
| 402 score += 16; | |
| 403 } | |
| 404 | |
| 405 // Prefer the system language after that. | |
| 406 if (!voice.lang.empty()) { | |
| 407 if (voice.lang == app_lang) | |
| 408 score += 8; | |
| 409 | |
| 410 if (TrimLanguageCode(voice.lang) == TrimLanguageCode(app_lang)) | |
|
David Tseng
2016/09/23 22:10:50
else if?
dmazzoni
2016/09/26 19:26:12
Done
| |
| 411 score += 4; | |
| 412 } | |
| 413 | |
| 414 // Next, prefer required event types. | |
| 415 if (utterance->required_event_types().size() > 0) { | |
| 416 bool has_all_required_event_types = true; | |
| 417 for (std::set<TtsEventType>::const_iterator iter = | |
| 418 utterance->required_event_types().begin(); | |
| 419 iter != utterance->required_event_types().end(); | |
| 420 ++iter) { | |
| 421 if (voice.events.find(*iter) == voice.events.end()) { | |
| 422 has_all_required_event_types = false; | |
| 423 break; | |
| 405 } | 424 } |
| 406 } | 425 } |
| 407 if (voice.gender != TTS_GENDER_NONE && | 426 if (has_all_required_event_types) |
| 408 utterance->gender() != TTS_GENDER_NONE && | 427 score += 2; |
| 409 voice.gender != utterance->gender()) { | 428 } |
| 410 continue; | |
| 411 } | |
| 412 | 429 |
| 413 if (utterance->required_event_types().size() > 0) { | 430 // Finally prefer the requested gender last. |
| 414 bool has_all_required_event_types = true; | 431 if (voice.gender != TTS_GENDER_NONE && |
| 415 for (std::set<TtsEventType>::const_iterator iter = | 432 utterance->gender() != TTS_GENDER_NONE && |
| 416 utterance->required_event_types().begin(); | 433 voice.gender == utterance->gender()) { |
| 417 iter != utterance->required_event_types().end(); | 434 score += 1; |
| 418 ++iter) { | 435 } |
| 419 if (voice.events.find(*iter) == voice.events.end()) { | |
| 420 has_all_required_event_types = false; | |
| 421 break; | |
| 422 } | |
| 423 } | |
| 424 if (!has_all_required_event_types) | |
| 425 continue; | |
| 426 } | |
| 427 | 436 |
| 428 return static_cast<int>(i); | 437 if (score > best_score) { |
| 438 best_score = score; | |
| 439 best_score_index = i; | |
| 429 } | 440 } |
| 430 } | 441 } |
| 431 | 442 |
| 432 return -1; | 443 return best_score_index; |
| 433 } | 444 } |
| 434 | 445 |
| 435 void TtsControllerImpl::VoicesChanged() { | 446 void TtsControllerImpl::VoicesChanged() { |
| 436 // Existence of platform tts indicates explicit requests to tts. Since | 447 // Existence of platform tts indicates explicit requests to tts. Since |
| 437 // |VoicesChanged| can occur implicitly, only send if needed. | 448 // |VoicesChanged| can occur implicitly, only send if needed. |
| 438 if (!platform_impl_) | 449 if (!platform_impl_) |
| 439 return; | 450 return; |
| 440 | 451 |
| 441 for (std::set<VoicesChangedDelegate*>::iterator iter = | 452 for (std::set<VoicesChangedDelegate*>::iterator iter = |
| 442 voices_changed_delegates_.begin(); | 453 voices_changed_delegates_.begin(); |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 486 } | 497 } |
| 487 | 498 |
| 488 void TtsControllerImpl::SetTtsEngineDelegate( | 499 void TtsControllerImpl::SetTtsEngineDelegate( |
| 489 TtsEngineDelegate* delegate) { | 500 TtsEngineDelegate* delegate) { |
| 490 tts_engine_delegate_ = delegate; | 501 tts_engine_delegate_ = delegate; |
| 491 } | 502 } |
| 492 | 503 |
| 493 TtsEngineDelegate* TtsControllerImpl::GetTtsEngineDelegate() { | 504 TtsEngineDelegate* TtsControllerImpl::GetTtsEngineDelegate() { |
| 494 return tts_engine_delegate_; | 505 return tts_engine_delegate_; |
| 495 } | 506 } |
| OLD | NEW |