1. 背景
iOS系统提供了非常丰富的音频相关的API,涵盖了从采集,处理到播放等各个环节,并且按照需求的层次进行了分组。
其中,离底层的驱动和硬件最近的就是AudioUnit系列的API,很多其他高层的API,都是对AudioUnit的封装. 比如 AVFoundation、AudioQueue、AVAudioEngine等。
- 缺点:
- 涉及到的专有概念比较多,接口复杂
- 提供C风格的API
- 优点:
- 低延时,从采集到播放回环可以到10ms的级别
- 可以动态变更配置组合
- 可以直接获得后台执行权限
2. AudioUnit简介
AudioUnit这个名字取得还是比较形象的,它的主体就是一系列的unit,不同unit能够实现不同的功能,将一个或多个unit添加到AUGraph
(Audio Processing Graph)中,并建立unit之间的连接,音频数据顺次通过各个节点即可完成我们最终需求。
在iOS上,系统一共提供了4类unit。
purpos | Audio units |
---|---|
Effect | eg. Equalizer |
mixing | eg. Multichannel Mixer |
I/O | eg. Remote I/O |
Format conversion | eg. Format Converter |
其中,I/O
主要负责和设备打交道,比如采集和播放; Mixing
负责将不同来源的音频数据进行混合; Effect
是对音频数据进行音效处理; Format Conversion
主要是进行格式转换比如重采样等。
3. 使用AudioUnit进行音频采集
在直播应用中,我们主要是使用Remote I/O
unit来进行采集工作。
在一个AUGraph
中只允许有一个I/O unit
。Remote I/O
需要同时负责采集和播放的功能。当用户开启耳返功能时,要将采集到的声音,处理之后再送回当前节点直接播放,这样可以将采集和播放的延时控制在50ms以内,主播才察觉不到声音的延时。
基本的步骤如下:
- 实例化
AUGraph
,将用到units添加进去; - 配置每个AudioUnit的属性;
- 设置
Render Callback Function
; - 将units 建立连接;
- 启动
AUGraph
。
以上过程大家都可以到Apple官方的文档中找到具体的说明和代码示例。
在直播录制中比较关键的一步就是Render Callback Function
。
typedef OSStatus
(*AURenderCallback)( void * inRefCon,
AudioUnitRenderActionFlags * ioActionFlags,
const AudioTimeStamp * inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList * __nullable ioData);
AudioUnit每次都是处理一段音频数据,每次处理完成一段数据的时候,这个回调函数就会被调用一次。
在这个回调函数中,通过AudioUnit的AudioUnitRender
方法,可以AUGraph中
的某一个节点中获取到一段处理后的音频PCM数据。同时,如果需要进行耳返播放,在这个回调中也需要将取得的音频数据送入到回调函数的最后一个参数ioData
对应的buffer中。
4. 使用AudioUnit进行音效处理
这里所谓的音效处理,主要是指对原本的声音进行一些改变,比如混响效果,变声效果等。
用到手段主要是数字信号处理提供的一系列时间和频域的工具,将输入的PCM数据经过运算后得到变化后的声音。
4.1 混响效果(reverberation
)
我们在音乐厅,剧院,礼堂等比较空旷的室内说话或唱歌时,往往能听到和平时不一样的声音,主要是声音在墙壁上多次反射后叠加在一起,听起来就有了混响的效果。
在声音处理中,我们可以人为的将声音缓存起来,延时一定时间后,和原声音叠加,就能够模拟出混响的效果。AudioUnit提供了
kAudioUnitSubType_Reverb2
来实现混响效果的生成。
将该unit接入到AUGraph
中之后,配置参数即可实现混响的效果。
虽然混响原理是比较简单,但实际上为了模拟自然界中实际的音效,计算过程还是相当复杂的,要模拟出不同的大小的空间,不同材质的墙壁,障碍物的多少,需要输入比较多的参数来参与运算。iOS的reverb unit
提供了7个参数。我们在直播应用中提供了4个不同场景的模拟(录音棚,演唱会,KTV,小舞台),主要是通过调整如下3个参数实现的:
-
kReverb2Param_DryWetMix
混响效果声的大小 与空间大小无关,而只与空间内杂物的多少以及墙壁及物体的材质有关; -
kReverb2Param_DecayTimeAt0Hz
/kReverb2Param_DecayTimeAtNyquist
衰减时间,整个混响的总长度,与空间大小比较相关,越空旷越长。
4.2 变声效果
变声效果主要是在频域上对人的声音进行一定的处理,我们知道男声一般比较低沉,女声比较尖锐,这个主要说的是声音的频率。通过对声音音调的调整,可以 让低沉的男声听上去像尖锐女声。iOS提供了kAudioUnitSubType_NewTimePitch
的unit来实现音调的调整。值得注意的是 kAudioUnitSubType_NewTimePitch
不是输入Effect类
的,而是属于 FormatConverter类
的。
通过设置TimePitch unit
的kNewTimePitchParam_Pitch
属性即可。
///pitchShift为具体数值(0表示不变,负数表明调低沉,正数调尖锐)
AudioUnitSetParameter(pitchUnit, kNewTimePitchParam_Pitch, kAudioUnitScope_Global, 0, pitchShift, 0);
- 变男声,就需要强化音调低沉的特点,将音调调低,设置负数参数即可;
- 变女声,需要强化尖锐的特点,将音调调高,设置正数即可;
- 机器人音效,机器人的音效是一个组合效果,我们印象中的机器人音效都是老电影中的那种,音调比较高,而且有重音。所以我们采用的是
TimePitch unit
+Delay unit
的方式。Delay unit
也是iOS提供的一个将声音延时叠加的unit,但是比混音要简单很多,只有单次叠加; - 庄严宏大音效,想象一下佛祖之类的声音,一般都是自带回声,而且比较男性化,所以我们选择的是
TimePitch unit
+Reverb unit
的方式来实现。
这里安利一个自己调音效的参考软件 voxal voice changer。
大家可以在这个软件上自己将不同的工具组件组合起来,调试参数,实时听到参数对应的结果。当效果满意后再移植到AudioUnit中。
5. 踩过的一些坑和优化
金山云多媒体SDK在直播开发过程中遇到了不少音频采集的坑,印象比较深的有这些:
- 蓝牙设备的支持
使用iOS内置的麦克风或者有线耳机时,设备支持的采样率比较高,44.1KHz 能正常工作,我们整条音频通路上基本上都采用的是44.1KHz。但是当使用蓝牙设备时,蓝牙设备无法支持44.1KHz采集和播放。此时I/O Unit
无法继续使用之前的配置。需要按照实际支持的采样率进行配置。 -
AUGraph
启动失败
不同单元衔接处的音频数据格式必须保持一致
在支持蓝牙设备或者使用回声消除模块时,I/O unit
要求的格式和其他单元的有可能不同,此时就需要分别设置格式。此时必须保证两两衔接节点的格式保持一致,否则AUGraph可能启动失败。 - 声音异常
MaximumFramesPerSlice
需要保持一致
MaximumFramesPerSlice
表示的是每次回调送入或取出的音频数据的长度,在AUGraph
的所有节点的这个属性也需要保持一致否则会出现声音异常 - 音频格式转换
Multichannel Mixer
本身就能够实现格式转换的功能,输入和输出的音频数据格式可以不同,利用这一点可以节省一个格式转换unit。
6. 总结
以上大概介绍了怎么用AudioUnit来实现iOS直播中的音频采集,怎么使用AudioUnit中的音效组件来实现混响和变声效果。但是AudioUnit的潜力还远没有挖掘完。后续还可以继续讲背景音乐播放,混音,回声消除等各种功能纳入到这个框架中。
也欢迎大家使用我们的多媒体SDK。
有关音视频的更多精彩内容,请参考https://github.com/ksvc
金山云SDK相关的QQ交流群:
- 视频云技术交流群:574179720
- 视频云iOS技术交流:621137661