ハイライト
iOS 17 と macOS Sonoma の Speech Synthesis フレームワークに 3 つの能力が追加されました。SSML 対応でより表現豊かな合成、Speech Synthesis Provider でカスタム音声エンジンを Audio Unit Extension としてシステムに接続、Personal Voice でユーザー自身の声から合成音声を生成して補助コミュニケーションに使えます。
主要内容
多くの人がデバイスとのやり取りに音声合成に依存しています。合成音声の選択は非常に個人的なものです。Apple システムには豊富な音声がありますが、サードパーティ開発者や特別なニーズを持つユーザーにはさらに多くが必要です。
SSML(Speech Synthesis Markup Language)は W3C 標準で、XML で音声属性を記述します。以前 AVSpeechSynthesizer はプレーンテキストのみ受け付け、休止、話速、音高などの細部を制御できませんでした。今は SSML を直接渡し、より自然で表現豊かな合成が可能です。
Speech Synthesis Provider は今年最大の新機能です。Audio Unit Extension アーキテクチャに基づき、開発者は自社の音声合成エンジンを Extension としてパッケージし、システム音声リストに接続できます。ユーザーはシステム設定であなたの音声を選べ、VoiceOver、Siri、サードパーティ App も利用できます。
Personal Voice はアクセシビリティ機能です。ユーザーが自分の声を録音すると、デバイス上でローカルにパーソナライズされた合成音声を生成します。ALS など発声能力に影響する疾患のユーザーにとって、自分の声で外界とコミュニケーションできることは大きな意味を持ちます。
詳細
SSML 音声マークアップ
(02:10)SSML は XML タグで音声属性を制御します。<break> で休止、<prosody> で話速と音高を制御します。
<speak>
Hello
<break time="1s"/>
<prosody rate="200%">nice to meet you!</prosody>
</speak>
(02:29)SSML を AVSpeechUtterance に渡します。
let ssml = """
<speak>
Hello
<break time="1s" />
<prosody rate="200%">nice to meet you!</prosody>
</speak>
"""
guard let ssmlUtterance = AVSpeechUtterance(ssmlRepresentation: ssml) else {
return
}
self.synthesizer.speak(ssmlUtterance)
キーポイント:
AVSpeechUtterance(ssmlRepresentation:)は SSML 文字列を受け取る新しいイニシャライザ- 戻り値は Optional。SSML 形式エラー時は nil
- SSML は WebSpeech と Apple 純正合成器の標準入力形式
- サポートタグに
break、prosodyなど。詳細は W3C 仕様を参照
Speech Synthesis Provider アーキテクチャ
(03:21)Provider は Audio Unit Extension ベースで、独立した Extension プロセスで動作します。システムがオーディオセッション管理と再生を担当し、Extension は SSML を音声にレンダリングするだけです。
プロジェクト作成時は Audio Unit Extension を選び、タイプは「Speech Synthesizer」、4 文字の subtype と manufacturer 識別子を指定します。
ホスト App が利用可能音声リストと購入フローを表示します。
struct ContentView: View {
var body: some View {
List {
Section("My Awesome Voices") {
ForEach(availableVoices) { voice in
HStack {
Text(voice.name)
Spacer()
Button("Buy") {
// Buy this voice...
}
}
}
}
}
}
var availableVoices: [WWDCVoice] {
return [
WWDCVoice(name: "Screen Reader Voice", id: "com.example.screen-reader-voice"),
WWDCVoice(name: "Reading Voice", id: "com.example.reading-voice")
]
}
}
キーポイント:
WWDCVoiceは例のカスタム構造体で、音声名と識別子を保持- ホスト App と Extension は App Group の
UserDefaultsで購入済み音声リストを共有 - 購入後は
AVSpeechSynthesisProviderVoice.updateSpeechVoices()でシステム音声リストを更新
(05:13)音声購入後にシステム音声リストを更新します。
func purchase(voice: WWDCVoice) {
purchasedVoices.append(voice)
// 共有 UserDefaults に書き込み
updatePurchasedVoices()
// システムに音声リスト変更を通知
AVSpeechSynthesisProviderVoice.updateSpeechVoices()
}
(06:25)システム音声リストの変化を監視します。
.onReceive(NotificationCenter.default
.publisher(for: AVSpeechSynthesizer.availableVoicesDidChangeNotification)) { _ in
systemVoices = AVSpeechSynthesisVoice.speechVoices()
}
Audio Unit Extension の実装
(06:53)Extension の核心は AVSpeechSynthesisProviderAudioUnit を継承し、4 つの重要部分を実装することです。
利用可能音声の宣言:
public class WWDCSynthAudioUnit: AVSpeechSynthesisProviderAudioUnit {
public override var speechVoices: [AVSpeechSynthesisProviderVoice] {
get {
let voices: [String : String] = groupDefaults.value(forKey: "voices") as? [String : String] ?? [:]
return voices.map { key, value in
return AVSpeechSynthesisProviderVoice(
name: value,
identifier: key,
primaryLanguages: ["en-US"],
supportedLanguages: ["en-US"]
)
}
}
}
}
キーポイント:
speechVoicesはAVSpeechSynthesisProviderVoice配列を返す- App Group の
UserDefaultsから購入済み音声リストを読み取る - 各音声に名称、識別子、主言語、サポート言語を指定
合成リクエストの受信:
public override func synthesizeSpeechRequest(speechRequest: AVSpeechSynthesisProviderRequest) {
currentBuffer = getAudioBuffer(for: speechRequest.voice, with: speechRequest.ssmlRepresentation)
framePosition = 0
}
キーポイント:
synthesizeSpeechRequestはシステムからの合成リクエストを受信speechRequest.ssmlRepresentationに SSML テキストが含まれるspeechRequest.voiceで使用する音声を指定- オーディオバッファを生成し、現在のフレーム位置を記録
リクエストのキャンセル:
public override func cancelSpeechRequest() {
currentBuffer = nil
}
オーディオフレームのレンダリング:
public override var internalRenderBlock: AUInternalRenderBlock {
return { [weak self]
actionFlags, timestamp, frameCount, outputBusNumber, outputAudioBufferList, _, _ in
guard let self else { return kAudio_ParamError }
var unsafeBuffer = UnsafeMutableAudioBufferListPointer(outputAudioBufferList)[0]
let frames = unsafeBuffer.mData!.assumingMemoryBound(to: Float32.self)
var sourceBuffer = UnsafeMutableAudioBufferListPointer(self.currentBuffer!.mutableAudioBufferList)[0]
let sourceFrames = sourceBuffer.mData!.assumingMemoryBound(to: Float32.self)
for frame in 0..<frameCount {
if frames.count > frame && sourceFrames.count > self.framePosition {
frames[Int(frame)] = sourceFrames[Int(self.framePosition)]
self.framePosition += 1
if self.framePosition >= self.currentBuffer!.frameLength {
break
}
}
}
return noErr
}
}
キーポイント:
internalRenderBlockはシステムが周期的に呼び出し、指定フレーム数の音声を埋める- 事前生成したオーディオバッファデータを出力バッファにコピー
- 全フレームレンダリング完了後、
actionFlagsをofflineUnitRenderAction_Completeに設定してシステムに通知
Personal Voice
(11:10)Personal Voice はユーザーが自分の声を録音し、デバイス上でローカルに合成音声を生成します。使用前に認可をリクエストします。
func fetchPersonalVoices() async {
AVSpeechSynthesizer.requestPersonalVoiceAuthorization() { status in
if status == .authorized {
personalVoices = AVSpeechSynthesisVoice.speechVoices().filter {
$0.voiceTraits.contains(.isPersonalVoice)
}
}
}
}
(11:34)Personal Voice で音声を合成します。
func speakUtterance(string: String) {
let utterance = AVSpeechUtterance(string: string)
if let voice = personalVoices.first {
utterance.voice = voice
synthesizer.speak(utterance)
}
}
キーポイント:
requestPersonalVoiceAuthorizationで Personal Voice へのアクセス許可をリクエスト- 認可後、Personal Voice は
AVSpeechSynthesisVoice.speechVoices()に現れる voiceTraits.contains(.isPersonalVoice)で Personal Voice をフィルタ- Personal Voice は主に AAC(補強代替コミュニケーション)App で使用され、センシティブな権限
重要ポイント
-
SSML 対応の読み上げ App を作る
- テキスト読み上げで SSML マークアップをサポートし、休止、話速、音高をユーザーが制御
- 価値:SSML で機械読み上げがより人間らしくなり、長文読書体験が大幅向上
- 始め方:テキストエディタに SSML タグ挿入ボタンを追加し、
AVSpeechUtterance(ssmlRepresentation:)で合成
-
自社 TTS エンジンを iOS システムに接続
- 自社音声合成技術があれば、Speech Synthesis Provider で Audio Unit Extension 化し、システムの全音声機能で利用可能に
- 価値:システム音声の制限を超え、VoiceOver や Siri などでもあなたの音声を使える
- 始め方:Audio Unit Extension プロジェクトを作成し、タイプは Speech Synthesizer、
AVSpeechSynthesisProviderAudioUnitの 4 つの核心メソッドを実装
-
AAC 補助コミュニケーション App に Personal Voice を統合
- 補助コミュニケーション App でユーザーが自分の Personal Voice を合成音声として選択
- 価値:発声能力を失う可能性のあるユーザーにとって、自分の声で話すことは大きな心理的価値
- 始め方:
requestPersonalVoiceAuthorizationで認可を取得し、isPersonalVoice音声をフィルタしてAVSpeechUtteranceに渡す
関連セッション
- Build accessible apps with SwiftUI and UIKit — 音声合成アクセシビリティと直接関連する、無障害 App 開発のベストプラクティス
- Meet Assistive Access — 認知障害向け補助機能。音声合成と合わせて完全なアクセシビリティエコシステムを構成
- Design considerations for vision and motion — 視覚・運動障害の設計考量。AAC App の設計原則と補完関係
コメント
GitHub Issues · utterances