前言:
AudioUnit是什么?在IOS平台下,AudioUnit是一个底层音频处理框架,主要功能如下:
1、音频播放与录制
2、音频数据格式的解析与保存
3、多路音频的合成
4、音频中的回声消除
5、音频特效如均衡器、压缩器、混响器
它的功能远不止这些,同时它具有较低的处理延时,所有的音频渲染框架的底层都是基于AudioUnit实现的,比较高层次的框架如MediaPlayer,AVFoundation, OpenAL和Audio Toolbox;
AudioUnit系列文章
AudioUnit之-播放裸PCM音频文件(一)
AudioUnit之-录制音频+耳返(二)
AudioUnit之-录制音频保存为m4a/CAF/WAV文件和播放m4a/CAF/WAV文件(三)
AudioUnit之-录制音频并添加背景音乐(四)
AudioUnit之-generic output(离线)混合音频文件(五)
音频相关知识
1、IOS支持的音频编码格式
格式定义在CoreAudio/CoreAudioTypes.h头文件的枚举AudioFormatID中
1、常见未压缩编码格式:LPCM(标准的线性脉冲编码,未压缩),IMA4;
2、常见压缩编码格式:aac,mp3,AMR等
3、多种LPCM的变体:对于LPCM音频编码是iPhone中使用非压缩音频数据最好的数据格式.同时,根据具体的存储方式,又有多种变种.音频数据可以存储于大端或者小端模式,用float或者integer存储,也可以使用不同的bit-width存储.而在iPhone中,使用的最平凡的是:little-endian integer 16bit(或者LEI16 short类型)的格式.
2、IOS常用音频容器格式
iPhone支持许多文件格式(音频容器)包括:MPEG-1(.mp3),MPEG-2 ADTS(.aac),AIFF,CAF,WAVE等.但是通常在iPhone中使用的容器格式就CAF,因为它可以用来封装iPhone所支持的所有音频格式.
使用AudioUnit播放音频
相关头文件:
#import
#import
#import
AudioUnit一端连接着扬声器,另一端连接着应用端,应用端不停的将PCM音频数据输入给AudioUnit就实现了音频的播放,使用AudioUnit来播放PCM音频数据的步骤如下:
1、创建音频会话
// 1、创建一个音频会话 它是单例;AVAudioSession 在AVFoundation/AVFAudio/AVAudioSession.h中定义
_aSession = [AVAudioSession sharedInstance];
// 2、======配置音频会话 ======//
/** 配置使用的音频硬件:
* AVAudioSessionCategoryPlayback:只是进行音频的播放(只使用听的硬件,比如手机内置喇叭,或者通过耳机)
* AVAudioSessionCategoryRecord:只是采集音频(只录,比如手机内置麦克风)
* AVAudioSessionCategoryPlayAndRecord:一边采集一遍播放(听和录同时用)
*/
[_aSession setCategory:category error:nil];
// 设置采样率,不管是播放还是录制声音 都需要设置采样率
[_aSession setPreferredSampleRate:rate error:nil];
// 设置I/O的Buffer,数值越小说明缓存的数据越小,延迟也就越低;这里意思就是麦克风采集声音时只缓存20ms的数据
[_aSession setPreferredIOBufferDuration:duration error:nil];
// 激活会话
[_aSession setActive:YES error:nil];
2.创建AudioUnit描述组件
这里主要是定义AudioUnit的类型,定义在AudioToolbox/AUComponent.h文件中
AudioUnit的类型,定义在AudioToolbox/AUComponent.h文件中
CF_ENUM(UInt32) {
kAudioUnitType_Output = 'auou',
kAudioUnitType_MusicDevice = 'aumu',
kAudioUnitType_MusicEffect = 'aumf',
kAudioUnitType_FormatConverter = 'aufc',
kAudioUnitType_Effect = 'aufx',
kAudioUnitType_Mixer = 'aumx',
kAudioUnitType_Panner = 'aupn',
kAudioUnitType_Generator = 'augn',
kAudioUnitType_OfflineEffect = 'auol',
kAudioUnitType_MIDIProcessor = 'aumi'
};
1、kAudioUnitType_Effect;主要用于提供声音特效的处理,包括的子类型有
均衡效果器:kAudioUnitSubType_NBandEQ,用于为声音的某些频带增强或减弱能量
压缩效果器:kAudioUnitSubType_DynamicsProcessor,增大或者减少音量
混响效果器:kAudioUnitSubType_Reverb2,提供混响效果
2、kAudioUnitType_Mixer:提供Mix多路声音功能
多路混音效果器:kAudioUnitSubType_MultiChannelMixer,可以接受多路音频的输入,然后分别调整每一路音频的增益与开关,并将多路音频合成一路
3、kAudioUnitType_Output:提供音频的录制,播放功能
录制和播放音频:kAudioUnitSubType_RemoteIO,后面通过AudioUnitSetProperty()方法具体是访问麦克风还是扬声器
访问音频数据:kAudioUnitSubType_GenericOutput
4、kAudioUnitType_FormatConverter:提供音频格式转化功能,比如采样率转换,声道数转换,采样格式转化,panner到packet转换等等
kAudioUnitSubType_AUConverter:提供格式转换功能
kAudioUnitSubType_AudioFilePlayer:直接从文件获取输入音频数据,它具有解码功能
kAudioUnitSubType_NewTimePitch:变速变调效果器
/**
* 与AudioUnit有关的错误类型枚举定义在AudioToolbox/AUComponent.h文件中
* CF_ENUM(OSStatus) {
* kAudioUnitErr_InvalidProperty = -10879,
* .......
* }
*/
// 创建指定的类型
+ (AudioComponentDescription)descriptionWithType:(OSType)type subType:(OSType)subType fucture:(OSType)manufuture
{
AudioComponentDescription acd;
acd.componentType = type;
acd.componentSubType = subType;
acd.componentManufacturer = manufuture;
return acd;
}
3、创建AudioUnit:
/** 创建 AudioUnit
* 和通过AUGraph创建;
*/
- (void)createAudioUnitByAugraph
{
OSStatus status = noErr;
//1、创建AUGraph
status = NewAUGraph(&_aGraph);
if (status != noErr) {
NSLog(@"create AUGraph fail %d",status);
}
//2.2 将指定的组件描述创建AUNode并添加到AUGraph中
status = AUGraphAddNode(_aGraph, &_ioDes, &_ioNode);
if (status != noErr) {
NSLog(@"AUGraphAddNode fail _ioDes %d",status);
}
status = AUGraphAddNode(_aGraph, &_cvtDes, &_cvtNode);
if (status != noErr) {
NSLog(@"AUGraphAddNode fail _cvtDes %d",status);
}
// 3、打开AUGraph(即初始化了AUGraph)
status = AUGraphOpen(_aGraph);
if (status != noErr) {
NSLog(@"AUGraphOpen fail %d",status);
}
// 4、打开了AUGraph之后才能获取指定的AudioUnit
status = AUGraphNodeInfo(_aGraph, _ioNode, NULL, &_ioUnit);
if (status != noErr) {
NSLog(@"AUGraphNodeInfo fail %d",status);
}
status = AUGraphNodeInfo(_aGraph, _cvtNode, NULL, &_cvtUnit);
if (status != noErr) {
NSLog(@"AUGraphNodeInfo fail %d",status);
}
}
4、设置AudioUnit属性
/** 设置AudioUnit属性
* 1、通过AudioUnitSetProperty
* 2、关于remoteIO的element,扬声器对应的AudioUnitElement值为0,app能控制的AudioUnitScope值为kAudioUnitScope_Input;麦克风对应的AudioUnitElement值为1
* app能控制的udioUnitScope值为kAudioUnitScope_Output
*/
- (void)setAudioUnitProperties
{
// 开启扬声器的播放功能;注:对于扬声器默认是开启的,对于麦克风则默认是关闭的
uint32_t flag = 1;// 1代表开启,0代表关闭
OSStatus status = AudioUnitSetProperty(_ioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &flag, sizeof(flag));
if (status != noErr) {
NSLog(@"AudioUnitSetProperty fail %d",status);
}
AudioFormatFlags flags = self.aSession.formatFlags;
CGFloat rate = self.aSession.currentSampleRate;
NSInteger chs = self.aSession.currentChannels;
//输入给扬声器的音频数据格式
AudioStreamBasicDescription odes = [ADUnitTool streamDesWithLinearPCMformat:kAudioFormatFlagIsFloat|kAudioFormatFlagIsNonInterleaved sampleRate:rate channels:chs];
// PCM文件的音频的数据格式
AudioStreamBasicDescription cvtInDes = [ADUnitTool streamDesWithLinearPCMformat:flags sampleRate:rate channels:chs];
// 设置扬声器的输入音频数据格式
status = AudioUnitSetProperty(_ioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &odes, sizeof(odes));
if (status != noErr) {
NSLog(@"AudioUnitSetProperty io fail %d",status);
}
// 设置格式转换器的输入输出音频数据格式;对于格式转换器AudioUnit 他的AudioUnitElement只有一个 element0
status = AudioUnitSetProperty(_cvtUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &cvtInDes, sizeof(cvtInDes));
if (status != noErr) {
NSLog(@"AudioUnitSetProperty convert in fail %d",status);
}
status = AudioUnitSetProperty(_cvtUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &odes, sizeof(odes));
if (status != noErr) {
NSLog(@"AudioUnitSetProperty convert ou fail %d",status);
}
/** 构建连接
* 只有构建连接之后才有一个完整的数据驱动链。如下将构成链条如下:
* _cvtUnit通过回调向文件要数据,得到数据后进行格式转换,将输出作为输入数据输送给_ioUnit,然后_ioUnit播放数据
*/
status = AUGraphConnectNodeInput(_aGraph, _cvtNode, 0, _ioNode, 0);
if (status != noErr) {
NSLog(@"AUGraphConnectNodeInput fail %d",status);
}
AURenderCallbackStruct callback;
callback.inputProc = InputRenderCallback;
callback.inputProcRefCon = (__bridge void*)self;
status = AudioUnitSetProperty(_cvtUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callback, sizeof(callback));
if (status != noErr) {
NSLog(@"AudioUnitSetProperty fail %d",status);
}
}
tips:根据步骤4的代码,总结,
AudioUnit播放音频的数据驱动链条为:
_cvtUnit通过回调向文件要数据,得到数据后进行格式转换,将输出作为输入数据输送给_ioUnit,然后_ioUnit播放数据
5、播放
- (void)play
{
OSStatus stauts;
CAShow(_aGraph);
// 7、初始化AUGraph,初始化之后才能正常启动播放
stauts = AUGraphInitialize(_aGraph);
if (stauts != noErr) {
NSLog(@"AUGraphInitialize fail %d",stauts);
}
stauts = AUGraphStart(_aGraph);
if (stauts != noErr) {
NSLog(@"AUGraphStart fail %d",stauts);
}
}
项目地址:
Demo
具体的代码实现在文件ADAudioUnitPlay.h/.m中
温馨小提示:
由于代码混合在一块了,后面多了对ffmpeg的代码整合,使用了git-lfs的文件传输功能,所以git clone之前需要先安装git-lfs,如下:
1、安装homebrew(如果没有) ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
2、安装git-lfs brew install git-lfs
3、git clone https://github.com/nldzsz/media-ios.git
否则会提示
"Undefined symbol: _av。。。之类"