iOS系统中处理实时音频,Core Audio是绕不开的底层框架。它提供了Audio Unit这套高性能的处理单元,其中Multichannel Mixer(kAudioUnitSubType_MultiChannelMixer)天然支持多路输入混音,配合AUGraph可以把输入采集、播放文件、混音、效果处理、输出到硬件这一整条链路串起来。整套方案延迟低、CPU占用小,非常适合直播连麦、在线K歌、多轨配音这类需要同时处理多路音频的场景。

理解Audio Unit与AUGraph的基本结构
Audio Unit是iOS音频处理的最小功能单元,每个Unit都有输入总线(Input Buses)、输出总线(Output Buses)和全局作用域的参数。Mixer单元的特殊之处在于它拥有多个输入总线,默认是8个,总线编号从0开始,也就是Bus 0到Bus 7。每一个总线可以接入一路独立的音频源,比如一路是麦克风采集的干声,一路是伴奏播放器,一路是音效文件,混音器内部把这几路按各自音量叠加成一路输出。
AUGraph则是把这些Unit组织成一张有向图的管理器。它负责维护节点间的连接关系、处理音频数据在节点间的流转,并且支持在运行时动态增删节点而不会中断音频线程。一个典型的图结构是:RemoteIO(负责麦克风采集和扬声器输出)、Mixer(多路混音)、AUX效果单元(比如混响)。采集到的数据先送进Mixer,Mixer输出再经过效果单元,最后回到RemoteIO的输出端播放。整个图通过AUGraphInitialize初始化后,音频渲染线程就会自动按连接顺序拉取数据。
需要注意的是,每个Audio Unit都运行在实时音频回调线程上,任何在回调中加锁、分配内存、访问文件系统或者做Objective-C消息派发的行为都可能引起卡顿甚至断音。正确的做法是在回调里只做数据搬运和参数读取,重活放到其他线程并通过无锁队列或原子变量传递结果。
搭建混音图:节点创建与总线分配
下面用一个AUGraph把RemoteIO、Mixer和效果器连起来。先创建图和节点,再建立连接:
AUGraph graph;
AUNode ioNode, mixerNode, effectNode;
AudioUnit ioUnit, mixerUnit, effectUnit;
// 创建音频图
NewAUGraph(&graph);
AUGraphOpen(graph);
// RemoteIO:输出到扬声器
AudioComponentDescription ioDesc = {};
ioDesc.componentType = kAudioUnitType_Output;
ioDesc.componentSubType = kAudioUnitSubType_RemoteIO;
ioDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
AUGraphAddNode(graph, &ioDesc, &ioNode);
// 多通道混音器
AudioComponentDescription mixerDesc = ioDesc;
mixerDesc.componentType = kAudioUnitType_Mixer;
mixerDesc.componentSubType = kAudioUnitSubType_MultiChannelMixer;
AUGraphAddNode(graph, &mixerDesc, &mixerNode);
// 混响效果器
AudioComponentDescription fxDesc = ioDesc;
fxDesc.componentType = kAudioUnitType_Effect;
fxDesc.componentSubType = kAudioUnitSubType_Reverb2;
AUGraphAddNode(graph, &fxDesc, &effectNode);
AUGraphNodeInfo(graph, ioNode, NULL, &ioUnit);
AUGraphNodeInfo(graph, mixerNode, NULL, &mixerUnit);
AUGraphNodeInfo(graph, effectNode, NULL, &effectUnit);
// 打开麦克风采集
UInt32 flag = 1;
AudioUnitSetProperty(ioUnit, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input, 1, &flag, sizeof(flag));
// 连接:Mixer输出 -> 效果器输入
AudioStreamBasicDescription asbd = {};
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mFormatFlags = kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagIsFloat;
asbd.mSampleRate = 44100;
asbd.mChannelsPerFrame = 2;
asbd.mBitsPerChannel = 32;
asbd.mFramesPerPacket = 1;
asbd.mBytesPerFrame = asbd.mChannelsPerFrame * sizeof(Float32);
asbd.mBytesPerPacket = asbd.mBytesPerFrame;
AUGraphConnectNodeInput(graph, mixerNode, 0, effectNode, 0);
AUGraphConnectNodeInput(graph, effectNode, 0, ioNode, 0);
AUGraphSetNodeInputCallback(graph, mixerNode, 0, &callbackStruct); // 总线0接麦克风
AUGraphSetNodeInputCallback(graph, mixerNode, 1, &callbackStruct2); // 总线1接伴奏
AUGraphInitialize(graph);
AUGraphStart(graph);总线分配的核心是kAudioUnitProperty_SetInputCallback。每个输入总线绑定一个独立的回调函数,回调里从对应的数据源拉取音频帧。比如总线0从环形缓冲区读麦克风数据,总线1从解码器读伴奏PCM。如果某路暂时没有数据,回调必须输出全零数据而不是跳过,否则混音器会认为该总线出现了数据饥荒,导致整条链路节奏错乱。当某路确定不再使用时,可以把该总线的输入数量设为0来释放资源,即设置kAudioUnitProperty_InputCount为更小的值,从而降低混音器内部的运算量。
混音参数控制与AUX效果器链
混音器上每个总线都有独立的参数作用域,通过AudioUnitSetParameter配合kAudioUnitScope_Input和总线编号即可单独控制某一路的音量、静音和声像。例如把麦克风音量设为1.0、伴奏压到0.6实现人声突出的效果,这在K歌场景里非常常见。全局作用域则控制混音完成后的总输出音量,适合做整体淡入淡出。
// 独立控制各总线音量
AudioUnitSetParameter(mixerUnit, kMultiChannelMixerParam_Volume,
kAudioUnitScope_Input, 0, 1.0, 0); // 总线0:人声
AudioUnitSetParameter(mixerUnit, kMultiChannelMixerParam_Volume,
kAudioUnitScope_Input, 1, 0.6, 0); // 总线1:伴奏
// 混响参数:干湿比与衰减时间
AudioUnitSetParameter(effectUnit, kAudioUnitProperty_Reverb2DryWetMix,
kAudioUnitScope_Global, 0, 30.0, 0);
AudioUnitSetParameter(effectUnit, kAudioUnitProperty_Reverb2DecayTimeAt0Hz,
kAudioUnitScope_Global, 0, 2.5, 0);AUX效果器链的搭建思路是级联:把混音器的输出接到第一个效果单元的输入,再把第一个效果单元的输出接到下一个效果单元,形成串行的处理链。想要混响加EQ的组合,就在Mixer和RemoteIO之间依次插入Reverb2单元和AUiPodEQ单元。如果需要并行效果,比如干声直达输出、湿声经过混响后再汇合,可以引入第二个Mixer作为汇总点,把干湿两路分别接到这个汇总Mixer的不同总线上,各自设定比例。这种发送式(Send/Return)结构就是专业调音台里AUX母线的等价实现。
工程上还有几个容易踩的坑值得强调。第一是采样率统一,链路上所有节点的ASBD必须一致,否则AUGraphInitialize会报错或出现变速变调;麦克风采集建议直接将RemoteIO客户端格式设为与混音器相同的44100或48000Hz浮点非交织格式,避免额外重采样。第二是延迟与缓冲大小,可以通过kAudioUnitProperty_PreferredHardwareIOBufferSizes调整,但过小的缓冲会导致CPU过载。第三是效果器链的顺序,通常把EQ放在混响之前,先修声再润色,效果更自然。掌握这些要点后,一套稳定低延迟的iOS混音引擎就成型了,后续再叠加实时变声、降噪等单元也只是往图里加节点而已。
Core AudioAUGraph音频混音修改时间:2026-09-07 23:53:49