Posted at

みんな大好き`AVSpeechSynthsizer`について

More than 3 years have passed since last update.

iOS Second Stageの21日目を担当させていただく@ushisantoasobuと申します。よろしくお願いします。

トレンディなネタでもなければ、ほとんどの人が興味ないであろうAVSpeechSynthsizerについての記事です。タイトルは冗談です。

AVSpeechSynthsizeriOS7から利用できるようになったテキストを音声で読み上げるAPIです。

AVSpeechSynthsizerを利用したアプリを当時個人でリリースしたのですが、iOS9で少し変更点があったので(iOS8のときは変更点なし)、今年そのアプリをフルスクラッチで書き直して別アプリとしてリリースしました。その備忘録としてこの記事では、



  • AVSpeechSynthsizerの基本的な話


  • AVSpeechSynthsizeriOS9での変更点について


  • AVSpeechSynthsizerを利用したサービスをつくってみて

といった内容で書いていきたいと思います


リリースしたアプリ

Twitilized

Twitterを耳で楽しむ、音声読み上げアプリです。


  • ホーム

  • トレンド

  • リスト

のツイートのリストを取得して、それらをAVSpeechSynthsizerが順次読み上げていき、耳で聞いて楽しむというアプリになっています。

こちらに実際に動いているところの動画もあげています(Nexus 9で撮ったのですが、画質悪いです)。普段自分は非公開のiOSデベロッパのリストから情報を追っているのですが、この動画ではそれを実際に読み上げたものになります。

もし気になった方は良ければ使ってみてください。


AVSpeechSynthsizerの基本的な使い方

以下がテキストを音声で読み上げるまでの基本的なコードです

import AVFoundation

class SomeViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

let synthesizer = AVSpeechSynthesizer()

// 読み上げる文字列を指定する
let utterance = AVSpeechUtterance(string: "ushisantoasobu")

// 読み上げの速度を指定する
utterance.rate = AVSpeechUtteranceMinimumSpeechRate
// 声の高さを指定する
utterance.pitchMultiplier = 1
// 声のボリュームを指定する
utterance.volume = 1.0

let voice = AVSpeechSynthesisVoice()
utterance.voice = voice

// 読み上げる
synthesizer.speakUtterance(utterance)
}
}

もちろん、読み上げを一時停止したりすることもできます

// 読み上げを途中で終了する(終了したところからまた再生したい場合は下のpauseを使う)

synthesizer.stopSpeakingAtBoundary(AVSpeechBoundary.Immediate)

// 読み上げを一時停止する
synthesizer.pauseSpeakingAtBoundary(AVSpeechBoundary.Immediate)

// 一時停止していた読み上げを再生する
synthesizer.continueSpeaking()

デリゲート(AVSpeechSynthesizerDelegate)メソッドには以下のようなものがあります

// 読み上げが開始したとき

func speechSynthesizer(synthesizer: AVSpeechSynthesizer, didStartSpeechUtterance utterance: AVSpeechUtterance)

// 読み上げが終了したとき
func speechSynthesizer(synthesizer: AVSpeechSynthesizer, didFinishSpeechUtterance utterance: AVSpeechUtterance)

// 読み上げが一時停止したとき
func speechSynthesizer(synthesizer: AVSpeechSynthesizer, didPauseSpeechUtterance utterance: AVSpeechUtterance)

// 読み上げが一時停止から再生されたとき
func speechSynthesizer(synthesizer: AVSpeechSynthesizer, didContinueSpeechUtterance utterance: AVSpeechUtterance)

// 読み上げが途中で終了したとき
func speechSynthesizer(synthesizer: AVSpeechSynthesizer, didCancelSpeechUtterance utterance: AVSpeechUtterance)

// 読み上げ中で字句ごとに呼ばれる
func speechSynthesizer(synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance)


iOS9で新しくなったポイント

残念なことにワクワクするような変更点はありません。以下が新たに追加された部分になります


AVSpeechSynthesis

@available(iOS 9.0, *)

public enum AVSpeechSynthesisVoiceQuality : Int {
case Default
case Enhanced
}

// Use the Alex identifier with voiceWithIdentifier:. If the voice is present on the system,
// an AVSpeechSynthesisVoice will be returned. Alex is en-US only.
@available(iOS 9.0, *)
public let AVSpeechSynthesisVoiceIdentifierAlex: String

/*!
@method voiceWithIdentifier:
@abstract Retrieve a voice by its identifier.
@param identifier
A unique identifier for a voice.
@discussion
Passing in an invalid identifier will return nil.
Returns nil if the identifier is valid, but the voice is not available on device (i.e. not yet downloaded by the user).
*/

@available(iOS 9.0, *)
public /*not inherited*/ init?(identifier: String)

@available(iOS 9.0, *)
public var identifier: String { get }
@available(iOS 9.0, *)
public var name: String { get }
@available(iOS 9.0, *)
public var quality: AVSpeechSynthesisVoiceQuality { get }


下の3つのプロパティはget-onlyです。なので注目すべきはinit?(identifier: String)になると思います。identifierをなにやら指定できるとのこと。ふむふむ。ただしここのidentifierに指定できるのが、現状だとAVSpeechSynthesisVoiceIdentifierAlexのみのようです。コメントに、


Alex is en-US only


とありますが、読み上げの言語設定がen-USの状態で日本語を読み上げようとしても、正しく読み上げられません(無音)。

ちなみにAlexの声を利用する場合はダウンロードする必要があるらしく、その方法がこちらに書かれていて、


「設定」>「一般」>「アクセシビリティ」>「スピーチ」>「声」


を言語設定が日本語の状態でみてみると、以下のような感じになっています。

image

ここらへんのSiriの声だったり、Mac OS XにあるOtoyaAVSpeechSynthsizerでいずれダウンロードすることで利用できるようになるのかなと思っています。

image


気をつけるべきこと

こちらはAVSpeechSynthsizerに限った話ではないのですが、音を再生するようなアプリケーションをつくるときにはいくつか考慮しておくべき項目がありますので、以下に挙げていこうと思います


バックグラウンド再生

バックグラウンド再生に対応するかどうかはもちろん考慮する必要があります。これについては他の記事で多く見つかると思うのでここでは割愛させていただきます


イヤホンジャックを抜いたとき

音を再生するアプリにおいて、イヤホンジャックを抜いたときに再生されていた音が停止する、というのは大抵の場合対応マストの項目だと思います。電車でイヤホンジャックが抜けて、聞いていたものが車内に響き渡って大恥をかかせる、なんてことはあってはいけないですからね。。。「ミュージック」や「Podcast」ももちろんそうなっています。

実装方法は以下のようになります

NSNotificationCenter.defaultCenter().addObserver(self, selector: "onAudioSessionRouteChange:", name: AVAudioSessionRouteChangeNotification, object: nil)


@objc func onAudioSessionRouteChange(notification :NSNotification) {
// ここで再生をとめる処理を書く
}


リモコン等からの操作に対応する

Apple純正のイヤホンやロックスクリーンからの操作に対応しておくとベターかと思います。

実装方法は以下のようになります

UIApplication.sharedApplication().beginReceivingRemoteControlEvents()

override func remoteControlReceivedWithEvent(event: UIEvent?) {

// 以下に各イベントに対する処理を書く
switch event!.subtype {
case UIEventSubtype.RemoteControlPlay:
//
case UIEventSubtype.RemoteControlPause:
//
case UIEventSubtype.RemoteControlTogglePlayPause:
//
case UIEventSubtype.RemoteControlPreviousTrack:
//
case UIEventSubtype.RemoteControlNextTrack:
//
default:
//
}
}


実際にサービスでつくってみて思ったこと

AVSpeechSynthsizerを使ったiOSアプリを実際につくってみて思ったことを以下に書いていきたいと思います


読みの精度はまだまだ(日本語)

少なくとも日本語の読み上げの精度はまだまだで、ここらへんは難しいとは思うのですが重要なところです。以下自分のタイムラインでよく出てくるテキストで読み上げに不満があるものです



  • (日) => 「ひ」(イベント情報なんかでよく出てくる)


  • Github => 「ギサブ」

ちなみにTwitilizedでは、'Twitter'クライアントアプリということで一部独自に変換しているものがあります(工夫しているつもり)。


  • 各種リンクは「リンク」という文字列に変換

  • 各種ハッシュタグは「#」を「ハッシュタグ」という文字列に変換

  • リツイートをあらわす「RT」は「リツイート」という文字列に変換


rateは実質Defaultのみ

utterance.rate = AVSpeechUtteranceDefaultSpeechRate

で指定できる読み上げの速度のパラメータであるrateについては以下の3つがあります。

@available(iOS 7.0, *)

public let AVSpeechUtteranceMinimumSpeechRate: Float
@available(iOS 7.0, *)
public let AVSpeechUtteranceMaximumSpeechRate: Float
@available(iOS 7.0, *)
public let AVSpeechUtteranceDefaultSpeechRate: Float

が、Minimumは遅すぎるし、Maximumは速すぎて・・・個人的にはDegault以外は使い物にならないように感じていますが・・・


参考記事

http://koze.hatenablog.jp/entry/2015/06/10/130000