#include <dlfcn.h>
#include <math.h>
#include "base/memory/singleton.h"
#include "chrome/browser/speech/extension_api/tts_extension_api_platform.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace {
const char kNotSupportedError[] =
"Native speech synthesis not supported on this platform.";
typedef enum {
SPD_MODE_SINGLE = 0,
SPD_MODE_THREADED = 1
} SPDConnectionMode;
typedef enum {
SPD_IMPORTANT = 1,
SPD_MESSAGE = 2,
SPD_TEXT = 3,
SPD_NOTIFICATION = 4,
SPD_PROGRESS = 5
} SPDPriority;
typedef enum {
SPD_EVENT_BEGIN,
SPD_EVENT_END,
SPD_EVENT_CANCEL,
SPD_EVENT_PAUSE,
SPD_EVENT_RESUME,
SPD_EVENT_INDEX_MARK
} SPDNotificationType;
typedef enum {
SPD_BEGIN = 1,
SPD_END = 2,
SPD_INDEX_MARKS = 4,
SPD_CANCEL = 8,
SPD_PAUSE = 16,
SPD_RESUME = 32
} SPDNotification;
typedef void (*SPDCallback)(
size_t msg_id, size_t client_id, SPDNotificationType state);
typedef void (*SPDCallbackIM)(size_t msg_id,
size_t client_id,
SPDNotificationType state,
char* index_mark);
typedef struct {
SPDCallback callback_begin;
SPDCallback callback_end;
SPDCallback callback_cancel;
SPDCallback callback_pause;
SPDCallback callback_resume;
SPDCallbackIM callback_im;
int socket;
FILE* stream;
SPDConnectionMode mode;
pthread_mutex_t* ssip_mutex;
pthread_t* events_thread;
pthread_mutex_t* comm_mutex;
pthread_cond_t* cond_reply_ready;
pthread_mutex_t* mutex_reply_ready;
pthread_cond_t* cond_reply_ack;
pthread_mutex_t* mutex_reply_ack;
char* reply;
} SPDConnection;
typedef SPDConnection* (*spd_open_func)(const char* client_name,
const char* connection_name,
const char* user_name,
SPDConnectionMode mode);
typedef int (*spd_say_func)(SPDConnection* connection,
SPDPriority priority,
const char* text);
typedef int (*spd_stop_func)(SPDConnection* connection);
typedef void (*spd_close_func)(SPDConnection* connection);
typedef int (*spd_set_notification_on_func)(SPDConnection* connection,
SPDNotification notification);
typedef int (*spd_set_voice_rate_func)(SPDConnection* connection, int rate);
typedef int (*spd_set_voice_pitch_func)(SPDConnection* connection, int pitch);
};
class SpeechDispatcherWrapper {
public:
static SPDNotificationType current_notification_;
SpeechDispatcherWrapper();
~SpeechDispatcherWrapper();
bool Speak(const char* text);
bool IsSpeaking();
bool StopSpeaking();
void SetRate(int rate);
void SetPitch(int pitch);
void Reset();
bool loaded() {
return loaded_;
}
private:
static void NotificationCallback(size_t msg_id,
size_t client_id,
SPDNotificationType type);
static void IndexMarkCallback(size_t msg_id,
size_t client_id,
SPDNotificationType state,
char* index_mark);
spd_open_func spd_open;
spd_say_func spd_say;
spd_stop_func spd_stop;
spd_close_func spd_close;
spd_set_notification_on_func spd_set_notification_on;
spd_set_voice_rate_func spd_set_voice_rate;
spd_set_voice_pitch_func spd_set_voice_pitch;
bool loaded_;
void* library_;
SPDConnection* conn_;
DISALLOW_COPY_AND_ASSIGN(SpeechDispatcherWrapper);
};
SPDNotificationType SpeechDispatcherWrapper::current_notification_ =
SPD_EVENT_END;
class ExtensionTtsPlatformImplLinux : public ExtensionTtsPlatformImpl {
public:
virtual bool PlatformImplAvailable();
virtual bool Speak(
int utterance_id,
const std::string& utterance,
const std::string& lang,
const UtteranceContinuousParameters& params);
virtual bool StopSpeaking();
virtual bool IsSpeaking();
virtual bool SendsEvent(TtsEventType event_type);
void OnSpeechEvent(SPDNotificationType type);
static ExtensionTtsPlatformImplLinux* GetInstance();
private:
ExtensionTtsPlatformImplLinux(): utterance_id_(0) {}
virtual ~ExtensionTtsPlatformImplLinux() {}
SpeechDispatcherWrapper spd_;
std::string utterance_;
int utterance_id_;
friend struct DefaultSingletonTraits<ExtensionTtsPlatformImplLinux>;
DISALLOW_COPY_AND_ASSIGN(ExtensionTtsPlatformImplLinux);
};
SpeechDispatcherWrapper::SpeechDispatcherWrapper() : loaded_(false) {
library_ = dlopen("libspeechd.so", RTLD_LAZY);
if (!library_)
return;
spd_open = reinterpret_cast<spd_open_func>(dlsym(library_, "spd_open"));
if (!spd_open)
return;
spd_say = reinterpret_cast<spd_say_func>(dlsym(library_, "spd_say"));
if (!spd_say)
return;
spd_stop = reinterpret_cast<spd_stop_func>(dlsym(library_, "spd_stop"));
if (!spd_stop)
return;
spd_close = reinterpret_cast<spd_close_func>(dlsym(library_, "spd_close"));
if (!spd_close)
return;
conn_ = spd_open("chrome", "extension_api", NULL, SPD_MODE_THREADED);
if (!conn_)
return;
spd_set_notification_on = reinterpret_cast<spd_set_notification_on_func>(
dlsym(library_, "spd_set_notification_on"));
if (!spd_set_notification_on)
return;
spd_set_voice_rate = reinterpret_cast<spd_set_voice_rate_func>(
dlsym(library_, "spd_set_voice_rate"));
if (!spd_set_voice_rate)
return;
spd_set_voice_pitch = reinterpret_cast<spd_set_voice_pitch_func>(
dlsym(library_, "spd_set_voice_pitch"));
if (!spd_set_voice_pitch)
return;
conn_->callback_begin =
conn_->callback_end =
conn_->callback_cancel =
conn_->callback_pause =
conn_->callback_resume =
&SpeechDispatcherWrapper::NotificationCallback;
conn_->callback_im = &SpeechDispatcherWrapper::IndexMarkCallback;
spd_set_notification_on(conn_, SPD_BEGIN);
spd_set_notification_on(conn_, SPD_END);
spd_set_notification_on(conn_, SPD_CANCEL);
spd_set_notification_on(conn_, SPD_PAUSE);
spd_set_notification_on(conn_, SPD_RESUME);
loaded_ = true;
}
SpeechDispatcherWrapper::~SpeechDispatcherWrapper() {
if (conn_) {
spd_close(conn_);
conn_ = NULL;
}
if (library_) {
dlclose(library_);
library_ = NULL;
}
}
bool SpeechDispatcherWrapper::Speak(const char* text) {
if (!loaded())
return false;
if (spd_say(conn_, SPD_TEXT, text) == -1) {
Reset();
return false;
}
return true;
}
bool SpeechDispatcherWrapper::IsSpeaking() {
return SpeechDispatcherWrapper::current_notification_ == SPD_EVENT_BEGIN;
}
bool SpeechDispatcherWrapper::StopSpeaking() {
if (!loaded())
return false;
if (spd_stop(conn_) == -1) {
Reset();
return false;
}
return true;
}
void SpeechDispatcherWrapper::SetRate(int rate) {
spd_set_voice_rate(conn_, rate);
}
void SpeechDispatcherWrapper::SetPitch(int pitch) {
spd_set_voice_pitch(conn_, pitch);
}
void SpeechDispatcherWrapper::Reset() {
if (conn_)
spd_close(conn_);
conn_ = spd_open("chrome", "extension_api", NULL, SPD_MODE_THREADED);
}
void SpeechDispatcherWrapper::NotificationCallback(
size_t msg_id, size_t client_id, SPDNotificationType type) {
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
SpeechDispatcherWrapper::current_notification_ = type;
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&ExtensionTtsPlatformImplLinux::OnSpeechEvent,
base::Unretained(ExtensionTtsPlatformImplLinux::GetInstance()),
type));
}
}
void SpeechDispatcherWrapper::IndexMarkCallback(size_t msg_id,
size_t client_id,
SPDNotificationType state,
char* index_mark) {
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
SpeechDispatcherWrapper::current_notification_ = state;
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&ExtensionTtsPlatformImplLinux::OnSpeechEvent,
base::Unretained(ExtensionTtsPlatformImplLinux::GetInstance()),
state));
}
}
bool ExtensionTtsPlatformImplLinux::PlatformImplAvailable() {
return spd_.loaded();
}
bool ExtensionTtsPlatformImplLinux::Speak(
int utterance_id,
const std::string& utterance,
const std::string& lang,
const UtteranceContinuousParameters& params) {
if (!spd_.loaded()) {
error_ = kNotSupportedError;
return false;
}
float rate = params.rate > 3 ? 3 : params.rate;
rate = params.rate < 0.334 ? 0.334 : rate;
float pitch = params.pitch > 3 ? 3 : params.pitch;
pitch = params.pitch < 0.334 ? 0.334 : pitch;
spd_.SetRate(100 * log10(rate) / log10(3));
spd_.SetPitch(100 * log10(pitch) / log10(3));
utterance_ = utterance;
utterance_id_ = utterance_id;
spd_.Speak(utterance.c_str());
return true;
}
bool ExtensionTtsPlatformImplLinux::StopSpeaking() {
return spd_.StopSpeaking();
}
bool ExtensionTtsPlatformImplLinux::IsSpeaking() {
return spd_.IsSpeaking();
}
bool ExtensionTtsPlatformImplLinux::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 ExtensionTtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type) {
ExtensionTtsController* controller = ExtensionTtsController::GetInstance();
switch (type) {
case SPD_EVENT_BEGIN:
case SPD_EVENT_RESUME:
controller->OnTtsEvent(utterance_id_, TTS_EVENT_START, 0, std::string());
break;
case SPD_EVENT_END:
case SPD_EVENT_PAUSE:
controller->OnTtsEvent(
utterance_id_, TTS_EVENT_END, utterance_.size(), std::string());
break;
case SPD_EVENT_CANCEL:
controller->OnTtsEvent(
utterance_id_, TTS_EVENT_CANCELLED, 0, std::string());
break;
case SPD_EVENT_INDEX_MARK:
controller->OnTtsEvent(utterance_id_, TTS_EVENT_MARKER, 0, std::string());
break;
}
}
ExtensionTtsPlatformImplLinux* ExtensionTtsPlatformImplLinux::GetInstance() {
return Singleton<ExtensionTtsPlatformImplLinux>::get();
}
ExtensionTtsPlatformImpl* ExtensionTtsPlatformImpl::GetInstance() {
return ExtensionTtsPlatformImplLinux::GetInstance();
}