如何在iOS上创建可暂停的音频录制应用
我们的应用程序允许志愿者记录盲人和印刷品受损人士的文章。 使用AVAudioRecorder
非常简单AVAudioRecorder
,它还允许暂停,但是它没有提供任何方法来监听录音,直到过程暂停为止。 必须停止记录才能将数据写入磁盘,并使播放器(例如AVAudioPlayer
, AVPlayer
, AVQueuePlayer
等)可以播放数据。
下图希望有助于突出显示本文中使用的类之间的联系。 NSObject
隐式隐含为没有继承箭头的超类。
注意:黑色继承箭头的方向选择可能很不幸。 要解决此问题,请阅读下图,
“ AVQueuePlayer
继承了 AVPlayer
”
要么
“ AVComposition
继承了 AVAsset
”
一个重要的推论是AVPlayerItem
将在其init
接受AVAsset
, AVComposition
和AVMutableComposition
。 (有关官方证明,请参阅AVMutableComposition
的文档。)
CAVEAT:该应用程序已经超出了这些控件的范围,但是仍然适用于一般用例(并且将提供全面的文档)。
发现这一事实后,很明显
- 在部分录音中必须保留参考(以最终由所选音频播放器控制的类型为准-参见下文),以及
- 阅读完文章后,使用
AVAssetExportSession
将它们连接起来。
所有这些都应在后台完成,使控件对用户透明:
- 记录 :实例化
AVAudioRecorder
对象,并开始记录。 - STOP / PAUSE :停止录音,取消
self.audioRecorder
属性,并将音频追加到self.articleChunks
数组。 - 播放 :用该点的部分录音实例化播放器(即
self.articleChunks
)并开始播放。 - 队列和提交 :除了目标略有不同外,它们都将连接录音片段,并使生成的音频文件充满元数据。
在使用RECORD,STOP / PAUSE和PLAY时,记录尚未完成,但在按QUEUE或SUBMIT后,将导出最终结果,并且UI会重置为其初始状态(即, self.articleChunks
设置为空数组,没有发布)已选择,标题已清除等)。
AVQueuePlayer
选择AVQueuePlayer
作为播放器,是因为在我们的方案中,它是最简单,最快的管理方式。 反对其他玩家的原因:
- 可以直接使用
URL
或Data
初始化AVAudioPlayer
,然后将播放器对象存储在数组中,但是需要花费大量精力才能实现连续播放。 -
AVPlayer
有点高级,它仍然允许通过URL
进行初始化,并且可以使用replaceCurrentItem
替换当前播放的项目,因此一个对象就足够了。 缺点是后一种方法需要提供AVPlayerItem
,并且需要帮助方法来以正确的顺序连续播放音频块。
AVQueuePlayer
是AVPlayer
的子类,这意味着它还要求其项目是中间的AVPlayerItem
,但是至少它需要它们的数组,并且要照顾正确的播放顺序(如果提供的数组将它们保存在中间)。正确的顺序)。
AVQueuePlayer
有其怪癖
- 没有标准的方法可以向前/向后跳到上一个/下一个音频项目²
- 重新初始化现有的
AVQueuePlayer
很麻烦
重新初始化是必需的,因为即使可以将新项目插入队列中,也只能在队列中的项目之后插入。 在播放过程中,当前项目也会从队列中删除,因此,如果我们在队列的末尾添加带有insert
新AVPlayerItem
,那么在添加更多录音并播放结果之后,我们仍然会错过前面的部分。 如果用户始终聆听整个录音,则不必担心,只需一个接一个地重新插入所有内容。 但是,如果读者只听某个部分,该怎么办? 这将使某些人排队,使我们检查AVQueuePlayer
的items
方法并决定如何清理。
作为替代方案,我们可以使用removeAllItems
然后insert
每个音频块中insert
一个insert
,但是使用正确的数组³从头开始创建新的AVQueuePlayer
更为干净。
“一个AVPlayerItem不能与一个以上的AVPlayer实例关联”
第一种方法是使用[AVPlayerItem]
类型将部分录音存储在self.articleChunks
。 由于一个AVPlayerItem
只能分配给一个(并且只有一个) AVPlayer
对象,并且由于每次播放都重新创建AVQueuePlayer
因此需要将其更改为[AVAsset]
。
事实证明这不是问题,因为当需要连接音频块时,所有涉及的类无论如何都需要AVAsset
,并且简单的map
可以为AVQueuePlayer
返回正确的数组以进行播放:
func startPlayer(){
让assetKeys = [“可玩”]
让playerItems =
self.articleChunks.map {
AVPlayerItem(
资产:$ 0,
automaticLoadedAssetKeys:assetKeys)
} self.queuePlayer = AVQueuePlayer(item:playerItems)
self.queuePlayer?.actionAtItemEnd = .advance self.queuePlayer?.play()
}
我最初遵循两个Stackoverflow答案(1、2),但是答案是在Objective-C中,仅限于连接两个音频文件,并且(至少在我们的情况下)不必要地弄乱了音轨。
AVMutableComposition
用于创建支架,以便将每个通过AVAsset
表示的部分音频准确地插入到前一个资产的末尾。
AVMutableComposition
和AVMutableTrack
都具有insertTimeRange
方法,由于在我们的情况下,我们只有一个轨道,因此AVAssetTrack
AVAssetTracks⁵是没有意义的。 如果是视频文件,则可以分开插入轨道,但是如果它们具有相同的长度并且已经同步,则我认为它们可以以相同的方式连接。 否则,在组合级别上包含insertTimeRange
的意义是什么?
AVAssetExportSession
需要初始化一个AVAsset
,但是AVComposition
和AVMutableComposition
都是AVMutableComposition
的子类,因此可以在此处使用它们。
AVAssetExportSession
的status
属性符合键值观察(KVO)⁶,因此可以代替switch
语句使用它。
- [^]请参阅《访问新闻》的github
RecordViewController
中的RecordViewController
或此简单指南。 - [^]解决方法上有一个不错的Stackoverflow线程,但是我个人仍然认为,重新创建
AVQueuePlayer
对象和处理以某种形式保存音频实体的数组会更容易。 - [^]我担心性能,但是我发现即使在旧的(6年以上)iPad上,质量也不会降低。 让我知道我是否错了。
- [^]我可能仍然没有正确的心理模型,什么是音轨,但是根据我目前的理解,它们允许混合多个不同的视听输入。 因此,立体声视频将具有3条轨道:1条视频和2条声音。 如果您像在移动设备上那样以单声道录制,最终将得到一个具有1个
AVAssetTrack
轨道的AVAssetTrack
。 - [^]注释行显示了如何完成此操作。
- [^]有关KVO的一些资源,从Swift的官方文档开始:
*将Swift与Cocoa和Objective-C结合使用(Swift 4.1)-关键价值观察
* CocoaCasts —键值观察(KVO)和Swift 3
* Erica Millado-玩键值观察(KVO)(Swift3)