iOS 音频边录边播

iOS利用AudioUnit实现音频边录边播功能

上一篇文章介绍了怎么用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,我们先看一张图:

iOS 音频边录边播_第1张图片

这就是我们使用所以用的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);

你可能感兴趣的:(iOS,音视频)