AudioUnit实现简单的录音和耳返

最近学习了AudioUnit的官方指南,按照官方文档简单实现录音和耳返的功能。
1、首先配置AudioSession,代码如下

  self.graphSampleRate = 44100.0;
    self.ioBufferDuration = 0.005;
    NSError *error = nil;
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    [audioSession setPreferredHardwareSampleRate:self.graphSampleRate error:&error];
    
    if (error) {
        NSLog(@"=====error===%@",error);
        exit(-1);
        return;
    }
    [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
    if (error) {
        NSLog(@"=====error===%@",error);
        exit(-1);
        return;
    }
    [audioSession setActive:YES error:&error];
    if (error) {
        NSLog(@"=====error===%@",error);
        exit(-1);
        return;
    }
    //    音频会话激活后,根据系统提供的实际采样率更新您自己的采样率变量。
    self.graphSampleRate = [audioSession currentHardwareSampleRate];
    
    //    还有一个其他硬件特性可能需要配置:音频硬件I / O缓冲区持续时间。44.1 kHz采样率的默认持续时间约为23 ms,相当于1,024个采样的切片大小。如果I / O延迟对您的应用程序至关重要,则可以请求较短的持续时间,下降到大约0.005 ms(相当于256个采样),如下所示:
    [audioSession setPreferredIOBufferDuration:self.ioBufferDuration error:&error];
    if (error) {
        NSLog(@"=====error===%@",error);
        exit(-1);
        return;
    }

2、创建一个AudioUnit对象
2.1:构建AudioComponentDescription,主要需要设置componentTypecomponentSubType。由于我们这里是需要录音然后输出耳机,设置如下:

 //1.创建AudioUnit
    AudioComponentDescription ioUnitDes;
    ioUnitDes.componentType = kAudioUnitType_Output;
    ioUnitDes.componentSubType = kAudioUnitSubType_RemoteIO;
    ioUnitDes.componentManufacturer = kAudioUnitManufacturer_Apple;
    ioUnitDes.componentFlags = 0;
    ioUnitDes.componentFlagsMask = 0;

不同的的componentTypecomponentSubType组成有不能的作用,下图为常用不同组合的作用

391523962280_.pic_hd.jpg

2.2 创建AudioUnit,AudioUnit的创建方式有两种,这里使用官方推荐的方式AUGraph来创建,

  //1.创建一个图
   OSStatus status;
    status = NewAUGraph(&processingGraph);
    CheckStatus(status, @"不能构造图", YES);
//2.创建一个结点。
    AUNode ioNode;
    status = AUGraphAddNode(processingGraph, &ioUnitDes, &ioNode);
    CheckStatus(status, @"添加节点失败", YES);
    
    //3、打开图,相当于间接创建了音频处理单元
    status = AUGraphOpen(processingGraph);
    CheckStatus(status, @"打开图失败", YES);
    //4、获取ioUnit
    status = AUGraphNodeInfo(processingGraph, ioNode, NULL, &_ioUnit);
    CheckStatus(status, @"不能获取 node info", YES);

上面代码中CheckStatus为检测是否成功的函数

static void CheckStatus(OSStatus status, NSString *message, BOOL fatal)
{
    if(status != noErr)
    {
        char fourCC[16];
        *(UInt32 *)fourCC = CFSwapInt32HostToBig(status);
        fourCC[4] = '\0';
        
        if(isprint(fourCC[0]) && isprint(fourCC[1]) && isprint(fourCC[2]) && isprint(fourCC[3]))
            NSLog(@"%@: %s", message, fourCC);
        else
            NSLog(@"%@: %d", message, (int)status);
        
        if(fatal)
            exit(-1);
    }
}

2.3 设置AudioUnit的属性

  //2.1设置
    UInt32 flag = 1;
    status =  AudioUnitSetProperty(_ioUnit,kAudioOutputUnitProperty_EnableIO , kAudioUnitScope_Input,1, &flag, sizeof(flag));
    CheckStatus(status, @"设置输入scope 失败", YES);
    status =  AudioUnitSetProperty(_ioUnit,kAudioOutputUnitProperty_EnableIO , kAudioUnitScope_Output,0, &flag, sizeof(flag));
    CheckStatus(status, @"设置输出scope 失败", YES);

上面的1表示Element1为和录音的麦克风相连,0表示Element0和输出硬件相连。
2.4 设置流的格式AudioStreamBasicDescription

 size_t bytesPerSample = sizeof(AudioUnitSampleType);
    AudioStreamBasicDescription asbd = {0};
    asbd.mFormatID = kAudioFormatLinearPCM;
    asbd.mFormatFlags = kAudioFormatFlagsAudioUnitCanonical;
    asbd.mBytesPerFrame = bytesPerSample;
    asbd.mBytesPerPacket = bytesPerSample;
    asbd.mBitsPerChannel = 8*bytesPerSample;
    asbd.mFramesPerPacket = 1;
    asbd.mChannelsPerFrame = 2;
    asbd.mSampleRate = self.graphSampleRate;
    
    status = AudioUnitSetProperty(_ioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &asbd, sizeof(AudioStreamBasicDescription));
    CheckStatus(status, @"设置输入流格式失败", YES);
    status = AudioUnitSetProperty(_ioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &asbd, sizeof(AudioStreamBasicDescription));
    CheckStatus(status, @"设置输出流格式失败", YES);

下面的图片为不同用途的AudioUnit的设置格式

401524022790_.pic_hd.jpg

411524022810_.pic_hd.jpg

421524022825_.pic_hd.jpg

431524022849_.pic_hd.jpg

3、设置回调函数,设置回调函数也要两种不同的方式,一种直接为AudioUnit设置可能线程不安全,还有一种就是AUGraph来设置
3.1 直接使用AudioUnit来设置

    //3、设置播放回调函数
    AURenderCallbackStruct playCallBack;
    playCallBack.inputProc = playCallBackFuc;
    playCallBack.inputProcRefCon = (__bridge void*)self;
    
    
   status =  AudioUnitSetProperty(_ioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Output, 0, &playCallBack, sizeof(playCallBack));
    CheckStatus(status, @"set renderCallBackError", YES);

  // 设置录音回调函数
    AURenderCallbackStruct recordCallback;
    recordCallback.inputProc = RecordCallbackFuc;
    recordCallback.inputProcRefCon = (__bridge void *)self;
    AudioUnitSetProperty(_ioUnit,
                         kAudioOutputUnitProperty_SetInputCallback,
                         kAudioUnitScope_Input,
                         1,
                         &recordCallback,
                         sizeof(recordCallback));

录音回调函数如下:

static OSStatus RecordCallbackFuc(    void *          inRefCon,
                                AudioUnitRenderActionFlags *    ioActionFlags,
                                const AudioTimeStamp *            inTimeStamp,
                                UInt32                            inBusNumber,
                                UInt32                            inNumberFrames,
                                AudioBufferList * __nullable    ioData){
    
    
    
    ViewController *viewC = (__bridge ViewController*)inRefCon;
     NSLog(@"录音");
    if (ioData) {
        NSLog(@"size2 = %d", ioData->mBuffers[0].mDataByteSize);
        //    memcpy(ioData->mBuffers[0].mData, buffList->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize);
        AudioUnitRender(viewC.ioUnit, ioActionFlags, inTimeStamp, 1, inNumberFrames, ioData);
    }
    return noErr; 
}

播放回调函数

 static OSStatus playCallBackFuc(    void *          inRefCon,
                    AudioUnitRenderActionFlags *    ioActionFlags,
                    const AudioTimeStamp *            inTimeStamp,
                    UInt32                            inBusNumber,
                    UInt32                            inNumberFrames,
                             AudioBufferList * __nullable    ioData){
    
    ViewController *viewC = (__bridge ViewController*)inRefCon;
    NSLog(@"size2 = %d", ioData->mBuffers[0].mDataByteSize);
//    memcpy(ioData->mBuffers[0].mData, buffList->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize);
    AudioUnitRender(viewC.ioUnit, ioActionFlags, inTimeStamp, 1, inNumberFrames, ioData);
    NSLog(@"播放");
    return noErr;
}

3.2 使用AUGraph来设置(缺点只能是指播放回调函数,优点线程安全)

   //3、设置回调函数
    AURenderCallbackStruct playCallBack;
    playCallBack.inputProc = playCallBackFuc;
    playCallBack.inputProcRefCon = (__bridge void*)self;
    
    //该方法是线程安全的(但是只能设置renderCallBack)
    AUGraphSetNodeInputCallback(processingGraph, ioNode, 0, &playCallBack);

4 初始化AUGraph

  //4初始化启动一个音频处理图
    OSStatus result = AUGraphInitialize(processingGraph);
    
    CheckStatus(result, @"初始化失败", YES);

5 开启AUGraph

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

推荐阅读更多精彩内容