【SwiftUI提高练中学】第16课 音频与视频:AVFoundation 进阶与底层机制

发布时间:2026/10/9 12:09:46
【SwiftUI提高练中学】第16课 音频与视频:AVFoundation 进阶与底层机制
主要目标系统掌握 AVFoundation 进阶能力深入理解 AVAudioEngine 的节点图架构、AVAudioSession 的深度管理、AVAudioConverter 的格式转换机制、以及 AVMutableComposition 的视频合成与导出流程。第一部分 深度精讲一、AVAudioEngine音频节点图架构1.1 AVAudioEngine 的核心定位AVAudioEngine是 AVFoundation 中用于实时和离线音频处理的核心类。它管理一个音频节点图audio node graph支持播放、录制、混音、效果处理和 3D 空间化。与AVAudioPlayer的简单播放不同AVAudioEngine提供可编程的音频路由能力开发者可以创建节点、连接节点、在节点之间插入效果器构建任意复杂的音频处理管线。1.2 三种核心节点类型AVAudioEngine 的节点图由三种核心节点类型构成节点类型代表类职责输出节点outputNode将音频发送到硬件输出扬声器混音器节点mainMixerNode、AVAudioMixerNode混合多路音频信号支持音量控制播放器节点AVAudioPlayerNode播放音频文件或缓冲区此外还有输入节点inputNode麦克风输入和效果节点AVAudioUnitEffect的子类如混响、延迟、失真。1.3 节点图的连接规则节点之间的连接是 AVAudioEngine 的核心操作。connect(_:to:format:)方法建立从源节点输出总线到目标节点输入总线的连接importAVFoundationletengineAVAudioEngine()letplayerAVAudioPlayerNode()letreverbAVAudioUnitReverb()engine.attach(player)engine.attach(reverb)// 连接player → reverb → mainMixerNode → outputNodeengine.connect(player,to:reverb,format:nil)engine.connect(reverb,to:engine.mainMixerNode,format:nil)关键细节mainMixerNode是引擎自动创建的无需手动 attach。当向mainMixerNode发起连接时引擎会自动配置outputNode的连接。format参数设为nil表示使用默认格式。1.4 多通道处理AVAudioEngine 支持多通道音频处理。对于多通道输入如 4 通道 USB 音频接口可以通过AVAudioConnectionPoint将一个多通道源连接到多个目标// 将 4 通道输入分发到 4 条独立的效果链letinputNodeengine.inputNodeletinputFormatinputNode.outputFormat(forBus:0)varconnectionPoints:[AVAudioConnectionPoint][]foriin0..4{letmixerAVAudioMixerNode()engine.attach(mixer)connectionPoints.append(AVAudioConnectionPoint(node:mixer,bus:0))}engine.connect(inputNode,to:connectionPoints,fromBus:0,format:inputFormat)需要注意AVAudioMixerNode的channelMap属性无法直接实现通道分离——混音器的channelMap仅能重映射声道不能将多通道总线拆分为多个单通道总线。真正的通道分离需要 Matrix Mixer 音频单元。1.5 Swift Concurrency 下的音频引擎管理在 Swift 6 中AVAudioEngine的节点操作需要在正确的 Actor 上执行。推荐使用MainActor隔离管理 UI 交互将音频处理保持在引擎的实时渲染线程上MainActorfinalclassAudioEngineManager{privateletengineAVAudioEngine()privateletplayerAVAudioPlayerNode()funcstart()throws{engine.attach(player)engine.connect(player,to:engine.mainMixerNode,format:nil)tryengine.start()}funcplay(buffer:AVAudioPCMBuffer){player.scheduleBuffer(buffer)player.play()}}重要提醒当引擎正在运行时动态 attach/detach 节点可能导致爆音或崩溃。应在引擎停止状态下修改节点图修改完成后再重新启动。二、AVAudioSession深度配置与管理2.1 音频会话的职责边界AVAudioSession是 App 与系统音频硬件之间的中介。它负责描述 App 的音频行为类别和模式、管理音频路由变化、处理音频中断、协调多 App 的音频竞争。2.2 类别与模式的完整选择类别静音拨片后台播放混合录音.playback忽略支持否否.ambient遵循否是否.record—否否是.playAndRecord遵循是可选是.multiRoute忽略否否可选模式进一步细化行为.moviePlayback、.spokenAudio、.voiceChat、.measurement、.gameChat。2.3 路由变化处理Apple 官方文档明确指出“应用程序应尊重此隐含的隐私请求并在用户断开耳机时自动暂停播放”。路由变化通知包含原因信息.newDeviceAvailable新设备可用如插入耳机.oldDeviceUnavailable旧设备不可用如拔出耳机funcobserveRouteChanges(){NotificationCenter.default.addObserver(forName:AVAudioSession.routeChangeNotification,object:AVAudioSession.sharedInstance(),queue:.main){notificationinguardletuserInfonotification.userInfo,letreasonValueuserInfo[AVAudioSessionRouteChangeReasonKey]as?UInt,letreasonAVAudioSession.RouteChangeReason(rawValue:reasonValue)else{return}ifreason.oldDeviceUnavailable{// 耳机被拔出——暂停播放}}}2.4 prefersInterruptionOnRouteDisconnectiOS 17从 iOS 17 开始AVAudioSession新增了prefersInterruptionOnRouteDisconnect属性。该属性默认为true表示当活动路由断开时系统会中断音频会话。这意味着 App 无需手动监听路由变化来暂停播放——系统会自动中断会话App 只需处理中断通知即可。三、AVAudioConverter音频格式转换3.1 AVAudioConverter 的核心能力AVAudioConverter用于在音频格式和编码之间转换。支持的转换类型包括PCM 浮点/整数/位深转换、PCM 采样率转换、PCM 交错/去交错、PCM 编码为压缩格式、压缩格式解码为 PCM。单个转换器实例可以同时执行多种转换。3.2 转换的基本流程importAVFoundationfuncconvertAudio(inputURL:URL,outputFormat:AVAudioFormat)throws{letinputFiletryAVAudioFile(forReading:inputURL)letinputFormatinputFile.processingFormatguardletconverterAVAudioConverter(from:inputFormat,to:outputFormat)else{throwConversionError.cannotCreateConverter}letoutputBufferAVAudioPCMBuffer(pcmFormat:outputFormat,frameCapacity:AVAudioFrameCount(outputFormat.sampleRate*1.0))!varinputBuffer:AVAudioPCMBuffer?AVAudioPCMBuffer(pcmFormat:inputFormat,frameCapacity:AVAudioFrameCount(inputFormat.sampleRate*1.0))// 使用 convert(to:error:withInputFrom:) 进行流式转换varerror:NSError?letstatusconverter.convert(to:outputBuffer,error:error){_,outStatusinifletbufferinputBuffer{inputBufferniloutStatus.pointee.haveDatareturnbuffer}else{outStatus.pointee.endOfStreamreturnnil}}// 处理 status 和 error}关键细节convert(to:error:withInputFrom:)方法通过 input block 按需拉取输入数据适合流式转换。AVAudioConverterInputStatus和AVAudioConverterOutputStatus枚举定义了转换状态。3.3 通道映射与混音AVAudioConverter提供channelMap属性用于指定输出通道与输入通道的映射关系。当需要将立体声降为单声道时设置downmix true让框架混合通道而非简单丢弃。3.4 采样率转换质量sampleRateConverterQuality属性控制采样率转换的算法质量。对于实时场景如音频播放较低的 quality 值可以减少 CPU 占用对于离线转换可以使用最高质量。四、AVMutableComposition视频合成与导出4.1 AVMutableComposition 的核心能力AVMutableComposition用于从已有资源创建新的合成媒体。它支持添加和移除轨道、添加和移除时间范围、缩放时间范围。主要操作包括方法用途insertTimeRange(_:of:at:)将资源的时间范围插入合成removeTimeRange(_:)移除指定时间范围scaleTimeRange(_:toDuration:)缩放时间范围调速insertEmptyTimeRange(_:)插入空白时间范围removeTrack(_:)移除轨道4.2 视频合成的基本流程funcmergeVideoAndAudio(videoURL:URL,audioURL:URL)asyncthrows-AVAsset{letcompositionAVMutableComposition()// 加载资源letvideoAssetAVURLAsset(url:videoURL)letaudioAssetAVURLAsset(url:audioURL)// 添加视频轨道guardletvideoTracktryawaitvideoAsset.loadTracks(withMediaType:.video).firstelse{throwCompositionError.noVideoTrack}letcompositionVideoTrackcomposition.addMutableTrack(withMediaType:.video,preferredTrackID:kCMPersistentTrackID_Invalid)trycompositionVideoTrack?.insertTimeRange(CMTimeRange(start:.zero,duration:videoAsset.duration),of:videoTrack,at:.zero)// 添加音频轨道guardletaudioTracktryawaitaudioAsset.loadTracks(withMediaType:.audio).firstelse{throwCompositionError.noAudioTrack}letcompositionAudioTrackcomposition.addMutableTrack(withMediaType:.audio,preferredTrackID:kCMPersistentTrackID_Invalid)trycompositionAudioTrack?.insertTimeRange(CMTimeRange(start:.zero,duration:audioAsset.duration),of:audioTrack,at:.zero)returncomposition}4.3 AVAssetExportSession导出合成结果AVAssetExportSession将合成后的资源导出为指定格式。推荐使用新的异步导出方法export(to:as:)iOS 13funcexportComposition(_composition:AVAsset,to outputURL:URL)asyncthrows{guardletexportSessionAVAssetExportSession(asset:composition,presetName:AVAssetExportPresetHighestQuality)else{throwExportError.cannotCreateSession}// 使用新的异步导出 APItryawaitexportSession.export(to:outputURL,as:.mp4)}关键细节export(to:as:)方法会抛出错误如果导出被取消或指定的文件类型不在supportedFileTypes中。可以通过states(updateInterval:)方法监控导出进度。4.4 导出预设的选择预设适用场景AVAssetExportPresetLowQuality低质量预览AVAssetExportPresetMediumQuality中等质量AVAssetExportPresetHighestQuality最高质量AVAssetExportPresetHEVCHighestQualityHEVC 编码最高质量五、本课小结AVAudioEngine节点图架构支持任意复杂的音频处理管线。三种核心节点类型输出节点、混音器节点、播放器节点通过connect(_:to:format:)连接。多通道处理需通过AVAudioConnectionPoint分发。AVAudioSession类别和模式决定 App 的音频行为。路由变化需观察routeChangeNotificationoldDeviceUnavailable时应暂停播放。iOS 17 的prefersInterruptionOnRouteDisconnect简化了路由断开处理。AVAudioConverter支持 PCM 位深、采样率、交错/去交错、编码/解码转换。convert(to:error:withInputFrom:)提供流式转换能力channelMap和downmix控制通道映射。AVMutableComposition AVAssetExportSession视频合成通过insertTimeRange(_:of:at:)插入轨道片段scaleTimeRange(_:toDuration:)调速。导出使用export(to:as:)异步方法。第二部分 习题与答案解读每题后紧跟答案和解读共15题。习题1【单选】AVAudioEngine 的三种核心节点类型以下哪项不是 AVAudioEngine 的核心节点类型A. 输出节点outputNodeB. 混音器节点AVAudioMixerNodeC. 播放器节点AVAudioPlayerNodeD. 编解码节点AVAudioCodecNode答案D解读AVAudioEngine 的三种核心节点类型是输出节点、混音器节点和播放器节点。AVAudioCodecNode不是标准节点类型。编解码通过 AVAudioConverter 或 AVAudioUnit 子类处理。习题2【判断】mainMixerNode 需要手动 attach 到引擎答案错误解读mainMixerNode是引擎自动创建的无需手动 attach。当向mainMixerNode发起连接时引擎会自动配置outputNode的连接。习题3【多选】AVAudioConverter 支持哪些转换类型A. PCM 浮点/整数位深转换B. PCM 采样率转换C. PCM 交错/去交错D. 音频特效处理E. PCM 编码为压缩格式答案A、B、C、E解读AVAudioConverter 支持 PCM 位深转换、采样率转换、交错/去交错、编码和解码。D 错误——音频特效处理属于 AVAudioUnitEffect 的范畴不是格式转换。习题4【单选】路由变化时暂停播放的正确原因值当用户拔出耳机时路由变化通知中的原因值是什么A..newDeviceAvailableB..oldDeviceUnavailableC..categoryChangeD..override答案B解读当用户移除设备时原因为.oldDeviceUnavailable。Apple 官方文档明确指出应用程序应在用户断开耳机时自动暂停播放尊重用户的隐私请求。习题5【简答】AVAudioEngine 的节点图连接规则是什么答案节点之间的连接通过connect(_:to:format:)方法建立。mainMixerNode是引擎自动创建的无需手动 attach。连接时需要先用attach(_:)将节点添加到引擎然后建立连接。format参数设为nil表示使用默认格式。引擎停止状态下修改节点图更安全运行时动态修改可能导致爆音或崩溃。习题6【多选】以下哪些是 AVMutableComposition 支持的操作A.insertTimeRange(_:of:at:)插入资源时间范围B.removeTimeRange(_:)移除时间范围C.scaleTimeRange(_:toDuration:)缩放时间范围D.export(to:as:)导出合成结果E.insertEmptyTimeRange(_:)插入空白时间范围答案A、B、C、E解读AVMutableComposition 支持插入、移除、缩放时间范围和插入空白时间范围。D 错误——export(to:as:)是 AVAssetExportSession 的方法不是 AVMutableComposition 的。习题7【代码补全】连接 AVAudioEngine 节点补全代码将播放器节点连接到混响效果器再连接到主混音器。letengineAVAudioEngine()letplayerAVAudioPlayerNode()letreverbAVAudioUnitReverb()engine.attach(player)engine.attach(reverb)engine.connect(player,to:__________,format:nil)engine.connect(reverb,to:__________,format:nil)答案engine.connect(player,to:reverb,format:nil)engine.connect(reverb,to:engine.mainMixerNode,format:nil)解读节点连接的方向是从源节点到目标节点。player 的输出连接到 reverb 的输入reverb 的输出连接到 mainMixerNode。习题8【简答】prefersInterruptionOnRouteDisconnect的作用是什么答案从 iOS 17 开始AVAudioSession新增了prefersInterruptionOnRouteDisconnect属性。该属性默认为true表示当活动路由断开时如拔出耳机系统会中断音频会话。这意味着 App 无需手动监听路由变化来暂停播放——系统会自动中断会话App 只需处理中断通知即可。习题9【判断】AVAudioConverter 的 downmix 属性用于通道映射答案错误解读downmix属性用于让框架混合通道如将立体声降为单声道而非通道映射。通道映射通过channelMap属性实现它指定输出通道与输入通道的映射关系。习题10【代码补全】使用 AVAssetExportSession 导出补全代码将合成后的资源导出为 MP4 文件。guardletexportSessionAVAssetExportSession(asset:composition,presetName:__________)else{throwExportError.cannotCreateSession}tryawaitexportSession.export(to:outputURL,as:__________)答案presetName:AVAssetExportPresetHighestQuality// ...tryawaitexportSession.export(to:outputURL,as:.mp4)解读AVAssetExportPresetHighestQuality是最高质量的导出预设。export(to:as:)是 iOS 13 引入的异步导出方法as参数指定输出文件类型。习题11【多选】以下关于 AVAudioSession 类别选择的说法哪些正确A..playback类别忽略静音拨片B..ambient类别遵循静音拨片C..record类别支持后台播放D..playAndRecord类别支持录音和播放E..multiRoute类别支持混合其他音频答案A、B、D解读.playback忽略静音拨片且支持后台播放。.ambient遵循静音拨片且可混合。.record不支持后台播放。.playAndRecord支持录音和播放。.multiRoute用于高级音频路由不自动支持混合。习题12【简答】AVAudioEngine 在多通道处理中如何将多通道输入分发到多个效果链答案使用AVAudioConnectionPoint将一个多通道源连接到多个目标。每个AVAudioConnectionPoint指定目标节点和总线号。需要注意AVAudioMixerNode的channelMap无法实现通道分离——真正的通道分离需要使用 Matrix Mixer 音频单元。varconnectionPoints:[AVAudioConnectionPoint][]foriin0..4{letmixerAVAudioMixerNode()engine.attach(mixer)connectionPoints.append(AVAudioConnectionPoint(node:mixer,bus:0))}engine.connect(inputNode,to:connectionPoints,fromBus:0,format:inputFormat)习题13【代码补全】处理路由变化补全代码在耳机被拔出时暂停播放。NotificationCenter.default.addObserver(forName:AVAudioSession.__________,object:AVAudioSession.sharedInstance(),queue:.main){notificationinguardletuserInfonotification.userInfo,letreasonValueuserInfo[AVAudioSessionRouteChangeReasonKey]as?UInt,letreasonAVAudioSession.RouteChangeReason(rawValue:reasonValue)else{return}ifreason.__________{// 耳机被拔出——暂停播放}}答案forName:AVAudioSession.routeChangeNotification// ...ifreason.oldDeviceUnavailable{解读routeChangeNotification是路由变化通知的名称。oldDeviceUnavailable表示旧设备如耳机不可用此时应暂停播放。习题14【简答】AVAudioConverter 的采样率转换质量属性有什么作用答案sampleRateConverterQuality属性控制采样率转换的算法质量。值越高转换质量越好但 CPU 占用也越高。对于实时场景如音频播放可以使用较低的质量值减少 CPU 占用对于离线转换如导出音频文件可以使用最高质量确保输出品质。习题15【综合论述】如何构建一个完整的音频处理管线答案构建完整的音频处理管线需要整合以下能力1. 引擎与节点配置。使用 AVAudioEngine 创建引擎实例通过attach(_:)添加节点通过connect(_:to:format:)建立连接。三种核心节点类型输出节点、混音器节点、播放器节点构成基础架构。2. 效果处理。在节点之间插入AVAudioUnitEffect子类混响、延迟、失真构建效果链。多通道场景使用AVAudioConnectionPoint分发。3. 会话管理。使用 AVAudioSession 配置类别和模式监听路由变化和中断通知确保音频行为符合用户预期。4. 格式转换。使用 AVAudioConverter 处理采样率、位深、编码格式的转换确保输入输出格式兼容。5. 并发管理。在 Swift 6 中节点操作在MainActor上执行音频处理保持在实时渲染线程。避免在引擎运行时动态修改节点图。核心原则音频处理管线的稳定性取决于对节点图连接规则、音频会话生命周期和并发边界的精确管理。第三部分 知识点总结1. AVAudioEngine 节点图节点类型代表类职责输出节点outputNode发送音频到硬件输出混音器节点mainMixerNode、AVAudioMixerNode混合多路音频信号播放器节点AVAudioPlayerNode播放音频文件或缓冲区效果节点AVAudioUnitEffect子类混响、延迟、失真等输入节点inputNode麦克风输入2. AVAudioSession 类别选择类别静音拨片后台播放混合录音.playback忽略支持否否.ambient遵循否是否.record—否否是.playAndRecord遵循是可选是3. AVAudioConverter 转换能力转换类型说明位深转换PCM 浮点/整数转换采样率转换质量由sampleRateConverterQuality控制交错/去交错PCM 交错格式转换编码/解码PCM ↔ 压缩格式通道映射channelMap和downmix4. AVMutableComposition 操作方法用途insertTimeRange(_:of:at:)插入资源时间范围removeTimeRange(_:)移除时间范围scaleTimeRange(_:toDuration:)缩放时间范围调速insertEmptyTimeRange(_:)插入空白时间范围5. 常见陷阱在引擎运行时动态 attach/detach 节点导致爆音或崩溃混淆AVAudioMixerNode.channelMap与通道分离无法将多通道拆分为多个单通道在 App 启动时立即激活音频会话应延迟到播放开始不处理路由变化耳机拔出时音频外放侵犯用户隐私使用已弃用的exportAsynchronously而非export(to:as:)6. 行动清单使用 AVAudioEngine 构建音频处理管线注意节点连接规则在引擎停止状态下修改节点图使用 AVAudioConnectionPoint 实现多通道分发配置 AVAudioSession 类别和模式监听路由变化和中断使用 iOS 17 的prefersInterruptionOnRouteDisconnect简化路由断开处理使用 AVAudioConverter 处理格式转换注意采样率质量设置使用 AVMutableComposition 合成媒体注意时间范围操作使用 AVAssetExportSession 的export(to:as:)异步导出在 Swift 6 中使用MainActor管理引擎生命周期为音频处理管线添加完整的错误处理