Core Audio

Core Audio

Core Audio 编程接口分层(官方文档)

The three API layers of Core Audio.png
Low-Level Services

I/O Kit:与硬件驱动交互
Audio HAL:音频硬件抽象层,使API调用与实际硬件相分离
Core MIDI:为MIDI流和设备提供软件抽象工作层
Host Time Services:访问硬件时钟

Mid-Level Services

Audio Convert Services:负责音频数据格式的转换
Audio File Services:负责音频数据读写
Audio Unit Services 和 Audio Processing Graph Services 使应用程序可以使用数字信号处理(DSP)插件,例如均衡器和混频器
Core Audio Clock Services:用于音频和MIDI同步以及时间格式管理
Audio File Stream Services:创建可以解析流的应用程序,负责流解析,对音频进行解码

High-Level Services

AVAudioPlayer:高级接口,可以完成整个音频播放的过程
Audio Queue Services:录制、播放、暂停、循环、同步音频
Extended Audio File Services:Audio File Services 和 Audio Converter services的结合体
OpenAL:游戏音频

Audio Session

官方文档 Audio Session Programming Guide

Audio Session
概览

我们可以使用 AVAudioSession 实例与应用程序的音频会话进行交互

  • 配置音频会话类别和模式,告诉系统使用音频的方式
  • 激活音频会话使设置的类别和模式配置生效
  • 订阅并响应音频会话通知,例如音频中断和路由更改
  • 执行高级别的音频设备配置,例如采样率,I/O缓冲持续时间和通道数

1、配置 Audio Session

let session = AVAudioSession.sharedInstance()
do {
    try session.setCategory(AVAudioSession.Category.playback,
                            mode: AVAudioSession.Mode.moviePlayback,
                            options: [])
} catch {
    print("Failed to set the audio session category and mode: \(error.localizedDescription)")
}

Category 设置应用基本音频行为,可以通过mode进一步设置这些行为;
如IP语音(VoIP)使用AVAudioSessionCategoryPlayAndRecord,mode设置为AVAudioSessionModeVoiceChat,此模式可确保通过系统提供的信号处理来优化语音信号

某些Category通过会话上设置一个或多个Category选项来支持覆盖默认行为,如AVAudioSessionCategoryPlayback类别的默认行为会在激活会话时中断其他系统音频,如果您希望音频与其他系统音频混合,则可以通过AVAudioSessionCategoryOptionMixWithOthers在会话上设置选项来覆盖此行为

Category 通过铃声/静音开关或锁屏是否需要静音 是否中断不可以混合的音频 支持录音和播放
AVAudioSessionCategoryAmbient Yes NO Output only
AVAudioSessionCategorySoloAmbient (Default) Yes YES Output only
AVAudioSessionCategoryPlayback NO Yes by default; no by using override switch Output only
AVAudioSessionCategoryRecord No (recording continues with screen locked) YES Input only
AVAudioSessionCategoryPlayAndRecord No Yes by default; no by using override switch Input and output
AVAudioSessionCategoryMultiRoute No YES Input and output

大多数应用在启动时只需要设置一次Category,但可以根据需要更改Category,可以在音频会话处于激活状态进行更改,最好在更改Category或者其他会话属性前停用音频会话,停用会话的同时进行这些更改可以防止音频系统不必要的重新配置

枚举mode

Mode identifiers Compatible categories
AVAudioSessionModeDefault All
AVAudioSessionModeMoviePlayback AVAudioSessionCategoryPlayback
AVAudioSessionModeVideoRecording AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord
AVAudioSessionModeVoiceChat AVAudioSessionCategoryPlayAndRecord
AVAudioSessionModeGameChat AVAudioSessionCategoryPlayAndRecord
AVAudioSessionModeVideoChat AVAudioSessionCategoryPlayAndRecord
AVAudioSessionModeSpokenAudio AVAudioSessionCategoryPlayback
AVAudioSessionModeMeasurement AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord AVAudioSessionCategoryPlayback

Audio Session 默认行为

  • 支持音频播放,但不允许录音
  • iOS中,将"铃声/静音"开关设置为静音模式会使应用播放的任何音频静音
  • iOS中,将设备锁定时,应用程序的音频将静音
  • 当你的应用播放音频时,其他任何背景音频(如音乐应用正在播放的音频)都将静音
多路由Category扩展

多路由Category使应用程序可以使用所有连接的输出端口,而不仅仅使用最后的使用端口,例如,你正在通过HDMI输出路径收听音频并插入耳机,则你的应用将继续通过HDMI输出路径输出音频,同时还通过耳机播放音频

还可以将不同的音频流发送到不同的输出路由,例如,应用可以将一个音频发送到左耳机,将另一个音频发送到右耳机,将第三个音频流发送到HDMI路由


WeChat6e2e4a5f3e2491240d752ccda4a7a321.png

有效输出路径组合

  • USB和耳机
  • HDMI和耳机
  • LineOut和耳机

注:仅当未连接任何其他输出端口(USB,HDMI,LineOut)时,才可以使用内置扬声器

选择AirPlay的Category和mode

仅特定Category和mode支持AirPlay,以下类别通知支持AirPlay的镜像和非镜像版本

  • AVAudioSessionCategorySoloAmbient
  • AVAudioSessionCategoryAmbient
  • AVAudioSessionCategoryPlayback
    AVAudioSessionCategoryPlayAndRecord 类别和以下mode仅支持AirPlay镜像版本
  • AVAudioSessionModeDefault
  • AVAudioSessionModeVideoChat
  • AVAudioSessionModeGameChat

从iOS 10开始,您可以通过使用选项AVAudioSessionCategoryPlayAndRecord激活会话来启用使用类别时的非镜像AirPlay输出AVAudioSessionCategoryOptionAllowAirPlay

背景音频

Capabilities 打开 Background Modes 的 Audio,AirPlay,and Picture in Picture

2、激活 Audio Session

let session = AVAudioSession.sharedInstance()
do {
    //1)configure your audio session category, options, and mode
    //2)active your audio session to enable your custom configuration
    try session.setActive(true)
} catch {
    print("Unable to active audio session: \(error.localizedDescription)")
}

使用AVFoundation对象(AVPlayer,AVAudioRecoder)播放或录制音频时,系统会在中断结束时重新激活音频会话,但是如果注册了通知消息并显式重新激活音频会话,则可以验证重新激活成功,还可以更新应用程序的状态和用户界面

检测是否正在播放其他音频
func setupNotification() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(handleSecondaryAudio(notification:)),
                                           name: AVAudioSession.silenceSecondaryAudioHintNotification,
                                           object: AVAudioSession.sharedInstance())
}

@objc func handleSecondaryAudio(notification: Notification) {
    guard let userinfo = notification.userInfo ,
          let typeValue = userinfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey] as? UInt,
          let type = AVAudioSession.SilenceSecondaryAudioHintType(rawValue: typeValue) else {
        return
    }
    if type == .begin {
        //其他应用音频开始播放 - 将辅助音频静音
    } else {
        //其他应用音频停止播放 - 重新启动辅助音频
    }
}
响应中断

中断生命周期


音频会话中断
  1. 应用处于活跃状态,正在播放音频
  2. FaceTime请求到达,系统激活FaceTime的音频会话
  3. 系统将停用你的音频会话,此时你的应用播放停止
  4. 系统发布通知,通知你的会话已被停用
  5. 你的应用处理通知,如更新界面保存停止播放点继续播放所需的信息
  6. 如果用户取消中断(忽略FaceTime请求),系统发送通知我们应用中断结束
  7. 你的应用处理中断结束操作,如更新界面重新激活音频会话并恢复播放
  8. 如果没有6的中断,而是接听了电话,你的应用将被暂停
  • 音频中断处理
    中断开始后:保存状态和上下文,更新用户界面
    中断结束后:恢复状态和上下文,更新用户界面,重新激活音频会话

  • 观察音频中断

func registerForNotification() {
    NotificationCenter.default.addObserver(self, selector: #selector(handleInterruption(notification:)),
                                           name: AVAudioSession.interruptionNotification,
                                           object: AVAudioSession.sharedInstance())
}

@objc func handleInterruption(notification: Notification) {
    guard let userinfo = notification.userInfo,
          let typeValue = userinfo[AVAudioSessionInterruptionTypeKey] as? UInt,
          let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
        return
    }
    if type == .began {
        //中断开始 采取适当措施(保存状态更新界面)
    } else if type == .ended {
        guard let optionsValue = userinfo[AVAudioSessionInterruptionOptionKey] as? UInt else {
            return
        }
        let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
        if options.contains(.shouldResume) {
            //中断结束 恢复播放
        }
    }
}
响应路由变更

当用户插入或拔出耳机时,系统会自动更改音频硬件路由


WeChat576df2dfbada51373b956952a4f49a1c.png

你的应用启动后,系统会首先确定音频路由,应用运行时,它将继续监听活动路由

录制期间,用户可以插入和拔出耳机,作为响应,系统发送包含更改原因和先前路由的路由更改通知,应用停止录制

  • 观察音频路由变更
func setupNotification() {
    NotificationCenter.default.addObserver(self, selector: #selector(handleRouteChange(notification:)),
                                           name: AVAudioSession.routeChangeNotification,
                                           object: AVAudioSession.sharedInstance())
}

@objc func handleRouteChange(notification: Notification) {
    guard let userinfo = notification.userInfo,
          let reasonValue = userinfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
          let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
        return
    }
    switch reason {
    case .newDeviceAvailable:
        print("处理可用新设备")
    case .oldDeviceUnavailable:
        print("处理旧设备")
    default:
        ()
    }
}

当有新设备时,可以查询音频会话currentRoute属性,返回AVAudioSessionRouteDescription对象,其中列出了音频会话的所有输入和输出

如果路由更改原因是原因AVAudioSessionRouteChangeReasonOldDeviceUnavailable,则媒体播放应用应暂停播放,但如果原因是,则不应暂停播放AVAudioSessionRouteChangeReasonOverride

配置设备硬件

设置 首选采样率 首选I/O缓冲区持续时间
High value 示例:48Hz +高音质 -大文件或缓冲区大小 示例:500ms +较少的文件访问 -更长的延迟
Low value 示例:8Hz +大文件或缓冲区大小 -低音频质量 示例:5ms +低延迟 -频繁的文件访问

如果音频质量在你应用中非常重要,并且文件或缓冲区的大小不是主要问题,则可以指定高采样率的首选项

默认音频I/O缓存持续时间(44.1kHz约0.02s)为大多数应用提供了足够的响应速度,可以对延迟有严格要求的应用(如现场乐器监控)设置较低的I/O持续时间,但对大多数应用,无需修改此设置

//Category and mode
let session = AVAudioSession.sharedInstance()
do {
    try session.setCategory(.record, mode: .default, options: [])
} catch {
    print("Unable to set Category: \(error.localizedDescription)")
}
//Set preferred sample rate
do {
    try session.setPreferredSampleRate(44_100)
} catch {
    print("Unable to set preferred sample rate \(error.localizedDescription)")
}
//Set preferred I/O buffer duration
do {
    try session.setPreferredIOBufferDuration(0.005)
} catch {
    print("Unable to set preferred I/O bufferr duration \(error.localizedDescription)")
}
//Active the audio session
do {
    try session.setActive(true)
} catch {
    print("Unable to active session \(error.localizedDescription)")
}
选择和配置麦克风
  • 设置首选输入
    要发现内置或已连接的输入端口,使用音频会话的availableInputs属性,返回一个AVAudioSessionPortDescription对象数组,这些对象描述设备的可用输入端口,可用通过端口protType属性标识端口,要设置首选输入端口(内置麦克风,有线麦克风,USB输入等)使用音频会话的setPreferredInput:error:方法

  • 设置首选数据源
    某些端口(如内置麦克风和某些USB附件)支持数据源,可用通过查询端口描述的DataSource属性发现可用数据源。
    对于内置麦克风,返回的数据源描述对象代表每个单独的麦克风,不同设备的内置麦克风返回不同的值。如iPhone4iPhone4s有两个麦克风:底部和顶部
    可通过数据源描述的location属性(上部下部)和orientation属性(正面背面)的组合来标识各个内置麦克风,使用setPreferredDataSource:error:方法设置首选数据源AVAudioSessionPortDescription

  • 设置首选极性图案
    某些iOS设备支持为某些内置麦克风配置麦克风极性模式,麦克风的极性模式定义了其对声音相对于声源方向的灵敏度
    supportedPolarPatterns数据源描述对象的属性返回可用模式。此属性返回数据源支持的极性图案的数组,例如心形或全向,或者nil在没有可用的可选图案时返回。如果数据源具有许多受支持的极性图案,则可以使用数据源描述的[setPreferredPolarPattern:error:方法来设置首选的极性图案

你可能感兴趣的:(Core Audio)