iOS录音
-
根据开发文档的图可知,iOS音频相关的用的比较多的自顶向下的又 AVFoundation -> AudioToolBox -> Audio Unit
技术选择上如果是录音然后获取内存中音频的数据,然后进行网络传输或者存本地,那么AudioQueue或者AudioUnit都能做到。因为之后可能会用到一些混响之类的,所以以下说明都是根据AudioUnit来讲述的。
文章会根据代码来引出每个参数涉及到的知识点,然后进行说明,个人比较喜欢参考官方文档,其实很多东西在官方文档里面都能找到答案。
-
iOS上提供了多重audio units,主要的功能大概可以分为,effect mixer io 和 format converter ,这次我们用到的是io模块文档地址
在iOS当中,涉及到硬件的一般都会初始化一个session,然后软件方面的一般都是一个description
录音的时候我们用到audio unit,第一部是先初始化一个session
AVAudioSession *auSession = [AVAudioSession sharedInstance];
[auSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
[auSession setActive:YES error:nil];
audio unit 提供了 6个常用的session类别对应着不同的功能
AVAudioSessionCategory | 描述 |
---|---|
AVAudioSessionCategoryAmbient | 常用于播放背景音乐 |
AVAudioSessionCategorySoloAmbient | 这个也是用于播放背景音乐但是会打断别的音乐的播放 |
AVAudioSessionCategoryPlayback | 用于播放音乐 |
AVAudioSessionCategoryRecord | 提供同时录音的功能 |
AVAudioSessionCategoryPlayAndRecord | 可以同时录音和播放,适合于通话等场合 |
AVAudioSessionCategoryAudioProcessing | 硬编码音频不能播放和录制 |
官网地址
- 设置buffer的duration,这个值决定音频的延迟。越小的话延迟越低
[auSession setPreferredIOBufferDuration:0.05 error:&error];
- 既然要用到audio unit 那么肯定要设置一些参数 AudioComponentDescription
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'
};
其实官方文档已经写得很清楚不同的unit对应的type和sub type 选取要用的设置。audio units guides
- componentManufacturer 厂家的这个参数一般固定位apple
- componentFlags和componentFlagsMask如果没有具体要设置的值得话就填写0,其他事根据具体用到的component来设置
- AudioComponentFindNext 获取系统中的componet
- AudioComponentInstanceNew 初始化 返回值是 OSStatus类型,具体代表什么可以通过OSStatus 查询
AudioComponentDescription inputDesc;
inputDesc.componentType = kAudioUnitType_Output;
inputDesc.componentSubType = kAudioUnitSubType_RemoteIO;
inputDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
inputDesc.componentFlags = 0;
inputDesc.componentFlagsMask = 0;
AudioComponent inputComponent = AudioComponentFindNext(NULL, &inputDesc);
status = AudioComponentInstanceNew(inputComponent, &audioUnit);
if (status != noErr) {
NSLog(@"AudioComponentInstanceNew: %d", status);
}
- 设置AudioStreamBasicDescription(下文简称ASBD)
- 先设置图中红色部分的声音要素
- kAudioFormatFlagIsNonInterleaved 这个参数其实是指定pcm中的左右声道排布,是lrlrlr形式还是说llllrrrr这样
- mBytesPerFrame 一般可以通过bits depth * channels / 8计算的到
-
关于audio bus的概念
- 这个是IO unit的模型,具体可以看出有两个element,一般称为bus,然后 1 是负责输入的,0 是负责输出的。输入的io一般是关闭状态所以要打开
- 每个bus都一个input scope和output scope 这些在设置参数的时候会用到
- 这次录音之后都是存本本地,所以不会用到 bus0
AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &inputFlag, sizeof(inputFlag));
AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat,kAudioUnitScope_Output , 1, &inputASBD, sizeof(inputASBD));
- 具体输入有了,那么要设置输出根据上面的思路就是用到element 1 的 output scope
AURenderCallbackStruct recordCallBackStruct;
recordCallBackStruct.inputProc = RecordProc;
recordCallBackStruct.inputProcRefCon = (__bridge void *)self;
AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Output, 1, &recordCallBackStruct, sizeof(recordCallBackStruct));
inputProc 是AURenderCallback类型的函数,录音的时候回在iodata里面拿到具体的数据,然后调用AudioUnitRender来进行渲染保存到自己的buffers里面
{
AudioBufferList *buffers;
}
//这里我先设置为1
buffers = (AudioBufferList *)malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer));
buffers->mNumberBuffers = 1;
buffers->mBuffers[0].mNumberChannels = 1;
buffers->mBuffers[0].mDataByteSize = RH_BFFER_SIZE;
buffers->mBuffers[0].mData = malloc(RH_BFFER_SIZE);
static OSStatus RecordProc(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
RHRecorder *objSelf = (__bridge RHRecorder *)inRefCon;
objSelf->buffers->mNumberBuffers = 1;
OSStatus status = noErr;
status = AudioUnitRender(objSelf->audioUnit,
ioActionFlags,
inTimeStamp,
inBusNumber,
inNumberFrames,
objSelf->buffers);
if (status != noErr) {
NSLog(@"render error :%d", status);
}
//save pcm steam
[objSelf saveRecordDataToLocalPCM:objSelf->buffers->mBuffers[0].mData
size:objSelf->buffers->mBuffers[0].mDataByteSize];
return 0;
}