工作生活Swift

iOS AudioUnit学习

2019-07-03  本文已影响0人  yitez

AudioUnit是iOS底层音频框架,相比于AudioQueue和AVAudioRecorder,能够对音频数据进行更多的控制,可以用来进行混音、均衡、格式转换、实时IO录制、回放、离线渲染等音频处理。

1、AudioUnit录音:

先看下苹果官方的原理图(此处以I/O单元为例):


image.png

1、一个AudioUnit包含2个element。
2、每个element包含输入输入部分(scope)。
3、硬件到element的部分(即图中淡蓝色部分)我们无法介入,我们能控的就element与我们APP关联的部分(即图中淡黄色部分)。
4、实现录音就是主要关注element1到APP的过程,播放则是关注APP到element0的过程。

代码实现:
代码只实现录音功能,所以只使用了element1:

设置音频格式:

-  (void)initRecordFormat {
    _recordFormat.mSampleRate =  32000;  //采样率
    _recordFormat.mChannelsPerFrame = 1; //声道数量
    //编码格式
    _recordFormat.mFormatID = kAudioFormatLinearPCM;
    _recordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
    //每采样点占用位数
    _recordFormat.mBitsPerChannel = 16;
    //每帧的字节数
    _recordFormat.mBytesPerFrame = (_recordFormat.mBitsPerChannel / 8) * _recordFormat.mChannelsPerFrame;
    //每包的字节数
    _recordFormat.mBytesPerPacket = _recordFormat.mBytesPerFrame;
    //每帧的字节数
    _recordFormat.mFramesPerPacket = 1;
}

接着实例化AudioUnit,配置相关属性和方法

- (void)initConfig {
    //配置描述
    AudioComponentDescription acd;
    acd.componentType = kAudioUnitType_Output;
    acd.componentManufacturer = kAudioUnitManufacturer_Apple;
    //remoteIO对应的就是(I/O)Unit
    acd.componentSubType = kAudioUnitSubType_RemoteIO;
    acd.componentFlags = 0;
    acd.componentFlagsMask = 0;
    
    //AudioComponent类似组件工厂,用于实例化audioUnit
    AudioComponent comp = AudioComponentFindNext(nil, &acd);
    OSStatus status =  AudioComponentInstanceNew(comp, &_audioUnit);
    if(status!= kAudioSessionNoError) {
        NSLog(@"InstanceError");
        return;
    }
    
    //开启麦克风到Element1的inputScope部分,参数1代表element1。
    UInt32 flag=1;
    OSStatus statusPerpotyIO =  AudioUnitSetProperty(_audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flag, sizeof(flag));  
    if(statusPerpotyIO!= kAudioSessionNoError) {
        NSLog(@"SetPropertyIOError");
        return;
    }
  
  
    //设置Element1到APP的outScope部分的流格式(即我们需要得到的音频数据格式)
    OSStatus statusPerpotyFR =  AudioUnitSetProperty(_audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &_recordFormat, sizeof(_recordFormat));
    if(statusPerpotyFR!= kAudioSessionNoError) {
        NSLog(@"SetPropertyFRError");
        return;
    }
    
    //设置录制过程的回调函数
    AURenderCallbackStruct callBackSt;
    callBackSt.inputProc = inputCallBack;
    //将对象指针传入回调函数内部,方便内部使用
    callBackSt.inputProcRefCon = (__bridge void * _Nullable)(self);
    OSStatus statusPerpotyCall =  AudioUnitSetProperty(_audioUnit,
                                                        kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &callBackSt, sizeof(callBackSt));
    if(statusPerpotyCall!= kAudioSessionNoError) {
        NSLog(@"SetPropertyCallError");
        return;
    }
}

设置AudioSession模式,开启

- (void)setAudioSessionEnable:(BOOL)YesOrNo {
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
    [[AVAudioSession sharedInstance] setActive:YesOrNo error:nil];
}

启动audioUnit,开始录音

- (void)startRecord {
    OSStatus statusInit = AudioUnitInitialize(_audioUnit);
    if(statusInit!= kAudioSessionNoError) {
        NSLog(@"statusInitError");
        return;
    }
    
    AudioOutputUnitStart(_audioUnit);
    self.isRecording = YES;
}

回调函数实现,获取音频数据:

OSStatus   inputCallBack    (void *                            inRefCon,
                              AudioUnitRenderActionFlags *      ioActionFlags,
                              const AudioTimeStamp *            inTimeStamp,
                              UInt32                            inBusNumber,
                              UInt32                            inNumberFrames,
                              AudioBufferList * __nullable      ioData)
{
    
    YTAudioUnitManager *audioManager = [YTAudioUnitManager sharedManager];
    //创建bufferlist
    AudioBufferList bufferList;
    bufferList.mNumberBuffers = 1;
    bufferList.mBuffers[0].mDataByteSize = sizeof(SInt16)*inNumberFrames;
    bufferList.mBuffers[0].mNumberChannels = 1;
    bufferList.mBuffers[0].mData = (SInt16*) malloc(sizeof(SInt16)*inNumberFrames);
    
    //将unit的数据渲染到bufferList
    OSStatus status =  AudioUnitRender(audioManager.audioUnit,
                    ioActionFlags,
                    inTimeStamp,
                    1,
                    inNumberFrames,
                    &bufferList);
    
    //这里是将数据写入文件
   // ExtAudioFileWrite(audioManager.fileInfo->extAudioFileRef, inNumberFrames, &bufferList);
    
    return status;
}

停止录音:

- (void)stopRecord {
    [self setAudioSessionEnable:NO];
    AudioOutputUnitStop(_audioUnit);
    AudioUnitUninitialize(_audioUnit);
    ExtAudioFileDispose(_fileInfo->extAudioFileRef);
    self.isRecording = NO;
}

2、AudioUnit混音:

如果只是使用AudioUnit实现播放和录音功能,未免太大材小用,我们来看看混音是如何实现的。
混音是把多种来源的声音,整合至一个立体音轨(Stereo)或单音音轨(Mono)中,此处是读取两段音频,由左右声道同时进行播放。

官方也很贴心的给出了demo:Apple官方Demo地址

原理:
首先我们看下官方的使用原理图:

image.png
1、整体是AudioGraph,用来管理多个AudioUnit,此处涉及到是I/O单元和Mix单元。
2、两个音频数据源,提供数据给Mix Unit,Mix Unit具有多个input通道,但是只有一个输出通道。
3、Mix Unit将处理完的数据传输给I/O单元,I/O单元负责播放。

具体实现:

变量定义:

//定义结构体用来保存音频数据的信息
typedef struct {
    AudioStreamBasicDescription asbd;
    Float32 *data;
    UInt32 numFrames;
    UInt32 startFrameNum;
} SoundBuffer;

@interface YTAudioMixManager() {
    SoundBuffer mSoundBuffer[2];  //两个音频文件
}
@property (nonatomic,assign)AUGraph auGraph;
//针对音频文件的格式
@property (nonatomic,strong)AVAudioFormat *fileFormat;
//针对unit的格式
@property (nonatomic,strong)AVAudioFormat *unitFormat;
@end

初始化Format:

-  (void)initRecordFormat {
     //Audiounit的描述 ,
     //声道为2,
    //interleaved为NO,使左右声道的数据分别存储在AudioBufferList的两个AudioBuffer中。
     _unitFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
                                                    sampleRate:44100
                                                      channels:2
                                                   interleaved:NO];
    //文件音数据的描述 ,
    //声道为1,
    //interleaved为YES
    _fileFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
                                                   sampleRate:44100
                                                     channels:1
                                                  interleaved:YES];
}

读取两个音频文件数据:

//读取音频文件数据
- (void)loadDataFromURLS:(NSArray *)urlNames {
    for (int i =0;i<urlNames.count;i++) {
        
        NSString *urlName = urlNames[i];
        CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)urlName, NULL);
        ExtAudioFileRef audioFileRef;
        ExtAudioFileOpenURL(url, &audioFileRef);
        
        
        OSStatus status;
        UInt32 propSize = sizeof(AudioStreamBasicDescription);
        //设置我们想要要获取的音频文件格式,ExtAudioFile是自带转码功能的。
        status = ExtAudioFileSetProperty(audioFileRef, kExtAudioFileProperty_ClientDataFormat, propSize, _fileFormat.streamDescription);
        if(status!=kAudioSessionNoError) {
            NSLog(@"FileGetProperty Error");
            return;
        }
        
        AudioStreamBasicDescription fileFormat;
        UInt32 formSize = sizeof(fileFormat);
     //读取文件格式属性  
        status =  ExtAudioFileGetProperty(audioFileRef, kAudioFileStreamProperty_FileFormat, &formSize, &fileFormat);
        if(status!=kAudioSessionNoError) {
            NSLog(@"FileGetProperty Error");
            return;
        }
        //读取文件帧数 
        UInt64 numFrames;
        UInt32 numFramesSize = sizeof(numFrames);
        status = ExtAudioFileGetProperty(audioFileRef, kExtAudioFileProperty_FileLengthFrames, &numFramesSize, &numFrames);
        if(status!=kAudioSessionNoError) {
            NSLog(@"FileGetProperty Error");
            return;
        }
        
        mSoundBuffer[i].numFrames = (UInt32)numFrames;
        mSoundBuffer[i].asbd = fileFormat;
        
        UInt64 samples = numFrames * mSoundBuffer[i].asbd.mChannelsPerFrame;
        mSoundBuffer[i].data = (Float32 *)calloc(samples, sizeof(Float32));
        mSoundBuffer[i].sampleNum = 0;
        
        //将文件数据读取传入BufferList
        AudioBufferList bufList;
        bufList.mNumberBuffers = 1;
        bufList.mBuffers[0].mNumberChannels = 1;
        bufList.mBuffers[0].mData = (Float32*)malloc(sizeof(Float32)*samples);
        bufList.mBuffers[0].mDataByteSize = (Float32)samples * sizeof(Float32);
        UInt32 numPackets = (UInt32)numFrames;
        status = ExtAudioFileRead(audioFileRef, &numPackets, &bufList);
        
        //将bufferList数据复制给mSoundBuffer
        memcpy(mSoundBuffer[i].data, bufList.mBuffers[0].mData , bufList.mBuffers[0].mDataByteSize);
        
        if(status!=kAudioSessionNoError) {
//            printf("ExtAudioFileRead Error");
            free(mSoundBuffer[i].data);
            mSoundBuffer[i].data = 0;
        }
        ExtAudioFileDispose(audioFileRef);
    }
}

设置AudioUnit相关对象

- (void)initAudioUnit {
    
    //IO单元描述
    AudioComponentDescription IOacd;
    IOacd.componentType = kAudioUnitType_Output;
    IOacd.componentManufacturer = kAudioUnitManufacturer_Apple;
    //remoteIO对应的就是(I/O)Unit
    IOacd.componentSubType = kAudioUnitSubType_RemoteIO;
    IOacd.componentFlags = 0;
    IOacd.componentFlagsMask = 0;
    
    //mix单元描述
    AudioComponentDescription mixacd;
    mixacd.componentType = kAudioUnitType_Mixer;
    mixacd.componentManufacturer = kAudioUnitManufacturer_Apple;
    mixacd.componentSubType = kAudioUnitSubType_MultiChannelMixer;
    mixacd.componentFlags = 0;
    mixacd.componentFlagsMask = 0;
    
    OSStatus status;
    status=  NewAUGraph (&_auGraph);
    if(status!=kAudioSessionNoError) {
        NSLog(@"NewAUGraph Error");
         return;
    }
    
    
    AUNode ioNode;
    AUNode mixNode;
    
    AudioUnit ioUnit;
    AudioUnit mixUnit;
    
    //添加node
    AUGraphAddNode (_auGraph,&IOacd,&ioNode);
    AUGraphAddNode (_auGraph,&mixacd, &mixNode);
    
    //建立两个node的输入和输出连接
    status = AUGraphConnectNodeInput(_auGraph, mixNode, 0, ioNode, 0);
    if (status!=kAudioSessionNoError) {
        printf("AUGraphConnect Error");
        return;
    }

    AUGraphOpen (_auGraph);
    if(status!=kAudioSessionNoError) {
        NSLog(@"AUGraphOpen Error");
         return;
    }
    
    //获取node对应的Aunit
    status = AUGraphNodeInfo(_auGraph, mixNode, NULL, &mixUnit);
    if(status!=kAudioSessionNoError) {
        NSLog(@"AUGraphNodeMixInfo Error");
        return;
    }
    status = AUGraphNodeInfo(_auGraph, ioNode, NULL, &ioUnit);
    if(status!=kAudioSessionNoError) {
        NSLog(@"AUGraphOpen Error");
        return;
    }
    //设置输入数量
    int elementCount = 2;
    status = AudioUnitSetProperty(mixUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &elementCount, sizeof(elementCount));
    if(status!=kAudioSessionNoError) {
        NSLog(@"AudioUnitSetProperty Error");
        return;
    }
    

    
    for (int i = 0; i < elementCount;i++) {
        // setup render callback struct
        AURenderCallbackStruct rcbs;
        rcbs.inputProc = &mixInputCallBack;
        rcbs.inputProcRefCon = mSoundBuffer;
        
        //给mix的两个element设置回调
        status = AUGraphSetNodeInputCallback(_auGraph, mixNode, i, &rcbs);
       if (status) { printf("AUGraphSetNodeInputCallback result %ld %08lX %4.4s\n", (long)status, (long)status, (char*)&status); return; }
        //设置输入格式
        status = AudioUnitSetProperty(mixUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, i,_unitFormat.streamDescription , sizeof(AudioStreamBasicDescription));
        
        if(status!=kAudioSessionNoError) {
            NSLog(@"AudioUnitSetProperty Error");
            return;
        }
    }
    
    
    status = AudioUnitSetProperty(mixUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, _unitFormat.streamDescription, sizeof(AudioStreamBasicDescription));
    if (status) {
        NSLog(@"AudioUnitSetProperty Error");
        return;
    }
    
    status = AudioUnitSetProperty(ioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, _unitFormat.streamDescription, sizeof(AudioStreamBasicDescription));
    if (status) {
        NSLog(@"AudioUnitSetProperty Error");
        return;
    }
    
    
    status = AUGraphInitialize(_auGraph);
    if (status) {
        NSLog(@"AudioUnitSetProperty Error");
        return;
    }
}

实现回调的方法,填充数据供输出:

OSStatus mixInputCallBack (void *                            inRefCon,
                           AudioUnitRenderActionFlags *      ioActionFlags,
                           const AudioTimeStamp *            inTimeStamp,
                           UInt32                            inBusNumber,
                           UInt32                            inNumberFrames,
                           AudioBufferList * __nullable      ioData) {
    
    SoundBuffer *sndbuf = (SoundBuffer *)inRefCon;
    
    UInt32 startFrame = sndbuf[inBusNumber].startFrameNum;      // 从哪一帧开始
    UInt32 numFrames = sndbuf[inBusNumber].numFrames;  // 总的帧数
    Float32 *bufferData = sndbuf[inBusNumber].data; // audio data buffer
    
    Float32 *outA = (Float32 *)ioData->mBuffers[0].mData; //  第一声道数据
    Float32 *outB = (Float32 *)ioData->mBuffers[1].mData; //  第二声道数据
    

    
    for (UInt32 i = 0; i < inNumberFrames; ++i) {
        if (inBusNumber == 0) {
            outA[i] = bufferData[startFrame++];   //填充第一声道数据(用的是第一个SoundBuffer)
        } else {
            outB[i] = bufferData[startFrame++];   //填充第二声道数据(用的是第二个SoundBuffer)
        }
    
        if (startFrame > numFrames) {
            // 结束了,再从0开始循环
            printf("looping data for bus %d after %ld source frames rendered\n", (unsigned int)inBusNumber, (long)startFrame-1);
            startFrame = 0;
        }
    }
    sndbuf[inBusNumber].startFrameNum = startFrame; // 记录帧数
    return noErr;
}

总结:AudioUnit更像是个看图编程的过程ㄟ( ▔, ▔ )ㄏ。

上一篇下一篇

猜你喜欢

热点阅读