OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 <dlfcn.h> | |
6 #include <math.h> | 5 #include <math.h> |
7 | 6 |
8 #include "base/memory/singleton.h" | 7 #include "base/memory/singleton.h" |
9 #include "chrome/browser/speech/extension_api/tts_extension_api_platform.h" | 8 #include "chrome/browser/speech/extension_api/tts_extension_api_platform.h" |
10 #include "content/public/browser/browser_thread.h" | 9 #include "content/public/browser/browser_thread.h" |
11 | 10 |
| 11 #include "library_loaders/libspeechd.h" |
| 12 |
12 using content::BrowserThread; | 13 using content::BrowserThread; |
13 | 14 |
14 namespace { | 15 namespace { |
| 16 |
15 const char kNotSupportedError[] = | 17 const char kNotSupportedError[] = |
16 "Native speech synthesis not supported on this platform."; | 18 "Native speech synthesis not supported on this platform."; |
17 | 19 |
18 // Speech dispatcher exports. | 20 } // namespace |
19 // The following types come from the libspeechd-dev package/libspeechd.h. | |
20 typedef enum { | |
21 SPD_MODE_SINGLE = 0, | |
22 SPD_MODE_THREADED = 1 | |
23 } SPDConnectionMode; | |
24 | |
25 typedef enum { | |
26 SPD_IMPORTANT = 1, | |
27 SPD_MESSAGE = 2, | |
28 SPD_TEXT = 3, | |
29 SPD_NOTIFICATION = 4, | |
30 SPD_PROGRESS = 5 | |
31 } SPDPriority; | |
32 | |
33 typedef enum { | |
34 SPD_EVENT_BEGIN, | |
35 SPD_EVENT_END, | |
36 SPD_EVENT_CANCEL, | |
37 SPD_EVENT_PAUSE, | |
38 SPD_EVENT_RESUME, | |
39 SPD_EVENT_INDEX_MARK | |
40 } SPDNotificationType; | |
41 | |
42 typedef enum { | |
43 SPD_BEGIN = 1, | |
44 SPD_END = 2, | |
45 SPD_INDEX_MARKS = 4, | |
46 SPD_CANCEL = 8, | |
47 SPD_PAUSE = 16, | |
48 SPD_RESUME = 32 | |
49 } SPDNotification; | |
50 | |
51 typedef void (*SPDCallback)( | |
52 size_t msg_id, size_t client_id, SPDNotificationType state); | |
53 typedef void (*SPDCallbackIM)(size_t msg_id, | |
54 size_t client_id, | |
55 SPDNotificationType state, | |
56 char* index_mark); | |
57 | |
58 typedef struct { | |
59 /* PUBLIC */ | |
60 SPDCallback callback_begin; | |
61 SPDCallback callback_end; | |
62 SPDCallback callback_cancel; | |
63 SPDCallback callback_pause; | |
64 SPDCallback callback_resume; | |
65 SPDCallbackIM callback_im; | |
66 | |
67 /* PRIVATE */ | |
68 int socket; | |
69 FILE* stream; | |
70 SPDConnectionMode mode; | |
71 | |
72 pthread_mutex_t* ssip_mutex; | |
73 | |
74 pthread_t* events_thread; | |
75 pthread_mutex_t* comm_mutex; | |
76 pthread_cond_t* cond_reply_ready; | |
77 pthread_mutex_t* mutex_reply_ready; | |
78 pthread_cond_t* cond_reply_ack; | |
79 pthread_mutex_t* mutex_reply_ack; | |
80 | |
81 char* reply; | |
82 } SPDConnection; | |
83 | |
84 typedef SPDConnection* (*spd_open_func)(const char* client_name, | |
85 const char* connection_name, | |
86 const char* user_name, | |
87 SPDConnectionMode mode); | |
88 typedef int (*spd_say_func)(SPDConnection* connection, | |
89 SPDPriority priority, | |
90 const char* text); | |
91 typedef int (*spd_stop_func)(SPDConnection* connection); | |
92 typedef void (*spd_close_func)(SPDConnection* connection); | |
93 typedef int (*spd_set_notification_on_func)(SPDConnection* connection, | |
94 SPDNotification notification); | |
95 typedef int (*spd_set_voice_rate_func)(SPDConnection* connection, int rate); | |
96 typedef int (*spd_set_voice_pitch_func)(SPDConnection* connection, int pitch); | |
97 }; | |
98 | |
99 class SpeechDispatcherWrapper { | |
100 public: | |
101 static SPDNotificationType current_notification_; | |
102 | |
103 SpeechDispatcherWrapper(); | |
104 ~SpeechDispatcherWrapper(); | |
105 | |
106 bool Speak(const char* text); | |
107 bool IsSpeaking(); | |
108 bool StopSpeaking(); | |
109 void SetRate(int rate); | |
110 void SetPitch(int pitch); | |
111 | |
112 // Resets the connection with speech dispatcher. | |
113 void Reset(); | |
114 | |
115 // States whether Speech Dispatcher loaded successfully. | |
116 bool loaded() { | |
117 return loaded_; | |
118 } | |
119 | |
120 private: | |
121 static void NotificationCallback(size_t msg_id, | |
122 size_t client_id, | |
123 SPDNotificationType type); | |
124 | |
125 static void IndexMarkCallback(size_t msg_id, | |
126 size_t client_id, | |
127 SPDNotificationType state, | |
128 char* index_mark); | |
129 | |
130 // Interface bindings. | |
131 spd_open_func spd_open; | |
132 spd_say_func spd_say; | |
133 spd_stop_func spd_stop; | |
134 spd_close_func spd_close; | |
135 spd_set_notification_on_func spd_set_notification_on; | |
136 spd_set_voice_rate_func spd_set_voice_rate; | |
137 spd_set_voice_pitch_func spd_set_voice_pitch; | |
138 | |
139 bool loaded_; | |
140 void* library_; | |
141 SPDConnection* conn_; | |
142 DISALLOW_COPY_AND_ASSIGN(SpeechDispatcherWrapper); | |
143 }; | |
144 | |
145 // static | |
146 SPDNotificationType SpeechDispatcherWrapper::current_notification_ = | |
147 SPD_EVENT_END; | |
148 | 21 |
149 class ExtensionTtsPlatformImplLinux : public ExtensionTtsPlatformImpl { | 22 class ExtensionTtsPlatformImplLinux : public ExtensionTtsPlatformImpl { |
150 public: | 23 public: |
151 virtual bool PlatformImplAvailable(); | 24 virtual bool PlatformImplAvailable(); |
152 virtual bool Speak( | 25 virtual bool Speak( |
153 int utterance_id, | 26 int utterance_id, |
154 const std::string& utterance, | 27 const std::string& utterance, |
155 const std::string& lang, | 28 const std::string& lang, |
156 const UtteranceContinuousParameters& params); | 29 const UtteranceContinuousParameters& params); |
157 virtual bool StopSpeaking(); | 30 virtual bool StopSpeaking(); |
158 virtual bool IsSpeaking(); | 31 virtual bool IsSpeaking(); |
159 virtual bool SendsEvent(TtsEventType event_type); | 32 virtual bool SendsEvent(TtsEventType event_type); |
160 void OnSpeechEvent(SPDNotificationType type); | 33 void OnSpeechEvent(SPDNotificationType type); |
161 | 34 |
162 // Get the single instance of this class. | 35 // Get the single instance of this class. |
163 static ExtensionTtsPlatformImplLinux* GetInstance(); | 36 static ExtensionTtsPlatformImplLinux* GetInstance(); |
164 | 37 |
165 private: | 38 private: |
166 ExtensionTtsPlatformImplLinux(): utterance_id_(0) {} | 39 ExtensionTtsPlatformImplLinux(); |
167 virtual ~ExtensionTtsPlatformImplLinux() {} | 40 virtual ~ExtensionTtsPlatformImplLinux(); |
168 | 41 |
169 SpeechDispatcherWrapper spd_; | 42 // Resets the connection with speech dispatcher. |
| 43 void Reset(); |
| 44 |
| 45 static void NotificationCallback(size_t msg_id, |
| 46 size_t client_id, |
| 47 SPDNotificationType type); |
| 48 |
| 49 static void IndexMarkCallback(size_t msg_id, |
| 50 size_t client_id, |
| 51 SPDNotificationType state, |
| 52 char* index_mark); |
| 53 |
| 54 static SPDNotificationType current_notification_; |
| 55 |
| 56 LibSpeechdLoader libspeechd_loader_; |
| 57 SPDConnection* conn_; |
170 | 58 |
171 // These apply to the current utterance only. | 59 // These apply to the current utterance only. |
172 std::string utterance_; | 60 std::string utterance_; |
173 int utterance_id_; | 61 int utterance_id_; |
174 | 62 |
175 friend struct DefaultSingletonTraits<ExtensionTtsPlatformImplLinux>; | 63 friend struct DefaultSingletonTraits<ExtensionTtsPlatformImplLinux>; |
176 | 64 |
177 DISALLOW_COPY_AND_ASSIGN(ExtensionTtsPlatformImplLinux); | 65 DISALLOW_COPY_AND_ASSIGN(ExtensionTtsPlatformImplLinux); |
178 }; | 66 }; |
179 | 67 |
180 SpeechDispatcherWrapper::SpeechDispatcherWrapper() : loaded_(false) { | 68 // static |
181 library_ = dlopen("libspeechd.so", RTLD_LAZY); | 69 SPDNotificationType ExtensionTtsPlatformImplLinux::current_notification_ = |
182 if (!library_) | 70 SPD_EVENT_END; |
| 71 |
| 72 ExtensionTtsPlatformImplLinux::ExtensionTtsPlatformImplLinux() |
| 73 : utterance_id_(0) { |
| 74 if (!libspeechd_loader_.Load("libspeechd.so.2")) |
183 return; | 75 return; |
184 | 76 |
185 spd_open = reinterpret_cast<spd_open_func>(dlsym(library_, "spd_open")); | 77 conn_ = libspeechd_loader_.spd_open( |
186 if (!spd_open) | 78 "chrome", "extension_api", NULL, SPD_MODE_THREADED); |
187 return; | |
188 | |
189 spd_say = reinterpret_cast<spd_say_func>(dlsym(library_, "spd_say")); | |
190 if (!spd_say) | |
191 return; | |
192 | |
193 spd_stop = reinterpret_cast<spd_stop_func>(dlsym(library_, "spd_stop")); | |
194 if (!spd_stop) | |
195 return; | |
196 | |
197 spd_close = reinterpret_cast<spd_close_func>(dlsym(library_, "spd_close")); | |
198 if (!spd_close) | |
199 return; | |
200 | |
201 conn_ = spd_open("chrome", "extension_api", NULL, SPD_MODE_THREADED); | |
202 if (!conn_) | 79 if (!conn_) |
203 return; | 80 return; |
204 | 81 |
205 spd_set_notification_on = reinterpret_cast<spd_set_notification_on_func>( | |
206 dlsym(library_, "spd_set_notification_on")); | |
207 if (!spd_set_notification_on) | |
208 return; | |
209 | |
210 spd_set_voice_rate = reinterpret_cast<spd_set_voice_rate_func>( | |
211 dlsym(library_, "spd_set_voice_rate")); | |
212 if (!spd_set_voice_rate) | |
213 return; | |
214 | |
215 spd_set_voice_pitch = reinterpret_cast<spd_set_voice_pitch_func>( | |
216 dlsym(library_, "spd_set_voice_pitch")); | |
217 if (!spd_set_voice_pitch) | |
218 return; | |
219 | |
220 // Register callbacks for all events. | 82 // Register callbacks for all events. |
221 conn_->callback_begin = | 83 conn_->callback_begin = |
222 conn_->callback_end = | 84 conn_->callback_end = |
223 conn_->callback_cancel = | 85 conn_->callback_cancel = |
224 conn_->callback_pause = | 86 conn_->callback_pause = |
225 conn_->callback_resume = | 87 conn_->callback_resume = |
226 &SpeechDispatcherWrapper::NotificationCallback; | 88 &NotificationCallback; |
227 | 89 |
228 conn_->callback_im = &SpeechDispatcherWrapper::IndexMarkCallback; | 90 conn_->callback_im = &IndexMarkCallback; |
229 | 91 |
230 spd_set_notification_on(conn_, SPD_BEGIN); | 92 libspeechd_loader_.spd_set_notification_on(conn_, SPD_BEGIN); |
231 spd_set_notification_on(conn_, SPD_END); | 93 libspeechd_loader_.spd_set_notification_on(conn_, SPD_END); |
232 spd_set_notification_on(conn_, SPD_CANCEL); | 94 libspeechd_loader_.spd_set_notification_on(conn_, SPD_CANCEL); |
233 spd_set_notification_on(conn_, SPD_PAUSE); | 95 libspeechd_loader_.spd_set_notification_on(conn_, SPD_PAUSE); |
234 spd_set_notification_on(conn_, SPD_RESUME); | 96 libspeechd_loader_.spd_set_notification_on(conn_, SPD_RESUME); |
235 | |
236 loaded_ = true; | |
237 } | 97 } |
238 | 98 |
239 SpeechDispatcherWrapper::~SpeechDispatcherWrapper() { | 99 ExtensionTtsPlatformImplLinux::~ExtensionTtsPlatformImplLinux() { |
240 if (conn_) { | 100 if (conn_) { |
241 spd_close(conn_); | 101 libspeechd_loader_.spd_close(conn_); |
242 conn_ = NULL; | 102 conn_ = NULL; |
243 } | 103 } |
244 | |
245 if (library_) { | |
246 dlclose(library_); | |
247 library_ = NULL; | |
248 } | |
249 } | |
250 bool SpeechDispatcherWrapper::Speak(const char* text) { | |
251 if (!loaded()) | |
252 return false; | |
253 if (spd_say(conn_, SPD_TEXT, text) == -1) { | |
254 Reset(); | |
255 return false; | |
256 } | |
257 return true; | |
258 } | |
259 | |
260 bool SpeechDispatcherWrapper::IsSpeaking() { | |
261 return SpeechDispatcherWrapper::current_notification_ == SPD_EVENT_BEGIN; | |
262 } | |
263 | |
264 bool SpeechDispatcherWrapper::StopSpeaking() { | |
265 if (!loaded()) | |
266 return false; | |
267 if (spd_stop(conn_) == -1) { | |
268 Reset(); | |
269 return false; | |
270 } | |
271 return true; | |
272 } | |
273 void SpeechDispatcherWrapper::SetRate(int rate) { | |
274 spd_set_voice_rate(conn_, rate); | |
275 } | |
276 | |
277 void SpeechDispatcherWrapper::SetPitch(int pitch) { | |
278 spd_set_voice_pitch(conn_, pitch); | |
279 } | |
280 | |
281 // Resets the connection with speech dispatcher. | |
282 void SpeechDispatcherWrapper::Reset() { | |
283 if (conn_) | |
284 spd_close(conn_); | |
285 conn_ = spd_open("chrome", "extension_api", NULL, SPD_MODE_THREADED); | |
286 } | |
287 | |
288 // static | |
289 void SpeechDispatcherWrapper::NotificationCallback( | |
290 size_t msg_id, size_t client_id, SPDNotificationType type) { | |
291 // We run Speech Dispatcher in threaded mode, so these callbacks should always | |
292 // be in a separate thread. | |
293 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | |
294 SpeechDispatcherWrapper::current_notification_ = type; | |
295 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
296 base::Bind(&ExtensionTtsPlatformImplLinux::OnSpeechEvent, | |
297 base::Unretained(ExtensionTtsPlatformImplLinux::GetInstance()), | |
298 type)); | |
299 } | |
300 } | 104 } |
301 | 105 |
302 // static | 106 void ExtensionTtsPlatformImplLinux::Reset() { |
303 void SpeechDispatcherWrapper::IndexMarkCallback(size_t msg_id, | 107 if (conn_) |
304 size_t client_id, | 108 libspeechd_loader_.spd_close(conn_); |
305 SPDNotificationType state, | 109 conn_ = libspeechd_loader_.spd_open( |
306 char* index_mark) { | 110 "chrome", "extension_api", NULL, SPD_MODE_THREADED); |
307 // TODO(dtseng): index_mark appears to specify an index type supplied by a | |
308 // client. Need to explore how this is used before hooking it up with existing | |
309 // word, sentence events. | |
310 // We run Speech Dispatcher in threaded mode, so these callbacks should always | |
311 // be in a separate thread. | |
312 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | |
313 SpeechDispatcherWrapper::current_notification_ = state; | |
314 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
315 base::Bind(&ExtensionTtsPlatformImplLinux::OnSpeechEvent, | |
316 base::Unretained(ExtensionTtsPlatformImplLinux::GetInstance()), | |
317 state)); | |
318 } | |
319 } | 111 } |
320 | 112 |
321 bool ExtensionTtsPlatformImplLinux::PlatformImplAvailable() { | 113 bool ExtensionTtsPlatformImplLinux::PlatformImplAvailable() { |
322 return spd_.loaded(); | 114 return libspeechd_loader_.loaded() && (conn_ != NULL); |
323 } | 115 } |
324 | 116 |
325 bool ExtensionTtsPlatformImplLinux::Speak( | 117 bool ExtensionTtsPlatformImplLinux::Speak( |
326 int utterance_id, | 118 int utterance_id, |
327 const std::string& utterance, | 119 const std::string& utterance, |
328 const std::string& lang, | 120 const std::string& lang, |
329 const UtteranceContinuousParameters& params) { | 121 const UtteranceContinuousParameters& params) { |
330 if (!spd_.loaded()) { | 122 if (!PlatformImplAvailable()) { |
331 error_ = kNotSupportedError; | 123 error_ = kNotSupportedError; |
332 return false; | 124 return false; |
333 } | 125 } |
334 | 126 |
335 // Speech dispatcher's speech params are around 3x at either limit. | 127 // Speech dispatcher's speech params are around 3x at either limit. |
336 float rate = params.rate > 3 ? 3 : params.rate; | 128 float rate = params.rate > 3 ? 3 : params.rate; |
337 rate = params.rate < 0.334 ? 0.334 : rate; | 129 rate = params.rate < 0.334 ? 0.334 : rate; |
338 float pitch = params.pitch > 3 ? 3 : params.pitch; | 130 float pitch = params.pitch > 3 ? 3 : params.pitch; |
339 pitch = params.pitch < 0.334 ? 0.334 : pitch; | 131 pitch = params.pitch < 0.334 ? 0.334 : pitch; |
340 | 132 |
341 // Map our multiplicative range to Speech Dispatcher's linear range. | 133 // Map our multiplicative range to Speech Dispatcher's linear range. |
342 // .334 = -100. | 134 // .334 = -100. |
343 // 3 = 100. | 135 // 3 = 100. |
344 spd_.SetRate(100 * log10(rate) / log10(3)); | 136 libspeechd_loader_.spd_set_voice_rate(conn_, 100 * log10(rate) / log10(3)); |
345 spd_.SetPitch(100 * log10(pitch) / log10(3)); | 137 libspeechd_loader_.spd_set_voice_pitch(conn_, 100 * log10(pitch) / log10(3)); |
346 | 138 |
347 utterance_ = utterance; | 139 utterance_ = utterance; |
348 utterance_id_ = utterance_id; | 140 utterance_id_ = utterance_id; |
349 | 141 |
350 spd_.Speak(utterance.c_str()); | 142 if (libspeechd_loader_.spd_say(conn_, SPD_TEXT, utterance.c_str()) == -1) { |
| 143 Reset(); |
| 144 return false; |
| 145 } |
351 return true; | 146 return true; |
352 } | 147 } |
353 | 148 |
354 bool ExtensionTtsPlatformImplLinux::StopSpeaking() { | 149 bool ExtensionTtsPlatformImplLinux::StopSpeaking() { |
355 return spd_.StopSpeaking(); | 150 if (!PlatformImplAvailable()) |
| 151 return false; |
| 152 if (libspeechd_loader_.spd_stop(conn_) == -1) { |
| 153 Reset(); |
| 154 return false; |
| 155 } |
| 156 return true; |
356 } | 157 } |
357 | 158 |
358 bool ExtensionTtsPlatformImplLinux::IsSpeaking() { | 159 bool ExtensionTtsPlatformImplLinux::IsSpeaking() { |
359 return spd_.IsSpeaking(); | 160 return current_notification_ == SPD_EVENT_BEGIN; |
360 } | 161 } |
361 | 162 |
362 bool ExtensionTtsPlatformImplLinux::SendsEvent(TtsEventType event_type) { | 163 bool ExtensionTtsPlatformImplLinux::SendsEvent(TtsEventType event_type) { |
363 return (event_type == TTS_EVENT_START || | 164 return (event_type == TTS_EVENT_START || |
364 event_type == TTS_EVENT_END || | 165 event_type == TTS_EVENT_END || |
365 event_type == TTS_EVENT_CANCELLED || | 166 event_type == TTS_EVENT_CANCELLED || |
366 event_type == TTS_EVENT_MARKER); | 167 event_type == TTS_EVENT_MARKER); |
367 } | 168 } |
368 | 169 |
369 void ExtensionTtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type) { | 170 void ExtensionTtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type) { |
(...skipping 12 matching lines...) Expand all Loading... |
382 controller->OnTtsEvent( | 183 controller->OnTtsEvent( |
383 utterance_id_, TTS_EVENT_CANCELLED, 0, std::string()); | 184 utterance_id_, TTS_EVENT_CANCELLED, 0, std::string()); |
384 break; | 185 break; |
385 case SPD_EVENT_INDEX_MARK: | 186 case SPD_EVENT_INDEX_MARK: |
386 controller->OnTtsEvent(utterance_id_, TTS_EVENT_MARKER, 0, std::string()); | 187 controller->OnTtsEvent(utterance_id_, TTS_EVENT_MARKER, 0, std::string()); |
387 break; | 188 break; |
388 } | 189 } |
389 } | 190 } |
390 | 191 |
391 // static | 192 // static |
| 193 void ExtensionTtsPlatformImplLinux::NotificationCallback( |
| 194 size_t msg_id, size_t client_id, SPDNotificationType type) { |
| 195 // We run Speech Dispatcher in threaded mode, so these callbacks should always |
| 196 // be in a separate thread. |
| 197 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
| 198 current_notification_ = type; |
| 199 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| 200 base::Bind(&ExtensionTtsPlatformImplLinux::OnSpeechEvent, |
| 201 base::Unretained(ExtensionTtsPlatformImplLinux::GetInstance()), |
| 202 type)); |
| 203 } |
| 204 } |
| 205 |
| 206 // static |
| 207 void ExtensionTtsPlatformImplLinux::IndexMarkCallback(size_t msg_id, |
| 208 size_t client_id, |
| 209 SPDNotificationType state, |
| 210 char* index_mark) { |
| 211 // TODO(dtseng): index_mark appears to specify an index type supplied by a |
| 212 // client. Need to explore how this is used before hooking it up with existing |
| 213 // word, sentence events. |
| 214 // We run Speech Dispatcher in threaded mode, so these callbacks should always |
| 215 // be in a separate thread. |
| 216 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
| 217 current_notification_ = state; |
| 218 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| 219 base::Bind(&ExtensionTtsPlatformImplLinux::OnSpeechEvent, |
| 220 base::Unretained(ExtensionTtsPlatformImplLinux::GetInstance()), |
| 221 state)); |
| 222 } |
| 223 } |
| 224 |
| 225 // static |
392 ExtensionTtsPlatformImplLinux* ExtensionTtsPlatformImplLinux::GetInstance() { | 226 ExtensionTtsPlatformImplLinux* ExtensionTtsPlatformImplLinux::GetInstance() { |
393 return Singleton<ExtensionTtsPlatformImplLinux, | 227 return Singleton<ExtensionTtsPlatformImplLinux, |
394 LeakySingletonTraits<ExtensionTtsPlatformImplLinux> >::get(); | 228 LeakySingletonTraits<ExtensionTtsPlatformImplLinux> >::get(); |
395 } | 229 } |
396 | 230 |
397 // static | 231 // static |
398 ExtensionTtsPlatformImpl* ExtensionTtsPlatformImpl::GetInstance() { | 232 ExtensionTtsPlatformImpl* ExtensionTtsPlatformImpl::GetInstance() { |
399 return ExtensionTtsPlatformImplLinux::GetInstance(); | 233 return ExtensionTtsPlatformImplLinux::GetInstance(); |
400 } | 234 } |
OLD | NEW |