iOS多播放源抢占扬声器主声道问题

声波图

  刚入职一周,接到一个调研的任务。问题是这样的,首先App的场景是开启着直播播放功能,然后同时开启一个UIWebView加载着可交互的网页,网页中同样包含着声音,这时在直播声音播放的同时UIWebView播放声音在iOS端会有UIWebView上的声音很小的问题。

问题重现

  因为刚入职嘛,忙于熟悉工作环境、搭建开发环境、熟悉代码,并没有太深入的了解到功能的实现方案。刚听到这个调研需求的时候,我的内心是懵*状态的。起初都没有理解到到底是什么任务,在前一天晚上有个类似的声音混响问题,我先入为主的以为就是那个问题。还好技术总监和公司现在的iOS老哥很好,在我问了几个问题之后,我正确了解到这个问题和问题发生的环境及功能实现的大概架构。
  需求确定之后,下面就是看一下具体的现象的。在iOS老哥的帮助下,我们搭好了测试的环境(新来的没有账号和权限)。于是老哥忙着搞上线,我就开始重现问题。在搭建好环境的基础上重现这个问题还是很容易的,很快问题的现象就出现在了我的面前。嗯~ 是个问题,不太好弄,不过为了证明自己的能力,搞吧!其实问题解决也得益于技术总监和iOS老哥给出了一个现象,就是点击Home键之后,UIWebView上的声音就会变大。知道了这种现象在我感觉就有了解决问题的切入点。

问题解决

  既然知道了什么情况下问题可以得到使问题得到解决,那么就模拟这个过程就可以了。首先需要清楚App的生命周期:

iOS App生命周期
  • Not running:App还没运行
  • Inactive:App运行在foreground但没有接收事件
  • Active:App运行在foreground和正在接收事件
  • Background:运行在background和正在执行代码
  • Suspended:运行在background但没有执行代码
    这里App的生命周期不做详细说明,先针对这个问题说说解决的思路,来日方长以后有时间了再详细探讨一下。
    运行中的App是Active状态,按下Home则进入Background状态。一般的思路是模拟App从Active到Background,但是iOS是闭源的我们没有办法看到App生命周期底层的实现,但是我们知道UIApplication是有一个协议的UIApplicationDelegate,遵循这个协议的就是UIApplication的代理类,这个代理类会监听UIApplication的状态。如何可以手动更改UIApplication的状态我们就可以直接完成这个过程了,UIApplication是有一个属性applicationState,不过这个属性的修饰是readonly,所以此路不通。再想想开发过程中,有的时候会用NSNotificationCenter去监听系统通知来获取App是否处于foreground或background状态,既然系统能发,那我们也能发,所以试试能不能发送一个通知,让相关的处理被触发。
      于是乎,方案一就出来了:
// 发送进入后台的通知
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]];
// 发送进入前台的通知
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]];

这个方案可以临时解决声音小的问题,但是有一个相对不妥的地方:发送系统的通知,自己的代码里监听系统通知的方法也会被调用,根据逻辑的不同我们很难说清楚会不会导致什么问题。那就再研究研究。
  因为涉及到播放硬件,所以我就在想别的播放硬件会不会有什么影响呢?结果用耳机试了一下,惊喜的是耳机声音是大的,由此我想到可以模拟耳机插拔,但是是类似于上面提到的,也会有一定的影响,不过很明显这个模拟系统通知的影响一定程度上的危险级别就比上面小了,于是方案二就尝试出来了:

- (void)webViewDidFinishLoadAction {
   [self devicePushRouteChangeWithValue:AVAudioSessionRouteChangeReasonNewDeviceAvailable];
   [self devicePushRouteChangeWithValue:AVAudioSessionRouteChangeReasonOldDeviceUnavailable];
}
- (void)devicePushRouteChangeWithValue:(AVAudioSessionRouteChangeReason)value {
   NSDictionary * dataDic = @{
                              AVAudioSessionRouteChangeReasonKey : [NSNumber numberWithInteger:value]
                              };
   [[NSNotificationCenter defaultCenter] postNotificationName:AVAudioSessionRouteChangeNotification object:[AVAudioSession sharedInstance] userInfo:dataDic];
}

由此作为初步解决问题的方案。
之后的一段时间内我一直查找问题出现的原理,后经过看了一些文章发现与AVAudioSession有可能有一定的关系:


Audio Session

  可以看到AVAudioSession就是用来管理多个APP对音频硬件设备(麦克风,扬声器)的资源使用。
举例一下AVAudioSession可以做这些事情

  • 设置自己的APP是否和其他APP音频同时存在,还是中断其他APP声音
  • 在手机调到静音模式下,自己的APP音频是否可以播放出声音
  • 电话或者其他APP中断自己APP的音频的事件处理
  • 指定音频输入和输出的设备(比如是听筒输出声音,还是扬声器输出声音)
  • 是否支持录音,录音同时是否支持音频播放

  Category:

  • AVAudioSessionCategoryAmbient,只支持音频播放。这个 Category,音频会被静音键和锁屏键静音。并且不会打断其他应用的音频播放。
  • AVAudioSessionCategorySoloAmbient,这个是系统默认使用的 Category,只支持音频播放。音频会被静音键和锁屏键静音。和AVAudioSessionCategoryAmbient不同的是,这个会打断其他应用的音频播放
  • AVAudioSessionCategoryPlayback,只支持音频播放。你的音频不会被静音键和锁屏键静音。适用于音频是主要功能的APP,像网易云这些音乐app,锁屏后依然可以播放。
  • AVAudioSessionCategoryRecord,只支持音频录制。不支持播放。
  • AVAudioSessionCategoryPlayAndRecord,支持音频播放和录制。音频的输入和输出不需要同步进行,也可以同步进行。需要音频通话类应用,可以使用这个 Category。
  • AVAudioSessionCategoryAudioProcessing,只支持本地音频编解码处理。不支持播放和录制。
  • AVAudioSessionCategoryMultiRoute,支持音频播放和录制。允许多条音频流的同步输入和输出。(比如USB连接外部扬声器输出音频,蓝牙耳机同时播放另一路音频这种特殊需求)

  AVAudioSession的属性名为:

@property(readonly) NSArray *availableCategories;

  开发需求中有时候需要对Category进行微调整,我们发现这个接口还有两个参数Mode和Options

  Mode:

  • AVAudioSessionModeDefault,默认模式,与所有的 Category 兼容
  • AVAudioSessionModeVoiceChat,适用于VoIP 类型的应用。只能是 AVAudioSessionCategoryPlayAndRecord Category下。在这个模式系统会自动配置AVAudioSessionCategoryOptionAllowBluetooth 这个选项。系统会自动选择最佳的内置麦克风组合支持语音聊天。
  • AVAudioSessionModeVideoChat,用于视频聊天类型应用,只能是 AVAudioSessionCategoryPlayAndRecord Category下。适在这个模式系统会自动配置 AVAudioSessionCategoryOptionAllowBluetooth 和 AVAudioSessionCategoryOptionDefaultToSpeaker 选项。系统会自动选择最佳的内置麦克风组合支持视频聊天。
  • AVAudioSessionModeGameChat,适用于游戏类应用。使用 GKVoiceChat 对象的应用会自动设置这个模式和 AVAudioSessionCategoryPlayAndRecord Category。实际参数和AVAudioSessionModeVideoChat一致
  • AVAudioSessionModeVideoRecording,适用于使用摄像头采集视频的应用。只能是 AVAudioSessionCategoryPlayAndRecord 和 AVAudioSessionCategoryRecord 这两个 Category下。这个模式搭配 AVCaptureSession API 结合来用可以更好地控制音视频的输入输出路径。(例如,设置 automaticallyConfiguresApplicationAudioSession 属性,系统会自动选择最佳输出路径。
  • AVAudioSessionModeMeasurement,最小化系统。只用于 AVAudioSessionCategoryPlayAndRecord、AVAudioSessionCategoryRecord、AVAudioSessionCategoryPlayback 这几种 Category。
  • AVAudioSessionModeMoviePlayback,适用于播放视频的应用。只用于 AVAudioSessionCategoryPlayback 这个Category。

  options:

  • AVAudioSessionCategoryOptionMixWithOthers:支持和其他APP音频mix,兼容的Category:AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryPlayback AVAudioSessionCategoryMultiRoute
  • AVAudioSessionCategoryOptionDuckOthers:系统智能调低其他APP音频音量,兼容的Category:AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryPlayback AVAudioSessionCategoryMultiRoute
  • AVAudioSessionCategoryOptionAllowBluetooth:支持蓝牙音频输入,兼容的Category:AVAudioSessionCategoryRecord AVAudioSessionCategoryPlayAndRecord
  • AVAudioSessionCategoryOptionDefaultToSpeaker:设置默认输出音频到扬声器,兼容的Category:AVAudioSessionCategoryPlayAndRecord

  在了解了上述知识后,我又做了一些尝试去修改音频的播放:

[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers error:nil];

  由此我猜测是直播服务商对于音频的播放做了处理,导致其他的声音播放时变小。经过询问和沟通验证了我的猜测,所以对于服务商做了相应的功能调整需求,直至服务商修改完成,这个出现的异常情况得到了根源性的修正。

你可能感兴趣的:(iOS多播放源抢占扬声器主声道问题)