一、音频会话 AVAudioSession
音频会话在应用程序和操作系统之间扮演着中间人的角色,提供一种简单实用的方法是OS得知应用程序应该如何与iOS音频环境进行交互。
AVAudioSession
有AVFOundation
框架引入。每个iOS应用程序都有自己的一个音频会话,这个会话可以被AVAudioSession
的类方法sharedInstance
访问。
音频会话是一个单例对象,可以使用它来设置应用程序的音频上下文环境,并向系统表达您的应用程序音频行为的意图。
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
使用它可以实现:
- 启用或停用应用程序中的音频工作
- 设置音频会话类别和模式,
- 配置音频设置,如采样率,I/O缓冲区持续时间和通道数
- 处理音频输出更改
- 相应重要的音频时间,如更改底层Media Services守护程序的可用性。
1、音频会话分类/类别
Ambient 游戏、效率应用程序
AVAudioSessionCategoryAmbient
/kAudioSessionCategory_AmbientSound
使用这个分类应用会随着静音键和屏幕关闭而静音,且不会终止其他应用播放的声音,可以和其他自带应用如iPod、Safari同时播放声音。该类别无法在后台播放声音Solo Ambient(默认) 游戏、效率应用程序
AVAudioSessionCategorySoloAmbient
/kAudioSessionCategory_SoloAmbientSound
类似Ambient
不同之处在于它会终止其它应用播放声音。该类别无法在后台播放声音Playback 音频和视频播放
AVAudioSessionCategoryPlayback
/kAudioSessionCategory_MediaPlayback
用于以音频为主的应用,不会随着静音键和平不关闭而静音。可在后台播放声音。Record 录音机、视频捕捉
AVAudioSessionCategoryRecord
/kAudioSessionCategory_RecordAudio
录音应用,除了来电铃声、闹钟、日历提醒之外的其他系统声音不会被播放。只提供单纯录音功能。Play and Record VoIP、语音聊天
AVAudioSessionCategoryPlayAndRecord
/kAudioSessionCategory_PlayAndRecord
提供录音和播放功能,如果应用需要用到iPhone上的听筒,这个类别是你唯一的选择,在这个类别下,声音的默认出口为听筒或者耳机。Audio Processing 离线会话和处理
AVAudioSessionCategoryAudioProcessing
/kAudioSessionCategory_AudioProcessing
在不播放或录制音频时使用音频硬件编解码器或信号处理器的类别。例如在执行离线音频格式转换时,此类别禁用播放和禁用录音。应用处于后台时,音频处理通常不会继续,但是可以在应用移至后台时,请求更多时间来完成处理。Multi-Route 使用外部硬件的高级A/V应用程序
AVAudioSessionCategoryMultiRoute
通过可以用的音频辅助设备和内置音频硬件设备,我们可以自定义使用类型
并不是一个应用只能使用一个category,可以根据实际需求来切换设置不同的category。
通过音频会话单例对象的setCategory: error:
设置iOS应用音频会话类别和模式。
NSError *error;
if (![_audioSession setCategory:AVAudioSessionCategoryPlayback error:&error]) { //设置类别
NSLog(@"Category error :%@",[error localizedDescription]);
}
2、配置音频会话
音频会话在应用程序的生命周期中是可以修改的,一般在应用程序启动时,对其进行配置。配置音频会话的最佳位置就是应用程序委托的application: didFinishLaunchingWithOptions:
方法。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
NSError *error;
if (![audioSession setCategory:AVAudioSessionCategoryPlayback error:&error]) {
NSLog(@"Category error :%@",[error localizedDescription]);
}
if (![audioSession setActive:YES error:&error]) {
NSLog(@"Activation Error :%@",[error localizedDescription]);
}
return YES;
}
AVAudioSession 提供了与应用程序音频会话交互的接口,通过设置合适的的分类,可以音频的播放指定需要的音频会话,定制一些行为。最后告知该音频会话激活该配置setActive:YES error:
。
- 配置可以在后台运行:
info.plist
文件天剑一个新的Required background modes
类型的数组,在其中添加名为App plays audio or streams audio/video using AirPlay
选项。或者右击info.plist
文件,在相应的XML部分编辑plist,以及选择Open as Source Code
,添加下面标签到文件底部的</dict>
前:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
两种方式效果一样,只是添加的方式不同。
三、使用AVAudioPlayer播放音频
AVAudioPlayer
提供了简单地从文本或内存中播放音频的方法。
AVAudioPlayer
构建于Core Audio
中的C-based Audio Queue Services
的最顶层。它可以提供在Audio Queue Service
中所能找到的核心功能。除非需要从网络流中播放音频、需要访问原始音频样本或者需要非常低的时延,否则它都能胜任。
1、创建AVAudioPlayer
两种方法创建AVAudioPlayer
:initWithData: error:nil
和initWithContentsOfURL: error:nil
使用包含要播放音频的内存的NSData,或者本地音频文件的NSURL。
NSURL *fileUrl = [[NSBundle mainBundle] URLForResource:@"崔健-假行僧" withExtension:@"mp3"];
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileUrl error:nil];
if (self.audioPlayer) {
[self.audioPlayer prepareToPlay];
}
如果返回一个有效的播放实例,建议调用prepareToPlay
方法。这样会取得需要音频硬件并预加载Audio Queue
的缓冲区。调用这个方法是可选的,在调用play
方法时会隐性激活,不过在创建时准备播放器可以降低调用paly方法和听到声音输出之间的延时。
2、对播放进行控制
常规方法:
-
play
-- 立即播放音频 -
pause
-- 暂停播放 -
stop
-- 停止播放
pause
和stop
都能停止播放,并且再次播放的时候继续播放。区别是stop
方法会撤销调用prepareToPlay
是所做的设置,而调用pause
不会。
其他方法:
- 修改播放器音量:播放器的音量独立于系统的音量,可以通过对播放器音量的处理实现一些效果,比如声音渐隐效果。音量或播放增益定义为0.0(静音)到1.0之间的浮点值。
- 修改播放器pan值:允许使用立体声播放声音,pan值范围
-1.0(极左)-1.0(极右)
,默认是为1.0(居中) - 调整播放率:允许用户在不改变音调的情况下调整播放率,范围从
0.5(半速)-2.0(2倍速)
。 - 通过设置
numberOfLoops
实现音频无缝循环:给这个属性设置一个大于0的数,可以实现播放器n次循环播放。相反如果为-1导致播放器无限循环。音频循环可以是未压缩的线性PCM音频,也可以是AAC之类的压缩格式音频。MP3格式片段可以实现无缝循环,但是MP3格式用作循环格式不被推崇。MP3格式的音频要实现循环的目的通常需要使用特殊工具进行处理。如果希望使用压缩格式的资源,建议使用AAC或者AppleLossless格式的内容。 - 进行音频计量:播放发生时从播放器读取播放力度的平均值和峰值。将这些数据提供给VU计量器或其他可视化元件。向用户提供可视化的反馈效果。
三、创建Audio Looper
四、处理中断事件
音频会话通知
添加通知监听,监听是否发生中断事件。通知名称为AVAudioSessionInterruptionNotification
。
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
推送的消息会包含许多重要信息的userInfo字典,通过关键字AVAudioSessionInterruptionTypeKey
获取中断类型AVAudioSessionInterruptionType
,根据中断状态执行不同操作
- (void)handleInterruption:(NSNotification *)notification {
NSDictionary *infoDict = notification.userInfo;
AVAudioSessionInterruptionType type = [infoDict[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
if (type == AVAudioSessionInterruptionTypeBegan) {
[self stop];//开始中断,停止播放
} else {
AVAudioSessionInterruptionOptions options = [infoDict[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];
if (options == AVAudioSessionInterruptionOptionShouldResume) {
[self play];
}
}
}
如果中断类型为AVAudioSessionInterruptionTypeEnded
,userInfo字典里会包含一个通过keyAVAudioSessionInterruptionOptionKey
取的AVAudioSessionInterruptionOptions
类型值,表示音频会话是否已经重新激活以及是否可以再次播放。上例中,如果options为AVAudioSessionInterruptionOptionShouldResume
,可以调用播放的方法继续播放音频。
五、对线路改变的响应
在iOS设备上添加或移除音频输入、输出线路时,会发生线路改变,有多重原因会导致线路的变化,比如插入耳机或断开USB麦克风。当这些时间发生时,音频会根据情况改变输入或输出线路,同时AVAudioSession
会广播一个描述该变化的通知给所有相关的监听者。
添加监听的通知名称:AVAudioSessionRouteChangeNotification
。该通知同样包含一个userInfo字典,带有相应通知发送的原因一前一个线路的描述,以此可以确定线路变化的情况。
判断线路变更发生的原因,取keyAVAudioSessionRouteChangeReasonKey
对应的AVAudioSessionRouteChangeReason
类型值。根据变更原因,作相应处理。
typedef NS_ENUM(NSUInteger, AVAudioSessionRouteChangeReason)
{
AVAudioSessionRouteChangeReasonUnknown = 0,
原因不明;
AVAudioSessionRouteChangeReasonNewDeviceAvailable = 1,
有新设备可用,如耳机插入
AVAudioSessionRouteChangeReasonOldDeviceUnavailable = 2,
一个旧设备不可用,如耳机拔出
AVAudioSessionRouteChangeReasonCategoryChange = 3,
音频类别被改变,如Audio从Play back 变成Play And Record
AVAudioSessionRouteChangeReasonOverride = 4,
音频线路(route)改变,如类别是Play and Record,输出社诶已经从默认的接收器改变成为扬声器
AVAudioSessionRouteChangeReasonWakeFromSleep = 6,
设备从休眠中醒来
AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory = 7,
没有路径返回当前的类别,如Record雷彪当前没有输入设备
AVAudioSessionRouteChangeReasonRouteConfigurationChange NS_ENUM_AVAILABLE_IOS(7_0) = 8
当前输入/输出口没变,但设置修改,如一个端口的数据选择已经改变。
}
知道有设备断开连接后,需要向userInfo字典提出请求,一会的其中用于描述前一个线路的AVAudioSessionRouteDescription
,其对应的key为AVAudioSessionRouteChangePreviousRouteKey
。线路的描述信息整合在一个输入NSArray和一个输出NSArray中。数组中的元素都是AVAudioSessionPortDescription
的实例。用于描述不同的I/O
接口属性。可以从线路描述中找到第一个输出接口,即前一次的接口。
输入口不同类型,input port type
AVAudioSessionPortLineIn
AVAudioSessionPortBuiltInMic :内置麦克风
AVAudioSessionPortHeadsetMic :耳机线中的麦克风
输出口不同类型,output port type
AVAudioSessionPortLineOut
AVAudioSessionPortHeadphones :耳机或者耳机式输出设备
AVAudioSessionPortBuiltInReceiver :帖耳朵时候内置扬声器(打电话的时候的听筒)
AVAudioSessionPortBuiltInSpeaker :iOS设备的扬声器
AVAudioSessionPortBluetoothA2DP :A2DP协议式的蓝牙设备
AVAudioSessionPortHDMI :高保真多媒体接口设备
AVAudioSessionPortAirPlay :远程AirPlay设备
AVAudioSessionPortBluetoothLE :蓝牙低电量输出设备
一个简单实例,拔出耳机之后,默认停止播放:
- (void)handleRouteChange:(NSNotification *)notification {
NSDictionary *infoDict = notification.userInfo;
AVAudioSessionRouteChangeReason reason = [infoDict[AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue];
if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
// AVAudioSessionRouteDescription
// AVAudioSessionPortDescription
AVAudioSessionRouteDescription *previousRoute = infoDict[AVAudioSessionRouteChangePreviousRouteKey];
//取出所有线路描述
NSLog(@"count :%zd",previousRoute.outputs.count);
AVAudioSessionPortDescription *previousOutput = previousRoute.outputs[0];
//取出前一次线路描述
NSString *portType = previousOutput.portType;
if ([portType isEqualToString:AVAudioSessionPortHeadphones]) {
[self stop];
}
}
}
六、使用AVAudioRecorder录制音频
AVAudioRecorder
构建于Audio Queue Services
之上,可以再iOS设备上使用这个类从内置的麦克风录制音频,也可以从外部音频设备进行录制,比如数字音频接口或USB麦克风等。
1、创建AVAudioRecorder
- (nullable instancetype)initWithURL:(NSURL *)url settings:(NSDictionary<NSString *, id> *)settings error:(NSError **)outError;
第一个参数:音频流写入文件的本地文件URL,第二个参数:包含用于配置录音会话信息,第三个参数:捕捉初始化阶段错误。
成功创建AVAudioRecorder
实例,建议调用prepareToRecord
。与AVAudioPlayer
的prepareToPlay
方法类似,执行底层Audio Queue
初始化的必要过程。在URL参数指定位置创建一个文件,将录制启动时的延时降到最小。
NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"1.m4a"];
NSURL *fileUrl = [NSURL fileURLWithPath:path];
NSDictionary *settings = @{
AVFormatIDKey:@(kAudioFormatMPEG4AAC),
AVSampleRateKey:@22050.0f,
AVNumberOfChannelsKey:@1,
};
NSError *error;
_audioRecorder = [[AVAudioRecorder alloc] initWithURL:fileUrl settings:settings error:&error];
if (_audioRecorder) {
[_audioRecorder prepareToRecord];
} else {
NSLog(@"init error :%@",[error localizedDescription]);
}
配置录音会话参数:
- AVFormatIDKey --写入内容的音频格式,常用的音频格式支持的值:
kAudioFormatLinearPCM
kAudioFormatMPEG4AAC
kAudioFormatAppleLossless
kAudioFormatAppleIMA4
kAudioFormatiLBC
kAudioFormatULaw
kAudioFormatLinearPCM -会将未压缩的音频流写入文件中,
这种格式的保真度最高,相应的文件也最大。
AAC或Apple IMA4的压缩格式会显著缩小文件,还能保证高质量的音频内容。
- AVSampleRateKey --定义录音器采样率。采样率定义了对输入的模拟音频信号每一秒内的采样数。采样率决定音频的质量及最终文件大小。一般标准的采样率:8k、16k、22.5k、44.1k。
- AVNumbeOfChannelsKey -- 定义记录音频通道数。默认值1,单声道录制。设置2-立体声录制。除非使用外部硬件进行录制,一般应该创建单声道录音。
2、控制录音过程
record
--开始或继续录音
stop
--停止录音,并关闭文件
pause
--暂停录音
七、使用Audio Metering
AVAudioPlayer
和AVAudioRecorder
中最强大和最实用的功能是对音频进行测量,Audio Metering
可让开发者读取音频的平均分贝和峰值分贝数据,并使这些数据以可视化方式将声音大小呈献给用户。
通过averagePowerForChannel:
和peakPowerForChannel:
获取平均分贝和峰值分贝,返回一个用于表示声音分贝(dB)等级的浮点值,这个值的范围是从表示最大分贝的0dB(full scale)到最小分贝或静音的-160dB。获取这两个值之前,要先设置属性meteringEnabled
为YES,才能对音频进行测量。另,每当需要读取值时,需要先调用updateMeters
方法才能获取最新的值。