关于 axiom-avfoundation-ref
面向 Apple 平台音频开发者的 AVFoundation 参考技能,汇总音频会话、引擎、路由与格式处理,并涵盖空间音频等新能力。适用于实现播放、录音及音频处理功能。
主要功能
- 01AVAudioSession 类别与中断处理
- 02AVAudioEngine 节点和音频分析
- 03格式转换与 USB DAC 路由
- 04空间音频录制和播放
- 05常见实现模式与错误排查
适用场景
- 01配置应用的后台音频播放
- 02实现麦克风录音和输入分析
- 03处理蓝牙设备及音频路由
- 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 | 语音录音机 | — | 是 |
.playAndRecord | VoIP、语音聊天 | 忽略 | 是 |
.multiRoute | DJ 应用、多个输出 | 忽略 | 是 |
模式
| 模式 | 用例 |
|---|---|
.default | 通用音频 |
.voiceChat | VoIP,降低回声 |
.videoChat | 类 FaceTime |
.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 | 时间伸缩/移调 |
安装 Tap(音频分析)
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 麦克风相当的音质:
// AVAudioSession approach
try AVAudioSession.sharedInstance().setCategory(
.playAndRecord,
options: [
.bluetoothHighQualityRecording, // New in iOS 26
.allowBluetoothA2DP // Fallback
]
)
// AVCaptureSession approach
let captureSession = AVCaptureSession()
captureSession.configuresApplicationAudioSessionForBluetoothHighQualityRecording = true
备注:
- 使用专为 AirPods 优化的专用蓝牙链路
- 如果设备不支持 HQ 模式,则回退到 HFP
- 支持 AirPods 杆柄控制,用于开始/停止录音
空间音频采集(iOS 26+)
一阶 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
输出文件结构
空间音频文件包含:
- 立体声 AAC 轨道 — 兼容性回退
- APAC 轨道 — 空间音频(FOA)
- 元数据轨道 — Audio Mix 调节参数
文件格式:.mov、.mp4、.qta(QuickTime Audio,iOS 26+)
ASAF / APAC(Apple Spatial Audio)
概述
| 组件 | 用途 |
|---|---|
| ASAF | Apple Spatial Audio Format — 制作格式 |
| APAC | Apple Positional Audio Codec — 交付编解码器 |
APAC 能力
- 比特率:64 kbps 至 768 kbps
- 支持:声道、对象、高阶 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)
蓝牙设备处理
// 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 了解更改
评论
支持 Markdown