和其他录音播放一样,需要配置录音播放的环境,响应耳机事件等。
NSError *error;
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
[audioSession setPreferredSampleRate:44100 error:&error];
[audioSession setPreferredInputNumberOfChannels:1 error:&error];
[audioSession setPreferredIOBufferDuration:0.05 error:&error];
AudioComponentDescription是用来描述unit 的类型,包括均衡器,3D混音,多路混音,远端输入输出,VoIP输入输出,通用输出,格式转换等,在这里使用远端输入输出。
AudioComponentDescription audioDesc;
audioDesc.componentType = kAudioUnitType_Output;
audioDesc.componentSubType = kAudioUnitSubType_RemoteIO;
audioDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
audioDesc.componentFlags = 0;
audioDesc.componentFlagsMask = 0;
AudioComponent inputComponent = AudioComponentFindNext(NULL, &audioDesc);
AudioComponentInstanceNew(inputComponent, &audioUnit);
设置采样率为44100,单声道,16位的格式,注意输入输出都要设置。
AudioStreamBasicDescription audioFormat;
audioFormat.mSampleRate = 44100;
audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 1;
audioFormat.mBitsPerChannel = 16;
audioFormat.mBytesPerPacket = 2;
audioFormat.mBytesPerFrame = 2;
AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
INPUT_BUS,
&audioFormat,
sizeof(audioFormat));
AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
OUTPUT_BUS,
&audioFormat,
sizeof(audioFormat));
在默认情况下,输入是关闭的,输出是打开的。在unit的Element中,Input用“1”(和I很像)表示,Output用“0”(和O很像)表示。
UInt32 flag = 1;
AudioUnitSetProperty(audioUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
INPUT_BUS,
&flag,
sizeof(flag));
AudioUnitSetProperty(audioUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
OUTPUT_BUS,
&flag,
sizeof(flag));
根据应用的场景需求,可以在输入输出设置回调,以输入回调为例:
AURenderCallbackStruct recordCallback;
recordCallback.inputProc = RecordCallback;
recordCallback.inputProcRefCon = (__bridge void *)self;
AudioUnitSetProperty(audioUnit,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global,
INPUT_BUS,
&recordCallback,
sizeof(recordCallback));
需要定义回调函数,回调函数是AURenderCallback类型的,按照AUComponent.h中定义的参数类型,定义出输入回调函数:
static OSStatus RecordCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
AudioUnitRender(audioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, buffList);
return noErr;
}
这是获取录音数据很重要的一步,需要分配缓存来存储实时的录音数据。如果不这样做,录音数据也可以在输出的时候获取,但意义不一样,获取录音数据应该在输入回调中完成,而不是输出回调。
UInt32 flag = 0;
AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output,
INPUT_BUS,
&flag,
sizeof(flag));
buffList = (AudioBufferList*)malloc(sizeof(AudioBufferList));
buffList->mNumberBuffers = 1;
buffList->mBuffers[0].mNumberChannels = 1;
buffList->mBuffers[0].mDataByteSize = 2048 * sizeof(short);
buffList->mBuffers[0].mData = (short *)malloc(sizeof(short) * 2048);
通过以上设置,可以实时录音,并实时播放(本例中,输入输出都打开了)。
- 在真机上运行的时候,会报错,错误信息如下:
这是因为没有开启录音权限,以source code的方式打开Info.plist文件,在dict标签中加入以下属性:
<key>NSMicrophoneUsageDescription</key>
<string>microphoneDesciption</string>
再次运行,就OK了。
2.回调时间间隔问题。 Audio Unit的延迟很低,回调时间非常稳定,很适合严格地实时处理音频,即使把时间设置成0.000725623582766秒,回调时间依然很准:
事实上,Audio Unit没有回调间隔的配置,但是我们可以通过上下文环境配置,即:
[audioSession setPreferredIOBufferDuration:0.05 error:&error];
这样设置duration为0.05秒,表示每隔0.05秒就去读取缓存数据。假设采样率为44100,采样位数16,这时buffer大小应该为44100 * 0.05 * 16 / 8 = 4410,但是,Audio Unit 的buffer的大小是2的幂次方,那么就不可能有4410,这时buffer实际大小为4096,反过来计算时间就是0.464秒,这也就解释了在Audio Queue中近似计算回调时间的原因了。 除此之外,如果不用AudioSession设置时间的话,会有一个默认大小的buffer,这个大小在模拟器和真机上不相同,所以为了程序可控,这个设置很有必要。
3.关于播放问题 测试发现,用耳机的效果更好,不用耳机在播放的时候会有噪声。如果想获得清晰的效果,可以将每次的PCM数据写入到文件,然后回放。推荐使用Lame,这个可以将PCM转换成MP3。
4.读取PCM数据 PCM数据存放在AudioBuffer的结构体中,音频数据是void *类型的数据:
/*!
@struct AudioBuffer
@abstract A structure to hold a buffer of audio data.
@field mNumberChannels
The number of interleaved channels in the buffer.
@field mDataByteSize
The number of bytes in the buffer pointed at by mData.
@field mData
A pointer to the buffer of audio data.
*/
struct AudioBuffer
{
UInt32 mNumberChannels;
UInt32 mDataByteSize;
void* __nullable mData;
};
typedef struct AudioBuffer AudioBuffer;
如果采样位数是16位,即2Byte,即mData中每2Byte是一个PCM数据,以获取第一个数据为例:
short *data = (short *)buffList->mBuffers[0].mData;
NSLog(@"%d", data[0]);
这里需要注意的就是类型转换的时候位数要一致。