AVAudioSessionの雑なメモ
オーディオセッション(AVAudioSession)概要
オーディオセッション(AVAudioSession)は, 音声の利用に関してアプリとOS間(macOS以外, iOS, iPadOS, tvOS, visionOS, watchOS)のインターフェイスとして機能する.
オーディオセッションの主要機能
- アプリでどのように音声を利用(動作)するかを決定する
- カテゴリ, モード, オプション, ルートシェアリングポリシの設定
- オーディオセッションの有効化(アクティベート)/無効化(非アクティベート)
- オーディオイベントの応答
- オーディオの割込みイベント, オーディオ・ビデオサービスのリセット
- iOS16までは録音関連, 録音許可リクエスト等(iOS17以降は
AVAudioApplication.recordPermission) - 音声ハードウェアの設定
- 音声入出力の選択
- サンプルレート, IOバッファの長さの設定
単に音声を出力する場合(音声・動画再生等)はデフォルト動作で何もする必要はない場合が多い. 特にAVFoundation(AVFAudio)のクラスを利用するのであれば, 1と3を実装すれば大抵十分で, 実装内容も難しくはない.
ここでは, "5. 音声ハードウェアの設定"以外について記すことにする.
環境
- ターゲットOS: iOS17以上 (iPadOS, tvOS, visionOS, watchOSであればある程度踏襲可能)
- 検証端末はiOS26を使用
- Xcode26
1. アプリでどのように音声を利用するかを決定する
アプリの音声の用途によって
- カテゴリ(
AVAudioSession.Category) - モード(
AVAudioSession.Mode) - カテゴリオプション(
AVAudioSession.CategoryOptions) - ルートシェアリングポリシ(
AVAudioSession.RouteSharingPolicy)
を設定する.
カテゴリ(AVAudioSession.Category)
カテゴリがオーディオセッションの広範な動作を定義する.
例えば以下のような動作がカテゴリによって決まる:
- 再生専用か録音可能か
- 他アプリの音声とミックスされるか
- 消音スイッチ・画面のロックでミュートされるか
- バックグラウンド再生可能か
カテゴリ・動作・ユースケース一覧
カテゴリは6つあり, 設定時の動作は以下の通りである.
デフォルトカテゴリはsoloAmbientで, 何も設定しない場合これが利用される.
| Category | 動作 | ユースケース |
|---|---|---|
| ambient | - 他の音声とミックスする - 消音スイッチでミュートする - バックグラウンド再生不可 |
ゲーム効果音 |
| multiRoute | - 異なるオーディオデータストリームを 異なる出力デバイスに同時にルーティングする - 他の音声を停止する - 消音スイッチでミュートされない - バックグラウンド再生可 |
DJアプリ(?) |
| playAndRecord | - 他の音声を停止する - 消音スイッチでミュートされない - バックグラウンド再生可 |
録音・再生 |
| playback | - 他の音声を停止する - 消音スイッチでミュートされない - バックグラウンド再生可 |
音楽の再生 |
| record | - 他の音声を停止する | 録音 |
| soloAmbient (default) | - 他の音声を停止する - 消音スイッチでミュートする - バックグラウンド再生不可 |
全般 |
※バックグラウンド再生をするには適切なカテゴリと有効化するためのplistの設定が必要である.
plistの設定はConfiguring your app for media playbackを参照.
モード(AVAudioSession.Mode)
モードはアプリのサウンド設定を調整し, 特定の目的に合わせてオーディオ機能を最適化するために利用される.
モードと目的・用途一覧
| Mode | 目的・用途 |
|---|---|
| default[1] | デフォルト |
| gameChat[2] | GameKit用 |
| measurement | 計測用 |
| moviePlayback | 動画再生用 |
| shortFormVideo[3] | ショート動画用 |
| spokenAudio | Podcast、オーディオブック用 |
| videoChat | 動画チャット |
| videoRecording | 動画録画 |
| voiceChat | ボイスチャット |
| voicePrompt | ボイスプロンプト用(TextToSpeech、ナビゲーション) |
カテゴリオプション(AVAudioSession.CategoryOptions)
カテゴリオプションでカテゴリの動作の追加カスタマイズができる.
カテゴリオプションを設定するとデフォルトで無効化されているものを有効化する. 逆に無効化することはできず, その場合はカテゴリを選択し直すことになる.
カテゴリによって無効化されているオプションも違ってくる.
例えば, ambientカテゴリではmixWithOthersはデフォルトで有効であるが, playbackカテゴリではmixWithOthersはデフォルト無効, オプションに指定することにより有効化できる. また, 別のカテゴリオプションも自動的に有効化されるものもある.
例えば, duckOthersを設定すると自動でmixWithOthersも設定される.
カテゴリオプションと内容・設定時の動作一覧
| Category Option | 内容・動作 |
|---|---|
| default | デフォルト |
| allowAirPlay | AirPlayの有効化 |
| allowBluetoothHFP | Bluetooth HFP(Hands-Free Profile)の有効化 |
| allowBluetoothA2DP | Bluetooth A2DPの有効化 |
| bluetoothHighQualityRecording | Bluetoothルートをサポートしている場合, フル帯域幅オーディオの有効化 (特定のAirPodsモデルなど) |
| defaultToSpeaker | デフォルトで, オーディオをレシーバー(上部スピーカー)ではなく下部スピーカーにルーティングする |
| duckOthers | 自分のアプリの音声を再生中に他のアプリの音量を下げる(ダッキングする) |
| interruptSpokenAudioAndMixWithOthers | スポークンオーディオ(オーディオブック等)は中断され, 他の音声の場合はミックスする |
| mixWithOthers | 他の音声とミックスする |
| overrideMutedMicrophoneInterruption | システムが内蔵マイクをミュートするときにオーディオセッションを中断させない |
ルートシェアリングポリシ(AVAudioSession.RouteSharingPolicy)
ルートシェアリングポリシは, 利用可能なとき音声出力をデフォルトシステム出力以外にルーティングする.
設定可能なカテゴリはplaybackのみでlongFormAudioとlongFormVideoのポリシーが設定可能である.
他のカテゴリで設定するとエラーになる.
ルートシェアリングポリシと設定時の動作一覧
| Route Sharing Policy | 設定時の動作 |
|---|---|
| default[4] | デフォルトポリシ |
| independent | ルートピッカーUIを介して動画がワイヤレスでルーティングされる場合のシステムが管理するポリシ(指定不可) |
| longFormAudio | 他の音楽/ポッドキャストアプリとルートを共有するポリシ |
| longFormVideo | 他の動画アプリとルートを共有するポリシ |
設定可能なカテゴリとモードの関係
設定可能なカテゴリとモードの対応一覧.
categoryOptionsとrouteSharingPolicyはdefaultを指定.
gameChatは設定用ではないので省略.
| category / mode | measurement | moviePlayback | shortFormVideo | spokenAudio |
|---|---|---|---|---|
| ambient | ✔︎ | |||
| multiRoute | ✔︎ | ✔︎ | ||
| playAndRecord | ✔︎ | ✔︎ | ||
| playback | ✔︎ | ✔︎ | ✔︎ | ✔︎ |
| record | ✔︎ | |||
| soloAmbient | ✔︎ |
| category / mode | videoChat | videoRecording | voiceChat | voicePrompt |
|---|---|---|---|---|
| ambient | ||||
| multiRoute | ||||
| playAndRecord | ✔︎ | ✔︎ | ✔︎ | |
| playback | ✔︎ | |||
| record | ✔︎ | |||
| soloAmbient |
※videoChatのオンラインドキュメントには "Use this mode for video chat apps that use the playAndRecord or record categories" とあるが実際はrecordカテゴリでvideoChatは設定不可(ソース上のコメントには正しい記載がされている)
※シミュレータで有効ではないカテゴリ・モードのペアを指定したとしてもエラーにはならないので注意
設定可能なカテゴリとカテゴリオプションの関係
設定可能なカテゴリとカテゴリオプションの対応一覧(modeとrouteSharingPolicyはdefault)
| category / option | allowAirPlay | allowBluetoothHFP | allowBluetoothA2DP |
|---|---|---|---|
| ambient | |||
| multiRoute | |||
| playAndRecord | ✔︎ | ✔︎ | |
| playback | |||
| record | |||
| soloAmbient |
| category / option | bluetoothHighQualityRecording | defaultToSpeaker | duckOthers |
|---|---|---|---|
| ambient | ️ | ✔︎ | |
| multiRoute | ✔︎ | ✔︎ | ✔︎ |
| playAndRecord | ✔︎ | ✔︎ | ✔︎ |
| playback | ︎ | ✔︎ | |
| record | ✔︎ | ✔︎ | |
| soloAmbient | ✔︎ |
| category / option | interruptSpokenAudioAndMixWithOthers | mixWithOthers | overrideMutedMicrophoneInterruption |
|---|---|---|---|
| ambient | ✔︎ | ️ ✔︎ | |
| multiRoute | ✔︎ | ✔︎ | ✔︎ |
| playAndRecord | ✔︎ | ✔︎ | ✔︎ |
| playback | ✔︎ | ✔︎ | ✔︎ |
| record | ✔︎ | ✔︎ | ✔︎ |
| soloAmbient | ✔︎ | ✔︎ | ✔︎ |
カテゴリ, モード, カテゴリオプション, ルートシェアリングポリシの設定
カテゴリ, モード, カテゴリオプション, ルートシェアリングポリシの設定がオーディオセッションを利用する上で最も重要である.
設定するには AVAudioSession.setCategory(_, mode:, policy:, options:) のようなメソッドを使う.
playAndRecordカテゴリを設定する場合は以下のようにする.
do {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord)
} catch {
// handle error: unable to set category
}
特に録音する場合はカテゴリが正しく設定されていないと入力が無効になるので, エラーハンドリングは必須.
音声出力だけの場合はカテゴリの設定で出力されないということにはならないので, エラーハンドリングは望ましいが, 最悪無視してもこの設定によって音がでないというような問題にはならない.
エラー内容はAVAudioSession.ErrorCode.badParam(無効なカテゴリ・モードのペアを指定したなど)のような明らかな設定ミスエラーから.unspecifiedのような雑なエラーまであるので, これらを分けて詳細にエラー処理をするのは難しい(エラー内容自体信頼できるのかという問題もある).
ここで設定した値はどのくらい有効なのかドキュメント等に記載されてない. 確実に設定したい場合は, 以下のように防御的に録音/再生開始の直前に常にチェックして設定するほうが, アプリ起動時等に一度しか設定しないより安全なのでお勧め(特に録音する場合).
// 防御的に録音・再生開始前に常にチェック・設定
// 最低でもカテゴリぐらいは確認する
let session = AVAudioSession.sharedInstance()
if session.category != expectedCategory || session.mode != expectedMode || session.options != expectedOptions || session.routeSharingPolicy != expectedRouteSharingPolicy {
try set audio session category, mode, options, route sharing
}
// カテゴリ等が設定できていれば録音・再生開始
// (i.e. try recorder.record() or try player.play())
2. オーディオセッションの有効化/無効化
オーディオセッションを有効化すると, カテゴリなどで設定した動作が反映され, 音声を利用できる「アクティブ」な状態(アクティブ状態)になる. ミックスしない設定の場合, たとえすぐに音声を利用しなくてもバックグラウンドの音声は停止する. ドキュメントでは, 音声を利用する直前に有効化することが推奨されている. 音声が利用されている間、オーディオセッションはアクティブ状態にあり, 非アクティブ状態では音声を利用できない.
明示的にオーディオセッションの有効化(activation)/無効化(deactivation)はほぼ不要である.
Audio UnitやAudio Queue Servicesを利用する場合は明示的に有効化する必要があるが(C APIなので当然),
AVFoundation(AVFAudio)のクラスを利用する場合は明示的にオーディオセッションの有効化は不要.
有効化についてAudio Session Programming Guideには以下のように記載されている.
Although the AVFoundation playback and recording classes automatically activate your audio session, manually activating it gives you an opportunity to test whether activation succeeded.
翻訳すると
AVFoundationの再生・録音クラスはオーディオセッションを自動的に有効化しますが, 手動で有効化することで, 有効化が成功したかどうかをテストする機会が得られます.
AVFoundationの再生・録音クラスはオーディオセッションを自動的に有効化するとあるので, AVFoundation(AVFAudio)内のクラスを利用している場合は明示的にオーディオセッションを有効化する必要はない. (ここでいう有効化が成功したかどうかをテストするようなユースケースがあるのだろうか, 目的が不明である?)
AVAudioEngine.prepare/startのソース上のコメントにも"暗黙的にオーディオセッションを有効化する"とある(オンラインドキュメント上にはないが).
this method may cause the audio session to be implicitly activated. Activating the audio session (implicitly or explicitly) may cause other audio sessions to be interrupted or ducked depending on the session's configuration. It is recommended to configure and activate the app's audio session before preparing the engine.
See Audio Session Programming Guide for details.
AVAudioPlayer等のAVFoundation(AVFAudio)のクラスを利用する場合:
// カテゴリなどの設定をする
let player = try AVAudioPlayer(...) // AVAudioPlayerの初期化
// オーディオセッションの有効化はAVAudioPlayerがする
let success = player.play()
if !success {
// オーディオセッションの有効化ができなかった場合もこのエラーになるはず (但しAVAudioPlayerからエラー内容は取得できない)
}
オーディオセッションを明示的に有効化/無効化する場合は以下のようにする.
try AVAudioSession.sharedInstance().setActive(true or false)
オーディオセッションが不要になったら無効化しても良いが, 基本的にオーディオセッションは利用側がセッションを奪取するので, 無効化する必要もないと思われる.
無効化することが有益なユースケースとしてはダッキングしている音声を元に戻す場合がある.
3. システムのオーディオイベントに応答する
オーディオイベントに応答する場合, 主要イベントは以下の2つである:
-
AVAudioSession.Interruption(割込み) -
AVAudioSession.mediaServicesWereResetNotification(メディアサービスのリセット)
※オーディオセッションがアクティブ時のみ通知される(逆に通知がくるということはオーディオセッションがアクティブといえる)
※AVPlayerのような高レイヤークラスはAVPlayer.timeControlStatus, AVPlayer.rateを監視するだけで十分な場合があるので, 実装する必要があるかドキュメントなど要確認
AVAudioSession.Interruption
他のアプリがオーディオセッションを有効化/無効化すると通知される.
実装方法はドキュメント通りに通知AVAudioSession.interruptionNotificationに登録し, 割込み開始/終了時にUIの更新・状態の保存/復元をする.
// 適当なクラスで通知を監視
NotificationCenter.default.addObserver(
forName: AVAudioSession.interruptionNotification,
object: nil,
queue: .main,
using: { [weak self] notification in
// self は MainActor を仮定
MainActor.assumeIsolated {
self?.handleInterruption(notification)
}
})
// ハンドラー
func handleInterruption(_ notification: Notification) {
guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
return
}
switch type {
// 割込み開始時
case .began:
// UIの更新(停止状態にする)・状態の保存する
// ※この時オーディオセッションはシステムによって無効化されているので, 手動で有効化する必要がある場合は注意(他のアプリがオーディオセッションを有効化したので, このアプリのオーディオセッションは無効化される)
// 割込み終了時
// ※終了が必ず通知されるとは限らないので注意
case .ended:
// 再開できるとき .shouldResume フラグが立っている
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
// UIの更新・状態の復元する
}
@unknown default:
break
}
}
出力が変更された場合(ヘッドホンが抜かれたときなど), iOS16以前はAVAudioSession.RouteChangeでハンドリングしていたが, iOS17以降はNow Playing session[5]の場合, Interruptionに通知される. AVAudioSession.sharedInstance().setPrefersInterruptionOnRouteDisconnect(true)を設定すれば, Now Playing session以外でもInterruptionに通知される.
AVAudioSession.mediaServicesWereResetNotification
メディアサービス(システムレベルのaudio/videoサービス)がリセットされたときに通知される.
ドキュメントには稀に起こるとあるが, 起こるので実装は必須だろう.
メディアサービスリセットの監視はAVAudioSession.mediaServicesWereResetNotificationに登録する.
// 適当なクラスで通知を監視
NotificationCenter.default.addObserver(
forName: AVAudioSession.mediaServicesWereResetNotification,
object: nil,
queue: .main,
using: { [weak self] notification in
// self は MainActor を仮定
MainActor.assumeIsolated {
self?.handleMediaServicesWereReset(notification)
}
})
// ハンドラー
func handleMediaServicesWereReset(_ notification: Notification) {
// audio/videoオブジェクトのリセット(再初期化)
// Audio sessionカテゴリなどの再設定
}
AVAudioSession.RouteChange (iOS16以前など)
ルート変更は, ヘッドホンの抜き差しなど, 音声入力/出力ルートが変更されたときに通知される. カテゴリの変更でplaybackからplaybackAndRecordに変更されて入力が追加, 逆に削除されたときなどでも通知される.
監視するにはAVAudioSession.routeChangeNotificationに登録する.
iOS17以降でAudioSession.setPrefersInterruptionOnRouteDisconnect(true)を設定しない場合,
ヘッドホンなどが抜かれたときに再生を停止する場合は実装する必要があるが, Interruptionで実装するほうが都合が良い.
iOS17以降はこの通知のユースケース自体あまりない(現在の入力/出力ルートを表示するなど).
ヘッドホンなどが抜かれたとき再生を停止する実装は以下のようにする.
// 適当なクラスで通知を監視
NotificationCenter.default.addObserver(
forName: AVAudioSession.routeChangeNotification,
object: nil,
queue: .main,
using: { [weak self] notification in
// self は MainActor を仮定
MainActor.assumeIsolated {
self?.handleRouteChange(notification)
}
})
// ハンドラー
func handleRouteChange(_ notification: Notification) {
guard let userInfo = notification.userInfo,
let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
return
}
switch reason {
case .oldDeviceUnavailable:
// 再生中であれば停止・UIの更新(停止状態にする)・状態の保存を実装する
default:
break
}
}
4. 録音許可のリクエスト
録音する場合は許可リクエストを実装する必要がある. 具体的には以下の2点を実装する.
- plistにMicrophone Usage Description (NSMicrophoneUsageDescription) を記載
- マイク利用許可のリクエストをしていない場合はリクエストする
iOS17以降の録音許可リクエストの実装
let permission = AVAudioApplication.shared.recordPermission
if permission == .undetermined {
Task {
let granted = await AVAudioApplication.requestRecordPermission()
if granted {
// microphone is available, false otherwise
}
}
}
iOS16以前の録音許可リクエストの実装
let permission = AVAudioSession.sharedInstance().recordPermission
if permission == .undetermined {
AVAudioSession.sharedInstance().requestRecordPermission { granted in
if granted {
// microphone is available, false otherwise
}
}
}
その他注意事項
- シミュレータではエラーにならなかったり, 動作に違いがでるので実機で確認すること
- カテゴリの設定などのスレッド安全性についてはドキュメントには記載されていないので, メインスレッドで実行するほうが安全だろう
- 音がでないと思ったら, まずはミュートになっていないか, 消音スイッチがオンになっていないか確認すること
まとめ
AVAudioSessionは, iOSをはじめとするAppleプラットフォーム(macOS以外)における音声の振る舞いを統括する中核的コンポーネントである. 設定や実装のパターン自体は多くないが, AVFoundation(AVFAudio)のクラスを利用する場合には暗黙的に処理される部分も多い.
必要に応じてドキュメントやソース上のコメントを参照し, 挙動を確認しておくとよい.
References
- Audio Session Programming Guide
- AVAudioSession
- AVAudioSession.ErrorCode
- Configuring your app for media playback
- Technical Q&A QA1749 AVAudioSession - General recommendations for handling AVAudioSessionMediaServicesWereResetNotification
- Technical Q&A QA1631 AVAudioSession - Requesting Audio Session Preferences
Discussion