最近學(xué)習(xí)了AudioUnit的官方指南讥巡,按照官方文檔簡(jiǎn)單實(shí)現(xiàn)錄音和耳返的功能。
1舔哪、首先配置AudioSession
欢顷,代碼如下:
self.graphSampleRate = 44100.0;
self.ioBufferDuration = 0.005;
NSError *error = nil;
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setPreferredHardwareSampleRate:self.graphSampleRate error:&error];
if (error) {
NSLog(@"=====error===%@",error);
exit(-1);
return;
}
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
if (error) {
NSLog(@"=====error===%@",error);
exit(-1);
return;
}
[audioSession setActive:YES error:&error];
if (error) {
NSLog(@"=====error===%@",error);
exit(-1);
return;
}
// 音頻會(huì)話激活后,根據(jù)系統(tǒng)提供的實(shí)際采樣率更新您自己的采樣率變量捉蚤。
self.graphSampleRate = [audioSession currentHardwareSampleRate];
// 還有一個(gè)其他硬件特性可能需要配置:音頻硬件I / O緩沖區(qū)持續(xù)時(shí)間抬驴。44.1 kHz采樣率的默認(rèn)持續(xù)時(shí)間約為23 ms,相當(dāng)于1,024個(gè)采樣的切片大小缆巧。如果I / O延遲對(duì)您的應(yīng)用程序至關(guān)重要布持,則可以請(qǐng)求較短的持續(xù)時(shí)間,下降到大約0.005 ms(相當(dāng)于256個(gè)采樣)陕悬,如下所示:
[audioSession setPreferredIOBufferDuration:self.ioBufferDuration error:&error];
if (error) {
NSLog(@"=====error===%@",error);
exit(-1);
return;
}
2题暖、創(chuàng)建一個(gè)AudioUnit
對(duì)象
2.1:構(gòu)建AudioComponentDescription
,主要需要設(shè)置componentType
和componentSubType
。由于我們這里是需要錄音然后輸出耳機(jī),設(shè)置如下:
//1.創(chuàng)建AudioUnit
AudioComponentDescription ioUnitDes;
ioUnitDes.componentType = kAudioUnitType_Output;
ioUnitDes.componentSubType = kAudioUnitSubType_RemoteIO;
ioUnitDes.componentManufacturer = kAudioUnitManufacturer_Apple;
ioUnitDes.componentFlags = 0;
ioUnitDes.componentFlagsMask = 0;
不同的的componentType
和componentSubType
組成有不能的作用捉超,下圖為常用不同組合的作用
2.2 創(chuàng)建AudioUnit
,AudioUnit
的創(chuàng)建方式有兩種胧卤,這里使用官方推薦的方式AUGraph
來(lái)創(chuàng)建,
//1.創(chuàng)建一個(gè)圖
OSStatus status;
status = NewAUGraph(&processingGraph);
CheckStatus(status, @"不能構(gòu)造圖", YES);
//2.創(chuàng)建一個(gè)結(jié)點(diǎn)。
AUNode ioNode;
status = AUGraphAddNode(processingGraph, &ioUnitDes, &ioNode);
CheckStatus(status, @"添加節(jié)點(diǎn)失敗", YES);
//3狂秦、打開(kāi)圖灌侣,相當(dāng)于間接創(chuàng)建了音頻處理單元
status = AUGraphOpen(processingGraph);
CheckStatus(status, @"打開(kāi)圖失敗", YES);
//4推捐、獲取ioUnit
status = AUGraphNodeInfo(processingGraph, ioNode, NULL, &_ioUnit);
CheckStatus(status, @"不能獲取 node info", YES);
上面代碼中CheckStatus
為檢測(cè)是否成功的函數(shù)
static void CheckStatus(OSStatus status, NSString *message, BOOL fatal)
{
if(status != noErr)
{
char fourCC[16];
*(UInt32 *)fourCC = CFSwapInt32HostToBig(status);
fourCC[4] = '\0';
if(isprint(fourCC[0]) && isprint(fourCC[1]) && isprint(fourCC[2]) && isprint(fourCC[3]))
NSLog(@"%@: %s", message, fourCC);
else
NSLog(@"%@: %d", message, (int)status);
if(fatal)
exit(-1);
}
}
2.3 設(shè)置AudioUnit
的屬性
//2.1設(shè)置
UInt32 flag = 1;
status = AudioUnitSetProperty(_ioUnit,kAudioOutputUnitProperty_EnableIO , kAudioUnitScope_Input,1, &flag, sizeof(flag));
CheckStatus(status, @"設(shè)置輸入scope 失敗", YES);
status = AudioUnitSetProperty(_ioUnit,kAudioOutputUnitProperty_EnableIO , kAudioUnitScope_Output,0, &flag, sizeof(flag));
CheckStatus(status, @"設(shè)置輸出scope 失敗", YES);
上面的1
表示Element1
為和錄音的麥克風(fēng)相連裂问,0
表示Element0
和輸出硬件相連。
2.4 設(shè)置流的格式AudioStreamBasicDescription
size_t bytesPerSample = sizeof(AudioUnitSampleType);
AudioStreamBasicDescription asbd = {0};
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mFormatFlags = kAudioFormatFlagsAudioUnitCanonical;
asbd.mBytesPerFrame = bytesPerSample;
asbd.mBytesPerPacket = bytesPerSample;
asbd.mBitsPerChannel = 8*bytesPerSample;
asbd.mFramesPerPacket = 1;
asbd.mChannelsPerFrame = 2;
asbd.mSampleRate = self.graphSampleRate;
status = AudioUnitSetProperty(_ioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &asbd, sizeof(AudioStreamBasicDescription));
CheckStatus(status, @"設(shè)置輸入流格式失敗", YES);
status = AudioUnitSetProperty(_ioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &asbd, sizeof(AudioStreamBasicDescription));
CheckStatus(status, @"設(shè)置輸出流格式失敗", YES);
下面的圖片為不同用途的AudioUnit
的設(shè)置格式
3牛柒、設(shè)置回調(diào)函數(shù)堪簿,設(shè)置回調(diào)函數(shù)也要兩種不同的方式,一種直接為
AudioUnit
設(shè)置可能線程不安全皮壁,還有一種就是AUGraph
來(lái)設(shè)置3.1 直接使用
AudioUnit來(lái)設(shè)置
//3椭更、設(shè)置播放回調(diào)函數(shù)
AURenderCallbackStruct playCallBack;
playCallBack.inputProc = playCallBackFuc;
playCallBack.inputProcRefCon = (__bridge void*)self;
status = AudioUnitSetProperty(_ioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Output, 0, &playCallBack, sizeof(playCallBack));
CheckStatus(status, @"set renderCallBackError", YES);
// 設(shè)置錄音回調(diào)函數(shù)
AURenderCallbackStruct recordCallback;
recordCallback.inputProc = RecordCallbackFuc;
recordCallback.inputProcRefCon = (__bridge void *)self;
AudioUnitSetProperty(_ioUnit,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Input,
1,
&recordCallback,
sizeof(recordCallback));
錄音回調(diào)函數(shù)如下:
static OSStatus RecordCallbackFuc( void * inRefCon,
AudioUnitRenderActionFlags * ioActionFlags,
const AudioTimeStamp * inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList * __nullable ioData){
ViewController *viewC = (__bridge ViewController*)inRefCon;
NSLog(@"錄音");
if (ioData) {
NSLog(@"size2 = %d", ioData->mBuffers[0].mDataByteSize);
// memcpy(ioData->mBuffers[0].mData, buffList->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize);
AudioUnitRender(viewC.ioUnit, ioActionFlags, inTimeStamp, 1, inNumberFrames, ioData);
}
return noErr;
}
播放回調(diào)函數(shù)
static OSStatus playCallBackFuc( void * inRefCon,
AudioUnitRenderActionFlags * ioActionFlags,
const AudioTimeStamp * inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList * __nullable ioData){
ViewController *viewC = (__bridge ViewController*)inRefCon;
NSLog(@"size2 = %d", ioData->mBuffers[0].mDataByteSize);
// memcpy(ioData->mBuffers[0].mData, buffList->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize);
AudioUnitRender(viewC.ioUnit, ioActionFlags, inTimeStamp, 1, inNumberFrames, ioData);
NSLog(@"播放");
return noErr;
}
3.2 使用AUGraph
來(lái)設(shè)置(缺點(diǎn)只能是指播放回調(diào)函數(shù),優(yōu)點(diǎn)線程安全)
//3蛾魄、設(shè)置回調(diào)函數(shù)
AURenderCallbackStruct playCallBack;
playCallBack.inputProc = playCallBackFuc;
playCallBack.inputProcRefCon = (__bridge void*)self;
//該方法是線程安全的(但是只能設(shè)置renderCallBack)
AUGraphSetNodeInputCallback(processingGraph, ioNode, 0, &playCallBack);
4 初始化AUGraph
//4初始化啟動(dòng)一個(gè)音頻處理圖
OSStatus result = AUGraphInitialize(processingGraph);
CheckStatus(result, @"初始化失敗", YES);
5 開(kāi)啟AUGraph
OSStatus status = AUGraphStart(processingGraph);
CheckStatus(status, @"啟動(dòng)圖失敗", YES);