AudioUnit是iOS底層音頻框架,相比于AudioQueue和AVAudioRecorder,能夠?qū)σ纛l數(shù)據(jù)進(jìn)行更多的控制,可以用來進(jìn)行混音、均衡、格式轉(zhuǎn)換、實(shí)時(shí)IO錄制、回放、離線渲染等音頻處理。
1、AudioUnit錄音:
先看下蘋果官方的原理圖(此處以I/O單元為例):

1、一個(gè)AudioUnit包含2個(gè)element。
2、每個(gè)element包含輸入輸入部分(scope)。
3、硬件到element的部分(即圖中淡藍(lán)色部分)我們無法介入,我們能控的就element與我們APP關(guān)聯(lián)的部分(即圖中淡黃色部分)。
4、實(shí)現(xiàn)錄音就是主要關(guān)注element1到APP的過程,播放則是關(guān)注APP到element0的過程。
代碼實(shí)現(xiàn):
代碼只實(shí)現(xiàn)錄音功能,所以只使用了element1:
設(shè)置音頻格式:
- (void)initRecordFormat {
_recordFormat.mSampleRate = 32000; //采樣率
_recordFormat.mChannelsPerFrame = 1; //聲道數(shù)量
//編碼格式
_recordFormat.mFormatID = kAudioFormatLinearPCM;
_recordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
//每采樣點(diǎn)占用位數(shù)
_recordFormat.mBitsPerChannel = 16;
//每幀的字節(jié)數(shù)
_recordFormat.mBytesPerFrame = (_recordFormat.mBitsPerChannel / 8) * _recordFormat.mChannelsPerFrame;
//每包的字節(jié)數(shù)
_recordFormat.mBytesPerPacket = _recordFormat.mBytesPerFrame;
//每幀的字節(jié)數(shù)
_recordFormat.mFramesPerPacket = 1;
}
接著實(shí)例化AudioUnit,配置相關(guān)屬性和方法
- (void)initConfig {
//配置描述
AudioComponentDescription acd;
acd.componentType = kAudioUnitType_Output;
acd.componentManufacturer = kAudioUnitManufacturer_Apple;
//remoteIO對(duì)應(yīng)的就是(I/O)Unit
acd.componentSubType = kAudioUnitSubType_RemoteIO;
acd.componentFlags = 0;
acd.componentFlagsMask = 0;
//AudioComponent類似組件工廠,用于實(shí)例化audioUnit
AudioComponent comp = AudioComponentFindNext(nil, &acd);
OSStatus status = AudioComponentInstanceNew(comp, &_audioUnit);
if(status!= kAudioSessionNoError) {
NSLog(@"InstanceError");
return;
}
//開啟麥克風(fēng)到Element1的inputScope部分,參數(shù)1代表element1。
UInt32 flag=1;
OSStatus statusPerpotyIO = AudioUnitSetProperty(_audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flag, sizeof(flag));
if(statusPerpotyIO!= kAudioSessionNoError) {
NSLog(@"SetPropertyIOError");
return;
}
//設(shè)置Element1到APP的outScope部分的流格式(即我們需要得到的音頻數(shù)據(jù)格式)
OSStatus statusPerpotyFR = AudioUnitSetProperty(_audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &_recordFormat, sizeof(_recordFormat));
if(statusPerpotyFR!= kAudioSessionNoError) {
NSLog(@"SetPropertyFRError");
return;
}
//設(shè)置錄制過程的回調(diào)函數(shù)
AURenderCallbackStruct callBackSt;
callBackSt.inputProc = inputCallBack;
//將對(duì)象指針傳入回調(diào)函數(shù)內(nèi)部,方便內(nèi)部使用
callBackSt.inputProcRefCon = (__bridge void * _Nullable)(self);
OSStatus statusPerpotyCall = AudioUnitSetProperty(_audioUnit,
kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &callBackSt, sizeof(callBackSt));
if(statusPerpotyCall!= kAudioSessionNoError) {
NSLog(@"SetPropertyCallError");
return;
}
}
設(shè)置AudioSession模式,開啟
- (void)setAudioSessionEnable:(BOOL)YesOrNo {
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[[AVAudioSession sharedInstance] setActive:YesOrNo error:nil];
}
啟動(dòng)audioUnit,開始錄音
- (void)startRecord {
OSStatus statusInit = AudioUnitInitialize(_audioUnit);
if(statusInit!= kAudioSessionNoError) {
NSLog(@"statusInitError");
return;
}
AudioOutputUnitStart(_audioUnit);
self.isRecording = YES;
}
回調(diào)函數(shù)實(shí)現(xiàn),獲取音頻數(shù)據(jù):
OSStatus inputCallBack (void * inRefCon,
AudioUnitRenderActionFlags * ioActionFlags,
const AudioTimeStamp * inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList * __nullable ioData)
{
YTAudioUnitManager *audioManager = [YTAudioUnitManager sharedManager];
//創(chuàng)建bufferlist
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mDataByteSize = sizeof(SInt16)*inNumberFrames;
bufferList.mBuffers[0].mNumberChannels = 1;
bufferList.mBuffers[0].mData = (SInt16*) malloc(sizeof(SInt16)*inNumberFrames);
//將unit的數(shù)據(jù)渲染到bufferList
OSStatus status = AudioUnitRender(audioManager.audioUnit,
ioActionFlags,
inTimeStamp,
1,
inNumberFrames,
&bufferList);
//這里是將數(shù)據(jù)寫入文件
// ExtAudioFileWrite(audioManager.fileInfo->extAudioFileRef, inNumberFrames, &bufferList);
return status;
}
停止錄音:
- (void)stopRecord {
[self setAudioSessionEnable:NO];
AudioOutputUnitStop(_audioUnit);
AudioUnitUninitialize(_audioUnit);
ExtAudioFileDispose(_fileInfo->extAudioFileRef);
self.isRecording = NO;
}
2、AudioUnit混音:
如果只是使用AudioUnit實(shí)現(xiàn)播放和錄音功能,未免太大材小用,我們來看看混音是如何實(shí)現(xiàn)的。
混音是把多種來源的聲音,整合至一個(gè)立體音軌(Stereo)或單音音軌(Mono)中,此處是讀取兩段音頻,由左右聲道同時(shí)進(jìn)行播放。
官方也很貼心的給出了demo:Apple官方Demo地址。
原理:
首先我們看下官方的使用原理圖:

1、整體是AudioGraph,用來管理多個(gè)AudioUnit,此處涉及到是I/O單元和Mix單元。
2、兩個(gè)音頻數(shù)據(jù)源,提供數(shù)據(jù)給Mix Unit,Mix Unit具有多個(gè)input通道,但是只有一個(gè)輸出通道。
3、Mix Unit將處理完的數(shù)據(jù)傳輸給I/O單元,I/O單元負(fù)責(zé)播放。
具體實(shí)現(xiàn):
變量定義:
//定義結(jié)構(gòu)體用來保存音頻數(shù)據(jù)的信息
typedef struct {
AudioStreamBasicDescription asbd;
Float32 *data;
UInt32 numFrames;
UInt32 startFrameNum;
} SoundBuffer;
@interface YTAudioMixManager() {
SoundBuffer mSoundBuffer[2]; //兩個(gè)音頻文件
}
@property (nonatomic,assign)AUGraph auGraph;
//針對(duì)音頻文件的格式
@property (nonatomic,strong)AVAudioFormat *fileFormat;
//針對(duì)unit的格式
@property (nonatomic,strong)AVAudioFormat *unitFormat;
@end
初始化Format:
- (void)initRecordFormat {
//Audiounit的描述 ,
//聲道為2,
//interleaved為NO,使左右聲道的數(shù)據(jù)分別存儲(chǔ)在AudioBufferList的兩個(gè)AudioBuffer中。
_unitFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
sampleRate:44100
channels:2
interleaved:NO];
//文件音數(shù)據(jù)的描述 ,
//聲道為1,
//interleaved為YES
_fileFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
sampleRate:44100
channels:1
interleaved:YES];
}
讀取兩個(gè)音頻文件數(shù)據(jù):
//讀取音頻文件數(shù)據(jù)
- (void)loadDataFromURLS:(NSArray *)urlNames {
for (int i =0;i<urlNames.count;i++) {
NSString *urlName = urlNames[i];
CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)urlName, NULL);
ExtAudioFileRef audioFileRef;
ExtAudioFileOpenURL(url, &audioFileRef);
OSStatus status;
UInt32 propSize = sizeof(AudioStreamBasicDescription);
//設(shè)置我們想要要獲取的音頻文件格式,ExtAudioFile是自帶轉(zhuǎn)碼功能的。
status = ExtAudioFileSetProperty(audioFileRef, kExtAudioFileProperty_ClientDataFormat, propSize, _fileFormat.streamDescription);
if(status!=kAudioSessionNoError) {
NSLog(@"FileGetProperty Error");
return;
}
AudioStreamBasicDescription fileFormat;
UInt32 formSize = sizeof(fileFormat);
//讀取文件格式屬性
status = ExtAudioFileGetProperty(audioFileRef, kAudioFileStreamProperty_FileFormat, &formSize, &fileFormat);
if(status!=kAudioSessionNoError) {
NSLog(@"FileGetProperty Error");
return;
}
//讀取文件幀數(shù)
UInt64 numFrames;
UInt32 numFramesSize = sizeof(numFrames);
status = ExtAudioFileGetProperty(audioFileRef, kExtAudioFileProperty_FileLengthFrames, &numFramesSize, &numFrames);
if(status!=kAudioSessionNoError) {
NSLog(@"FileGetProperty Error");
return;
}
mSoundBuffer[i].numFrames = (UInt32)numFrames;
mSoundBuffer[i].asbd = fileFormat;
UInt64 samples = numFrames * mSoundBuffer[i].asbd.mChannelsPerFrame;
mSoundBuffer[i].data = (Float32 *)calloc(samples, sizeof(Float32));
mSoundBuffer[i].sampleNum = 0;
//將文件數(shù)據(jù)讀取傳入BufferList
AudioBufferList bufList;
bufList.mNumberBuffers = 1;
bufList.mBuffers[0].mNumberChannels = 1;
bufList.mBuffers[0].mData = (Float32*)malloc(sizeof(Float32)*samples);
bufList.mBuffers[0].mDataByteSize = (Float32)samples * sizeof(Float32);
UInt32 numPackets = (UInt32)numFrames;
status = ExtAudioFileRead(audioFileRef, &numPackets, &bufList);
//將bufferList數(shù)據(jù)復(fù)制給mSoundBuffer
memcpy(mSoundBuffer[i].data, bufList.mBuffers[0].mData , bufList.mBuffers[0].mDataByteSize);
if(status!=kAudioSessionNoError) {
// printf("ExtAudioFileRead Error");
free(mSoundBuffer[i].data);
mSoundBuffer[i].data = 0;
}
ExtAudioFileDispose(audioFileRef);
}
}
設(shè)置AudioUnit相關(guān)對(duì)象
- (void)initAudioUnit {
//IO單元描述
AudioComponentDescription IOacd;
IOacd.componentType = kAudioUnitType_Output;
IOacd.componentManufacturer = kAudioUnitManufacturer_Apple;
//remoteIO對(duì)應(yīng)的就是(I/O)Unit
IOacd.componentSubType = kAudioUnitSubType_RemoteIO;
IOacd.componentFlags = 0;
IOacd.componentFlagsMask = 0;
//mix單元描述
AudioComponentDescription mixacd;
mixacd.componentType = kAudioUnitType_Mixer;
mixacd.componentManufacturer = kAudioUnitManufacturer_Apple;
mixacd.componentSubType = kAudioUnitSubType_MultiChannelMixer;
mixacd.componentFlags = 0;
mixacd.componentFlagsMask = 0;
OSStatus status;
status= NewAUGraph (&_auGraph);
if(status!=kAudioSessionNoError) {
NSLog(@"NewAUGraph Error");
return;
}
AUNode ioNode;
AUNode mixNode;
AudioUnit ioUnit;
AudioUnit mixUnit;
//添加node
AUGraphAddNode (_auGraph,&IOacd,&ioNode);
AUGraphAddNode (_auGraph,&mixacd, &mixNode);
//建立兩個(gè)node的輸入和輸出連接
status = AUGraphConnectNodeInput(_auGraph, mixNode, 0, ioNode, 0);
if (status!=kAudioSessionNoError) {
printf("AUGraphConnect Error");
return;
}
AUGraphOpen (_auGraph);
if(status!=kAudioSessionNoError) {
NSLog(@"AUGraphOpen Error");
return;
}
//獲取node對(duì)應(yīng)的Aunit
status = AUGraphNodeInfo(_auGraph, mixNode, NULL, &mixUnit);
if(status!=kAudioSessionNoError) {
NSLog(@"AUGraphNodeMixInfo Error");
return;
}
status = AUGraphNodeInfo(_auGraph, ioNode, NULL, &ioUnit);
if(status!=kAudioSessionNoError) {
NSLog(@"AUGraphOpen Error");
return;
}
//設(shè)置輸入數(shù)量
int elementCount = 2;
status = AudioUnitSetProperty(mixUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &elementCount, sizeof(elementCount));
if(status!=kAudioSessionNoError) {
NSLog(@"AudioUnitSetProperty Error");
return;
}
for (int i = 0; i < elementCount;i++) {
// setup render callback struct
AURenderCallbackStruct rcbs;
rcbs.inputProc = &mixInputCallBack;
rcbs.inputProcRefCon = mSoundBuffer;
//給mix的兩個(gè)element設(shè)置回調(diào)
status = AUGraphSetNodeInputCallback(_auGraph, mixNode, i, &rcbs);
if (status) { printf("AUGraphSetNodeInputCallback result %ld %08lX %4.4s\n", (long)status, (long)status, (char*)&status); return; }
//設(shè)置輸入格式
status = AudioUnitSetProperty(mixUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, i,_unitFormat.streamDescription , sizeof(AudioStreamBasicDescription));
if(status!=kAudioSessionNoError) {
NSLog(@"AudioUnitSetProperty Error");
return;
}
}
status = AudioUnitSetProperty(mixUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, _unitFormat.streamDescription, sizeof(AudioStreamBasicDescription));
if (status) {
NSLog(@"AudioUnitSetProperty Error");
return;
}
status = AudioUnitSetProperty(ioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, _unitFormat.streamDescription, sizeof(AudioStreamBasicDescription));
if (status) {
NSLog(@"AudioUnitSetProperty Error");
return;
}
status = AUGraphInitialize(_auGraph);
if (status) {
NSLog(@"AudioUnitSetProperty Error");
return;
}
}
實(shí)現(xiàn)回調(diào)的方法,填充數(shù)據(jù)供輸出:
OSStatus mixInputCallBack (void * inRefCon,
AudioUnitRenderActionFlags * ioActionFlags,
const AudioTimeStamp * inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList * __nullable ioData) {
SoundBuffer *sndbuf = (SoundBuffer *)inRefCon;
UInt32 startFrame = sndbuf[inBusNumber].startFrameNum; // 從哪一幀開始
UInt32 numFrames = sndbuf[inBusNumber].numFrames; // 總的幀數(shù)
Float32 *bufferData = sndbuf[inBusNumber].data; // audio data buffer
Float32 *outA = (Float32 *)ioData->mBuffers[0].mData; // 第一聲道數(shù)據(jù)
Float32 *outB = (Float32 *)ioData->mBuffers[1].mData; // 第二聲道數(shù)據(jù)
for (UInt32 i = 0; i < inNumberFrames; ++i) {
if (inBusNumber == 0) {
outA[i] = bufferData[startFrame++]; //填充第一聲道數(shù)據(jù)(用的是第一個(gè)SoundBuffer)
} else {
outB[i] = bufferData[startFrame++]; //填充第二聲道數(shù)據(jù)(用的是第二個(gè)SoundBuffer)
}
if (startFrame > numFrames) {
// 結(jié)束了,再從0開始循環(huán)
printf("looping data for bus %d after %ld source frames rendered\n", (unsigned int)inBusNumber, (long)startFrame-1);
startFrame = 0;
}
}
sndbuf[inBusNumber].startFrameNum = startFrame; // 記錄幀數(shù)
return noErr;
}
總結(jié):AudioUnit更像是個(gè)看圖編程的過程ㄟ( ▔, ▔ )ㄏ。