カテゴリー: iOS

[Swift]AVSpeechSynthesizerで音声再生

はじめに

こんにちは、nukkyです。
音声読み上げはちょっと今更感が漂いますが、案件で使用したばっかというのと、備忘録兼、初心者向けということで書いていきたいと思います。

 

実装

コード

今回は、シングルトンでどこでも即音声読み上げみたいな形で作りました。
細かい説明は後でとりあえずコードを貼ります。

import AVFoundation

class SpeechManager: NSObject, AVSpeechSynthesizerDelegate {
    
    static let sharedInstance = SpeechManager()
    
    let synthesizer = AVSpeechSynthesizer()
    
    override init() {
        super.init()
        // delegateの設定
        synthesizer.delegate = self
    }
    
    // 音声再生開始
    func startSpeech(speechText: String) {
        synthesizer.speak(setSpeechText(text: speechText))
    }
    
    // 音声再生停止
    func stopSpeech() {
        synthesizer.stopSpeaking(at: AVSpeechBoundary.immediate)
    }
    
    // 音声設定
    private func setSpeechText(text: String) -> AVSpeechUtterance {
        // AVSpeechUtteranceを作成
        let utterance = AVSpeechUtterance(string: text)
        // 言語を設定
        utterance.voice = AVSpeechSynthesisVoice(language: "ja-JP")
        // 再生速度を設定
        utterance.rate = AVSpeechUtteranceDefaultSpeechRate
        // 声の高さを設定
        utterance.pitchMultiplier = 1.0
        
        return utterance
    }
    
    // 読み上げが開始したとき
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didStart utterance: AVSpeechUtterance) {
        print("didStart")
    }
    
    // 読み上げが終了したとき
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
        print("didFinish")
    }
    
    // 読み上げが一時停止したとき
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didPause utterance: AVSpeechUtterance) {
        print("didPause")
    }
    
    // 読み上げが一時停止から再生されたとき
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didContinue utterance: AVSpeechUtterance) {
        print("didContinue")
    }
    
    // 読み上げが途中で終了したとき
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) {
        print("didCancel")
    }
    
    // 読み上げ中で字句ごとに呼ばれる
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) {
        print("willSpeak")
    }
}

音声の再生処理は以下の処理です、
引数にAVSpeechUtteranceを指定しますがこのクラスで読み上げの声や言語の設定を行います。

synthesizer.speak(setSpeechText(text: speechText))

喋らせるテキストと言語を設定します。

// AVSpeechUtteranceを作成
let utterance = AVSpeechUtterance(string: “ここで喋らせるテキストを設定”)
// 言語を設定
utterance.voice = AVSpeechSynthesisVoice(language: "ja-JP")

音声の読み上げ速度を設定します、3段階ありますが正直Default以外は設定しないかと思います。。。

// 再生速度を設定
utterance.rate = AVSpeechUtteranceDefaultSpeechRate
utterance.rate = AVSpeechUtteranceMinimumSpeechRate
utterance.rate = AVSpeechUtteranceMaximumSpeechRate

読み上げる声の高さを設定します、0.5〜2.0の範囲で設定できます
1.0で標準のSiriと同じ高さです、個人の感想ですが1.5だと○ッパーみたいな声になります。

// 声の高さを設定
utterance.pitchMultiplier = 0.5//低い
utterance.pitchMultiplier = 1.0//普通
utterance.pitchMultiplier = 2.0//高い

呼び出し

サンプルコードを呼び出す際は以下の呼び出し方になります。

SpeechManager.sharedInstance.startSpeech(speechText: "2017年の流行語大賞は、インスタ映えと忖度")

さいごに

音声読み上げいかがでしょうか、ウォークスルーやチュートリアルなどちょっとしたところで使ってみたくなりますね。

nukky

コメントを見る

  • nukky様
    初めてコメントを送付いたします、当方、大阪在住のアプリ開発初心者:kkです。
    非常にわかりやすい記事をありがとうございます。
    独学でアプリ開発をする中で、WEB上の色々な記事を検索、勉強してきましたが、
    なかなかうまく行かず、限界を感じているところですが、『RE:ENGINES』の活動を
    読ませて頂き、一度相談させて頂こうと思い、本コメントを送信させて頂いております。
    見当違いであれば大変失礼となってしまい申し訳ありません。

    私は、下記のような質問があるのですが、本内容に対する回答を頂くにあたり、
    有料で受け取れる場合は、そのお見積り金額を頂ければありがたいです。
    また、他に必要な事項があればご指摘下さい。
    お手数をおかけしますが、どうか、よろしくお願いします。

    私は、現在、
    ・自分で入力した文章を、iPhoneに喋ってもらうアプリを作っております。
    その中で、下記の2つの課題が起こり、アプリ開発が止まってしまっております。

    課題1:for文を使った、繰り返し発声をしたいが、pre/postUtteranceDelayを
        使った待機時間を設定した場合、発声開始、停止を繰り返すと、簡単に
        動かなくなってしまう。
        (開始、停止、一時停止等が動かなくなり、アプリ再起動しないと復帰しない)
    アプリは以下の簡単な構成です。
     ①文章を、指定した回数で、幾度か(10回等)発声させる
     ②開始、停止、一時停止ボタンで制御する
     ③発声の前または後に、待機時間を作る
    という簡単なアプリを作ったのですが、上記③の機能を
    使うと、発声開始と停止を何度か繰り返す(ボタンを押す)と、
    アプリが簡単に止まってしまい、動かなくなってしまします。
    上記③をゼロにすると、止まってしまう現象は起こりません。
    何か、足りないコマンドがあるのでしょうか。

    サンプルコードは、以下の通りです。
    環境は、swift4(Xcode9.4.1)です。
    —————————————————————————————————————
    import UIKit
    import AVFoundation
    class ViewController: UIViewController, AVSpeechSynthesizerDelegate{
    var talker = AVSpeechSynthesizer()
    let utterance = AVSpeechUtterance()
    var counter = 0
    @IBOutlet weak var label: UILabel!
    @IBAction func didTapButton(_ sender: UIButton){ //Start/Pause button
    if talker.isPaused{
    talker.continueSpeaking()
    print("continue")
    }else{
    talker.continueSpeaking()
    print("play")
    }
    for n in 0...10{ //for(1)
    label.text = "I'm looking forward to seeing you."
    let utterance = AVSpeechUtterance(string:self.label.text!)
    utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
    utterance.rate = 0.5
    utterance.pitchMultiplier = 0.5

    //読み上げ前の待機時間
    utterance.preUtteranceDelay = 0
    //読み上げ後の待機時間
    utterance.postUtteranceDelay = 1

    self.talker.speak(utterance)
    self.talker.delegate = self

    }// for(1)
    } //Start/Pause

    //playback finish
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance)
    {
    print("***Finish***")
    }

    //STOP
    @IBAction func speechStop(_ sender: UIButton) {//Stop Button
    talker.stopSpeaking(at: AVSpeechBoundary.immediate)
    print("stop")
    }
    //Pause
    @IBAction func speechPause(_ sender: UIButton) {//Pause Button
    talker.pauseSpeaking(at: AVSpeechBoundary.immediate)
    print("pause")
    }

    override func viewDidLoad() {
    super.viewDidLoad()
    self.talker.delegate = self
    }

    override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    }
    }
    ———————————————————————————————————
    課題2:ヘッドフォンを使用してアプリを動作させている場合に、ヘッドフォンが
        抜けたら動作を停止させたいが、WEBで見つけた下記コマンドを、class下に
        実装したが動作しない。

    参考記事は、swift3対応と書かれており、実装後、Xcodeの指示通りにswift4対応に
    書き直したのですが、動作しておりません。
    また、swift4対応の記事を見つけることができず、間違い等のご指摘、動作原理を
    御教示頂けないでしょうか。

    参考URL:https://nackpan.net/blog/2015/09/29/ios-swift-phone-call-and-route-change/
    ————————————————————————————————————————————
    //ここから、ヘッドフォン抜き差し対応追加(9/1)
    /// 電話による割り込みと、オーディオルートの変化を監視します
    func addAudioSessionObservers() {
    AVAudioSession.sharedInstance()
    let center = NotificationCenter.default
    center.addObserver(self, selector: #selector(self.handleInterruption), name: NSNotification.Name.AVAudioSessionInterruption, object: nil)
    center.addObserver(self, selector: #selector(self.audioSessionRouteChanged), name: NSNotification.Name.AVAudioSessionRouteChange, object: nil)
    //center.addObserver(self, selector: #selector(ViewController.handleInterruption(_:)), name: NSNotification.Name.AVAudioSessionInterruption, object: nil)
    //center.addObserver(self, selector: #selector(ViewController.audioSessionRouteChanged(_:)), name: NSNotification.Name.AVAudioSessionRouteChange, object: nil)
    }
    /// Interruption : 電話による割り込み
    @objc func handleInterruption(_ notification: Notification) {

    let interruptionTypeObj = (notification as NSNotification).userInfo![AVAudioSessionInterruptionTypeKey] as! NSNumber
    if let interruptionType = AVAudioSessionInterruptionType(rawValue:
    interruptionTypeObj.uintValue) {

    switch interruptionType {
    case .began:
    // interruptionが開始した時(電話がかかってきたなど)
    // 音楽は自動的に停止される
    // (ここにUI更新処理などを書きます)

    //
    break
    case .ended:
    // interruptionが終了した時の処理
    //
    break
    }
    }
    }
    /// Audio Session Route Change : ルートが変化した(ヘッドセットが抜き差しされた)
    @objc func audioSessionRouteChanged(_ notification: Notification) {
    let reasonObj = (notification as NSNotification).userInfo![AVAudioSessionRouteChangeReasonKey] as! NSNumber
    if let reason = AVAudioSessionRouteChangeReason(rawValue: reasonObj.uintValue) {
    switch reason {
    case .newDeviceAvailable:
    print("New connect")
    // 新たなデバイスのルートが使用可能になった
    // (ヘッドセットが差し込まれた)
    break
    case .oldDeviceUnavailable:
    print("Disconnected")
    // 従来のルートが使えなくなった
    // (ヘッドセットが抜かれた)
    // 音楽は自動的に停止される
    // (ここにUI更新処理などを書きます)
    break
    default:
    break
    }
    }
    }
    ——————————————————————————————————————
    以上、一方的に送信させて頂くため、失礼等があればご指摘下さい。
    何卒よろしくお願い致します。

    • kk様
      コメント拝見させていただきました。
      全体がわからないため予測にはなりますが頂いているコードから予測した結果をお返しします。
      課題1に関してですがおそらく、Delayをしている最中が再生状態じゃないのでpauseSpeakingがうまく働いていないのだと思います。
      また、一時停止からの再生など制御を行うならばfor文を使った再生ではなく、AVSpeechSynthesizerDelegateで状態を管理した方が良いと思います。
      (例えば連続で再生するならば再生回数の状態を保持しdidFinishのタイミングで次の再生を行うなど現在の再生状況を管理する)
      課題2に関しては動作しないという状態がどのような状態を指しているのかがわからないのですが、addObserverに関しては問題なさそうに見えるので
      AVAudioSessionRouteChangeのタイミングでselectorに指定したメソッドが呼び出せない状況が考えられます。
      一旦AppDelegateでaddObserverを用意してみて、そもそもAVAudioSessionRouteChangeが呼ばれているのか切り出して確認してみると良いかもしれません。

      こちらの内容に関して引き続き確認事項がある場合や、ブログ記事以外にも私たちの活動への興味や個人の学習、ご依頼などあれば、下記よりお問い合わせください。
      https://re-engines.com/about/

      • nukky様
        kkです。
        返信、ありがとうございました。
        頂いたアドバイスで、再検討して見ます。
        取り急ぎ、返信いたします。

シェア
執筆者:
nukky
タグ: Swift

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

2週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

3週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前