播放和录制音频

播放和录制音频音频会话 AVAudioSession(即音频会话)参与,音频会话是应用程序和操作系统交互的中介者,通过语义描述来调度系统音频功能。 音频会话分类 AVFoundation定义了7中分类来描述应用程序

音频会话

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退出前台后,依然能在后台持续不断的播放音频直至用户停止。

想要在后台也能播放音频其实也不难,只需要两个步骤:

  1. 将音频会话的会话分类设置为Playback,该会话能让音频在手机静音情况下正常播放
  2. 需要在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创建过程十分简单,主要有两步:

  1. 生成一个URL,附加给AVAudioRecorder作为音频流写入地址
  2. 生成一个字典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带上文字转语音的功能,只需要两步即可:

  1. 创建一个AVSpeechUtterance对象,并附加上内容字符串,语音参数,如声音、速率等
  2. 将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

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注