357 lines
		
	
	
	
		
			11 KiB
			
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			357 lines
		
	
	
	
		
			11 KiB
			
		
	
	
	
		
			C++
		
	
	
	
	
	
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
 | 
						|
// Use of this source code is governed by a BSD-style license that can be
 | 
						|
// found in the LICENSE file.
 | 
						|
 | 
						|
#include <math.h>
 | 
						|
 | 
						|
#include <map>
 | 
						|
 | 
						|
#include "base/command_line.h"
 | 
						|
#include "base/debug/leak_annotations.h"
 | 
						|
#include "base/memory/scoped_ptr.h"
 | 
						|
#include "base/memory/singleton.h"
 | 
						|
#include "base/synchronization/lock.h"
 | 
						|
#include "chrome/browser/speech/tts_platform.h"
 | 
						|
#include "content/public/browser/browser_thread.h"
 | 
						|
#include "content/public/common/content_switches.h"
 | 
						|
 | 
						|
#include "library_loaders/libspeechd.h"
 | 
						|
 | 
						|
using content::BrowserThread;
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
const char kNotSupportedError[] =
 | 
						|
    "Native speech synthesis not supported on this platform.";
 | 
						|
 | 
						|
struct SPDChromeVoice {
 | 
						|
  std::string name;
 | 
						|
  std::string module;
 | 
						|
};
 | 
						|
 | 
						|
}  // namespace
 | 
						|
 | 
						|
class TtsPlatformImplLinux : public TtsPlatformImpl {
 | 
						|
 public:
 | 
						|
  bool PlatformImplAvailable() override;
 | 
						|
  bool Speak(int utterance_id,
 | 
						|
             const std::string& utterance,
 | 
						|
             const std::string& lang,
 | 
						|
             const VoiceData& voice,
 | 
						|
             const UtteranceContinuousParameters& params) override;
 | 
						|
  bool StopSpeaking() override;
 | 
						|
  void Pause() override;
 | 
						|
  void Resume() override;
 | 
						|
  bool IsSpeaking() override;
 | 
						|
  void GetVoices(std::vector<VoiceData>* out_voices) override;
 | 
						|
 | 
						|
  void OnSpeechEvent(SPDNotificationType type);
 | 
						|
 | 
						|
  // Get the single instance of this class.
 | 
						|
  static TtsPlatformImplLinux* GetInstance();
 | 
						|
 | 
						|
 private:
 | 
						|
  TtsPlatformImplLinux();
 | 
						|
  ~TtsPlatformImplLinux() override;
 | 
						|
 | 
						|
  // Initiate the connection with the speech dispatcher.
 | 
						|
  void Initialize();
 | 
						|
 | 
						|
  // Resets the connection with speech dispatcher.
 | 
						|
  void Reset();
 | 
						|
 | 
						|
  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);
 | 
						|
 | 
						|
  static SPDNotificationType current_notification_;
 | 
						|
 | 
						|
  base::Lock initialization_lock_;
 | 
						|
  LibSpeechdLoader libspeechd_loader_;
 | 
						|
  SPDConnection* conn_;
 | 
						|
 | 
						|
  // These apply to the current utterance only.
 | 
						|
  std::string utterance_;
 | 
						|
  int utterance_id_;
 | 
						|
 | 
						|
  // Map a string composed of a voicename and module to the voicename. Used to
 | 
						|
  // uniquely identify a voice across all available modules.
 | 
						|
  scoped_ptr<std::map<std::string, SPDChromeVoice> > all_native_voices_;
 | 
						|
 | 
						|
  friend struct base::DefaultSingletonTraits<TtsPlatformImplLinux>;
 | 
						|
 | 
						|
  DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplLinux);
 | 
						|
};
 | 
						|
 | 
						|
// static
 | 
						|
SPDNotificationType TtsPlatformImplLinux::current_notification_ =
 | 
						|
    SPD_EVENT_END;
 | 
						|
 | 
						|
TtsPlatformImplLinux::TtsPlatformImplLinux()
 | 
						|
    : utterance_id_(0) {
 | 
						|
  const base::CommandLine& command_line =
 | 
						|
      *base::CommandLine::ForCurrentProcess();
 | 
						|
  if (!command_line.HasSwitch(switches::kEnableSpeechDispatcher))
 | 
						|
    return;
 | 
						|
 | 
						|
  BrowserThread::PostTask(BrowserThread::FILE,
 | 
						|
                          FROM_HERE,
 | 
						|
                          base::Bind(&TtsPlatformImplLinux::Initialize,
 | 
						|
                                     base::Unretained(this)));
 | 
						|
}
 | 
						|
 | 
						|
void TtsPlatformImplLinux::Initialize() {
 | 
						|
  base::AutoLock lock(initialization_lock_);
 | 
						|
 | 
						|
  if (!libspeechd_loader_.Load("libspeechd.so.2"))
 | 
						|
    return;
 | 
						|
 | 
						|
  {
 | 
						|
    // spd_open has memory leaks which are hard to suppress.
 | 
						|
    // http://crbug.com/317360
 | 
						|
    ANNOTATE_SCOPED_MEMORY_LEAK;
 | 
						|
    conn_ = libspeechd_loader_.spd_open(
 | 
						|
        "chrome", "extension_api", NULL, SPD_MODE_THREADED);
 | 
						|
  }
 | 
						|
  if (!conn_)
 | 
						|
    return;
 | 
						|
 | 
						|
  // Register callbacks for all events.
 | 
						|
  conn_->callback_begin =
 | 
						|
    conn_->callback_end =
 | 
						|
    conn_->callback_cancel =
 | 
						|
    conn_->callback_pause =
 | 
						|
    conn_->callback_resume =
 | 
						|
    &NotificationCallback;
 | 
						|
 | 
						|
  conn_->callback_im = &IndexMarkCallback;
 | 
						|
 | 
						|
  libspeechd_loader_.spd_set_notification_on(conn_, SPD_BEGIN);
 | 
						|
  libspeechd_loader_.spd_set_notification_on(conn_, SPD_END);
 | 
						|
  libspeechd_loader_.spd_set_notification_on(conn_, SPD_CANCEL);
 | 
						|
  libspeechd_loader_.spd_set_notification_on(conn_, SPD_PAUSE);
 | 
						|
  libspeechd_loader_.spd_set_notification_on(conn_, SPD_RESUME);
 | 
						|
}
 | 
						|
 | 
						|
TtsPlatformImplLinux::~TtsPlatformImplLinux() {
 | 
						|
  base::AutoLock lock(initialization_lock_);
 | 
						|
  if (conn_) {
 | 
						|
    libspeechd_loader_.spd_close(conn_);
 | 
						|
    conn_ = NULL;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void TtsPlatformImplLinux::Reset() {
 | 
						|
  base::AutoLock lock(initialization_lock_);
 | 
						|
  if (conn_)
 | 
						|
    libspeechd_loader_.spd_close(conn_);
 | 
						|
  conn_ = libspeechd_loader_.spd_open(
 | 
						|
      "chrome", "extension_api", NULL, SPD_MODE_THREADED);
 | 
						|
}
 | 
						|
 | 
						|
bool TtsPlatformImplLinux::PlatformImplAvailable() {
 | 
						|
  if (!initialization_lock_.Try())
 | 
						|
    return false;
 | 
						|
  bool result = libspeechd_loader_.loaded() && (conn_ != NULL);
 | 
						|
  initialization_lock_.Release();
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
bool TtsPlatformImplLinux::Speak(
 | 
						|
    int utterance_id,
 | 
						|
    const std::string& utterance,
 | 
						|
    const std::string& lang,
 | 
						|
    const VoiceData& voice,
 | 
						|
    const UtteranceContinuousParameters& params) {
 | 
						|
  if (!PlatformImplAvailable()) {
 | 
						|
    error_ = kNotSupportedError;
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  // Speech dispatcher's speech params are around 3x at either limit.
 | 
						|
  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;
 | 
						|
 | 
						|
  std::map<std::string, SPDChromeVoice>::iterator it =
 | 
						|
      all_native_voices_->find(voice.name);
 | 
						|
  if (it != all_native_voices_->end()) {
 | 
						|
    libspeechd_loader_.spd_set_output_module(conn_, it->second.module.c_str());
 | 
						|
    libspeechd_loader_.spd_set_synthesis_voice(conn_, it->second.name.c_str());
 | 
						|
  }
 | 
						|
 | 
						|
  // Map our multiplicative range to Speech Dispatcher's linear range.
 | 
						|
  // .334 = -100.
 | 
						|
  // 3 = 100.
 | 
						|
  libspeechd_loader_.spd_set_voice_rate(conn_, 100 * log10(rate) / log10(3));
 | 
						|
  libspeechd_loader_.spd_set_voice_pitch(conn_, 100 * log10(pitch) / log10(3));
 | 
						|
 | 
						|
  // Support languages other than the default
 | 
						|
  if (!lang.empty())
 | 
						|
    libspeechd_loader_.spd_set_language(conn_, lang.c_str());
 | 
						|
 | 
						|
  utterance_ = utterance;
 | 
						|
  utterance_id_ = utterance_id;
 | 
						|
 | 
						|
  if (libspeechd_loader_.spd_say(conn_, SPD_TEXT, utterance.c_str()) == -1) {
 | 
						|
    Reset();
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool TtsPlatformImplLinux::StopSpeaking() {
 | 
						|
  if (!PlatformImplAvailable())
 | 
						|
    return false;
 | 
						|
  if (libspeechd_loader_.spd_stop(conn_) == -1) {
 | 
						|
    Reset();
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
void TtsPlatformImplLinux::Pause() {
 | 
						|
  if (!PlatformImplAvailable())
 | 
						|
    return;
 | 
						|
  libspeechd_loader_.spd_pause(conn_);
 | 
						|
}
 | 
						|
 | 
						|
void TtsPlatformImplLinux::Resume() {
 | 
						|
  if (!PlatformImplAvailable())
 | 
						|
    return;
 | 
						|
  libspeechd_loader_.spd_resume(conn_);
 | 
						|
}
 | 
						|
 | 
						|
bool TtsPlatformImplLinux::IsSpeaking() {
 | 
						|
  return current_notification_ == SPD_EVENT_BEGIN;
 | 
						|
}
 | 
						|
 | 
						|
void TtsPlatformImplLinux::GetVoices(
 | 
						|
    std::vector<VoiceData>* out_voices) {
 | 
						|
  if (!all_native_voices_.get()) {
 | 
						|
    all_native_voices_.reset(new std::map<std::string, SPDChromeVoice>());
 | 
						|
    char** modules = libspeechd_loader_.spd_list_modules(conn_);
 | 
						|
    if (!modules)
 | 
						|
      return;
 | 
						|
    for (int i = 0; modules[i]; i++) {
 | 
						|
      char* module = modules[i];
 | 
						|
      libspeechd_loader_.spd_set_output_module(conn_, module);
 | 
						|
      SPDVoice** native_voices =
 | 
						|
          libspeechd_loader_.spd_list_synthesis_voices(conn_);
 | 
						|
      if (!native_voices) {
 | 
						|
        free(module);
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      for (int j = 0; native_voices[j]; j++) {
 | 
						|
        SPDVoice* native_voice = native_voices[j];
 | 
						|
        SPDChromeVoice native_data;
 | 
						|
        native_data.name = native_voice->name;
 | 
						|
        native_data.module = module;
 | 
						|
        std::string key;
 | 
						|
        key.append(native_data.name);
 | 
						|
        key.append(" ");
 | 
						|
        key.append(native_data.module);
 | 
						|
        all_native_voices_->insert(
 | 
						|
            std::pair<std::string, SPDChromeVoice>(key, native_data));
 | 
						|
        free(native_voices[j]);
 | 
						|
      }
 | 
						|
      free(modules[i]);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  for (std::map<std::string, SPDChromeVoice>::iterator it =
 | 
						|
           all_native_voices_->begin();
 | 
						|
       it != all_native_voices_->end();
 | 
						|
       it++) {
 | 
						|
    out_voices->push_back(VoiceData());
 | 
						|
    VoiceData& voice = out_voices->back();
 | 
						|
    voice.native = true;
 | 
						|
    voice.name = it->first;
 | 
						|
    voice.events.insert(TTS_EVENT_START);
 | 
						|
    voice.events.insert(TTS_EVENT_END);
 | 
						|
    voice.events.insert(TTS_EVENT_CANCELLED);
 | 
						|
    voice.events.insert(TTS_EVENT_MARKER);
 | 
						|
    voice.events.insert(TTS_EVENT_PAUSE);
 | 
						|
    voice.events.insert(TTS_EVENT_RESUME);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void TtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type) {
 | 
						|
  TtsController* controller = TtsController::GetInstance();
 | 
						|
  switch (type) {
 | 
						|
  case SPD_EVENT_BEGIN:
 | 
						|
    controller->OnTtsEvent(utterance_id_, TTS_EVENT_START, 0, std::string());
 | 
						|
    break;
 | 
						|
  case SPD_EVENT_RESUME:
 | 
						|
    controller->OnTtsEvent(utterance_id_, TTS_EVENT_RESUME, 0, std::string());
 | 
						|
    break;
 | 
						|
  case SPD_EVENT_END:
 | 
						|
    controller->OnTtsEvent(
 | 
						|
        utterance_id_, TTS_EVENT_END, utterance_.size(), std::string());
 | 
						|
    break;
 | 
						|
  case SPD_EVENT_PAUSE:
 | 
						|
    controller->OnTtsEvent(
 | 
						|
        utterance_id_, TTS_EVENT_PAUSE, 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;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
void TtsPlatformImplLinux::NotificationCallback(
 | 
						|
    size_t msg_id, size_t client_id, SPDNotificationType type) {
 | 
						|
  // We run Speech Dispatcher in threaded mode, so these callbacks should always
 | 
						|
  // be in a separate thread.
 | 
						|
  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
 | 
						|
    current_notification_ = type;
 | 
						|
    BrowserThread::PostTask(
 | 
						|
        BrowserThread::UI,
 | 
						|
        FROM_HERE,
 | 
						|
        base::Bind(&TtsPlatformImplLinux::OnSpeechEvent,
 | 
						|
                   base::Unretained(TtsPlatformImplLinux::GetInstance()),
 | 
						|
                   type));
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
void TtsPlatformImplLinux::IndexMarkCallback(size_t msg_id,
 | 
						|
                                                      size_t client_id,
 | 
						|
                                                      SPDNotificationType state,
 | 
						|
                                                      char* index_mark) {
 | 
						|
  // TODO(dtseng): index_mark appears to specify an index type supplied by a
 | 
						|
  // client. Need to explore how this is used before hooking it up with existing
 | 
						|
  // word, sentence events.
 | 
						|
  // We run Speech Dispatcher in threaded mode, so these callbacks should always
 | 
						|
  // be in a separate thread.
 | 
						|
  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
 | 
						|
    current_notification_ = state;
 | 
						|
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
 | 
						|
        base::Bind(&TtsPlatformImplLinux::OnSpeechEvent,
 | 
						|
        base::Unretained(TtsPlatformImplLinux::GetInstance()),
 | 
						|
        state));
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
TtsPlatformImplLinux* TtsPlatformImplLinux::GetInstance() {
 | 
						|
  return base::Singleton<
 | 
						|
      TtsPlatformImplLinux,
 | 
						|
      base::LeakySingletonTraits<TtsPlatformImplLinux>>::get();
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
TtsPlatformImpl* TtsPlatformImpl::GetInstance() {
 | 
						|
  return TtsPlatformImplLinux::GetInstance();
 | 
						|
}
 |