音视频开发-AVAudioSession在开发中的应用

文章目录

  • AVAudioSession简介
  • 音频的激活
    • 音频竞争
    • 音频打断恢复
      • AVAudioSessionInterruptionNotification
      • AVAudioSessionRouteChangeNotification
        • 枚举
        • 通知处理
      • 远程控制的监听
      • Media Server Reset 媒体服务器重置
        • AVAudioSessionMediaServicesWereResetNotification
        • AVAudioSessionMediaServicesWereLostNotification
  • Mode的影响
    • 不要频繁的切换Mode
    • Category是可以切换的
    • 各个Mode的隐式设置
  • 为AirPlay选择类别和模式
  • 权限获取-录制音频的许可
  • 关于如何打开后台Audio模式
  • 官方指南
    • 音频指南按应用程序类型
      • 游戏类应用程序
      • 播放和录制类应用程序
      • VoIP和聊天应用程序
      • 计量的应用程序
      • 类似浏览器的应用程序
      • 导航和健身应用
      • 合作音乐应用程序

AVAudioSession简介

官方文档镇楼
https://developer.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/Introduction/Introduction.html

音视频开发-AVAudioSession在开发中的应用_第1张图片
音频在iOS,tvOS是一种托管服务。系统通过使用音频会话管理应用程序、应用程序间和设备级别的音频行为。
我们使用AVAudioSession来与系统沟通在我们的应用程序中使用音频。我们可以做的交互如下

  • 配置音频会话类别(Category)和模式(mode),以便与系统通信,了解您打算如何在应用程序中使用音频
  • 激活应用程序的音频会话,将类别(Category)和模式(mode)配置放入操作中
  • 订阅和响应重要的音频会话通知,例如音频中断(AVAudioSessionInterruptionNotification)和路由更改(AVAudioSessionRouteChangeNotification)
  • 执行高级音频设备配置,如设置采样速率I/O缓冲区持续时间和通道数量

AVAudioSession是App和System之间的中介,在App启动的时候,应用程序会自动提供一个AVAudioSession单例,你通过设置这个单例来实现音频配置。

音视频开发-AVAudioSession在开发中的应用_第2张图片
https://developer.apple.com/videos/play/wwdc2012/505/
https://developer.apple.com/library/archive/qa/qa1754/_index.html

音频的激活

官方文档
https://developer.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/ConfiguringanAudioSession/ConfiguringanAudioSession.html#//apple_ref/doc/uid/TP40007875-CH2-SW1

音频竞争

当我们的App启动时,内置的应用程序或者其他App(消息、音乐、Safari、手机)可能正在后台运行。它们中的每一个都可能产生音频:一条文本消息到达,音乐后台播放。

Q1:当QQ音乐后台播放时,你的App需要播放声音,如何处理这种竞争关系呢?

系统处理音频竞争图如下:
音视频开发-AVAudioSession在开发中的应用_第3张图片

背景:Music App正在占用音频播放。此时我们的MyApp需要进行音频播放
1-2.MyApp请求CoreAudio控制中心,我请求激活音频会话
//get your app's audioSession singleton object
AVAudioSession* session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord
                                             withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker |AVAudioSessionCategoryOptionAllowBluetooth
                                                   error:nil];
                                                   
[session setMode:AVAudioSessionModeDefault error:&error];
                                                   
[session setActive:YES error:&error];

3-4.通过MyApp的设置代码,CoreAudio考虑其类别,对其他音频进行处理
如果,您的应用程序使用了一个类别(不是AVAudioSessionCategoryOptionMixWithOthers),该类别要求对其他音频进行静音,系统将关闭音乐应用程序的音频会话,停止其音频播放。
5.系统(CoreAudio)激活您的应用程序的音频会话和播放可以开始。

音频打断恢复

Q2:当你的App正在播放,此时来了一个电话,接听电话后,如何让App恢复音频播放?

AVAudioSessionInterruptionNotification

https://developer.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/HandlingAudioInterruptions/HandlingAudioInterruptions.html

当电话打断,短信打断时,此类音频打断我们都会受到一个AVAudioSessionInterruptionNotification通知,通过监听这个通知,我们处理App的音频恢复播放

AVAudioSession *session = [AVAudioSession sharedInstance];
NSNotificationCenter *noficationCenter = [NSNotificationCenter defaultCenter];
[noficationCenter addObserver: self
                     selector: @selector(handleRouteChange:)
                         name: AVAudioSessionRouteChangeNotification
                       object: session]; //这是route改变通知,例如耳机插拔
[noficationCenter addObserver: self
                     selector: @selector(handleInterruption:)
                         name: AVAudioSessionInterruptionNotification
                       object: session]; //这是音频打断通知

以下是我的项目中处理打断通知的模板,[AVAudioSession sharedInstance].secondaryAudioShouldBeSilencedHint 指示另一应用程序是否正在播放音频,与otherAudioPlaying一样,只是在iOS 8之后,其判断更加严格

- (void)handleInterruption:(NSNotification*)notification {
    NSInteger reason = 0;
    NSString *reasonStr = @"";
    if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
        //Posted when an audio interruption occurs.
        //bug https://blog.csdn.net/shengpeng3344/article/details/83617979
        if ([[[UIDevice currentDevice] systemVersion] compare:@"10.3" options:NSNumericSearch] != NSOrderedAscending) {
            BOOL isSuspend = [[[notification userInfo] objectForKey:AVAudioSessionInterruptionWasSuspendedKey] boolValue];
            if (isSuspend) {
                LogInfo(@"[GSAudioStreamer] AVAudioSessionInterruptionWasSuspendedKey is YES");
                return;
            }
        } else {
            // Fallback on earlier versions
        }
        reason = [[[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey] integerValue];
        if (reason == AVAudioSessionInterruptionTypeBegan) { //在非后台模式下  打断处理 - 后台打断处理会导致底层线程卡死
            reasonStr = @"AVAudioSessionInterruptionTypeBegan";
            if ([self pause]) {
                pausedByInterruption = YES;
            }
             //should stop whatever in background or app is not background audio type
        }
        
        if (reason == AVAudioSessionInterruptionTypeEnded) {
            reasonStr = @"AVAudioSessionInterruptionTypeEnded";
            NSNumber *seccondReason = [[notification userInfo] objectForKey:AVAudioSessionInterruptionOptionKey];
            switch ([seccondReason integerValue]) {
                case AVAudioSessionInterruptionOptionShouldResume:{ //在非后台模式下  打断处理 - 后台打断处理会导致底层线程卡死
                    reasonStr = @"AVAudioSessionInterruptionTypeEnded - resume ";
                }
                case 0:{
                    reasonStr = @"AVAudioSessionInterruptionTypeEnded - unknown";
                }
                    // Indicates that the audio session is active and immediately ready to be used. Your app can resume the audio operation that was interrupted.
                    break;
                default:
                    break;
            }
            
            if (pausedByInterruption) {
                [self resume];
                pausedByInterruption = NO;
            }
        }
    }
    
    LogInfo(@"[GSAudioStreamer] handleInterruption reason %@ , userInfo : %@.", reasonStr,[notification userInfo]);
}

其他App调用[[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];,自己的App会收到AVAudioSessionInterruptionNotification,且在音频打断通知AVAudioSessionInterruptionNotificationAVAudioSessionInterruptionOptionKey设置为AVAudioSessionInterruptionOptionShouldResume,这就是当接入电话后,QQ音乐会自动恢复播放的原因。如果不设置AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation,则无法做到这点。

AVAudioSessionRouteChangeNotification

附上handleRouteChange的通知处理,这个处理尤其重要,大部分音频App都需要很好的处理这个通知

参考
https://developer.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/HandlingAudioHardwareRouteChanges/HandlingAudioHardwareRouteChanges.html

枚举

最重要的是理解这几个枚举

/* values for AVAudioSessionRouteChangeReasonKey in AVAudioSessionRouteChangeNotification用户信息字典
AVAudioSessionRouteChangeReasonUnknown
原因不明。

AVAudioSessionRouteChangeReasonNewDeviceAvailable
一种新的设备出现了(例如,耳机已经插好)。例如蓝牙耳机连上

AVAudioSessionRouteChangeReasonOldDeviceUnavailable
旧设备无法使用(如耳机未插拔)。例如蓝牙耳机airpod从耳朵上拿下来

AVAudioSessionRouteChangeReasonCategoryChange
音频类别已更改(例如AVAudioSessionCategoryPlayback已更改为AVAudioSessionCategoryPlayAndRecord)。
一般由[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:newOption error:nil];触发
        
AVAudioSessionRouteChangeReasonOverride
路由已被覆盖(例如,category是AVAudioSessionCategoryPlayAndRecord和输出
已从默认的接收方更改为扬声器)。一般由[[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];触发

AVAudioSessionRouteChangeReasonWakeFromSleep
设备从休眠中醒来。

AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory
当当前类别没有路由时返回(例如,该类别是AVAudioSessionCategoryRecord)
但是没有输入设备可用)。

AVAudioSessionRouteChangeReasonRouteConfigurationChange
表示输入和/或输出端口的集合没有改变,只是改变了它们的某些方面
配置发生了变化。例如,端口选择的数据源发生了更改。
*/
typedef NS_ENUM(NSUInteger, AVAudioSessionRouteChangeReason)
{
	AVAudioSessionRouteChangeReasonUnknown = 0,
	AVAudioSessionRouteChangeReasonNewDeviceAvailable = 1,
	AVAudioSessionRouteChangeReasonOldDeviceUnavailable = 2,
	AVAudioSessionRouteChangeReasonCategoryChange = 3,
	AVAudioSessionRouteChangeReasonOverride = 4,
	AVAudioSessionRouteChangeReasonWakeFromSleep = 6,
	AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory = 7,
	AVAudioSessionRouteChangeReasonRouteConfigurationChange = 8 // added in iOS 7
};

值得注意的是

  1. overrideOutputAudioPort:会触发AVAudioSessionRouteChangeReasonOverride
  2. setCategory: withOptions:error:会触发AVAudioSessionRouteChangeReasonCategoryChange

两者都可以改变音频输出为听筒/蓝牙(Receiver)或者扬声器(Speaker),例如:

//切听筒 - override
[[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:nil];
//切听筒 - category
AVAudioSessionCategoryOptions newOption = (self.sessionCategoryOption | AVAudioSessionCategoryOptionDefaultToSpeaker)^AVAudioSessionCategoryOptionDefaultToSpeaker;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:newOption error:nil];

//切扬声器 - override
[[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];
//切扬声器 - category
AVAudioSessionCategoryOptions newOption = self.sessionCategoryOption | AVAudioSessionCategoryOptionDefaultToSpeaker;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:newOption error:nil];

//区别在于,category方式,在蓝牙连接的情况下,改变是无效的,而override是默认会调用,还会收到通知,随后会收到newDevice的通知,表示收到蓝牙设备。但override不会改变category,仅仅改变输出port

通知处理

我们一般都需要处理打断通知路由改变通知

这里对inputs和outputs数组进行了空值判断,是存在为空的情况,请开发者注意

//MARK:AVAudioSession
//FIXME: due to audio session route change, will cause audio queue slient while playing, to fix this bug we handler this notification
//由于音频会话路由的改变,将导致音频队列在播放时保持静音,为了修复这个bug我们处理这个通知

- (void)handleRouteChange:(NSNotification *)notification {
    AVAudioSession *session = [ AVAudioSession sharedInstance];
    NSString *seccReason = @"";
    NSInteger reason = [[[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
    AVAudioSessionRouteDescription* prevRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey];
    switch (reason) {
        case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
            seccReason = @"The route changed because no suitable route is now available for the specified category.";
            break;
        case AVAudioSessionRouteChangeReasonWakeFromSleep:
            seccReason = @"The route changed when the device woke up from sleep.";
            break;
        case AVAudioSessionRouteChangeReasonOverride:
            seccReason = @"The output route was overridden by the app.";
            break;
        case AVAudioSessionRouteChangeReasonCategoryChange:
            seccReason = @"The category of the session object changed.";
            break;
        case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
            seccReason = @"The previous audio output path is no longer available.";
            break;
        case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
            seccReason = @"A preferred new audio output path is now available.";
            break;
        case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
            seccReason = @"The route configuration changed.";
            break;
        case AVAudioSessionRouteChangeReasonUnknown:
        default:
            seccReason = [NSString stringWithFormat:@"The reason for the change is unknown.(%ld)",(long)reason];
            break;
    }
//    if ([[[UIDevice currentDevice] systemVersion] compare:@"8.3" options:NSNumericSearch] != NSOrderedAscending) {
//        shouldResume = NO;
//    }
    struct timeval time = {
        .tv_sec = 0
    };
    gettimeofday(&time, NULL);
    NSTimeInterval nowval = time.tv_sec + time.tv_usec/1e9;
    if (category_time == 0 || nowval > category_time + 2) {
        category_time = nowval;
        [self reset];
        [self resume];
    }
    
    LogInfo(@"[GSAudioStreamer] handleRouteChange session %@ %@ %lu", session.mode,session.category,(unsigned long)session.categoryOptions);
    LogInfo(@"[GSAudioStreamer] handleRouteChange reason is %@", seccReason);
    if (session.currentRoute) {
        if (session.currentRoute.outputs) {
            NSArray<AVAudioSessionPortDescription *>*outputs = session.currentRoute.outputs;
            if (outputs.count > 0) {
                AVAudioSessionPortDescription *output = [outputs firstObject];
                LogInfo(@"[GSAudioStreamer] inport port type is %@ , name : %@", output.portType,output.portType);
                if (output.portType == AVAudioSessionPortBuiltInReceiver) {
                    NSError *error = nil;
                    //这里强制转为扬声器
                    [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];
                    if (!error) {
                        LogInfo(@"[GSAudioStreamer] overrideOutputAudioPort Error : %@",error);
                    }
                }
            }

        }
    }
}

远程控制的监听

另外远程控制的监听需要 MPRemoteCommandCenter 相关的知识 https://developer.apple.com/documentation/mediaplayer/mpremotecommandcenter

http://blog.cocoachina.com/article/29610
这篇文章写得不错,注意的是MPRemoteCommand是屏保时的控制按键事件,如果你需要监听airpod的从耳取下的事件,是需要处理MPRemoteCommandpauseCommandAVAudioSessionRouteChangeReasonOldDeviceUnavailable

Media Server Reset 媒体服务器重置

AVAudioSessionMediaServicesWereResetNotification

媒体服务器通过共享服务器进程提供音频和其他多媒体功能。虽然很少见,但媒体服务器可以在您的应用程序处于活动状态时重置。注册AVAudioSessionMediaServicesWereResetNotification通知,以监视媒体服务器重置。收到通知后,您的app需要做以下操作:

  • 处理孤立的音频对象(如播放器、录音机、转换器或音频队列)并创建新对象
  • 重置任何被跟踪的内部音频状态,包括AVAudioSession的所有属性
  • 在适当的时候,使用setActive:error:方法重新激活AVAudioSession实例

重要提示:应用程序不需要重新注册任何AVAudioSession通知或重置AVAudioSession属性上的key-value observer。

AVAudioSessionMediaServicesWereLostNotification

如果您想知道媒体服务器何时首次不可用,还可以注册AVAudioSessionMediaServicesWereLostNotification通知。然而,大多数应用程序只需要响应重置通知。只有当应用程序必须响应在媒体服务器丢失后但在媒体服务器重置之前发生的用户事件时,才使用丢失的通知。

如何测试:您可以通过在“设置”->“开发者”->Reset Media Services模拟。使用此实用程序可以方便地确保您的应用程序在重置媒体服务时作出适当的响应。

Mode的影响

关于Category和Mode的配置参数,官方文档说明了

https://developer.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/AudioSessionCategoriesandModes/AudioSessionCategoriesandModes.html#//apple_ref/doc/uid/TP40007875-CH10-SW3

不要频繁的切换Mode

值得注意的几个点,例如AVAudioSessionCategoryPlayAndRecord会默认切换到AVAudioSessionModeVoiceChat
文档中说明如下:

/* Only valid with AVAudioSessionCategoryPlayAndRecord.  Appropriate for Voice over IP
(VoIP) applications.  Reduces the number of allowable audio routes to be only those
that are appropriate for VoIP applications and may engage appropriate system-supplied
signal processing.  Has the side effect of setting AVAudioSessionCategoryOptionAllowBluetooth */
extern AVAudioSessionMode const AVAudioSessionModeVoiceChat API_AVAILABLE(ios(5.0), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macos);

所以当你切换AVAudioSessionCategoryPlayAndRecordAVAudioSessionCategoryPlayBack时,默认是切换AVAudioSessionModeDefaultAVAudioSessionModeVoiceChat,如果你的app需要同时录音和播放,你就一直指定为AVAudioSessionCategoryPlayAndRecord就好了,如果需要响应MPNowPlayingInfoCenter在锁屏界面进行显示播放信息以及控制MPCommand,你则需要设置为AVAudioSessionCategoryPlayBack

if (session.mode != AVAudioSessionModeDefault) {
        success = [session setMode:AVAudioSessionModeDefault error:&error];
        if (!success) MLog(@"AVAudioSession setMode error: %@",error);
    }

Category是可以切换的

在各个App的使用中,如果场景比较复杂,是可以切换Category来实现功能,但同样不因太频繁

各个Mode的隐式设置

对于开发对应mode的音视频应用,各有差异,例如:
对于AVAudioSessionModeDefaultAVAudioSessionModeVoiceChat两种模式,AVAudioSessionModeVoiceChat会隐式采用outportReceiver(AVAudioSessionPortBuiltInReceiver),在AVAudioSessionModeDefault会隐式使用Speaker(AVAudioSessionPortBuiltInSpeaker),你可以自己监听RouteChanged通知来判断它默认采用的哪种。
这两种的表现为AVAudioSessionModeVoiceChat声音特别小,因为是从听筒出来的。
大同小异,需要开发者自己注意。

为AirPlay选择类别和模式

只有特定的类别和模式支持AirPlay。以下类别同时支持AirPlay的镜像和非镜像版本

  • AVAudioSessionCategorySoloAmbient
  • AVAudioSessionCategoryAmbient
  • AVAudioSessionCategoryPlayback

AVAudioSessionCategoryPlayAndRecord类别和以下模式只支持AirPlay的镜像版本:

  • AVAudioSessionModeDefault
  • AVAudioSessionModeVideoChat
  • AVAudioSessionModeGameChat

权限获取-录制音频的许可

为了保护用户隐私,您的应用程序在录制音频之前必须征得用户的同意。如果用户不授予权限,则只记录静默。当你使用一个支持记录的类别,而应用程序试图使用输入路径时,系统会自动提示用户获得许可。

您可以使用requestRecordPermission:方法手动请求权限,而不是等待系统提示用户记录权限。使用这种方法,您的应用程序可以在不中断应用程序的自然流程的情况下获得许可,从而获得更好的用户体验。

AVAudioSession.sharedInstance().requestRecordPermission { granted in
    if granted {
        // User granted access. Present recording interface.
    } else {
        // Present message to user indicating that recording
        // can't be performed until they change their preference
        // under Settings -> Privacy -> Microphone
    }
}

重要提示:从iOS 10开始,所有访问设备麦克风的应用程序都必须静态声明它们的意图。要做到这一点,应用程序现在必须在其信息中包含NSMicrophoneUsageDescription键。并为该键提供一个目的字符串。当系统提示用户允许访问时,此字符串将作为警告的一部分显示。
如果一个应用程序试图在没有该密钥和值的情况下访问设备的任何麦克风,该应用程序将终止。
即应该是在plist中需要进行设置键值对

如果一个应用程序试图在没有该密钥和值的情况下访问设备的任何麦克风,该应用程序将终止。

关于如何打开后台Audio模式

OStvOS应用程序要求您为某些后台操作启用某些功能。回放应用程序需要的一个常见功能是播放背景音频。启用此功能后,当用户切换到其他应用程序或锁定iOS设备时,应用程序的音频可以继续。这一功能也需要支持先进的播放功能,如AirPlay流媒体和图片中的图片在iOS中播放。

配置这些功能的最简单方法是使用Xcode。在Xcode中选择应用程序的目标并选择capability选项卡。在capability选项卡下,将背景模式切换为ON,并从可用模式列表中选择“Audio, AirPlay, and Picture in Picture”选项。
音视频开发-AVAudioSession在开发中的应用_第4张图片

官方指南

音频指南按应用程序类型

以下内容都摘自官方文档
https://developer.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/AudioGuidelinesByAppType/AudioGuidelinesByAppType.html#//apple_ref/doc/uid/TP40007875-CH11-SW1
本人加上了自己的理解,翻译水平有限

游戏类应用程序

大多数游戏都需要用户交互才能让游戏中发生任何事情。在设计游戏时使用AVAudioSessionCategoryAmbient或AVAudioSessionCategorySoloAmbient类别。当用户打开另一个应用程序或锁定屏幕时,他们不希望该应用程序继续播放。通常他们希望在游戏程序播放时,来自另一个应用程序的音频能够继续播放。
以下是官方文档的指南:

  • 在应用程序委托的applicationDidBecomeActive:方法中激活音频会话。
  • 播放应用程序的音效,同时允许其他应用程序的音频播放。Tips: 一般使用AVAudioSessionCategoryOptionDuckOthers
  • 当其他音频不播放时,播放应用程序原声音频,否则允许前一个音频播放。
  • 在结束中断事件后,始终尝试重新激活和恢复声音效果的播放。(AVAudioSessionInterruptionNotification)
  • 查询音频会话的secondaryAudioShouldBeSilencedHint属性,以确定是否应该重新播放游戏的原声带。
  • 忽略所有路由更改,除非应用程序特别需要注意它们。
  • 在应用程序启动显示视频闪屏前设置音频类别。

播放和录制类应用程序

播放和录制类App,应该选用AVAudioSessionCategoryRecord、AVAudioSessionCategoryPlayAndRecord或AVAudioSessionCategoryPlayback类别,当它们的音频会话被激活时,通常会中断其他系统音频。
以下是官方文档的指南:

  • 当应用程序进入前台时,等待用户按下播放或录制按钮,然后激活音频会话。
  • 当应用程序在前台时,保持音频会话活动,除非它被中断。
  • 如果App在切换到后台时没有积极地播放或录制音频,则禁用其音频会话。这可以防止它的音频会话被另一个不可混合的App或系统在App被挂起时中断。当你的app是非后台模式时,你需要在进入后台时,显式的关闭AudioSession,否则会收到不必要的打断通知,详情见: https://blog.csdn.net/shengpeng3344/article/details/83617979
  • 更新UI,以指示回放或录制在中断时已暂停。不要停用音频会话。
  • 观察AVAudioSessionInterruptionNotification类型的通知,以便在音频会话中断时得到通知。当中断结束时,不要再开始播放或录制音频,除非应用程序在中断之前已经这样做了
  • 如果路由更改是由拔出事件引起的,则暂停播放或录制,但要保持音频会话处于活动状态。
  • 假设应用程序的音频会话在从暂停状态过渡到前台状态时处于非活动状态。当用户按下播放或录制按钮时,重新激活音频会话。
  • 确保设置了audio UIBackgroundModes标志,即后台允许音频播放。
  • 注册远程控制事件(请参阅[MPRemoteCommandCenter](https://developer.apple.com/documentation/mediaplayer/mpremotecommandcenter)),并为您的媒体提供适当的NowPlaying信息(请参阅MPNowPlayingInfoCenter)。
  • 使用MPVolumeView对象来显示系统音量调节和路由选择器。
  • 使用后台任务而不是流媒体静音来防止应用程序被暂停。
  • 通过requestRecordPermission:来向用户请求记录权限。不要依赖操作系统来提示用户。
  • 对于录制应用程序,使用AVAudioSessionCategoryPlayAndRecord类别而不是AVAudioSessionCategoryRecord类别。只记录的类别实际上会使所有系统输出静默,而且通常对大多数应用程序来说限制太大。

VoIP和聊天应用程序

VoIP和聊天应用程序要求输入和输出路由(input and output routes)都可用。这类应用程序使用AVAudioSessionCategoryPlayAndRecord类别,不与其他应用程序混合。

  • 只有当用户接听或发起呼叫时,才激活音频会话。
  • 在收到中断通知之后更新UI以反映调用的音频已被中断。
  • 在中断之后,不要激活音频会话,直到用户回答或发起一个调用。
  • 在调用结束后,使用AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation常量禁用音频会话。

Tips:意为调用[[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];,会告诉CoreAudio自己的App音频关闭了,CoreAudio会恢复其他需要播放的App,这就是当接入电话后,QQ音乐会自动恢复播放的原因。如果不设置AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation,则无法做到这点。

  • 忽略所有路由更改,除非应用程序特别需要注意它们。例如,路由更改可能导致对会话的采样率、缓冲区持续时间或延迟的更改。如果这些值与您的应用程序相关,请在路由更改后查询它们,以获得它们的最新状态。
  • 对于VoIP应用程序,使用苹果的语音处理I/O音频单元。
  • 确保设置了audio UIBackgroundModes标志。
  • 使用MPVolumeView对象来显示系统音量调节和路由选择器。
  • 通过requestRecordPermission:来向用户请求记录权限。不要依赖操作系统来提示用户。

从iOS 10开始,要构建与内置电话应用(电话和FaceTime应用)具有相同功能的VoIP应用程序,请使用CallKit框架。有关更多信息,请参见CallKit框架参考。

计量的应用程序

计量应用程序需要对输入和输出路由应用最少的系统提供的信号处理。设置AVAudioSessionCategoryPlayAndRecord类别和测量模式,以最小化信号处理。此外,这类应用程序不会与其他应用程序混合。

  • 总是尝试在结束中断事件后重新激活并恢复播放。
  • 忽略所有路由更改,除非应用程序特别需要注意它们。
  • 在应用程序启动显示视频闪屏前设置音频类别。
  • 通过requestRecordPermission:来向用户请求记录权限。不要依赖操作系统来提示用户。

类似浏览器的应用程序

社交媒体或其他类似浏览器的应用程序经常播放短视频。它们使用AVAudioSessionCategoryPlayback类别,不服从铃声开关。这些应用程序也不与其他应用程序混合。

  • 总是等待用户开始回放。
  • 在视频结束后,使用AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation常量禁用音频会话。
  • 由于断开事件导致路由更改,请暂停音频会话,但要保持音频会话处于活动状态。
  • 视频播放时注册远程控制事件,视频结束时注销。
  • 当应用程序收到开始中断事件时更新UI。
  • 在接收到结束中断事件后,等待用户开始回放。

导航和健身应用

导航和锻炼应用程序使用AVAudioSessionCategoryPlaybackAVAudioSessionCategoryPlayAndRecord类别。这些应用程序的音频通常由简短的语音提示组成。当播放时,这些提示会中断语音,比如播客或有声书,并与(和鸭子)其他音频混合,比如音乐应用程序的播放。

  • 使用AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthersAVAudioSessionCategoryOptionDuckOthers选项激活音频会话。即开启混合,或者使得其他App的音频变小
  • 在需要提示之前不要激活音频会话。
  • 总是在播放提示后禁用音频会话。
  • 不要试图重新播放被中断的提示符。

合作音乐应用程序

合作音乐应用程序是为在其他应用程序播放时播放而设计的。这些类型的应用程序使用AVAudioSessionCategoryPlayback或AVAudioSessionCategoryPlayAndRecord`类别,并与其他应用程序混合使用。

  • 使用AVAudioSessionCategoryOptionMixWithOthers选项激活音频会话。
  • 如果应用程序的UI没有提供开始/停止按钮,请遵循游戏应用程序指南。
  • 如果应用程序的UI提供了一个start/stop按钮,那么只在用户按下play按钮时激活音频会话。
  • 不要注册远程控制事件。
  • 确保设置了audio UIBackgroundModes标志。
  • 通过requestRecordPermission:来向用户请求记录权限。不要依赖操作系统来提示用户。

推荐文章:
https://juejin.im/post/5b3c40bef265da0f774a8a4d

学习不断,有错误请指出,如果对你有帮助,点个赞

你可能感兴趣的:(音视频开发)