mcpField
A
axiom-avfoundation-ref

axiom-avfoundation-ref

github.com/charleswiltgen開発1.2k221 回インストール今日更新
リポジトリ

axiom-avfoundation-ref について

Appleプラットフォームのオーディオ開発者向けAVFoundationリファレンススキルです。オーディオセッション、エンジン、ルーティング、フォーマット処理に加え、空間オーディオも解説し、再生や録音、音声処理の実装に役立ちます。

主な機能

  1. 01AVAudioSessionのカテゴリと割り込み処理
  2. 02AVAudioEngineのノードと音声解析
  3. 03フォーマット変換とUSB DACルーティング
  4. 04空間オーディオの録音と再生
  5. 05一般的な実装パターンと問題解決

活用シーン

  1. 01バックグラウンド音声再生の設定
  2. 02マイク録音と入力音声の解析
  3. 03Bluetooth機器と音声ルートの処理
  4. 04空間オーディオの録音やミキシング

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-avfoundation-ref

インストール後、Claude Code で/axiom-avfoundation-refを入力して読み込まれたか確認します。

詳細

AVFoundation オーディオリファレンス

クイックリファレンス

// AUDIO SESSION SETUP
import AVFoundation

try AVAudioSession.sharedInstance().setCategory(
    .playback,                              // or .playAndRecord, .ambient
    mode: .default,                         // or .voiceChat, .measurement
    options: [.mixWithOthers, .allowBluetooth]
)
try AVAudioSession.sharedInstance().setActive(true)

// AUDIO ENGINE PIPELINE
let engine = AVAudioEngine()
let player = AVAudioPlayerNode()
engine.attach(player)
engine.connect(player, to: engine.mainMixerNode, format: nil)
try engine.start()
player.scheduleFile(audioFile, at: nil)
player.play()

// INPUT PICKER (iOS 26+)
import AVKit
let picker = AVInputPickerInteraction()
picker.delegate = self
myButton.addInteraction(picker)
// In button action: picker.present()

// AIRPODS HIGH QUALITY (iOS 26+)
try AVAudioSession.sharedInstance().setCategory(
    .playAndRecord,
    options: [.bluetoothHighQualityRecording, .allowBluetoothA2DP]
)

AVAudioSession

カテゴリ
カテゴリ用途サイレントスイッチバックグラウンド
.ambientゲームサウンド、主音声ではない場合消音されますいいえ
.soloAmbientデフォルト、ほかのオーディオを中断します消音されますいいえ
.playback音楽プレーヤー、ポッドキャスト無視しますはい
.recordボイスレコーダー—はい
.playAndRecordVoIP、ボイスチャット無視しますはい
.multiRouteDJ アプリ、複数の出力無視しますはい
モード
モード用途
.default一般的なオーディオ
.voiceChatVoIP、エコーを低減
.videoChatFaceTime スタイル
.gameChatゲーム内のボイスチャット
.videoRecordingカメラ録画
.measurementフラットな応答、処理なし
.moviePlaybackビデオ再生
.spokenAudioポッドキャスト、オーディオブック
オプション
// Mixing
.mixWithOthers          // Play with other apps
.duckOthers             // Lower other audio while playing
.interruptSpokenAudioAndMixWithOthers  // Pause podcasts, mix music

// Bluetooth
.allowBluetooth         // HFP (calls)
.allowBluetoothA2DP     // High quality stereo
.bluetoothHighQualityRecording  // iOS 26+ AirPods recording

// Routing
.defaultToSpeaker       // Route to speaker (not receiver)
.allowAirPlay           // Enable AirPlay
割り込みの処理
NotificationCenter.default.addObserver(
    forName: AVAudioSession.interruptionNotification,
    object: nil,
    queue: .main
) { notification in
    guard let userInfo = notification.userInfo,
          let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
          let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
        return
    }

    switch type {
    case .began:
        // Pause playback
        player.pause()

    case .ended:
        guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
        let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
        if options.contains(.shouldResume) {
            player.play()
        }

    @unknown default:
        break
    }
}
ルート変更の処理
NotificationCenter.default.addObserver(
    forName: AVAudioSession.routeChangeNotification,
    object: nil,
    queue: .main
) { notification in
    guard let userInfo = notification.userInfo,
          let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
          let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
        return
    }

    switch reason {
    case .oldDeviceUnavailable:
        // Headphones unplugged — pause playback
        player.pause()

    case .newDeviceAvailable:
        // New device connected
        break

    case .categoryChange:
        // Category changed by system or another app
        break

    default:
        break
    }
}

AVAudioEngine

基本的なパイプライン
let engine = AVAudioEngine()

// Create nodes
let player = AVAudioPlayerNode()
let reverb = AVAudioUnitReverb()
reverb.loadFactoryPreset(.largeHall)
reverb.wetDryMix = 50

// Attach to engine
engine.attach(player)
engine.attach(reverb)

// Connect: player → reverb → mixer → output
engine.connect(player, to: reverb, format: nil)
engine.connect(reverb, to: engine.mainMixerNode, format: nil)

// Start
engine.prepare()
try engine.start()

// Play file
let url = Bundle.main.url(forResource: "audio", withExtension: "m4a")!
let file = try AVAudioFile(forReading: url)
player.scheduleFile(file, at: nil)
player.play()
ノードの種類
ノード目的
AVAudioPlayerNodeオーディオファイル/バッファを再生します
AVAudioInputNodeマイク入力(engine.inputNode)
AVAudioOutputNodeスピーカー出力(engine.outputNode)
AVAudioMixerNode複数の入力をミックスします
AVAudioUnitEQイコライザー
AVAudioUnitReverbリバーブエフェクト
AVAudioUnitDelayディレイエフェクト
AVAudioUnitDistortionディストーションエフェクト
AVAudioUnitTimePitchタイムストレッチ/ピッチシフト
タップのインストール(オーディオ分析)
let inputNode = engine.inputNode
let format = inputNode.outputFormat(forBus: 0)

inputNode.installTap(onBus: 0, bufferSize: 1024, format: format) { buffer, time in
    // Process audio buffer
    guard let channelData = buffer.floatChannelData?[0] else { return }
    let frameLength = Int(buffer.frameLength)

    // Calculate RMS level
    var sum: Float = 0
    for i in 0..<frameLength {
        sum += channelData[i] * channelData[i]
    }
    let rms = sqrt(sum / Float(frameLength))
    let dB = 20 * log10(rms)

    DispatchQueue.main.async {
        self.levelMeter = dB
    }
}

// Don't forget to remove when done
inputNode.removeTap(onBus: 0)
フォーマット変換
// AVAudioEngine mic input is always 44.1kHz/32-bit float
// Use AVAudioConverter for other formats

let inputFormat = engine.inputNode.outputFormat(forBus: 0)
let outputFormat = AVAudioFormat(
    commonFormat: .pcmFormatInt16,
    sampleRate: 48000,
    channels: 1,
    interleaved: false
)!

let converter = AVAudioConverter(from: inputFormat, to: outputFormat)!

// In tap callback:
let outputBuffer = AVAudioPCMBuffer(
    pcmFormat: outputFormat,
    frameCapacity: AVAudioFrameCount(outputFormat.sampleRate * 0.1)
)!

var error: NSError?
converter.convert(to: outputBuffer, error: &error) { inNumPackets, outStatus in
    outStatus.pointee = .haveData
    return inputBuffer
}

ビットパーフェクトオーディオ/DAC 出力

iOS の動作

iOS は USB DAC に対して、デフォルトでビットパーフェクト出力を提供します。リサンプリングは行われず、DAC にはソースのサンプルレートがそのまま送られます。

// iOS automatically matches source sample rate to DAC
// No special configuration needed for bit-perfect output

let player = AVAudioPlayerNode()
// File at 96kHz → DAC receives 96kHz
リサンプリングの回避
// Check hardware sample rate
let hardwareSampleRate = AVAudioSession.sharedInstance().sampleRate

// Match your audio format to hardware when possible
let format = AVAudioFormat(
    standardFormatWithSampleRate: hardwareSampleRate,
    channels: 2
)
USB DAC へのルーティング
// List available outputs
let currentRoute = AVAudioSession.sharedInstance().currentRoute
for output in currentRoute.outputs {
    print("Output: \(output.portName), Type: \(output.portType)")
    // USB DAC shows as .usbAudio
}

// Prefer USB output
try AVAudioSession.sharedInstance().setPreferredInput(usbPort)
サンプルレートに関する考慮事項
ソースiOS の動作備考
44.1 kHzパススルーCD 音質
48 kHzパススルービデオ標準
96 kHzパススルーハイレゾ
192 kHzパススルーハイレゾ
DSD非対応DoP を使用するか変換してください

iOS 26 以降の入力選択

AVInputPickerInteraction

ライブレベルメーターを備えたネイティブの入力デバイス選択:

import AVKit

class RecordingViewController: UIViewController {
    let inputPicker = AVInputPickerInteraction()

    override func viewDidLoad() {
        super.viewDidLoad()

        // Configure audio session first
        try? AVAudioSession.sharedInstance().setCategory(.playAndRecord)
        try? AVAudioSession.sharedInstance().setActive(true)

        // Setup picker
        inputPicker.delegate = self
        selectMicButton.addInteraction(inputPicker)
    }

    @IBAction func selectMicTapped(_ sender: UIButton) {
        inputPicker.present()
    }
}

extension RecordingViewController: AVInputPickerInteractionDelegate {
    // Implement delegate methods as needed
}

機能:

  • サウンドレベルのライブメーター
  • マイクモードの選択
  • システムがアプリごとに選択内容を記憶

iOS 26 以降の AirPods 高音質録音

コンテンツクリエイター向けの LAV-microphone 相当の音質:

// AVAudioSession approach
try AVAudioSession.sharedInstance().setCategory(
    .playAndRecord,
    options: [
        .bluetoothHighQualityRecording,  // New in iOS 26
        .allowBluetoothA2DP              // Fallback
    ]
)

// AVCaptureSession approach
let captureSession = AVCaptureSession()
captureSession.configuresApplicationAudioSessionForBluetoothHighQualityRecording = true

注意事項:

  • AirPods に最適化された専用 Bluetooth リンクを使用します
  • デバイスが HQ モードに対応していない場合は HFP にフォールバックします
  • 録音の開始/停止に対応する AirPods のステムコントロールをサポートします

空間オーディオのキャプチャ(iOS 26 以降)

First Order Ambisonics(FOA)

デバイスのマイクアレイを使用して 3D 空間オーディオを録音します:

// With AVCaptureMovieFileOutput (simple)
let audioInput = AVCaptureDeviceInput(device: audioDevice)
audioInput.multichannelAudioMode = .firstOrderAmbisonics

// With AVAssetWriter (full control)
// Requires two AudioDataOutputs: FOA (4ch) + Stereo (2ch)
AVAssetWriter の空間オーディオ設定
// Configure two AudioDataOutputs
let foaOutput = AVCaptureAudioDataOutput()
foaOutput.spatialAudioChannelLayoutTag = kAudioChannelLayoutTag_HOA_ACN_SN3D  // 4 channels

let stereoOutput = AVCaptureAudioDataOutput()
stereoOutput.spatialAudioChannelLayoutTag = kAudioChannelLayoutTag_Stereo    // 2 channels

// Create metadata generator
let metadataGenerator = AVCaptureSpatialAudioMetadataSampleGenerator()

// Feed FOA buffers to generator
func captureOutput(_ output: AVCaptureOutput,
                   didOutput sampleBuffer: CMSampleBuffer,
                   from connection: AVCaptureConnection) {
    metadataGenerator.append(sampleBuffer)
    // Also write to FOA AssetWriterInput
}

// When recording stops, get metadata sample
let metadataSample = metadataGenerator.createMetadataSample()
// Write to metadata track
出力ファイルの構造

空間オーディオファイルには次の内容が含まれます:

  1. ステレオ AAC トラック — 互換性のためのフォールバック
  2. APAC トラック — 空間オーディオ(FOA)
  3. メタデータトラック — Audio Mix のチューニングパラメーター

ファイル形式:.mov、.mp4、.qta(QuickTime Audio、iOS 26 以降)


ASAF/APAC(Apple Spatial Audio)

概要
コンポーネント目的
ASAFApple Spatial Audio Format — 制作形式
APACApple Positional Audio Codec — 配信コーデック
APAC の機能
  • ビットレート:64 kbps~768 kbps
  • 対応対象:チャンネル、オブジェクト、Higher Order Ambisonics、ダイアログ、バイノーラル
  • リスナーの位置/向きに適応するヘッドトラッキングレンダリング
  • Apple Immersive Video で必須
再生
// Standard AVPlayer handles APAC automatically
let player = AVPlayer(url: spatialAudioURL)
player.play()

// Head tracking enabled automatically on AirPods
プラットフォームのサポート

watchOS を除くすべての Apple プラットフォームで APAC の再生に対応しています。


Audio Mix(Cinematic Framework)

空間録音の音声と環境音を分離して再ミックスします:

AVPlayer との統合
import Cinematic

// Load spatial audio asset
let asset = AVURLAsset(url: spatialAudioURL)
let audioInfo = try await CNAssetSpatialAudioInfo(asset: asset)

// Configure mix parameters
let intensity: Float = 0.5  // 0.0 to 1.0
let style = CNSpatialAudioRenderingStyle.cinematic

// Create and apply audio mix
let audioMix = audioInfo.audioMix(
    effectIntensity: intensity,
    renderingStyle: style
)
playerItem.audioMix = audioMix
レンダリングスタイル
スタイル効果
.cinematic音声と環境音のバランス調整
.studio音声の明瞭さを向上
.inFrame画面内に表示されている話者にフォーカス
+ 6 種類の抽出モード音声のみ、環境音のみのステム
AUAudioMix(直接の AudioUnit)

AVPlayer を使用しないアプリ向け:

// Input: 4 channels FOA
// Output: Separated speech + ambient

// Get tuning metadata from file
let audioInfo = try await CNAssetSpatialAudioInfo(asset: asset)
let remixMetadata = audioInfo.spatialAudioMixMetadata as CFData

// Apply to AudioUnit via AudioUnitSetProperty

よくあるパターン

バックグラウンドでの音声再生
// 1. Set category
try AVAudioSession.sharedInstance().setCategory(.playback)

// 2. Enable background mode in Info.plist
// <key>UIBackgroundModes</key>
// <array><string>audio</string></array>

// 3. Set Now Playing info (recommended)
let nowPlayingInfo: [String: Any] = [
    MPMediaItemPropertyTitle: "Song Title",
    MPMediaItemPropertyArtist: "Artist",
    MPNowPlayingInfoPropertyElapsedPlaybackTime: player.currentTime,
    MPMediaItemPropertyPlaybackDuration: duration
]
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
他の音声のダッキング
try AVAudioSession.sharedInstance().setCategory(
    .playback,
    options: .duckOthers
)

// When done, restore others
try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
Bluetooth デバイスの処理
// Allow all Bluetooth
try AVAudioSession.sharedInstance().setCategory(
    .playAndRecord,
    options: [.allowBluetooth, .allowBluetoothA2DP]
)

// Check current Bluetooth route
let route = AVAudioSession.sharedInstance().currentRoute
let hasBluetoothOutput = route.outputs.contains {
    $0.portType == .bluetoothA2DP || $0.portType == .bluetoothHFP
}

アンチパターン

カテゴリの誤り
// WRONG — music player using ambient (silenced by switch)
try AVAudioSession.sharedInstance().setCategory(.ambient)

// CORRECT — music needs .playback
try AVAudioSession.sharedInstance().setCategory(.playback)
割り込み処理の不足
// WRONG — no interruption observer
// Audio stops on phone call and never resumes

// CORRECT — always handle interruptions
NotificationCenter.default.addObserver(
    forName: AVAudioSession.interruptionNotification,
    // ... handle began/ended
)
タップによるメモリーリーク
// WRONG — tap installed, never removed
engine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: format) { ... }

// CORRECT — remove tap when done
deinit {
    engine.inputNode.removeTap(onBus: 0)
}
フォーマットの不一致によるクラッシュ
// WRONG — connecting nodes with incompatible formats
engine.connect(playerNode, to: mixerNode, format: wrongFormat)  // Crash!

// CORRECT — use nil for automatic format negotiation, or match exactly
engine.connect(playerNode, to: mixerNode, format: nil)
セッションの有効化忘れ
// WRONG — configure but don't activate
try AVAudioSession.sharedInstance().setCategory(.playback)
// Audio doesn't work!

// CORRECT — always activate
try AVAudioSession.sharedInstance().setCategory(.playback)
try AVAudioSession.sharedInstance().setActive(true)

リソース

WWDC:2025-251、2025-403、2019-510

ドキュメント:/avfoundation、/avkit、/cinematic


対象: iOS 12 以降(コア機能)、iOS 26 以降(空間機能) フレームワーク: AVFoundation、AVKit、Cinematic(iOS 26 以降) 履歴: 変更内容は git log を参照

コメント