こんにちはミラティブ2年目突入の福山です。 今回は、ユーザーが追加でインストール可能な拡張版の読み上げ音声がiOS 16で多くのメモリを消費するということがわかったので、ライブ配信用のアプリ拡張内で対策を行いました。
メモリ使用量50MB制限
MirrativのiOSアプリは、AVSpeechSynthesizer
を使用してライブ配信時のコメントを読み上げる機能を提供しています。この機能は配信用のアプリ拡張であるBroadcast Upload Extension
内で動作し、そのメモリ制限は50MBとなっています。もしもこの制限を少しでも超えると、アプリはクラッシュし、ライブ配信は終了してしまいます。したがって、メモリ使用量が増加する可能性のある要素に対しては無視するわけにはいきません。
拡張版の音声を使用するとすぐに50MBのメモリ制限に達してしまう可能性があります。メモリ使用量が50MBに到達すると、Broadcast Upload Extension
がクラッシュし、「Mirrativへのライブブロードキャストは次の利用により停止しました: 無効なブロードキャストセッションを開始しようとしました」というシステムアラートが表示されます。
メモリ使用量の計測
音声の変更は、「設定」アプリの「アクセシビリティ」 > 「読み上げコンテンツ」 > 「声」から可能で、日本語には女性音声のKyokoと男性音声のOtoyaがあります(Siriについては後述)。それぞれの拡張版はユーザー側でインストール可能です。
iOS version | Kyoko通常 | Otoya通常 | Kyoko拡張 | Otoya拡張 |
---|---|---|---|---|
iOS 15.7.1 | 6.6MB | 6.2MB | 6.8MB | 6.5MB |
iOS 16.1.1 | 6.7MB | 6.8MB | 23.8MB | 21.6MB |
iOS 16.5.0 | 14.7MB | 14.9MB | 32.1MB | 29.9MB |
import SwiftUI import AVKit struct ContentView: View { @StateObject var speaker = Speaker() var body: some View { Button { speaker.speak(text: "ハロー") } label: { Text("Speak") } } } class Speaker: ObservableObject { private var synthesizer = AVSpeechSynthesizer() func speak(text: String) { let utterance = AVSpeechUtterance(string: text) synthesizer.speak(utterance) } }
対策
ユーザーが拡張版の音声を選択するとライブ配信が突然終了しやすくなるという問題を解消するため、拡張版が選択されていても音声の性別を保ったまま通常版の音声を使うことでメモリ使用量を抑えることにしました。
ちなみに日本語の通常品質のSiri音声はデフォルトでインストールされており、コードからのみアクセス可能です。ユーザーは「設定」アプリから拡張版のSiri音声をインストールし選択可能ですが、それはサードパーティのアプリからは使用することも検出することもできません。
private lazy var preferredVoice: AVSpeechSynthesisVoice? = { // 言語ごとのデフォルトorユーザー選択音声は以下のように取得可能、しかしgenderが常に.unspecifiedとなる // ユーザーが日本語のSiriを選択していた場合、サードパーティアプリでは強制的に女性音声Kyokoになる let defaultVoice = AVSpeechSynthesisVoice(language: AVSpeechSynthesisVoice.currentLanguageCode()) // デフォルト音質の場合はそれを使う。 guard defaultVoice?.quality != .default else { return defaultVoice } // defaultVoiceのgenderが.unspecifiedのため、identifierからユーザーが選択している音声の性別を判定 let isMaleVoice = defaultVoice?.identifier.contains("Otoya") ?? false let gender: AVSpeechSynthesisVoiceGender = isMaleVoice ? .male : .female // Siri音声identifierの先頭に使われている文字列 let siriIdentifierPrefix = "com.apple.ttsbundle.siri" let currentDefaultLanguageVoice = AVSpeechSynthesisVoice.speechVoices() .first(where: { $0.quality == .default && $0.gender == gender && !$0.identifier.hasPrefix(siriIdentifierPrefix) && $0.language == AVSpeechSynthesisVoice.currentLanguageCode() }) return currentDefaultLanguageVoice }() private var synthesizer = AVSpeechSynthesizer() func speak(text: String) { let utterance = AVSpeechUtterance(string: text) utterance.voice = preferredVoice // 声を指定 synthesizer.speak(utterance) }
おわりに
一般的なアプリでは無視できる程度のメモリ使用量ですが、Broadcast Upload Extension
のような制約がある場合は問題となることがあります。この記事が他の開発者の皆さんの参考になれば幸いです。
We are hiring!
ミラティブでは『好きでつながり、自分の物語(ナラティブ)が生まれる居場所』を実現するエンジニアを募集中です!