上一篇文章介绍了怎么用AudioUnit实现录音功能,今天在之前的录音基础上添加播放功能。
AVAudioSession配置和音频单元初始化和录音保持一致。
在录制过程中我们对音频单元配置了麦克风作为音频输入, 现在我们要实现播放功能,现在配置扬声器作为音频输出:
UInt32 flag = 1;
CheckStatus(AudioUnitSetProperty(_audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &flag, sizeof(flag)), @"扬声器配置失败", NO);
输出配置成功之后我们需要给输出配置对应的音频编码, 我们将输入输出音频编码保持一致:
CheckStatus(AudioUnitSetProperty(_ioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &_streamDescription, sizeof(_streamDescription)), @"设置麦克风音频参数", NO);
CheckStatus(AudioUnitSetProperty(_ioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &_streamDescription, sizeof(_streamDescription)), @"设置扬声器音频参数", NO);
注:这里肯定会有人有疑惑为什么麦克风音频参数选择Output,而扬声器却选择Input,我们先看一张图:
这就是我们使用所以用的RemoteIO图解,他包含两个I(Element),分别负责输入(用1表示),输出(用0表示)。而没个Element有自己对应的输入输出。
我们可以这样理解,Element1负责输入,Element1的input负责音频采集,然后通过自己的output输出音频,
Element0负责输出, Element0的input负责读取音频,然后通过output播放音频
我们回过头看看我们设置音频编码参数代码:
输入端参数:这里的output并非真正的扬声器输出, 而是Element1的输出
kAudioUnitScope_Output, 1
输出端参数:这里的input同样并非真正的麦克风输入, 而是Element0的输入
kAudioUnitScope_Input, 0
下一步设置播放回掉:
AURenderCallbackStruct playCallbackStruct;
playCallbackStruct.inputProcRefCon = (__bridge void *)self;
playCallbackStruct.inputProc = playCallback;
CheckStatus(AudioUnitSetProperty(_audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Group, 0, &playCallbackStruct, sizeof(playCallbackStruct)), @"设置播放回掉", NO);
最后一步就是将麦克风的音频通过回掉传入扬声器进行播放,我们看看两个回掉代码的处理:
static OSStatus recordingCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {
ViewController *controller = (__bridge ViewController *)inRefCon;
controller->_bufferList.mNumberBuffers = 1;
controller->_bufferList.mBuffers[0].mNumberChannels = 1;
controller->_bufferList.mBuffers[0].mDataByteSize = 2 * inNumberFrames;
controller->_bufferList.mBuffers[0].mData = malloc(inNumberFrames * 2);
// 我们利用AudioUnitRender函数将音频数据存储起来
CheckStatus(AudioUnitRender(controller->_audioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &(controller->_bufferList)), @"获取数据失败", NO);
return noErr;
}
// 这个是播放驱动, 在播放过程中音频单元会通过回掉来读取音频数据,我们只需要在这里将音频数据塞入ioData中即可, AudioBufferList类似缓冲区,录制的时候将音频数据写入缓冲区, 然后播放的时候从缓冲区读取数据
static OSStatus playCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {
ViewController *controller = (__bridge ViewController *)inRefCon;
// memcpy是一个c和c++内存拷贝函数, 函数原型为: void *memcpy(void *dest, const void *src, size_t n), 从scr所指内存拷贝值dest,拷贝长度为n
// 我们通过拷贝将录音回掉中存入的音频, 塞入播放器的缓冲区
memcpy(ioData->mBuffers[0].mData, controller->_bufferList.mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize);
return noErr;
}
AudioOutputUnitStart(_audioUnit);