最近一直在做iOS音频技术相关的项目,由于单项直播SDK,互动直播SDK(iOS/Mac),短视频SDK,都会用到音频技术,因此在这里收集三个SDK的音频技术需求,开发一个通用的音频模块用于三个SDK,同时支持iOS和Mac。

需求实现

主要包括音频采集,音频格式转换,音频多路混音(本地文件和网络文件),写WAV/AAC音频文件,通话录制,音频文件播放,耳返,自定义音频输入,音视频设备管理等功能。

本文大部分图片和技术概念阐述均来自Apple官网。

概念介绍

Core Audio 是iOS和 Mac 的关于数字音频处理的基础,它提供应用程序用来处理音频的一组软件框架,所有关于iOS音频开发的接口都是由Core Audio来提供或者经过它提供的接口来进行封装的,按照官方的说法是集播放、音频处理、录制为一体的专业技术,通过它我们的程序可以同时录制,播放一个或者多个音频流,自动适应耳机,蓝牙耳机等硬件,响应各种电话中断,静音,震动等。

ios 音频处理库 iphone音频模块_ios 音频处理库

Low-Level

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

Mid-Level

  • Audio Convert Services 负责音频数据格式的转换
  • Audio File Services 负责音频数据的读写
  • Audio Unit Services 和 Audio Processing Graph
  • Services 支持均衡器和混音器等数字信号处理的插件
  • Audio File Scream Services 负责流解析
  • Core Audio Clock Services 负责音频时钟同步

High-Level

  • Audio Queue Services 提供录制、播放、暂停、循环、和同步音频,它自动采用必要的编解码器处理压缩的音频格式
  • AVAudioPlayer 是专为iOS平台提供的基于Objective-C接口的音频播放类,可以支持iOS所支持的所有音频的播放
  • Extended Audio File Services 由Audio File与Audio Converter组合而成,提供压缩及无压缩音频文件的读写能力
  • OpenAL 是CoreAudio对OpenAL标准的实现,可以播放3D混音效果

OS X 和 iOS 的核心音频架构

ios 音频处理库 iphone音频模块_python_02

ios 音频处理库 iphone音频模块_java_03

Audio Unit

iOS提供了混音、均衡、格式转换、实时IO录制、回放、离线渲染、语音对讲(VoIP)等音频处理插件,它们都属于不同AudioUnit,支持动态载入和使用。AudioUnit可以单独创建使用,但更多的是被组合使用在Audio Processing Graph容器中以达到多样的处理需要。

ios 音频处理库 iphone音频模块_python_04

ios 音频处理库 iphone音频模块_ios 音频处理库_05

ios 音频处理库 iphone音频模块_嵌入式_06

一个I/O Unit包含两个实体对象,两个实体对象(Element 0、Element 1)相互独立,根据需求可通过kAudioOutputUnitProperty_EnableIO属性去开关它们。Element 1与硬件输入连接,并且Element 1的输入域(input scope)对你不可见,你只能读取它的输出域的数据及设置其输出域的音频格式;Element 0与硬件输出连接,并且Element 0的输出域(ouput scope)对你不可见,你只能写入它的输入域的数据及设置其输入域的音频格式。

ios 音频处理库 iphone音频模块_java_07

Audio Session

AVAudioSession构建了一个音频使用生命周期的上下文。当前状态是否可以录音、对其他App有怎样的影响、是否响应系统的静音键、如何感知来电话了等都可以通过它来实现。

ios 音频处理库 iphone音频模块_python_08

Audio Processing Graphs

AUGraph可以用来构建和管理一个音频单元处理链。能够利用多个音频单元的功能和多个渲染回调函数,允许您创建几乎任何你可以想象的音频处理的解决方案。同时它也是线程安全的。

ios 音频处理库 iphone音频模块_ios 音频处理库_09

Audio Flows Through a Graph Using “Pull”

在一个音频处理图,当需要更多的音频数据时,使用者调用提供者。有源源不断的音频数据流的请求,这个控制流的方向和音频流方向相反。

ios 音频处理库 iphone音频模块_java_10

具体实现

一. 音频采集

iOS采集:

kAudioUnitSubType_RemoteIO

kAudioUnitSubType_VoiceProcessingIO

Mac采集:

kAudioUnitSubType_VoiceProcessingIO

ios 音频处理库 iphone音频模块_python_11

一个I/O Unit包含两个实体对象,两个实体对象(Element 0、Element 1)相互独立。Element 1与硬件输入(麦克风或者听筒)连接,并且Element 1的输入域(input scope)对你不可见,你只能读取它的输出域的数据及设置其输出域的音频格式;Element 0与硬件输出(扬声器或者听筒)连接,并且Element 0的输出域(ouput scope)对你不可见,你只能写入它的输入域的数据及设置其输入域的音频格式。

操作步骤:

  1. 创建AudioUnit。
  2. 开启麦克风或者听筒的输入开关;开启扬声器或者听筒的输出开关。
  3. 设置输入和输出的采集回调和播放回调。
  4. 设置输入和输出的音频格式。
  5. 初始化AudioUnit。
  6. 开启AudioUnit。

Mac采集:

kAudioUnitSubType_HALOutput

ios 音频处理库 iphone音频模块_java_12

Mac的音频采集使用的是kAudioUnitSubType_HALOutput,音频硬件抽象层HAL。因此它使用的是2个I/O Uint串联,前一个I/O Uint的输出作为后一个I/O Uint的输入。

操作步骤:

  1. 创建2个AudioUnit。
  2. 开启第一个I/O Uint的麦克风或者听筒的输入开关,关闭第一个I/O Uint的扬声器或者听筒的输出开关;开启第二个I/O Uint的扬声器或者听筒的输出开关,关闭第二个I/O Uint的麦克风或者听筒的输入开关。
  3. 将第一个I/O Unit设为Mac的kAudioHardwarePropertyDefaultInputDevice,第二个I/O Unit设为Mac的kAudioHardwarePropertyDefaultOutputDevice,
  4. 设置第二个I/O Uint的输入和第一个I/O Uint的输出的采集回调和播放回调。
  5. 设置第二个I/O Uint的输入和第一个I/O Uint的输出的音频格式。
  6. 初始化2个AudioUnit。
  7. 开启2个AudioUnit。

二. 音频架构

ios 音频处理库 iphone音频模块_ios 音频处理库_13

从图中可以看出,我们使用了一个I/O Unit作为最核心的部件,用于驱动整个流程,同时使用三个Audio Processing Graphs作为混音器。三个Audio Processing Graphs分别代表播放混音器,发送混音器,录制混音器。每个混音器有三个Unit最为其部件,音频混音Mixing(kAudioUnitSubType_MultiChannelMixer),音频格式转换(kAudioUnitSubType_AUConverter),音频通用输出(kAudioUnitSubType_GenericOutput)。同时支持多路输入,一路输出。

1. 播放混音器支持来自服务器的多路音频流和一路本地伴音以及一路耳返音频,每一路输入都会接一个音频格式转换,同时设置一个输入回调,用于音频数据的主动拉取。并将混音器的输出作为Audio Unit的输入。

2. 发送混音器支持一路Audio Unit的采集和本地多路音频伴音的输入,每一路输入都会接一个音频格式转换,同时设置一个输入回调,用于音频数据的主动拉取。并将混音器的输出作为音频编码和发送的输入。

3. 录制混音器支持Audio Unit的一路采集和Audio Unit的一路播放,将整个通话过程涉及到的音频数据都合成一路。每一路输入都会接一个音频格式转换,同时设置一个输入回调,用于音频数据的主动拉取。并将混音器的输出作为通话录制的输入,并写WAV/AAC文件。

4. Audio Unit的采集回调驱动音频编码,从而驱动整个发送混音器;Audio Unit的采集回调驱动通话录制,从而驱动整个录制混音器;Audio Unit的播放回调驱动播放,从而驱动整个播放混音器。

5. 目前最新的音频架构,我们使用了两个I/O Unit作为最核心的部件,用于驱动整个流程。同时统一了iOS和Mac 2个版本,也解决了采集和播放同一个线程的问题,为我们的音频前处理提供了安全的线程保障。

三. AVAudioSeeion管理

AVAudioSession 的主要功能包括以下几点功能:

  • 向系统说明你的app使用音频的模式(比如是播放还是录音,是否支持蓝牙播放,是否支持后台播放)
  • 为你的app选择音频的输入输出设备(比如输入用的麦克风,输出是耳机、手机功放或者airplay)
  • 协助管理多个音源需要播放时的行为(例如同时使用多个音乐播放app,或者突然有电话接入)

如果需要音频支持后台运行,需要按下图配置:

ios 音频处理库 iphone音频模块_android_14

在需要完成上述功能点的前提下,我们需要监听中断响应,外设改变,媒体服务器终止,媒体服务器重新启动,前后台切换的通知。在不同的通知下,做出相应的调整。

系统中断响应:

AVAudioSession提供了多种Notifications来进行此类状况的通知。其中将来电话、闹铃响等都归结为一般性的中断,用AVAudioSessionInterruptionNotification来通知。其回调回来的userInfo主要包含两个键:AVAudioSessionInterruptionTypeKey: 取值为AVAudioSessionInterruptionTypeBegan表示中断开始,我们应该暂停播放和采集,取值为AVAudioSessionInterruptionTypeEnded表示中断结束,我们可以继续播放和采集。

AVAudioSessionInterruptionOptionKey: 当前只有一种值AVAudioSessionInterruptionOptionShouldResume表示此时也应该恢复继续播放和采集。

外设改变:

在NSNotificationCenter中对AVAudioSessionRouteChangeNotification进行注册。在其userInfo中有键:AVAudioSessionRouteChangeReasonKey : 表示改变的原因

ios 音频处理库 iphone音频模块_嵌入式_15