原定假设需要混音的时候,就需要两个不同的unit,一个是输入输出的Remote i/o Unit 另一个是混合用的MixUnit
上图中,MixUnit的作用主要是将两个文件流合成一个,然后输出
MixUnit可以拥有多个Bus
AuGraph 连接一组audio Unit 之间的输入和输出,构成一个Unit,同时也为audio unit的输入提供了回调。AUGraph抽象了音频流的处理过程,子结构可以作为一个AUNode潜入到更大的结构里面进行处理。AUGraph可以遍历整个图的信息,每个节点都是一个或者多个AUNode,音频数据在点与点之间流通,并且每个图都有一个输出节点。输出节点可以用来启动、停止整个处理过程。
AUGraph的调用过程如下:
//新建并打开
NewAUGraph(&auGraph)
AUGraphOpen(auGraph)
//定义AUNode
AUNode outputNode;
//加入新的节点1
AUGraphAddNode(auGraph, &outputAudioDesc, &outputNode) //这里的outputAudioDesc为对音频流的描述
AUGraphNodeInfo(auGraph, outputNode, NULL, &outputUnit)//AudioUnit outputUnit
//加入新节点2
AUGraphAddNode(auGraph, &mixAudioDesc, &mixNode) //这里的mixAudioDesc为对音频流的描述
AUGraphNodeInfo(auGraph, mixNode, NULL, &mixUnit) //AudioUnit mixUnit 另一个Unit
//连接到对应
AUGraphConnectNodeInput(auGraph, mixNode, MIX_UNIT_OUTPUT_BUS, outputNode, REMOTE_IO_UNIT_OUTPUT_BUS)
//打开录制
AudioUnitSetProperty(outputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, REMOTE_IO_UNIT_INPUT_BUS, &flag,sizeof(1))
//设置一个回调
AURenderCallbackStruct recordCallback;
recordCallback.inputProc = RecordCallback;
recordCallback.inputProcRefCon = (__bridge void *)self;
AudioUnitSetProperty(outputUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Output, REMOTE_IO_UNIT_INPUT_BUS, &recordCallback, sizeof(recordCallback))
//开始
AUGraphInitialize(auGraph)
AUGraphStart(auGraph)
需要提前初始化的Buffer and Format;
AudioBufferList *bufferList;
Byte *buffer;
AudioStreamBasicDescription audioFormat;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[[AVAudioSession sharedInstacne] setPreferredIOBufferDruation:0.02 error:nil];
//buffer
unit32_t numberBuffers = 1;
bufferList = (AudioBufferList *)malloc(sizeof(AudioBufferList));
bufferList->mNumberBuffers = numberBuffers;
bufferList->mBuffers[0].mNumberChannels = 1;
bufferList->mBuffers[0].mDataByteSize = 2048*2*10;
bufferList->mBuffers[0].mData = malloc(2048*2*10);
buffer = malloc(2048*2*10);
//audio format
audioForamt.mSampleRate = 44100;
audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsNonInterleaved;
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 1;
audioFormat.mBytesPerPacket = 2;
audioFormat.mBytesPerFrame = 2;
audioFormat.mBitsPerChannel = 16;
1、初始化AUGraph
//AUGraph 初始化
CheckError(NewAUGraph(&auGraph), "NewAUGraph error");
CheckError(AUGraphOpen(auGraph), "open graph fail”);
// output audio unit
AudioComponentDescription outputAudioDesc;
outputAudioDesc.componentType = kAudioUnitType_Output;
outputAudioDesc.componentSubType = kAudioUnitSubType_RemoteIO;
outputAudioDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
outputAudioDesc.componentFlags = 0;
outputAudioDesc.componentFlagsMask = 0;
AUNode outputNode;
CheckError(AUGraphAddNode(auGraph, &outputAudioDesc, &outputNode), "add node fail");
CheckError(AUGraphNodeInfo(auGraph, outputNode, NULL, &outputUnit), "get audio unit fail”);
// mix audio unit
AudioComponentDescription mixAudioDesc;
mixAudioDesc.componentType = kAudioUnitType_Mixer;
mixAudioDesc.componentSubType = kAudioUnitSubType_MultiChannelMixer;
mixAudioDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
mixAudioDesc.componentFlags = 0;
mixAudioDesc.componentFlagsMask = 0;
AUNode mixNode;
CheckError(AUGraphAddNode(auGraph, &mixAudioDesc, &mixNode), "add node fail");
CheckError(AUGraphNodeInfo(auGraph, mixNode, NULL, &mixUnit), "get audio unit fail”);
//connect
CheckError(AUGraphConnectNodeInput(auGraph, mixNode, MIX_UNIT_OUTPUT_BUS, outputNode, REMOTE_IO_UNIT_OUTPUT_BUS), "connect fail");
// set format
CheckError(AudioUnitSetProperty(outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, REMOTE_IO_UNIT_INPUT_BUS, &audioFormat, sizeof(audioFormat)), "set format fail");
CheckError(AudioUnitSetProperty(outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, REMOTE_IO_UNIT_OUTPUT_BUS, &audioFormat, sizeof(audioFormat)), "set fomat fail”);
//enable record
UInt32 flag = 1;
CheckError(AudioUnitSetProperty(outputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, REMOTE_IO_UNIT_INPUT_BUS, &flag,sizeof(flag)), "set flag fail”);
// set callback
AURenderCallbackStruct recordCallback;
recordCallback.inputProc = RecordCallback;
recordCallback.inputProcRefCon = (__bridge void *)self;
CheckError(AudioUnitSetProperty(outputUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Output, REMOTE_IO_UNIT_INPUT_BUS, &recordCallback, sizeof(recordCallback)), "set property fail");
CheckError(AUGraphInitialize(auGraph), "init augraph fail");
CheckError(AUGraphStart(auGraph), "start graph fail");
对应的音频内容混合后回调
static OSStatus RecordCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
ViewController *vc = (__bridge ViewController *)inRefCon;
vc->buffList->mNumberBuffers = 1;
OSStatus status = AudioUnitRender(vc->outputUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, vc->buffList);
if (status != noErr) {
NSLog(@"AudioUnitRender error:%d", status);
}
NSLog(@"RecordCallback size = %d", vc->buffList->mBuffers[0].mDataByteSize);
[vc writePCMData:vc->buffList->mBuffers[0].mData size:vc->buffList->mBuffers[0].mDataByteSize];
return noErr;
}
2、初始化MixUnit 和output Unit
UInt32 busCount = 2;
CheckError(AudioUnitSetProperty(mixUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, MIX_UNIT_INPUT_BUS0, &busCount, sizeof(UInt32)), "set property fail”);
UInt32 size = sizeof(UInt32);
CheckError(AudioUnitGetProperty(mixUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, MIX_UNIT_INPUT_BUS0, &busCount, &size), "get property fail");
CheckError(AudioUnitGetProperty(outputUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Global, REMOTE_IO_UNIT_INPUT_BUS, &busCount, &size), "get property fail”);
//文件流读取回调
AURenderCallbackStruct callback0;
callback0.inputProc = mixCallback0;
callback0.inputProcRefCon = (__bridge void *)self;
CheckError(AudioUnitSetProperty(mixUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, MIX_UNIT_INPUT_BUS0, &callback0, sizeof(AURenderCallbackStruct)), "add mix callback fail");
CheckError(AudioUnitSetProperty(mixUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, MIX_UNIT_INPUT_BUS0, &audioFormat, sizeof(AudioStreamBasicDescription)), "set mix format fail”);
//麦克风采麦回调
AURenderCallbackStruct callback1;
callback1.inputProc = mixCallback1;
callback1.inputProcRefCon = (__bridge void *)self;
CheckError(AudioUnitSetProperty(mixUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, MIX_UNIT_INPUT_BUS1, &callback1, sizeof(AURenderCallbackStruct)), "add mix callback fail”);
CheckError(AudioUnitSetProperty(mixUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, MIX_UNIT_INPUT_BUS1, &audioFormat, sizeof(AudioStreamBasicDescription)), "set mix format fail");
音频输入的回调
#pragma mark - callback
static OSStatus mixCallback0(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) {
ViewController *vc = (__bridge ViewController *)inRefCon;
NSInteger bytes = CONST_BUFFER_SIZE < ioData->mBuffers[0].mDataByteSize * 2 ? CONST_BUFFER_SIZE : ioData->mBuffers[0].mDataByteSize * 2; //
bytes = [vc->inputSteam read:vc->buffer maxLength:bytes];
for (int i = 0; i < bytes; ++i) {
((Byte*)ioData->mBuffers[0].mData)[i/2] = vc->buffer[i];
}
ioData->mBuffers[1].mDataByteSize = (UInt32)bytes / 2;
return noErr;
}
static OSStatus mixCallback1(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) {
ViewController *vc = (__bridge ViewController *)inRefCon;
memcpy(ioData->mBuffers[0].mData, vc->buffList->mBuffers[0].mData, vc->buffList->mBuffers[0].mDataByteSize);
ioData->mBuffers[0].mDataByteSize = vc->buffList->mBuffers[0].mDataByteSize;
return noErr;
}
写入的函数
- (void)writePCMData:(Byte *)buffer size:(int)size {
static FILE *file = NULL;
NSString *path = [NSTemporaryDirectory() stringByAppendingString:@"/record.pcm"];
if (!file) {
file = fopen(path.UTF8String, "w");
}
fwrite(buffer, size, 1, file);
}
检查函数
static void CheckError(OSStatus error, const char *operation)
{
if (error == noErr) return;
char str[20];
// see if it appears to be a 4-char-code
*(UInt32 *)(str + 1) = CFSwapInt32HostToBig(error);
if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) {
str[0] = str[5] = '\'';
str[6] = '\0';
} else
// no, format it as an integer
sprintf(str, "%d", (int)error);
fprintf(stderr, "Error: %s (%s)\n", operation, str);
exit(1);
}