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
3 // found in the LICENSE file.
9 #include "base/command_line.h"
10 #include "base/debug/leak_annotations.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/memory/singleton.h"
13 #include "base/synchronization/lock.h"
14 #include "chrome/browser/speech/tts_platform.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/common/content_switches.h"
18 #include "library_loaders/libspeechd.h"
20 using content::BrowserThread
;
24 const char kNotSupportedError
[] =
25 "Native speech synthesis not supported on this platform.";
27 struct SPDChromeVoice
{
34 class TtsPlatformImplLinux
: public TtsPlatformImpl
{
36 bool PlatformImplAvailable() override
;
37 bool Speak(int utterance_id
,
38 const std::string
& utterance
,
39 const std::string
& lang
,
40 const VoiceData
& voice
,
41 const UtteranceContinuousParameters
& params
) override
;
42 bool StopSpeaking() override
;
43 void Pause() override
;
44 void Resume() override
;
45 bool IsSpeaking() override
;
46 void GetVoices(std::vector
<VoiceData
>* out_voices
) override
;
48 void OnSpeechEvent(SPDNotificationType type
);
50 // Get the single instance of this class.
51 static TtsPlatformImplLinux
* GetInstance();
54 TtsPlatformImplLinux();
55 ~TtsPlatformImplLinux() override
;
57 // Initiate the connection with the speech dispatcher.
60 // Resets the connection with speech dispatcher.
63 static void NotificationCallback(size_t msg_id
,
65 SPDNotificationType type
);
67 static void IndexMarkCallback(size_t msg_id
,
69 SPDNotificationType state
,
72 static SPDNotificationType current_notification_
;
74 base::Lock initialization_lock_
;
75 LibSpeechdLoader libspeechd_loader_
;
78 // These apply to the current utterance only.
79 std::string utterance_
;
82 // Map a string composed of a voicename and module to the voicename. Used to
83 // uniquely identify a voice across all available modules.
84 scoped_ptr
<std::map
<std::string
, SPDChromeVoice
> > all_native_voices_
;
86 friend struct DefaultSingletonTraits
<TtsPlatformImplLinux
>;
88 DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplLinux
);
92 SPDNotificationType
TtsPlatformImplLinux::current_notification_
=
95 TtsPlatformImplLinux::TtsPlatformImplLinux()
97 const base::CommandLine
& command_line
=
98 *base::CommandLine::ForCurrentProcess();
99 if (!command_line
.HasSwitch(switches::kEnableSpeechDispatcher
))
102 BrowserThread::PostTask(BrowserThread::FILE,
104 base::Bind(&TtsPlatformImplLinux::Initialize
,
105 base::Unretained(this)));
108 void TtsPlatformImplLinux::Initialize() {
109 base::AutoLock
lock(initialization_lock_
);
111 if (!libspeechd_loader_
.Load("libspeechd.so.2"))
115 // spd_open has memory leaks which are hard to suppress.
116 // http://crbug.com/317360
117 ANNOTATE_SCOPED_MEMORY_LEAK
;
118 conn_
= libspeechd_loader_
.spd_open(
119 "chrome", "extension_api", NULL
, SPD_MODE_THREADED
);
124 // Register callbacks for all events.
125 conn_
->callback_begin
=
126 conn_
->callback_end
=
127 conn_
->callback_cancel
=
128 conn_
->callback_pause
=
129 conn_
->callback_resume
=
130 &NotificationCallback
;
132 conn_
->callback_im
= &IndexMarkCallback
;
134 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_BEGIN
);
135 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_END
);
136 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_CANCEL
);
137 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_PAUSE
);
138 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_RESUME
);
141 TtsPlatformImplLinux::~TtsPlatformImplLinux() {
142 base::AutoLock
lock(initialization_lock_
);
144 libspeechd_loader_
.spd_close(conn_
);
149 void TtsPlatformImplLinux::Reset() {
150 base::AutoLock
lock(initialization_lock_
);
152 libspeechd_loader_
.spd_close(conn_
);
153 conn_
= libspeechd_loader_
.spd_open(
154 "chrome", "extension_api", NULL
, SPD_MODE_THREADED
);
157 bool TtsPlatformImplLinux::PlatformImplAvailable() {
158 if (!initialization_lock_
.Try())
160 bool result
= libspeechd_loader_
.loaded() && (conn_
!= NULL
);
161 initialization_lock_
.Release();
165 bool TtsPlatformImplLinux::Speak(
167 const std::string
& utterance
,
168 const std::string
& lang
,
169 const VoiceData
& voice
,
170 const UtteranceContinuousParameters
& params
) {
171 if (!PlatformImplAvailable()) {
172 error_
= kNotSupportedError
;
176 // Speech dispatcher's speech params are around 3x at either limit.
177 float rate
= params
.rate
> 3 ? 3 : params
.rate
;
178 rate
= params
.rate
< 0.334 ? 0.334 : rate
;
179 float pitch
= params
.pitch
> 3 ? 3 : params
.pitch
;
180 pitch
= params
.pitch
< 0.334 ? 0.334 : pitch
;
182 std::map
<std::string
, SPDChromeVoice
>::iterator it
=
183 all_native_voices_
->find(voice
.name
);
184 if (it
!= all_native_voices_
->end()) {
185 libspeechd_loader_
.spd_set_output_module(conn_
, it
->second
.module
.c_str());
186 libspeechd_loader_
.spd_set_synthesis_voice(conn_
, it
->second
.name
.c_str());
189 // Map our multiplicative range to Speech Dispatcher's linear range.
192 libspeechd_loader_
.spd_set_voice_rate(conn_
, 100 * log10(rate
) / log10(3));
193 libspeechd_loader_
.spd_set_voice_pitch(conn_
, 100 * log10(pitch
) / log10(3));
195 // Support languages other than the default
197 libspeechd_loader_
.spd_set_language(conn_
, lang
.c_str());
199 utterance_
= utterance
;
200 utterance_id_
= utterance_id
;
202 if (libspeechd_loader_
.spd_say(conn_
, SPD_TEXT
, utterance
.c_str()) == -1) {
209 bool TtsPlatformImplLinux::StopSpeaking() {
210 if (!PlatformImplAvailable())
212 if (libspeechd_loader_
.spd_stop(conn_
) == -1) {
219 void TtsPlatformImplLinux::Pause() {
220 if (!PlatformImplAvailable())
222 libspeechd_loader_
.spd_pause(conn_
);
225 void TtsPlatformImplLinux::Resume() {
226 if (!PlatformImplAvailable())
228 libspeechd_loader_
.spd_resume(conn_
);
231 bool TtsPlatformImplLinux::IsSpeaking() {
232 return current_notification_
== SPD_EVENT_BEGIN
;
235 void TtsPlatformImplLinux::GetVoices(
236 std::vector
<VoiceData
>* out_voices
) {
237 if (!all_native_voices_
.get()) {
238 all_native_voices_
.reset(new std::map
<std::string
, SPDChromeVoice
>());
239 char** modules
= libspeechd_loader_
.spd_list_modules(conn_
);
242 for (int i
= 0; modules
[i
]; i
++) {
243 char* module
= modules
[i
];
244 libspeechd_loader_
.spd_set_output_module(conn_
, module
);
245 SPDVoice
** native_voices
=
246 libspeechd_loader_
.spd_list_synthesis_voices(conn_
);
247 if (!native_voices
) {
251 for (int j
= 0; native_voices
[j
]; j
++) {
252 SPDVoice
* native_voice
= native_voices
[j
];
253 SPDChromeVoice native_data
;
254 native_data
.name
= native_voice
->name
;
255 native_data
.module
= module
;
257 key
.append(native_data
.name
);
259 key
.append(native_data
.module
);
260 all_native_voices_
->insert(
261 std::pair
<std::string
, SPDChromeVoice
>(key
, native_data
));
262 free(native_voices
[j
]);
268 for (std::map
<std::string
, SPDChromeVoice
>::iterator it
=
269 all_native_voices_
->begin();
270 it
!= all_native_voices_
->end();
272 out_voices
->push_back(VoiceData());
273 VoiceData
& voice
= out_voices
->back();
275 voice
.name
= it
->first
;
276 voice
.events
.insert(TTS_EVENT_START
);
277 voice
.events
.insert(TTS_EVENT_END
);
278 voice
.events
.insert(TTS_EVENT_CANCELLED
);
279 voice
.events
.insert(TTS_EVENT_MARKER
);
280 voice
.events
.insert(TTS_EVENT_PAUSE
);
281 voice
.events
.insert(TTS_EVENT_RESUME
);
285 void TtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type
) {
286 TtsController
* controller
= TtsController::GetInstance();
288 case SPD_EVENT_BEGIN
:
289 controller
->OnTtsEvent(utterance_id_
, TTS_EVENT_START
, 0, std::string());
291 case SPD_EVENT_RESUME
:
292 controller
->OnTtsEvent(utterance_id_
, TTS_EVENT_RESUME
, 0, std::string());
295 controller
->OnTtsEvent(
296 utterance_id_
, TTS_EVENT_END
, utterance_
.size(), std::string());
298 case SPD_EVENT_PAUSE
:
299 controller
->OnTtsEvent(
300 utterance_id_
, TTS_EVENT_PAUSE
, utterance_
.size(), std::string());
302 case SPD_EVENT_CANCEL
:
303 controller
->OnTtsEvent(
304 utterance_id_
, TTS_EVENT_CANCELLED
, 0, std::string());
306 case SPD_EVENT_INDEX_MARK
:
307 controller
->OnTtsEvent(utterance_id_
, TTS_EVENT_MARKER
, 0, std::string());
313 void TtsPlatformImplLinux::NotificationCallback(
314 size_t msg_id
, size_t client_id
, SPDNotificationType type
) {
315 // We run Speech Dispatcher in threaded mode, so these callbacks should always
316 // be in a separate thread.
317 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
318 current_notification_
= type
;
319 BrowserThread::PostTask(
322 base::Bind(&TtsPlatformImplLinux::OnSpeechEvent
,
323 base::Unretained(TtsPlatformImplLinux::GetInstance()),
329 void TtsPlatformImplLinux::IndexMarkCallback(size_t msg_id
,
331 SPDNotificationType state
,
333 // TODO(dtseng): index_mark appears to specify an index type supplied by a
334 // client. Need to explore how this is used before hooking it up with existing
335 // word, sentence events.
336 // We run Speech Dispatcher in threaded mode, so these callbacks should always
337 // be in a separate thread.
338 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
339 current_notification_
= state
;
340 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
341 base::Bind(&TtsPlatformImplLinux::OnSpeechEvent
,
342 base::Unretained(TtsPlatformImplLinux::GetInstance()),
348 TtsPlatformImplLinux
* TtsPlatformImplLinux::GetInstance() {
349 return Singleton
<TtsPlatformImplLinux
,
350 LeakySingletonTraits
<TtsPlatformImplLinux
> >::get();
354 TtsPlatformImpl
* TtsPlatformImpl::GetInstance() {
355 return TtsPlatformImplLinux::GetInstance();