AudioSession 基础

Overwrite

  • Audio Session:音频会话

Apple 系统通过 Audio Session 在应用程序中、应用程序之间和应用程序与硬件之间的管理。通过使用 Audio Session 可以在不了解相关硬件操作下监督管理应用程序中的音频使用。

ASPG_intro_2x.png

AVAudioSession 用途:

  • 配置Audio Session类别和模式去告诉系统在APP中音频的使用方式

  • 激活APP中的 Audio Session 配置的类别与模式

  • 订阅和响应 Audio Session 重要的通知,比如音频打断和线路切换

  • 进行相关高级音频设备配置,比如采样率、I/O缓冲时长和声道数

0. 音频会话管理音频行为的解释

一个Audio Session是APP与操作系统之间配置音频行为的媒介。在应用启动后,APP会自动提供一个Audio Session的单例,我们通过配置这个单例的行为并激活它,以此来告诉系统该如何使用音频。

1. 配置音频会话

1.1 音频会话类别(Category)

音频会话类别(Category)是应用程序一组音频行为,通过设置类别我们可以向系统指明我们使用音频的意图,主要有以下几个功能:

  • “铃声/静音”按钮和锁屏控制静音:音频会在用户打开静音按钮或锁屏时静音。

  • 打断不可混音的APP音频:当我们激活App的音频会话时,会打断其他不可混音App的音频会话。

  • 音频输入支持:允许使用麦克风等录音设备

  • 音频输出支持:允许使用扬声器等播放设备

类别(Category) “铃声/静音”按钮和锁屏控制静音 打断不可混音的APP音频 允许音频输入(录音)和输出(播放)
AVAudioSessionCategoryAmbient Yes No 只有输出
AVAudioSessionCategorySoloAmbient (默认) Yes Yes 只有输出
AVAudioSessionCategoryPlayback No 默认为Yes;重写开关为NO 只有输出
AVAudioSessionCategoryRecord No (锁屏后会继续录制) Yes 只有输入
AVAudioSessionCategoryPlayAndRecord No 默认为Yes;重写开关为NO 输入和输出
AVAudioSessionCategoryMultiRoute No Yes 输入和输出

注意:若想在锁屏或静音模式下继续播放音乐,还需要在App的Info.plist中添加UIBackgroundModes音频键。

一般大部分App只会在应用启动的时候都会根据自身所需设置相应的音频类别,但是我们也可以根据我们的需要在应用修改类别:先停用音频会话再修改音频类别,然后重新激活应用修改。

1.2 音频会话模式(mode)

音频会话模式(mode)用于指明音频配置的功能,是对音频会话类别的关联,告诉系统音频具体的使用用途。随着系统的升级,音频会话类别可能会进行完善,通过模式来指定功能,可以兼容不同的系统实现。当前的音频模式如下图所示:

模式标识 关联类别
AVAudioSessionModeDefault All
AVAudioSessionModeMoviePlayback AVAudioSessionCategoryPlayback
AVAudioSessionModeVideoRecording AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord
AVAudioSessionModeVoiceChat AVAudioSessionCategoryPlayAndRecord
AVAudioSessionModeGameChat AVAudioSessionCategoryPlayAndRecord
AVAudioSessionModeVideoChat AVAudioSessionCategoryPlayAndRecord
AVAudioSessionModeSpokenAudio AVAudioSessionCategoryPlayback
AVAudioSessionModeMeasurement AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord AVAudioSessionCategoryPlayback

在使用音频会话中,我们可以先配置类别作为一个音频基本使用方式,再通过配置模式来指定特定行为。

1.3 音频会话类别选项(Options)

音频会话类别选项(Options),用于配置其他的音频用途。在不同的音频类别中,有这些选项的默认值,但我们可以通过设置其值完成相应功能。其值如下所示:

  • AVAudioSessionCategoryOptionMixWithOthers:应用程序在音频会话即将激活时,设置中断其他会话或与其他会话混合。

    • PlayAndRecord、MultiRoute、Playback:默认为关闭,但可手动开启,使其他应用程序的会话依然在后台运行。(AVAudioSessionCategoryPlayback 不受“铃声”开关控制)

    • Others:默认关闭且不可改变。

  • AVAudioSessionCategoryOptionDuckOthers:应用程序在音频会话即将激活时,减小其他音频会话的音量

    • Playback、PlayAndRecord、MultiRoute:默认是不可混音和不可减弱的。

    • Others:设置这个属性就相当于设置了AVAudioSessionCategoryOptionMixWithOthers,因为只有混音了才需要减弱其他音频会话。

  • AVAudioSessionCategoryOptionAllowBluetooth:是否支持当前音频会话使用蓝牙(HFP)设备作为音频线路。

    • PlayAndRecord、Record:默认关闭,但可手动开启。

    • Others:默认关闭且不可改变。

  • AVAudioSessionCategoryOptionDefaultToSpeaker:是否默认使用扬声器。

    • PlayAndRecord:默认关闭且不可改变。当没有其他音频线路连接时,他只会连接到扬声器。

    • Others:默认关闭,但可手动开启。

  • AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers:应用程序在音频会话即将激活时,允许打断其他设置模式为AVAudioSessionModeSpokenAudio的会话,也可以与其他会话进行混合。

  • AVAudioSessionCategoryOptionAllowBluetoothA2DP:是否支持使用蓝牙A2DP耳机

    • PlayAndRecord:默认关闭,但可手动开启。

    • MultiRoute、Record:默认关闭且不可改变。

    • Others:默认开启且不可改变。

  • AVAudioSessionCategoryOptionAllowAirPlay:是否支持使用AirPlay

1.4 配置音频会话

配置音频会话最重要是配置类别,模式和选项都是可选次要的。

// Access the shared, singleton audio session instance
let session = AVAudioSession.sharedInstance()
do {
    // Configure the audio session for movie playback
    try session.setCategory(AVAudioSessionCategoryPlayback,
                            mode: AVAudioSessionModeMoviePlayback,
                            options: [])
} catch let error as NSError {
    print("Failed to set the audio session category and mode: \(error.localizedDescription)")
}

更多详细配置参考官方文档

2. 音频会话的激活

我们可以通过类别(category)、模式(mode)和选项(options)配置一个Audio Session。为了使我们的配置生效,我们需要激活这个Audio Session。激活音频会话顾名思义就是我们告诉系统,我们需要使用音频设备资源。反过来,系统也可以激活和停用音频会话来告诉我们是否可以使用设备资源。

2.1 系统对音频需求冲突的解决

当你的APP启动时,已经构建好的APP(例如Message、Music、Safari)可能会在后台运行。这些软件都有可能生产音频,例如消息推送的提醒、设置的延时播放等等。如果你将设备想象成一个”飞机场“,那么APP就是里面”正在滑行的飞机“,系统服务就是”控制塔”。我们的应用程序可以发送音频请求并声明自己的优先级,但是最终掌控“停机坪“的最终还是系统。我们在使用 Audio Session 与”控制塔“进行交流难免会遇到冲突,下图展示的是MyApp与Music App的冲突解决的过程:

competing_audio_demands_2x.png
  1. MyApp 请求激活 Audio Session,比如APP启动、用户点击UI进行录音与回放
  2. 系统理解激活请求,比如音频会话的类别
  3. 系统告知Music APP的Audio Session无效,停止他的音频播放
  4. Music App 告知系统他的音频会话已失效
  5. 系统激活 MyApp 的音频会话并允许其可以开始播放

2.2 激活和停用你的音频会话

所有AVFoundation可以在播放和录音的时候自动激活音频会话,但是手动激活音频会话可以给我们一个机会去提前激活音频会话并且能测试是否能激活成功。系统会停用你的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) // pass false to deactivate your audio session
} catch let error as NSError {
    print("Unable to activate audio session:  \(error.localizedDescription)")
}

当我们使用AVFoundation对象(AVPlayer, AVAudioRecorder等),系统会负责在中断结束时重新激活音频会话。然而,如果你注册了通知去重新激活音频会话,你可以验证是否激活成功并且更新用户界面。

很多APP没有必要去明确地停用它们的音频会话,除了VoIP App、导航App和播放录制App。因此一般在使用音频会话都要遵守以下规矩:

  • 确保在后台运行的VoIP App的音频会话仅在应用程序处理呼叫时才处于激活状态。在后台,若未收到呼叫,VoIP App 的音频会话不应该是激活的。

  • 确保使用录制类别的应用程序的音频会话仅在录制时处于激活状态。在录制开始和停止之前,请确保您的会话处于未激活状态,以允许播放其他声音,例如消息推送。

  • 如果应用程序支持后台音频播放或录制,但在应用程序未主动使用音频(或准备使用音频)时,在进入后台时应该停用其音频会话。这样做是允许系统释放音频资源,以便其他进程可以使用它们。

总的来说就是用的时候能用,不用的时候必须出来!!!

2.3 检查是否有其他音频在播放?

当我们App的音频会话被激活前,当前设备可能正在播放别的声音,我们可以通过检查是否有其他音频在播放来做相应的处理。有两个获取方式:

  • AppDelegate中的applicationDidBecomeActive:代理方法中读取音频会话的secondaryAudioShouldBeSilencedHint属性(Bool值),当别的App正在播放的音频会话为不可混音配置时,该值为true。App 可以使用此属性关闭次要与应用程序的音频。
  • 订阅AVAudioSessionSilenceSecondaryAudioHintNotification的通知获取AVAudioSessionSilenceSecondaryAudioHintType属性(枚举值),该操作如下:
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
    }
}

3. 响应音频打断

我们除了需要配置当前音频会话,还需要应对在音频会话使用中,其他优先级更高的应用程序抢夺系统音频资源的情况。在这种情况下,系统会发出一个“音频打断开始通知”并停用我们的音频会话, 在其他应用程序用完音频资源后,系统会发出一个“音频打断结束通知”并重新激活我们的音频会话。这两个通知的开始到结束就是我们音频会话的打断与恢复,我们需要在代码层面上对这种打断过程做恰当的处理,比如打断开始时暂停播放音频,打断结束后继续播放音频。

3.1 音频打断生命周期

下图描述的是在同一个时间线上,两个音频会话之间的使用音频设备资源的过程,“Your audio session”表示我们配置的音频会话,“Device audio session”表示FaceTime App的音频会话:

audio_session_interrupted_2x.png
  1. 激活我们配置好的音频会话,开始播放我们的音频
  2. 突然有一个 FaceTime 的电话打来,系统激活了 FaceTime App 的音频会话
  3. 这时系统停用了我们的音频会话,在此时系统不负责我们的音频播放了
  4. 系统发送一个“打断开始通知”给我们,并告知我们的音频会话已经停用了
  5. 我们接收到“打断开始通知”,并在通知收到后进行界面刷新并暂停播放逻辑
  6. 用户此时忽略了FaceTime电话,这时系统会停用FaceTime的音频会话,然后重新激活我们的音频会话,并发送一个“打断结束通知”给我们
  7. 我们接收到“打断结束通知”,并在通知收到后进行界面刷新并恢复播放逻辑
  8. (上图没有)如果用户接听了电话,那么我们的音频会话将会被一直挂起,直至用户结束通话。

3.2 音频打断处理方法

我们可以通过注册AVAudioSession发出的打断通知,该通知的名称为AVAudioSessionInterruptionNotification,并且根据里面的AVAudioSessionInterruptionTypeKey值获取打断的状态做出恰当的逻辑处理,保证音频打断结束后能恢复正常。

  • 音频打断开始 - AVAudioSessionInterruptionTypeBegan
    • 保存当前状态与上下文
    • 更新用户界面
  • 音频打断结束- AVAudioSessionInterruptionTypeEnded
    • 重置状态和上下文
    • 更新用户界面
    • 重新激活音频会话

对于我们使用不同框架,Apple推荐我们以下打断处理方式:

  • AVFoundation framework:系统在中断时会自动暂停录制与播放,当中断结束后重新激活音频会话,恢复录制与播放
  • Audio Queue Services, I/O audio unit:系统会发出中断通知,我们可以保存播放与录制状态并且在中断结束后重新激活音频会话
  • System Sound Services:使用系统声音服务在中断来临时保持静音,如果中断结束,声音自动播放

通知监听的代码如下:

func registerForNotifications() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(handleInterruption),
                                           name: .AVAudioSessionInterruption,
                                           object: AVAudioSession.sharedInstance())
}
 
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
        }
    }
}

3.3 Siri 打断处理

当处理Siri时,应该与其他打断不同。我们在打断期间需要对Siri进行监听,如果用户要求Siri去暂停我们的音频播放,在app收到打断结束的通知时,就不应该自动恢复播放。同时,用户界面需要跟Siri要求的保持一致。

3.4 媒体服务重置处理

媒体服务器在共用服务过程中提供音频和其他多媒体方法,在APP运行中,媒体服务器可能会被重置(不常见)。我们可以通过注册AVAudioSessionMediaServicesWereResetNotification通知去管理媒体服务器重置,接收到通知后,我们需要做以下事情:

  • 销毁没有被引用的音频对象(比如players、recorders、 converters,或audio queues)并重新创建一个
  • 重置内部所有被跟踪的音频状态,包括AVAudioSession中的所有属性
  • 在适当的时机使用setActive:error:方法重新激活音频会话

注册AVAudioSessionMediaServicesWereLostNotification通知可以在媒体服务器不可用时收到通知。

4. 响应线路切换

用户在听歌的过程中,难免会接上或拔出耳机,在这个过程中,音频的线路就在扬声器和耳机中进行切换,这就是线路切换。一个好的应用程序应该对线路切换就行处理,比如在用户拔下耳机后,进行静音处理避免尴尬。除此之外导致音频线路切换有多种情况,包括用户插拔蓝牙耳机、插拔USB设备等等。

4.1 多种音频硬件线路切换

音频硬件线路是一个有线的音频电子信号线路,下图描述的是在录音和回放过程中,进行耳机的插入和拔出引发一系列的线路切换事件,总共有四种可能的处理结果:

audio_route_change_2x.png

系统在App启动后才开始决定音频线路,并开始管理激活的线路。

  1. 用户通过选择“开始录音”使当前情况进入左分支
  2. 在录音过程中,用户拔出耳机应该停止录音
  3. 在录音过程中,用户插入耳机应该停止录音
  4. 用户通过选择“开始回放”使当前情况进入左分支
  5. 在回放过程中,用户拔出耳机后,应该暂停播放;
  6. 在回放过程中,用户插入耳机后,应该重新播放

4.2 监听音频线路切换

开发者可以通过注册AVAudioSessionRouteChangeNotification通知来获取线路切换事件:

func setupNotifications() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(handleRouteChange),
                                           name: .AVAudioSessionRouteChange,
                                           object: AVAudioSession.sharedInstance())
}
 
func handleRouteChange(_ notification: Notification) {
 
}

通知中的userInfo中提供了线路切换的相关信息,可以通过查询AVAudioSessionRouteChangeReason键来获取切换原因(以下枚举值省略AVAudioSessionRouteChangeReason前缀):

  • Unknown:切换原因未知,用于处理未指定情况
  • NewDeviceAvailable:有新的音频设备接入
  • OldDeviceUnavailable:之前的音频输出路径不可用,例如断开旧的音频设备连接
  • CategoryChange:音频会话的类别发生了变化
  • Override:音频输出路线被覆盖
  • WakeFromSleep:设备从休眠中唤醒导致线路切换
  • NoSuitableRouteForCategory:由于当前音频类别不支持当前音频线路引发的切换
  • RouteConfigurationChange:I/O端口切换引发的线路切换
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: ()
    }
}

开发者还可以通过音频会话中的currentRoute属性获取当前的音频线路,通过userInfo中的AVAudioSessionRouteChangePreviousRouteKey获取上一个音频线路:

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: ()
    }
}

具体的音频线路(以下名称省略AVAudioSessionPort前缀):

  • 输入端口
    • LineIn:连接在基座接口的有线音频输入设备
    • BuiltInMic:iOS 设备上的内置麦克风
    • HeadsetMic:有线耳机的麦克风
  • 输出端口
    • LineOut:连接在基座接口的有线音频输出设备
    • Headphones:耳机
    • BluetoothA2DP:蓝牙 A2DP 设备,比如 AirPods
    • BuiltInReceiver:手机通话使用的耳边听筒
    • BuiltInSpeaker:iOS 设备上的内置扬声器
    • HDMI:通过HDMI接口连接的设备
    • AirPlay:通过AirPlay输出的设备
    • BluetoothLE:低能耗蓝牙输出设备
  • 输入输出端口:
    • BluetoothHFP:使用HFP的蓝牙设备
    • USBAudio:使用USB接口连接的设备
    • CarAudio:车载输出设备

TODO: - 无线耳机的麦克风?

注意:音频线路切换可能会导致音频会话的采集率、I/O缓冲时间、声道数量和其他硬件属性的变化。如果这些对于应用的影响较大,需要进行兼容操作。

5. 配置物理设备

在音频硬件运行过程中,开发者可以通过使用音频会话的属性进行运行优化。比如:

  • 指定适合硬件的采样率和I/O缓冲时长
  • 获取输入输出的延迟、输入输出的的声道数、硬件采样率、硬件音量设置和音频输入的可用性

5.1 推荐音频硬件值

如果需要获取高质量音频就需要设置一个高采样率,除非出现文件过大或缓冲过大问题

配置 推荐采样率 推荐I/O缓冲时长
高质量 Example: 48 kHz,+ High audio quality,– Large file or buffer size Example: 500 mS,+ Less-frequent file access, – Longer latency
低质量 Example: 8 kHz,+ Small file or buffer size,– Low audio quality Example: 5 mS, + Low latency,– Frequent file access

注意:默认音频输入输出缓冲时间为大多数应用提供足够的相应时间,如44.1kHz音频大概为20ms响应一次。设置更低的延迟,相应数据量每次过来的也会降低,因此需要根据自己的需要设置。

5.2 配置音频硬件值

开发者需要在音频会话激活前配置相关音频硬件值,如果在音频会话激活后调整硬件值则需要先停用,再设置,最后重新激活。

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)")

注意:当两个冲突的音频会话同时配置硬件值,系统会优先配置不允许混合的会话。因此Ambient类别或MixWithOthers类别选项的音频会话不会被优先设置。

5.3 选择并配置麦克风

一个iOS设备中会有两个或多个麦克风,系统会根据配置的音频会话模式自动选择麦克风。开发者也可以手动选择麦克风,甚至选择麦克风的极性模式(polar pattern)。

  • 配置偏好输入:一个设备中可能有许多可用的麦克风种类。

    • 通过音频会话的availableInputs属性获取内置或链接的输入麦克风,这个属性返回的是一个AVAudioSessionPortDescription数组。这个AVAudioSessionPortDescription对象类似于音频线路的AVAudioSessionPort
    • 通过setPreferredInput:error:方法配置上面数组中唯一一个对象
  • 配置偏好数据源:一些端口,例如内置麦克风和一些USB链接设备,支持数据源配置,即设备上有不同的麦克风。

    • 在选中的AVAudioSessionPortDescription能通过dataSources属性获取它们可用的数据源数组,比如内置的麦克风会有多个独立的麦克风组成,这些麦克风使用数据源描述信息的location属性(upper, lower)和orientation属性(top,bottom,front, back,left,right)共同描述。
    • 通过setPreferredDataSource:error:方法配置对应的数据源对象
  • 配置偏好极性模式:极性模式定义了其对声音相对于声源方向的灵敏度。

    • 在选中的AVAudioSessionDataSourceDescription能通过supportedPolarPatterns获取可用的极性模式,如心形或全向。
    • 通过setPreferredPolarPattern:error:方法配置对应的极性模式

对象范围:AVAudioSessionPortDescription > AVAudioSessionDataSourceDescription

以下代码为手动配置麦克风全过程:

// 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]")")
    }
}

5.4 模拟器运行

我们可以在模拟器或真机上运行添加了音频会话的应用程序,但是模拟器不会模拟音频会话不同进程或音频线切换的大多数交互,比如:

  • 调用打断
  • 模拟插入或拔出耳机
  • 更改静音开关的设置
  • 模拟屏幕锁定
  • 测试音频混合行为 - 即播放音频以及来自其他应用(例如音乐应用)的音频

附:模拟器判断的代码:

#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

6. 保护用户隐私

为了保护用户隐私,在录制音频前我们必须向用户询问并获取使用麦克风的权限。如果用户不给权限,我们只能录制静音。向用户咨询麦克风权限有两种方式,一种是在用户首次使用麦克风功能时会自动提醒用户,另一种是通过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键,并为其提供目的用途字符串。当系统提示用户允许访问时,此字符串将显示为警报提示的一部分。如果应用程序尝试访问任何设备的麦克风而没有此键值,则应用程序将直接闪退。

参考文献

Audio Session Programming Guide

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345