AudioUnit之-播放裸PCM音频文件(一)

前言:

AudioUnit是什么?在IOS平台下,AudioUnit是一个底层音频处理框架,主要功能如下:
1、音频播放与录制
2、音频数据格式的解析与保存
3、多路音频的合成
4、音频中的回声消除
5、音频特效如均衡器、压缩器、混响器
它的功能远不止这些,同时它具有较低的处理延时,所有的音频渲染框架的底层都是基于AudioUnit实现的,比较高层次的框架如MediaPlayer,AVFoundation, OpenAL和Audio Toolbox;


AudioUnit之-播放裸PCM音频文件(一)_第1张图片
image.png

AudioUnit系列文章

AudioUnit之-播放裸PCM音频文件(一)
AudioUnit之-录制音频+耳返(二)
AudioUnit之-录制音频保存为m4a/CAF/WAV文件和播放m4a/CAF/WAV文件(三)
AudioUnit之-录制音频并添加背景音乐(四)
AudioUnit之-generic output(离线)混合音频文件(五)

音频相关知识

1、IOS支持的音频编码格式

格式定义在CoreAudio/CoreAudioTypes.h头文件的枚举AudioFormatID中
1、常见未压缩编码格式:LPCM(标准的线性脉冲编码,未压缩),IMA4;
2、常见压缩编码格式:aac,mp3,AMR等
3、多种LPCM的变体:对于LPCM音频编码是iPhone中使用非压缩音频数据最好的数据格式.同时,根据具体的存储方式,又有多种变种.音频数据可以存储于大端或者小端模式,用float或者integer存储,也可以使用不同的bit-width存储.而在iPhone中,使用的最平凡的是:little-endian integer 16bit(或者LEI16 short类型)的格式.

2、IOS常用音频容器格式

iPhone支持许多文件格式(音频容器)包括:MPEG-1(.mp3),MPEG-2 ADTS(.aac),AIFF,CAF,WAVE等.但是通常在iPhone中使用的容器格式就CAF,因为它可以用来封装iPhone所支持的所有音频格式.

使用AudioUnit播放音频

相关头文件:
#import
#import
#import

AudioUnit一端连接着扬声器,另一端连接着应用端,应用端不停的将PCM音频数据输入给AudioUnit就实现了音频的播放,使用AudioUnit来播放PCM音频数据的步骤如下:
1、创建音频会话

// 1、创建一个音频会话 它是单例;AVAudioSession 在AVFoundation/AVFAudio/AVAudioSession.h中定义
_aSession = [AVAudioSession sharedInstance];
        
//  2、======配置音频会话 ======//
/** 配置使用的音频硬件:
 *  AVAudioSessionCategoryPlayback:只是进行音频的播放(只使用听的硬件,比如手机内置喇叭,或者通过耳机)
 *  AVAudioSessionCategoryRecord:只是采集音频(只录,比如手机内置麦克风)
 *  AVAudioSessionCategoryPlayAndRecord:一边采集一遍播放(听和录同时用)
 */
[_aSession setCategory:category error:nil];
// 设置采样率,不管是播放还是录制声音 都需要设置采样率
[_aSession setPreferredSampleRate:rate error:nil];
        
// 设置I/O的Buffer,数值越小说明缓存的数据越小,延迟也就越低;这里意思就是麦克风采集声音时只缓存20ms的数据
[_aSession setPreferredIOBufferDuration:duration error:nil];
// 激活会话
[_aSession setActive:YES error:nil];

2.创建AudioUnit描述组件
这里主要是定义AudioUnit的类型,定义在AudioToolbox/AUComponent.h文件中

AudioUnit的类型,定义在AudioToolbox/AUComponent.h文件中
CF_ENUM(UInt32) {
kAudioUnitType_Output = 'auou',
kAudioUnitType_MusicDevice = 'aumu',
kAudioUnitType_MusicEffect = 'aumf',
kAudioUnitType_FormatConverter = 'aufc',
kAudioUnitType_Effect = 'aufx',
kAudioUnitType_Mixer = 'aumx',
kAudioUnitType_Panner = 'aupn',
kAudioUnitType_Generator = 'augn',
kAudioUnitType_OfflineEffect = 'auol',
kAudioUnitType_MIDIProcessor = 'aumi'
};
1、kAudioUnitType_Effect;主要用于提供声音特效的处理,包括的子类型有
均衡效果器:kAudioUnitSubType_NBandEQ,用于为声音的某些频带增强或减弱能量
压缩效果器:kAudioUnitSubType_DynamicsProcessor,增大或者减少音量
混响效果器:kAudioUnitSubType_Reverb2,提供混响效果
2、kAudioUnitType_Mixer:提供Mix多路声音功能
多路混音效果器:kAudioUnitSubType_MultiChannelMixer,可以接受多路音频的输入,然后分别调整每一路音频的增益与开关,并将多路音频合成一路
3、kAudioUnitType_Output:提供音频的录制,播放功能
录制和播放音频:kAudioUnitSubType_RemoteIO,后面通过AudioUnitSetProperty()方法具体是访问麦克风还是扬声器
访问音频数据:kAudioUnitSubType_GenericOutput
4、kAudioUnitType_FormatConverter:提供音频格式转化功能,比如采样率转换,声道数转换,采样格式转化,panner到packet转换等等
kAudioUnitSubType_AUConverter:提供格式转换功能
kAudioUnitSubType_AudioFilePlayer:直接从文件获取输入音频数据,它具有解码功能
kAudioUnitSubType_NewTimePitch:变速变调效果器


/**
 *  与AudioUnit有关的错误类型枚举定义在AudioToolbox/AUComponent.h文件中
 *  CF_ENUM(OSStatus) {
 *      kAudioUnitErr_InvalidProperty            = -10879,
 *      .......
 *  }
 */
// 创建指定的类型
+ (AudioComponentDescription)descriptionWithType:(OSType)type subType:(OSType)subType fucture:(OSType)manufuture
{
    AudioComponentDescription acd;
    acd.componentType = type;
    acd.componentSubType = subType;
    acd.componentManufacturer = manufuture;
    return acd;
}

3、创建AudioUnit:

/** 创建 AudioUnit
 *  和通过AUGraph创建;
*/
- (void)createAudioUnitByAugraph
{
    OSStatus status = noErr;
    //1、创建AUGraph
    status = NewAUGraph(&_aGraph);
    if (status != noErr) {
        NSLog(@"create AUGraph fail %d",status);
    }
    
    //2.2 将指定的组件描述创建AUNode并添加到AUGraph中
    status = AUGraphAddNode(_aGraph, &_ioDes, &_ioNode);
    if (status != noErr) {
        NSLog(@"AUGraphAddNode fail _ioDes %d",status);
    }
    status = AUGraphAddNode(_aGraph, &_cvtDes, &_cvtNode);
    if (status != noErr) {
        NSLog(@"AUGraphAddNode fail _cvtDes %d",status);
    }
    
    // 3、打开AUGraph(即初始化了AUGraph)
    status = AUGraphOpen(_aGraph);
    if (status != noErr) {
        NSLog(@"AUGraphOpen fail %d",status);
    }
    
    // 4、打开了AUGraph之后才能获取指定的AudioUnit
    status = AUGraphNodeInfo(_aGraph, _ioNode, NULL, &_ioUnit);
    if (status != noErr) {
        NSLog(@"AUGraphNodeInfo fail %d",status);
    }
    status = AUGraphNodeInfo(_aGraph, _cvtNode, NULL, &_cvtUnit);
    if (status != noErr) {
        NSLog(@"AUGraphNodeInfo fail %d",status);
    }
    
}

4、设置AudioUnit属性

/** 设置AudioUnit属性
 *  1、通过AudioUnitSetProperty
 *  2、关于remoteIO的element,扬声器对应的AudioUnitElement值为0,app能控制的AudioUnitScope值为kAudioUnitScope_Input;麦克风对应的AudioUnitElement值为1
 *  app能控制的udioUnitScope值为kAudioUnitScope_Output
 */
- (void)setAudioUnitProperties
{
    // 开启扬声器的播放功能;注:对于扬声器默认是开启的,对于麦克风则默认是关闭的
    uint32_t flag = 1;// 1代表开启,0代表关闭
    OSStatus status = AudioUnitSetProperty(_ioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &flag, sizeof(flag));
    if (status != noErr) {
        NSLog(@"AudioUnitSetProperty fail %d",status);
    }
    
    AudioFormatFlags flags = self.aSession.formatFlags;
    CGFloat rate = self.aSession.currentSampleRate;
    NSInteger chs = self.aSession.currentChannels;
    //输入给扬声器的音频数据格式
    AudioStreamBasicDescription odes = [ADUnitTool streamDesWithLinearPCMformat:kAudioFormatFlagIsFloat|kAudioFormatFlagIsNonInterleaved sampleRate:rate channels:chs];
    // PCM文件的音频的数据格式
    AudioStreamBasicDescription cvtInDes = [ADUnitTool streamDesWithLinearPCMformat:flags sampleRate:rate channels:chs];
    
    // 设置扬声器的输入音频数据格式
    status = AudioUnitSetProperty(_ioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &odes, sizeof(odes));
    if (status != noErr) {
        NSLog(@"AudioUnitSetProperty io fail %d",status);
    }
    
    // 设置格式转换器的输入输出音频数据格式;对于格式转换器AudioUnit 他的AudioUnitElement只有一个 element0
    status = AudioUnitSetProperty(_cvtUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &cvtInDes, sizeof(cvtInDes));
    if (status != noErr) {
        NSLog(@"AudioUnitSetProperty convert in fail %d",status);
    }
    status = AudioUnitSetProperty(_cvtUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &odes, sizeof(odes));
    if (status != noErr) {
        NSLog(@"AudioUnitSetProperty convert ou fail %d",status);
    }
    
    /** 构建连接
     *  只有构建连接之后才有一个完整的数据驱动链。如下将构成链条如下:
     *  _cvtUnit通过回调向文件要数据,得到数据后进行格式转换,将输出作为输入数据输送给_ioUnit,然后_ioUnit播放数据
     */
    status = AUGraphConnectNodeInput(_aGraph, _cvtNode, 0, _ioNode, 0);
    if (status != noErr) {
        NSLog(@"AUGraphConnectNodeInput fail %d",status);
    }
    AURenderCallbackStruct callback;
    callback.inputProc = InputRenderCallback;
    callback.inputProcRefCon = (__bridge void*)self;
    status = AudioUnitSetProperty(_cvtUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callback, sizeof(callback));
    if (status != noErr) {
        NSLog(@"AudioUnitSetProperty fail %d",status);
    }
}

tips:根据步骤4的代码,总结,
AudioUnit播放音频的数据驱动链条为:
_cvtUnit通过回调向文件要数据,得到数据后进行格式转换,将输出作为输入数据输送给_ioUnit,然后_ioUnit播放数据

5、播放

- (void)play
{
    OSStatus stauts;
    CAShow(_aGraph);
    
    // 7、初始化AUGraph,初始化之后才能正常启动播放
    stauts = AUGraphInitialize(_aGraph);
    if (stauts != noErr) {
        NSLog(@"AUGraphInitialize fail %d",stauts);
    }
    stauts = AUGraphStart(_aGraph);
    if (stauts != noErr) {
        NSLog(@"AUGraphStart fail %d",stauts);
    }
}

项目地址:

Demo

image.png

具体的代码实现在文件ADAudioUnitPlay.h/.m中

温馨小提示:
由于代码混合在一块了,后面多了对ffmpeg的代码整合,使用了git-lfs的文件传输功能,所以git clone之前需要先安装git-lfs,如下:

1、安装homebrew(如果没有) ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
2、安装git-lfs brew install git-lfs
3、git clone https://github.com/nldzsz/media-ios.git

否则会提示
"Undefined symbol: _av。。。之类"

QQ&&微信技术交流群

AudioUnit之-播放裸PCM音频文件(一)_第2张图片
WechatIMG4.jpeg
AudioUnit之-播放裸PCM音频文件(一)_第3张图片
WechatIMG5.jpeg

你可能感兴趣的:(AudioUnit之-播放裸PCM音频文件(一))