iOS 混音实现

原定假设需要混音的时候,就需要两个不同的unit,一个是输入输出的Remote i/o Unit 另一个是混合用的MixUnit

image.png

上图中,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);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容

  • Linear PCM 在介绍Core Audio之前,先介绍一下最常用的非压缩数字音频格式Linear PCM(线...
    huangjun0阅读 4,290评论 0 2
  • 前言 相关文章:使用VideoToolbox硬编码H.264使用VideoToolbox硬解码H.264使用Aud...
    落影loyinglin阅读 8,204评论 8 33
  • 教程一:视频截图(Tutorial 01: Making Screencaps) 首先我们需要了解视频文件的一些基...
    90后的思维阅读 4,638评论 0 3
  • 我们小区的大妈走出去都有种趾高气昂的势头,无他,在本市的广场舞江湖中,我们小区组建了最有实力的帮派。从服装...
    我的喜欢是蓝色的阅读 538评论 2 5
  • 这是个随心所欲的世界 我愿意听从自己的内心 当呼吸急促的时候 应该是闻到了你的气息 长大了就不会痛苦流涕 回头就可...
    挂居宝阅读 129评论 0 0