blob: b25c9855acfa223db8971ca24d3ac30b09294ef3 [file] [log] [blame]
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001package android.speech.tts;
2
3import android.speech.tts.TextToSpeechClient.EngineStatus;
4
5import java.util.Locale;
6
7/**
8 * Set of common heuristics for selecting {@link VoiceInfo} from
9 * {@link TextToSpeechClient#getEngineStatus()} output.
10 */
11public final class RequestConfigHelper {
12 private RequestConfigHelper() {}
13
14 /**
15 * Interface for scoring VoiceInfo object.
16 */
17 public static interface VoiceScorer {
18 /**
19 * Score VoiceInfo. If the score is less than or equal to zero, that voice is discarded.
20 * If two voices have same desired primary characteristics (highest quality, lowest
21 * latency or others), the one with the higher score is selected.
22 */
23 public int scoreVoice(VoiceInfo voiceInfo);
24 }
25
26 /**
27 * Score positively voices that exactly match the locale supplied to the constructor.
28 */
29 public static final class ExactLocaleMatcher implements VoiceScorer {
30 private final Locale mLocale;
31
32 /**
33 * Score positively voices that exactly match the given locale
34 * @param locale Reference locale. If null, the default locale will be used.
35 */
36 public ExactLocaleMatcher(Locale locale) {
37 if (locale == null) {
38 mLocale = Locale.getDefault();
39 } else {
40 mLocale = locale;
41 }
42 }
43 @Override
44 public int scoreVoice(VoiceInfo voiceInfo) {
45 return mLocale.equals(voiceInfo.getLocale()) ? 1 : 0;
46 }
47 }
48
49 /**
50 * Score positively voices that match exactly the given locale (score 3)
51 * or that share same language and country (score 2), or that share just a language (score 1).
52 */
53 public static final class LanguageMatcher implements VoiceScorer {
54 private final Locale mLocale;
55
56 /**
57 * Score positively voices with similar locale.
58 * @param locale Reference locale. If null, default will be used.
59 */
60 public LanguageMatcher(Locale locale) {
61 if (locale == null) {
62 mLocale = Locale.getDefault();
63 } else {
64 mLocale = locale;
65 }
66 }
67
68 @Override
69 public int scoreVoice(VoiceInfo voiceInfo) {
70 final Locale voiceLocale = voiceInfo.getLocale();
71 if (mLocale.equals(voiceLocale)) {
72 return 3;
73 } else {
74 if (mLocale.getLanguage().equals(voiceLocale.getLanguage())) {
75 if (mLocale.getCountry().equals(voiceLocale.getCountry())) {
76 return 2;
77 }
78 return 1;
79 }
80 return 0;
81 }
82 }
83 }
84
85 /**
86 * Get the highest quality voice from voices that score more than zero from the passed scorer.
87 * If there is more than one voice with the same highest quality, then this method returns one
88 * with the highest score. If they share same score as well, one with the lower index in the
89 * voices list is returned.
90 *
91 * @param engineStatus
92 * Voices status received from a {@link TextToSpeechClient#getEngineStatus()} call.
93 * @param voiceScorer
94 * Used to discard unsuitable voices and help settle cases where more than
95 * one voice has the desired characteristic.
96 * @param hasToBeEmbedded
97 * If true, require the voice to be an embedded voice (no network
98 * access will be required for synthesis).
99 */
100 private static VoiceInfo getHighestQualityVoice(EngineStatus engineStatus,
101 VoiceScorer voiceScorer, boolean hasToBeEmbedded) {
102 VoiceInfo bestVoice = null;
103 int bestScoreMatch = 1;
104 int bestVoiceQuality = 0;
105
106 for (VoiceInfo voice : engineStatus.getVoices()) {
107 int score = voiceScorer.scoreVoice(voice);
108 if (score <= 0 || hasToBeEmbedded && voice.getRequiresNetworkConnection()
109 || voice.getQuality() < bestVoiceQuality) {
110 continue;
111 }
112
113 if (bestVoice == null ||
114 voice.getQuality() > bestVoiceQuality ||
115 score > bestScoreMatch) {
116 bestVoice = voice;
117 bestScoreMatch = score;
118 bestVoiceQuality = voice.getQuality();
119 }
120 }
121 return bestVoice;
122 }
123
124 /**
125 * Get highest quality voice.
126 *
127 * Highest quality voice is selected from voices that score more than zero from the passed
128 * scorer. If there is more than one voice with the same highest quality, then this method
129 * will return one with the highest score. If they share same score as well, one with the lower
130 * index in the voices list is returned.
131
132 * @param engineStatus
133 * Voices status received from a {@link TextToSpeechClient#getEngineStatus()} call.
134 * @param hasToBeEmbedded
135 * If true, require the voice to be an embedded voice (no network
136 * access will be required for synthesis).
137 * @param voiceScorer
138 * Scorer is used to discard unsuitable voices and help settle cases where more than
139 * one voice has highest quality.
140 * @return RequestConfig with selected voice or null if suitable voice was not found.
141 */
142 public static RequestConfig highestQuality(EngineStatus engineStatus,
143 boolean hasToBeEmbedded, VoiceScorer voiceScorer) {
144 VoiceInfo voice = getHighestQualityVoice(engineStatus, voiceScorer, hasToBeEmbedded);
145 if (voice == null) {
146 return null;
147 }
148 return RequestConfig.Builder.newBuilder().setVoice(voice).build();
149 }
150
151 /**
152 * Get highest quality voice for the default locale.
153 *
154 * Call {@link #highestQuality(EngineStatus, boolean, VoiceScorer)} with
155 * {@link LanguageMatcher} set to device default locale.
156 *
157 * @param engineStatus
158 * Voices status received from a {@link TextToSpeechClient#getEngineStatus()} call.
159 * @param hasToBeEmbedded
160 * If true, require the voice to be an embedded voice (no network
161 * access will be required for synthesis).
162 * @return RequestConfig with selected voice or null if suitable voice was not found.
163 */
164 public static RequestConfig highestQuality(EngineStatus engineStatus,
165 boolean hasToBeEmbedded) {
166 return highestQuality(engineStatus, hasToBeEmbedded,
167 new LanguageMatcher(Locale.getDefault()));
168 }
169
170}