征服iOSiOS之功能细节iOS开发常用

iOS音频掌柜-- AVAudioSession

2016-11-29  本文已影响14413人  CZ_iOS

音频输出作为硬件资源,对于iOS系统来说是唯一的,那么要如何协调和各个App之间对这个稀缺的硬件持有关系呢?

iOS给出的解决方案是"AVAudioSession" ,通过它可以实现对App当前上下文音频资源的控制,比如
插拔耳机、接电话、是否和其他音频数据混音等。当你遇到:

这些场景的时候,就可以考虑一下“AVAudioSession”了。
������
在很久以前(其实也是不是太久--iOS7以前)还有个AudioSession的存在,其功能与AVAudioSession类似,但是在iOS7 以后就已经被标记为
“Not Applicable”,所以如果Google到了说AudioSession的内容而不是用的AVAudioSession,那么就可以直接PASS了,当然如果要兼容iOS6
就另当别论了,不过现在QQ/微信都是要求iOS7的情况下,是否需要兼容iOS6就看老板们的意思吧。

Session默认行为

默认的行为相当于设置了Category为“AVAudioSessionCategorySoloAmbient”

来看Demo

demo_player

通过这播放器demo可以验证上面的默认Session行为。

AVAudioSession

AVAudioSession以一个单例实体的形式存在,通过类方法:

+ (AVAudioSession *)sharedInstance;

获得单例。

虽然系统会在App启动的时候,激活这个唯一的AVAudioSession,但是最好还是在自己ViewController的viewDidLoad里面再次进行激活:

- (BOOL)setActive:(BOOL)active 
        error:(NSError * _Nullable *)outError;

通过设置active为"YES"激活Session,设置为“�NO”解除Session的激活状态。BOOL返回值表示是否成功,如果失败的话可以通过NSError的error.localizedDescription查看出错原因。

因为AVAudioSession会影响其他App的表现,当自己App的Session被激活,其他App的就会被解除激活,如何要让自己的Session解除激活后恢复其他App Session的激活状态呢?

此时可以使用:

  • (BOOL)setActive:(BOOL)active
    withOptions:(AVAudioSessionSetActiveOptions)options
    error:(NSError * _Nullable *)outError;

这里的options传入AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation 即可。

当然,也可以通过otherAudioPlaying变量来提前判断当前是否有其他App在播放音频。

可以通过:

@property(readonly) NSString *category;

属性,获取当前的Category,比如上面的播放其,默认是

 NSLog(@"Current Category:%@", [AVAudioSession sharedInstance].category);

输出:

Current Category:AVAudioSessionCategorySoloAmbien

七大Category

AVAudioSession主要能控制App的哪些表现以及如何控制的呢?首先AVAudioSession将使用音频的场景分成七大类,通过设置Session为不同的类别,可以控制:

类别 当按“静音”或者锁屏是是否静音 是否引起不支持混音的App中断 是否支持录音和播放
AVAudioSessionCategoryAmbient 只支持播放
AVAudioSessionCategoryAudioProcessing - 都不支持
AVAudioSessionCategoryMultiRoute 既可以录音也可以播放
AVAudioSessionCategoryPlayAndRecord 默认不引起 既可以录音也可以播放
AVAudioSessionCategoryPlayback 默认引起 只用于播放
AVAudioSessionCategoryRecord 只用于录音
AVAudioSessionCategorySoloAmbient 只用于播放

可以看到,其实默认的就是“AVAudioSessionCategorySoloAmbient”类别。从表中我们可以总结如下:

了解了这七大类别,我们就可以根据自己的需要进行对应类别的设置了:

- (BOOL)setCategory:(NSString *)category error:(NSError **)outError;

传入对应的列表枚举即可。如果返回"NO"可以通过NSError的error.localizedDescription查看原因。

可以通过:

@property(readonly) NSArray<NSString *> *availableCategories;

属性,查看当前设备支持哪些类别,然后再进行设置,从而保证传入参数的合法,减少错误的可能。

比如修改上面的Demo例子:

    NSLog(@"Current Category:%@", [AVAudioSession sharedInstance].category);
    NSError *error = nil;
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
    if (nil != error) {
        NSLog(@"set Option error %@", error.localizedDescription);
    }
    NSLog(@"Current Category:%@", [AVAudioSession sharedInstance].category);

此时在播放音乐的时候,再去按下静音键,会发现,音乐还在继续播放,不会被静音。

类别的选项

上面介绍的这个七大类别,可以认为是设定了七种主场景,而这七类肯定是不能满足开发者所有的需求的。CoreAudio提供的方法是,首先定下七种的一种基调,然后在进行微调。CoreAudio为每种Category都提供了些许选项来进行微调。
在设置完类别后,可以通过

@property(readonly) AVAudioSessionCategoryOptions categoryOptions;

属性,查看当前类别设置了哪些选项,注意这里的返回值是AVAudioSessionCategoryOptions,实际是多个options的“|”运算。默认情况下是0。

选项 适用类别 作用
AVAudioSessionCategoryOptionMixWithOthers AVAudioSessionCategoryPlayAndRecord, AVAudioSessionCategoryPlayback, and AVAudioSessionCategoryMultiRoute 是否可以和其他后台App进行混音
AVAudioSessionCategoryOptionDuckOthers AVAudioSessionCategoryAmbient, AVAudioSessionCategoryPlayAndRecord, AVAudioSessionCategoryPlayback, and AVAudioSessionCategoryMultiRoute 是否压低其他App声音
AVAudioSessionCategoryOptionAllowBluetooth AVAudioSessionCategoryRecord and AVAudioSessionCategoryPlayAndRecord 是否支持蓝牙耳机
AVAudioSessionCategoryOptionDefaultToSpeaker AVAudioSessionCategoryPlayAndRecord 是否默认用免提声音

目前主要的选项有这几种,都有对应的使用场景,除此之外,在iOS9还提供了AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers最新的iOS10又新加了两个AVAudioSessionCategoryOptionAllowBluetoothA2DPAVAudioSessionCategoryOptionAllowAirPlay用来支持蓝牙A2DP耳机和AirPlay。

来看每个选项的基本作用:

通过接口:
- (BOOL)setCategory:(NSString *)category withOptions:(AVAudioSessionCategoryOptions)options error:(NSError **)outError
来对当前的类别进行选项的设置。

比如Demo中:

    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error];
    if (nil != error) {
        NSLog(@"set Option error %@", error.localizedDescription);
    }
    options = [[AVAudioSession sharedInstance] categoryOptions];
    NSLog(@"Category[%@] has %lu options",  [AVAudioSession sharedInstance].category, options);

此时,先打开QQ音乐播放器,然后再开始进行播放,会发现,QQ和我们的播放器都在播放,并且进行了自动混音。

不过这个过程,感觉CoreAudio缺少一个setOption的接口,既然已经是当前处于的Category,干嘛还要再设置选项的时候再指定Category呢??疑惑。。。

七大模式

刚讲完七大类别,现在再来七大模式。通过上面的七大类别,我们基本覆盖了常用的主场景,在每个主场景中可以通过Option进行微调。为此CoreAudio提供了七大比较常见微调后的子场景。叫做各个类别的模式。

模式 适用的类别 场景
AVAudioSessionModeDefault 所有类别 默认的模式
AVAudioSessionModeVoiceChat AVAudioSessionCategoryPlayAndRecord VoIP
AVAudioSessionModeGameChat AVAudioSessionCategoryPlayAndRecord 游戏录制,由GKVoiceChat自动设置,无需手动调用
AVAudioSessionModeVideoRecording AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord 录制视频时
AVAudioSessionModeMoviePlayback AVAudioSessionCategoryPlayback 视频播放
AVAudioSessionModeMeasurement AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord AVAudioSessionCategoryPlayback 最小系统
AVAudioSessionModeVideoChat AVAudioSessionCategoryPlayAndRecord 视频通话

每个模式有其适用的类别,所以,并不是有“七七 四十九”种组合。如果当前处于的类别下没有这个模式,那么是设置不成功的。设置完Category后可以通过:

@property(readonly) NSArray<NSString *> *availableModes;

属性,查看其支持哪些属性,做合法性校验。

来看具体应用:

另外几种和音频APP关系不大,一般我们只需要关注VoIP或者视频通话即可。

通过调用:

- (BOOL)setMode:(NSString *)mode error:(NSError **)outError

可以在设置Category之后再设置模式。

当然,这些模式只是CoreAduio总结的,不一定完全满足要求,对于具体的模式,在iOS10中还是可以微调的。通过接口:

  • (BOOL)setCategory:(NSString *)category mode:(NSString *)mode options:(AVAudioSessionCategoryOptions)options error:(NSError **)outError

但是在iOS9及以下就只能在Category上调了,其实本质是一样的,可以认为是个API糖,接口封装。

系统中断响应

上面说的这些Category啊、Option啊以及Mode都是对自己作为播放主体时的表现,但是假设,现在正在播放着,突然来电话了、闹钟响了或者你在后台放歌但是用户启动其他App用上面的方法影响的时候,我们的App该如何表现呢?最常用的场景当然是先暂停,待恢复的时候再继续。那我们的App要如何感知到这个终端以及何时恢复呢?

AVAudioSession提供了多种Notifications来进行此类状况的通知。其中将来电话、闹铃响等都归结为一般性的中断,用
AVAudioSessionInterruptionNotification来通知。其回调回来的userInfo主要包含两个键:

而将其他App占据AudioSession的时候用AVAudioSessionSilenceSecondaryAudioHintNotification来进行通知。其回调回来的userInfo键为:

AVAudioSessionSilenceSecondaryAudioHintTypeKey

可能包含的值:

外设改变

除了其他App和系统服务,会对我们的App产生影响以外,用户的手也会对我们产生影响。默认情况下,AudioSession会在App启动时选择一个最优的输出方案,比如插入耳机的时候,就用耳机。但是这个过程中,用户可能拔出耳机,我们App要如何感知这样的情况呢?

同样AVAudioSession也是通过Notifications来进行此类状况的通知。

假设有这样的App:

route_changeroute_change

最开始在录音时,用户插入和拔出耳机我们都停止录音,这里通过Notification来通知有新设备了,或者设备被退出了,然后我们控制停止录音。或者在播放时,当耳机被拔出出时,Notification给了通知,我们先暂停音乐播放,待耳机插回时,在继续播放。

在NSNotificationCenter中对AVAudioSessionRouteChangeNotification进行注册。在其userInfo中有键:

枚举值 意义
AVAudioSessionRouteChangeReasonUnknown 未知原因
AVAudioSessionRouteChangeReasonNewDeviceAvailable 有新设备可用
AVAudioSessionRouteChangeReasonOldDeviceUnavailable 老设备不可用
AVAudioSessionRouteChangeReasonCategoryChange 类别改变了
AVAudioSessionRouteChangeReasonOverride App重置了输出设置
AVAudioSessionRouteChangeReasonWakeFromSleep 从睡眠状态呼醒
AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory 当前Category下没有合适的设备
AVAudioSessionRouteChangeReasonRouteConfigurationChange Rotuer的配置改变了

总结:

AVAudioSession构建了一个音频使用生命周期的上下文。当前状态是否可以录音、对其他App有怎样的影响、是否响应系统的静音键、如何感知来电话了等都可以通过它来实现。尤为重要的是AVAudioSession不仅可以和AVFoundation中的AVAudioPlyaer/AVAudioRecorder配合,其他录音/播放工具比如AudioUnit、AudioQueueService也都需要他进行录音、静音等上下文配合。

文中Demo参见GitHub

参考文档

  1. Audio Session Programming Guide
  2. AVAudioSession Class Reference
  3. Audio Session Services Reference
上一篇下一篇

猜你喜欢

热点阅读