音频会话
AVAudioSession(即音频会话)参与,音频会话是应用程序和操作系统交互的中介者,通过语义描述来调度系统音频功能。
音频会话分类
AVFoundation定义了7中分类来描述应用程序所使用的音频行为,如下表。
分类 | 作用 | 是否允许混音 | 音频输入 | 音频输出 |
---|---|---|---|---|
Ambient | 游戏、效率应用程序 | 允许 | 允许 | |
Solo Ambient | 游戏、效率应用程序 | 允许 | ||
Playback | 音频和视频播放器 | 可选 | 允许 | |
Record | 录音机、音频捕捉 | 允许 | ||
Play And Record | VoIP、语音聊天 | 可选 | 允许 | 允许 |
Audio Processing | 离线会话和处理 | |||
Multi-Route | 使用外部硬件的高级 A/V 应用程序 | 允许 | 允许 |
当为应用程序选择合适的分类时,你需要思考一些问题。不如音频播放是核心功能还是次要功能?应用程序的音频是否可以和背景声音相混合?应用程序是否需要捕捉音频输入进行录制或通过网络发送音频?确定了应用程序的核心音频后,选择合适的分类就变得比较容易了。
配置音频会话
AVAudioSession的配置在应用程序的生命周期内可以修改,但通常只会对其配置一次,一般在启动方法application(_:didFinishLaunchingWithOptions:)中进行配置,如下代码所示:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
AVAudioSession *session = [AVAudioSession sharedInstaance];
NSError *error;
if (![session setCategory:AVAudioSessionCategoryPlayback error:&error]) {
NSLog(@"Category Error:%@",[error localizedDescription]);
}
if (![session setActive:YES error:&error]) {
NSLog(@"ACtivation Error:%@",[error localizedDescription]);
}
return YES;
}
使用AVAudioPlayer播放音频
AVAudioPlayer是AV Foundation音频播放的首选,也可以说是整个iOS音频播放的首先,其提供了Audio Queue Service中所有核心功能,适合本地播放或对时延无敏感要求的场景。 AVAudioPlayer的创建有两类接口,一类是针对本地路径文件,如init(contentsOf: URL),另一类是针对内存Data,如init(data: Data),初始化参考代码如下所示:
NSURL *fileURL = ...;
self.player = ...
if (self.player) {
[self.player prepareToPlay];
}
在初始化的时候建议先调用prepareToPlay(),是因为会在调用play()方法之前获取需要的音频硬件并预加载AudioQueue缓冲区,可以降低调用play()方法和听到声音输出之间的时延。如果不调用prepareToPlay()方法,在调用play()方法时也会隐式调用类似于prepareToPlay()之类的方法来激活音频
AVAudioPlayer提供了一些列关于播放的生命周期控制方法,如下所示:
- play():正式播放音频,可重新恢复被 pause 和 stop 停止的音频
- pause():暂停音频播放,可从 play 重新恢复,但不清除掉prepareToPlay 的内容
- stop():停止音频播放,清除掉 prepareToPlay 的内容,可从 play 重新恢复
AVAudioPlayer也提供了一部分音频控制的属性变量,如下所示:
- volume:修改播放器的音量,范围从0.0~1.0,单位是浮点值
- pan:播放器的声道,范围从-1(极左)到1.0(极右),默认值是0.0(居中)
- rate:调整播放速率,从0.5~2.0
- numberOfLoops: 循环播放次数,n > 0,实现 n 次播放,n = -1,表示无限循环
- isMeteringEnabled: 是否启动音频计量,即输出音频的可视化计量数据
后台播放
在播放音频时,一个很常用的场景就是让App退出前台后,依然能在后台持续不断的播放音频直至用户停止。
想要在后台也能播放音频其实也不难,只需要两个步骤:
- 将音频会话的会话分类设置为Playback,该会话能让音频在手机静音情况下正常播放
- 需要在Info.plist文件中添加一个Required background modes类型数组,添加一个item项为App plays audio or streams audio/video using AirPlay
通过以上这两步,便可以让音频播放继续在后台服务。
中断处理
音频在播放时,有时候也会受到电话呼叫或Face Time呼叫,导致音频突然被中断,等到用户拒绝或者呼叫结束时,音频又开始从暂停位置重新播放。这一系列操作的成功实现,需要依赖AVAudioSession的中断通知,通过监听中断通知,当中断开始或中断结束,系统都会告诉外界发生的变化,示例代码如下所示:
func setupNotifications() {
let nc = NotificationCenter.default
nc.addObserver(self,
selector: #selector(handleInterruption),
name: AVAudioSession.interruptionNotification,
object: AVAudioSession.sharedInstance)
}
@objc func handleInterruption(notification: Notification) {
}
- 中断通知会包含一个带有重要信息的userInfo,根据这个字典来决定音频的行为,暂停还是播放
- handleInterruption(notification:):用来集中处理中断通知
handleInterruption(notification:)中处理中断通知的代码示例:
@objc func handleInterruption(notification: Notification) {
guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
return
}
switch type {
case .began:
case .ended:
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
} else {
}
default: ()
}
}
路线更换处理
在使用音乐软件时,经常会在有一个场景,比如从扬声器切换成耳机,或从耳机切换成扬声器等。有时候将耳机切换成扬声器时还继续播放用户的音频内容,是一件十分危险的事,这是因为可能用户目前听到的音频是十分隐私的内容。
正因为有这种需求场景,AVAudioSession提供了线路更换的通知,当手机设备上的线路(如扬声器切换成耳机)发生更改时,会触发AVAudioSession.routeChangeNotification通知给开发者,开发者需遵循《iOS用户体验规范》对音频实现播放或暂停等功能。
监听路线更换的通知示例代码如下所示:
func setupNotifications() {
let nc = NotificationCenter.default
nc.addObserver(self,
selector: #selector(handleRouteChange),
name: AVAudioSession.routeChangeNotification,
object: nil)
}
@objc func handleRouteChange(notification: Notification) {
}
- 当输出音频或者输出设备发生变化时,都会发出该通知
- notification 包含一个 userInfo 字典,可以获取通知发送的原因及前一个线路的描述
handleRouteChange(notification:) 处理代码示例:
@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: //找到新设备
let session = AVAudioSession.sharedInstance()
headphonesConnected = hasHeadphones(in: session.currentRoute)
case .oldDeviceUnavailable://老设备断开
// 获取线路描述信息
if let previousRoute =
userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {
headphonesConnected = hasHeadphones(in: previousRoute)
}
default: ()
}
}
func hasHeadphones(in routeDescription: AVAudioSessionRouteDescription) -> Bool {
// 找到第一个输出口,判断是否是耳机入口
return !routeDescription.outputs.filter({$0.portType == .headphones}).isEmpty
}
使用AVAudioRecorder录制音频
AVAudioRecorder是AV Foundation上用来作音频录制的接口之一,是对Audio Queue Services的高级封装,使用AVAudioRecorder录制也不复杂。
AVAudioRecorder创建过程十分简单,主要有两步:
- 生成一个URL,附加给AVAudioRecorder作为音频流写入地址
- 生成一个字典settings,用来规范音频流的格式,同样附加给AVAudioRecorder
AVAudioRecorder创建过程的示例代码:
do {
self.recorder = try AVAudioRecorder(url: fileURL, settings: setting)
self.recorder.delegate = self
self.recorder.isMeteringEnabled = true
self.recorder.prepareToRecord()
} catch {
fatalError(error.localizedDescription)
}
- prepareToRecord()方法的作用是初始化录制需要的资源,包括创建文件等,将录制启动的延时降到最小
- setting中的键值信息包含音频格式、采样率等
- URL文件路径的后缀要和音频格式对应上,否则会存在问题
setting是用来规范音频流的录制格式,常见的键值有:
- AVFormatIDKey:音频格式
- AVSampleRateKey:采样率
- AVNumberOfChannelsKey:通道数
- AVEncoderBitDepthHintKey:量化位数
- AVEncoderAudioQualityKey:音质
使用AVAudioRecorder录制音频时,需要将音频会话的会话分类设置成playAndRecord,创建AVAudioRecorder,实现AVAudioRecordeDelegate协议,AVAudioRecordeDelegate的内容很简单,主要是录制完成和录制出错的回调,其他方法基本上已经废弃。
文字转语音
AV Foundation 提供了一个语音合成框架,用于管理语音和语音合成,其中最常用的一个功能就是文字转语音,即AVSpeechSynthesisVoice
要让App带上文字转语音的功能,只需要两步即可:
- 创建一个AVSpeechUtterance对象,并附加上内容字符串,语音参数,如声音、速率等
- 将AVSpeechUtterance对象附加到即AVSpeechSynthesisVoice实例上即可,有AVSpeechSynthesisVoice实例控制语音的生命周期
代码示例如下:
// 创建AVSpeechUtterance实例,并附加字符内容
let utterance = AVSpeechUtterance(string: "The quick brown fox jumped over the lazy dog.")
utterance.rate = 0.57 // 速率
utterance.pitchMultiplier = 0.8
utterance.postUtteranceDelay = 0.2
utterance.volume = 0.8 // 音量
let voice = AVSpeechSynthesisVoice(language: "en-GB")
utterance.voice = voice
let synthesizer = AVSpeechSynthesizer()
synthesizer.speak(utterance)
AVSpeechUtterance实例也有相应的一个Delegate,即AVSpeechSynthesizerDelegate,主要是对文字语音过程中的生命周期进行回调,有时间读者们可以自己看看相关的API。
今天的文章播放和录制音频分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/21961.html