Apple经过audio sessions管理app, app与其余app, app与外部音频硬件间的行为.使用audio session能够向系统传达你将如何使用音频.audio session充当着app与系统间的中介.这样咱们无需了解硬件相关却能够操控硬件行为.html
audio session是应用程序与系统间的中介,用于配置音频行为,APP启动时,会自动得到一个audio session的单例对象,配置而且激活它以让音频按照指望开始工做.数组
audio session category表明音频的主要行为.经过设置类别, 能够指明app是否使用的当前的输入或输出音频设备,以及当别的app中正在播放音频进入咱们app时他们的音频是强制中止仍是与咱们的音频一块儿播放等等.浏览器
AVFoundation中定义了不少audio session categories, 你能够根据须要自定义音频行为,不少类别支持播放,录制,录制与播放同时进行,当系统了解了你定义的音频规则,它将提供给你合适的路径去访问硬件资源.系统也将确保别的app中的音频以适合你应用的方式运行.bash
一些categories能够根据Mode进一步定制,该模式用于专门指定类别的行为,例如当使用视频录制模式时,系统可能会选择一个不一样于默认内置麦克风的麦克风,系统还能够针对录制调整麦克风的信号强度.服务器
若是audio意外中断,系统会将aduio session置为停用状态,音频也会所以当即中止.当一个别的app的audio session被激活而且它的类别未设置与系统类别或你应用程序类别混合时,中断就会发生.你的应用程序在收到中断通知后应该保存当时的状态,以及更新用户界面等相关操做.经过注册AVAudioSessionInterruptionNotification能够观察中断的开始与结束点.session
当用户作出链接,断开音频输入,输出设备时,(如:插拔耳机)音频线路发生变化,经过注册AVAudioSessionRouteChangeNotification
能够在音频线路发生变化时作出相应处理.app
App不能直接控制设备的硬件,可是audio session提供了一些接口去获取或设置一些高级的音频设置,如采样率,声道数等等.ide
App若是想使用音频录制功能必须请求用户受权,不然没法使用.测试
在设置了audio session的category, options, mode后,咱们能够激活它以启动音频.优化
随着app的启动,内置的一些服务(短信,音乐,浏览器,电话等)也将在后台运行.前面的这些内置服务均可能产生音频,若有电话打来,有短信提示等等...
虽然AVFoundation中播放与录制能够自动激活你的audio session, 但你能够手动激活而且测试是否激活成功.
系统会停用你的audio session当有电话打进来,闹钟响了,或是日历提醒等消息介入.当处理完这些介入的消息后,系统容许咱们手动从新激活audio sesseion.
let session = AVAudioSession.sharedInstance()
do {
// 1) Configure your audio session category, options, and mode
// 2) Activate your audio session to enable your custom configuration
try session.setActive(true)
} catch let error as NSError {
print("Unable to activate audio session: \(error.localizedDescription)")
}
复制代码
若是咱们使用AVFoundation对象(AVPlayer, AVAudioRecorder等),系统负责在中断结束时从新激活audio session.然而,若是你注册了通知去从新激活audio session,你能够验证是否激活成功而且更新用户界面.
当你的app被激活前,当前设备可能正在播放别的声音,若是你的app是一个游戏的app,知作别的声音来源显得十分重要,由于许多游戏容许同时播放别的音乐以加强用户体验.
在app进入前台前,咱们能够经过applicationDidBecomeActive:
代理方法在其中使用secondaryAudioShouldBeSilencedHint
属性来肯定音频是否正在播放.当别的app正在播放的audio session为不可混音配置时,该值为true. app可使用此属性消除次要音频.
func setupNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(handleSecondaryAudio),
name: .AVAudioSessionSilenceSecondaryAudioHint,
object: AVAudioSession.sharedInstance())
}
func handleSecondaryAudio(notification: Notification) {
// Determine hint type
guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey] as? UInt,
let type = AVAudioSessionSilenceSecondaryAudioHintType(rawValue: typeValue) else {
return
}
if type == .begin {
// Other app audio started playing - mute secondary audio
} else {
// Other app audio stopped playing - restart secondary audio
}
}
复制代码
在app中断后能够经过代码作出响应.音频中断将会致使audio session停用,同时应用程序中音频当即终止.当一个来自其余app的竞争的audio session被激活且这个audio session类别不支持与你的app进行混音时,中断发生.注册通知后咱们能够在得知音频中断后作出相应处理.
App会由于中断被暂停,当用户接到电话时,闹钟,或其余系统事件被触发时,当中断结束后,App会继续运行,可是须要咱们手动从新激活audio session.
下图简单展现了当收到facetime后app的audio session与系统的audio session间激活与未激活状态变化.
经过注册监听中断的通知能够在中断来的时候进行处理.处理中断取决于你当前正在执行的操做:播放,录制,音频格式转换,读取音频数据包等等.通常而言,咱们应尽可能避免中断而且作到中断后尽快恢复.
中断前
中断后
Audio technology | How interruptions work |
---|---|
AVFoundation framework | 系统在中断时会自动暂停录制与播放,当中断结束后从新激活audio session,恢复录制与播放 |
Audio Queue Services, I/O audio unit | 系统会发出中断通知,开发者能够保存播放与录制状态而且在中断结束后从新激活audio session |
System Sound Services | 使用系统声音服务在中断来临时保持静音,若是中断结束,声音自动播放. |
当处理Siri时,与其余中断不一样,咱们在中断期间须要对Siri进行监听,如在中断期间,用户要求Siri去暂停开发者app中的音频播放,当app收到中断结束的通知时,不该该自动恢复播放.同时,用户界面须要跟Siri要求的保持一致.
注册AVAudioSessionInterruptionNotification
通知能够监听中断.
func registerForNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(handleInterruption),
name: .AVAudioSessionInterruption,
object: AVAudioSession.sharedInstance())
}
func handleInterruption(_ notification: Notification) {
// Handle interruption
}
func handleInterruption(_ notification: Notification) {
guard let info = notification.userInfo,
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSessionInterruptionType(rawValue: typeValue) else {
return
}
if type == .began {
// Interruption began, take appropriate actions (save state, update user interface)
}
else if type == .ended {
guard let optionsValue =
userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else {
return
}
let options = AVAudioSessionInterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
// Interruption Ended - playback should resume
}
}
}
复制代码
注意: 没法确保在开始中断后必定有一个结束中断,因此,若是没有结束中断,咱们在app从新播放音频时须要老是检查aduio session是否被激活.
media server经过一个共享服务器进程提供了音频和其余多媒体功能.尽管不多见,可是若是在你的app正在运行时收到一条重置命令,能够经过注册AVAudioSessionMediaServicesWereResetNotification
通知监听media server是否重置.收到通知后须要作以下操做.
注册AVAudioSessionMediaServicesWereLostNotification
能够在media server不可用时收到通知.
若是开发者的应用程序中须要重置功能,如设置中有重置选项,可使用这个方法轻松重置.
audio hardware route指定的设备音频硬件线路发生改变.当用户插拔耳机,系统会自动改变硬件的线路.开发者能够注册AVAudioSessionRouteChangeNotification
通知在线路变化时做出相应调整.
如上图,系统在app启动时会肯定一套音频线路,然后程序运行期间会继续监听当前活跃的音频线路,在录制期间,用户可能插拔耳机,系统会发送一份改变线路的通知告诉开发者同时音频中止,开发者能够经过代码决定是否从新激活.
播放与录制稍有不一样,播放时若是用户拔掉耳机,默认暂停音频,若是插上耳机,默认继续播放.
缘由
func setupNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(handleRouteChange),
name: .AVAudioSessionRouteChange,
object: AVAudioSession.sharedInstance())
}
func handleRouteChange(_ notification: Notification) {
}
复制代码
userInfo
中提供了关于线路改变的详细信息.能够查询改变缘由经过字典中的AVAudioSessionRouteChangeReason
,如当新的设备接入时,缘由为AVAudioSessionRouteChangeReason
,移除时为AVAudioSessionRouteChangeReasonOldDeviceUnavailable
func handleRouteChange(_ notification: Notification) {
guard let userInfo = notification.userInfo,
let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
let reason = AVAudioSessionRouteChangeReason(rawValue:reasonValue) else {
return
}
switch reason {
case .newDeviceAvailable:
// Handle new device available.
case .oldDeviceUnavailable:
// Handle old device removed.
default: ()
}
}
复制代码
当有音频硬件插入时,你能够查询audio session的currentRoute
属性去肯定当前音频输出的位置.它将返回一个AVAudioSessionRouteDescription
对象包含audio session所有的输入输出信息.当一个音频硬件被移除时,咱们也能够从该对象中查询上一个线路.在以上两种状况中,咱们均可以查询outputs
属性,经过返回的AVAudioSessionPortDescription
对象提供了音频输出的所有信息.
func handleRouteChange(notification: NSNotification) {
guard let userInfo = notification.userInfo,
let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
let reason = AVAudioSessionRouteChangeReason(rawValue:reasonValue) else {
return
}
switch reason {
case .newDeviceAvailable:
let session = AVAudioSession.sharedInstance()
for output in session.currentRoute.outputs where output.portType == AVAudioSessionPortHeadphones {
headphonesConnected = true
}
case .oldDeviceUnavailable:
if let previousRoute =
userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {
for output in previousRoute.outputs where output.portType == AVAudioSessionPortHeadphones {
headphonesConnected = false
}
}
default: ()
}
}
复制代码
使用audio session属性,能够在运行时优化硬件音频行为.这样可让代码适配运行设备的特性.这样作一样适用于用户对音频硬件做出的更改.
使用audio session指定音频设备的设置,如采样率, I/O缓冲区时间.
Setting | Preferred sample rate | Preferred I/O buffer duration |
---|---|---|
High value | Example: 48 kHz, + High audio quality, – Large file or buffer size | Example: 500 mS, + Less-frequent file access, – Longer latency |
Low value | Example: 8 kHz, + Small file or buffer size, – Low audio quality | Example: 5 mS,+ Low latency, – Frequent file access |
Note: 默认音频输入输出缓冲时间(I/O buffer duration)为大多数应用提供足够的相应时间,如44.1kHz音频大概为20ms响应一次,你能够设置更低的延迟但相应数据量每次过来的也会下降,根据本身的需求进行选择.
在激活audio session前必须完成设置内容.若是你正在运行audio session, 先停用它,而后改变设置从新激活.
let session = AVAudioSession.sharedInstance()
// Configure category and mode
do {
try session.setCategory(AVAudioSessionCategoryRecord, mode: AVAudioSessionModeDefault)
} catch let error as NSError {
print("Unable to set category: \(error.localizedDescription)")
}
// Set preferred sample rate
do {
try session.setPreferredSampleRate(44_100)
} catch let error as NSError {
print("Unable to set preferred sample rate: \(error.localizedDescription)")
}
// Set preferred I/O buffer duration
do {
try session.setPreferredIOBufferDuration(0.005)
} catch let error as NSError {
print("Unable to set preferred I/O buffer duration: \(error.localizedDescription)")
}
// Activate the audio session
do {
try session.setActive(true)
} catch let error as NSError {
print("Unable to activate session. \(error.localizedDescription)")
}
// Query the audio session's ioBufferDuration and sampleRate properties // to determine if the preferred values were set print("Audio Session ioBufferDuration: \(session.ioBufferDuration), sampleRate: \(session.sampleRate)") 复制代码
一个设备可能有多个麦克风(内置,外接),iOS会根据当前使用的audio session mode自动选择一个.mode指定了输入数字信号处理(DSP)和可能的线路.输入线路针对每种模式的用例进行了优化,设置mode还可能影响正在使用的音频线路.
开发者能够手动选择麦克风,甚至能够设置polar pattern若是硬件支持.
在使用任何音频设备以前,请为您的应用设置音频会话类别和模式,而后激活音频会话。
为了找到当前设备链接的音频输入设备,可使用audio session的availableInputs
属性,该属性返回一个AVAudioSessionPortDescription
对象的数组,描述当前可用输入设备端口,端口用portType
进行标识.可使用setPreferredInput:error:
设置可用的音频输入设备.
部分端口如内置麦克风,USB等支持数据源(data source),应用程序能够经过查询端口的dataSources
属性发现可用的数据源.对于内置麦克风,返回的数据源描述对象表明每一个单独的麦克风。不一样的设备为内置麦克风返回不一样的值。例如,iPhone 4和iPhone 4S有两个麦克风:底部和顶部。 iPhone 5有三个麦克风:底部,前部和后部。
能够经过数据源描述的location
属性(上,下)和orientation
属性(前,后等)的组合来识别各个内置麦克风。应用程序可使用AVAudioSessionPortDescription对象的setPreferredDataSource:error:方法设置首选数据源。
某些iOS设备支持为某些内置麦克风配置麦克风极性模式。麦克风的极性模式定义了其对声音相对于声源方向的灵敏度。使用supportedPolarPatterns
属性返回数据源是否支持此模式,此属性返回数据源支持的极坐标模式数组(如心形或全向),或者在没有可选模式时返回nil。若是数据源具备许多支持的极坐标模式,则可使用数据源描述的setPreferredPolarPattern:error:方法设置首选极坐标模式。
// Preferred Mic = Front, Preferred Polar Pattern = Cardioid
let preferredMicOrientation = AVAudioSessionOrientationFront
let preferredPolarPattern = AVAudioSessionPolarPatternCardioid
// Retrieve your configured and activated audio session
let session = AVAudioSession.sharedInstance()
// Get available inputs
guard let inputs = session.availableInputs else { return }
// Find built-in mic
guard let builtInMic = inputs.first(where: {
$0.portType == AVAudioSessionPortBuiltInMic
}) else { return }
// Find the data source at the specified orientation
guard let dataSource = builtInMic.dataSources?.first (where: {
$0.orientation == preferredMicOrientation
}) else { return }
// Set data source's polar pattern do { try dataSource.setPreferredPolarPattern(preferredPolarPattern) } catch let error as NSError { print("Unable to preferred polar pattern: \(error.localizedDescription)") } // Set the data source as the input's preferred data source
do {
try builtInMic.setPreferredDataSource(dataSource)
} catch let error as NSError {
print("Unable to preferred dataSource: \(error.localizedDescription)")
}
// Set the built-in mic as the preferred input
// This call will be a no-op if already selected
do {
try session.setPreferredInput(builtInMic)
} catch let error as NSError {
print("Unable to preferred input: \(error.localizedDescription)")
}
// Print Active Configuration
session.currentRoute.inputs.forEach { portDesc in
print("Port: \(portDesc.portType)")
if let ds = portDesc.selectedDataSource {
print("Name: \(ds.dataSourceName)")
print("Polar Pattern: \(ds.selectedPolarPattern ?? "[none]")")
}
}
Running this code on an iPhone 6s produces the following console output:
Port: MicrophoneBuiltIn
Name: Front
Polar Pattern: Cardioid
复制代码
能够在模拟器或设备上运行您的应用。可是,Simulator不会模拟不一样进程或音频线路更改中的音频会话之间的大多数交互。在Simulator中运行应用程序时,您不能:
#if arch(i386) || arch(x86_64)
// Execute subset of code that works in the Simulator
#else
// Execute device-only code as well as the other code
#endif
复制代码
为了保护用户隐私,应用必须在录制音频以前询问并得到用户的许可。若是用户未授予许可,则仅记录静音。当您使用支持录制的类别而且应用程序尝试使用输入线路时,系统会自动提示用户得到权限。
您可使用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开始,全部访问任何设备麦克风的应用都必须静态声明其意图。为此,应用程序如今必须在其Info.plist文件中包含NSMicrophoneUsageDescription键,并为此密钥提供目的字符串。当系统提示用户容许访问时,此字符串将显示为警报的一部分。若是应用程序尝试访问任何设备的麦克风而没有此键和值,则应用程序将终止。