AVSpeechSynthesizer による音声読み上げ


iOS7からAPIが公開された音声読み上げ機能。
これを使うと、アプリに読み上げ機能をつけることができます。
メールを読んだり、ニュースを読んだりしてくれるアプリもできるかも。
……と期待して、ちょっと使い方をまとめてみました。
(iOS7.0.2、Xcode5.0、iPhone 5sで動作確認しています。)

基本的な使い方

まずは、文章を設定して読ませてみましょう。

最初にプロジェクトにAVFoundationをリンクします。
それから、適当な場所で AVFoundation/AVFoundation.h をimportしてください。

そして、AVSpeechSynthesizerをちょこちょこっといじってみます。

  // AVSpeechSynthesizerを初期化する。
  AVSpeechSynthesizer* speechSynthesizer = [[AVSpeechSynthesizer alloc] init];

  // AVSpeechUtteranceを読ませたい文字列で初期化する。
  NSString* speakingText = @"おはよう!";
  AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:speakingText];
 
  // AVSpeechSynthesizerにAVSpeechUtteranceを設定して読んでもらう
  [self.speechSynthesizer speakUtterance:utterance];

これで指定した文章が読み上げてもらえるはずです。
(ただし、システム設定を英語にしているとこの文章は読めません。@”おはよう!”を@”Hello!”にしてください。)

AVSpeechSynthesizer は読み上げを行うクラスです。
ちなみに、AVSpeechSynthesizer のインスタンスを複数作って同時にしゃべらせようとしても駄目でした。
(おそらく内部ではシングルトンとして処理されているのでしょうね。)

そして AVSpeechUtteranceは、一回の読み上げ処理をまとめたクラスです。
この AVSpeechUtterance には、文字列や読むスピードなど、読み上げ時のいろいろなプロパティを設定することができます。
文字列の設定は初期化のみで行うことができますが、それ以外の下記のプロパティは後で変更できます。

@property(nonatomic, retain) AVSpeechSynthesisVoice *voice;
@property(nonatomic) float rate;
@property(nonatomic) float pitchMultiplier;
@property(nonatomic) float volume;
@property(nonatomic) NSTimeInterval preUtteranceDelay;
@property(nonatomic) NSTimeInterval postUtteranceDelay;

voiceプロパティについてはまた後ほど。
rateは読むときのスピード。
pitchMultiplierは声のピッチ。小さい数値にすると男性っぽく、大きい数値にすると女性っぽくなります。
volumeは読みあげのボリューム。
preUtteranceDelayは読み出すまでの待ち時間、postUtteranceDelayは読み終わってからの待ち時間です。

AVSpeechSynthesisVoice

さて、AVSpeechUtterance に設定するvoiceは、読み上げ時の音声を指定するプロパティです。
iOSではロケール(言語+国)に対して一つの voice があり、基本的には読ませたい文章のロケールによって voice を設定する必要があります。

例えば「フランス語-フランス」のロケールの voice、「日本語-日本」のロケールのvoiceを設定する場合にはこんな感じです。

  AVSpeechSynthesisVoice* FVoice = [AVSpeechSynthesisVoice voiceWithLanguage:@"fr-FR"];
  AVSpeechSynthesisVoice* JVoice = [AVSpeechSynthesisVoice voiceWithLanguage:@"ja-JP"];

  // 日本語voiceをAVSpeechUtteranceに指定。
  utterance.voice =  JVoice;

AVSpeechUtteranceに何も設定しないと、システムのロケールのvoice が使われます。

システムで使える読み上げ音声の一覧はこれで取得できます。

  [AVSpeechSynthesisVoice speechVoices];

iPhone 5s/iOS 7.0.2で見てみたら、システムで用意されていたvoiceはこの36個でした。

Language for speaking on iOS7
Locale Language/Country
ar-SA Arabic (Saudi Arabia)
en-ZA English (South Africa)
th-TH Thai (Thailand)
nl-BE Dutch (Belgium)
en-AU English (Australia)
de-DE German (Germany)
en-US English (United States)
pt-BR Portuguese (Brazil)
pl-PL Polish (Poland)
en-IE English (Ireland)
el-GR Greek (Greece)
id-ID Indonesian (Indonesia)
sv-SE Swedish (Sweden)
tr-TR Turkish (Turkey)
pt-PT Portuguese (Portugal)
ja-JP Japanese (Japan)
ko-KR Korean (Korea)
hu-HU Hungarian (Hungary)
cs-CZ Czech (Czech Republic)
da-DK Danish (Denmark)
es-MX Spanish (Mexico)
fr-CA French (Canada)
nl-NL Dutch (Netherlands)
fi-FI Finnish (Finland)
es-ES Spanish (Spain)
it-IT Italian (Italy)
ro-RO Romanian (Romania)
no-NO Norwegian(Norway)
zh-HK Chinese (Hong Kong)
zh-TW Chinese (Taiwan)
sk-SK Slovak (Slovakia)
zh-CN Chinese (China)
ru-RU Russian (Russia)
en-GB English (United Kingdom)
fr-FR French (France)
hi-IN Hindi (India)

読み上げの制御

さて、AVSpeechSynthesizer の speakUtterance で読み上げを始めることができますが、読み上げを一時停止したり停止することもできます。

まず停止は stopSpeakingAtBoundary を使います。

  [speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];

このときに、引数として AVSpeechBoundaryImmediate か AVSpeechBoundaryWord を指定することができます。
AVSpeechBoundaryWord を指定すると単語がきれたところで停止、AVSpeechBoundaryImmediateだと即時停止しますが、AVSpeechBoundaryWord を指定してもなかなか思うところで止まってくれません。
通常は AVSpeechBoundaryImmediate で停止しておいたほうがいいでしょう。

一時停止は pauseSpeakingAtBoundary、一時停止からの再開は continueSpeakingです。
AVSpeechSynthesizer の pausedプロパティで一時停止かどうかわかるので、Pauseボタンのコードはこんな感じになります。

  if( speechSynthesizer.paused){
    [speechSynthesizer continueSpeaking];
   
  }
  else{
    [speechSynthesizer pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
  }

Delegate処理

AVSpeechSynthesizer の Delegateを設定すると、再生状態が変わったときの通知を受けたり、今しゃべっている単語を取得したりすることができます。

こちらは、再生状態が変わったときの通知。

- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didStartSpeechUtterance:(AVSpeechUtterance *)utterance;//読み上げが始まった通知
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance;//読み上げが終わった通知
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didPauseSpeechUtterance:(AVSpeechUtterance *)utterance;//読み上げが一時停止された通知
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didContinueSpeechUtterance:(AVSpeechUtterance *)utterance;//読み上げが再開された通知
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didCancelSpeechUtterance:(AVSpeechUtterance *)utterance;//読み上げが停止された通知

willSpeakRangeOfSpeechStringでは、今読み上げられている単語を取得することができます。
単語がそのまま渡される訳ではなく、AVSpeechUtteranceとNSRangeが渡されるので、AVSpeechUtteranceから読み上げ文字列を取得して、NSRangeの範囲を切り出します。
こんな感じです。

- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(AVSpeechUtterance *)utterance{
     
  NSString* strReading = [utterance.speechString substringWithRange:characterRange];
}

まとめ

上のコードが全部入ったサンプルをGithubにおいておきました。(https://github.com/toyship/StoryTeller)
読み上げの基本機能をだいたい試すことができます。
StoryTeller

読み上げ機能は便利なんですが、アプリで使おうとすると、voiceの設定にちょっと困ってしまいそうですね。
実際に、Apple製のメモ帳アプリやメールアプリで動作を見てみると、選択された文章のロケールによって、voiceを変更しているようです。
(日本語の文章を選択したときには日本語のvoice、英語の文章を選択したときには英語のvoiceで読み上げています)
この動作から判断すると、選択した文章のロケールを判断するAPIがどこかにある可能性はあります。
おそらく private APIだとは思いますが……。

AVSpeechSynthesizerは使い方も簡単だし、今まで公開されていなかった読み上げAPIなんだから積極的に使って行きたいですね。