iOS 音视频处理小记
iOS 音视频处理小记
本文记录了处理音频时候踩的坑, 主要是其中因不熟悉导致耗时较长的点
- 从视频中提取音频
- 音频文件的播放
- AVAudioPlayer 创建报错
- 系统中音频文件的格式
- iOS-音频-AVAudioSession
从视频中提取音频
视频由视轨和音轨组成, 从视频中提取音轨需要用到 AVFoundation
框架中 AVAsset & AVURLAsset
.
主要分为 3 步:
- 给创建一个音轨对象
- 拿到视频按照音轨对象规格提取(指定格式 m4a/caf)
- 指定输出地址 URL 输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
fileprivate extension AVAsset {
// 对外提供写入的方法
func writeAudioTrack(to url: URL,
success: @escaping () -> (),
failure: @escaping (Error) -> ()) {
do {
let asset = try audioAsset()
asset.write(to: url, success: success, failure: failure)
} catch {
failure(error)
}
}
// 写入到指定 URL
private func write(to url: URL, success: @escaping () -> (), failure: @escaping (Error) -> ()) {
try? FileManager().removeItem(at: url)
// Create an export session that will output an audio track (CAF file)
if let exportSession = AVAssetExportSession(asset: self, presetName: AVAssetExportPresetPassthrough) {
exportSession.outputFileType = .caf
exportSession.outputURL = url
exportSession.exportAsynchronously {
switch exportSession.status {
case .completed:
success()
default:
let error = NSError(domain: "提取音频失败", code: 0, userInfo: nil)
failure(error)
break
}
}
}else{
let error = NSError(domain: "提取音频失败", code: 0, userInfo: nil)
failure(error)
}
}
// 生成要提取音频的音轨对象
private func audioAsset() throws -> AVAsset {
let composition = AVMutableComposition()
let audioTracks = tracks(withMediaType: .audio)
for track in audioTracks {
let compositionTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
try compositionTrack?.insertTimeRange(track.timeRange, of: track, at: track.timeRange.start)
compositionTrack?.preferredTransform = track.preferredTransform
}
return composition
}
}
使用方式如下:
1
2
3
4
5
6
7
8
9
10
chooseVideo { videoUrl in
let asset = AVURLAsset(url: videoUrl)
let audioUrl = XYFileManager.rootURL!.appendingPathComponent( UUID().uuidString + ".caf")
asset.writeAudioTrack(to: audioUrl) {
callback(audioUrl)
} failure: { error in
Toast.make(error.localizedDescription)
}
}
音频文件的播放
音频文件播放很简单, 直接使用 AVAudioPlayer
.
但是这里有个坑点: 就是 audioPlayer 必须作为当前控制器的属性被持有才能播放, 否则它会直接被释放而无法播放
1
2
3
4
5
6
7
8
// 先解析成 audioData. 并播放
static func playAudio(data: Data) {
let audioData = data
shared.audioPlayer = try? AVAudioPlayer(data: audioData)
shared.audioPlayer?.volume = 1.0
shared.audioPlayer?.prepareToPlay()
shared.audioPlayer?.play()
}
AVAudioPlayer 创建报错
创建方式 try? AVAudioPlayer(data: audioData)
, 可能的错误如下:
1
The operation couldn’t be completed. (OSStatus error 1685348671.)
这个错误本质是初始化时候的音频格式不正确, 比如你名称是 mp3, 实际上文件格式是 caf. 这里需要正确对应
1
The operation couldn’t be completed. (OSStatus error 2003334207.)
这个错误是文件不存在,指定了一个空数据源.
系统中音频文件的格式
要获取导出音频支持的格式, 可参考
具体我们能指定的可用的输出格式可用下面方法得到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 初始化一个导出会话, presetName 会直接影响后面可导出的文件格式
let exportSession = AVAssetExportSession(asset: self, presetName: AVAssetExportPresetPassthrough)
// 理论上支持的类型
let types = exportSession.supportedFileTypes
print(types)
// 打印数据
__C.AVFileType(_rawValue: com.apple.quicktime-movie),
__C.AVFileType(_rawValue: com.apple.m4a-audio),
__C.AVFileType(_rawValue: public.mpeg-4),
__C.AVFileType(_rawValue: com.apple.m4v-video),
__C.AVFileType(_rawValue: public.3gpp),
__C.AVFileType(_rawValue: org.3gpp.adaptive-multi-rate-audio),
__C.AVFileType(_rawValue: com.microsoft.waveform-audio),
__C.AVFileType(_rawValue: public.aiff-audio),
__C.AVFileType(_rawValue: public.aifc-audio),
__C.AVFileType(_rawValue: com.apple.coreaudio-format)
// 运行时真实能支持的类型, 所选择类型需要在这里面选取
exportSession.determineCompatibleFileTypes { types in
print(types)
}
// 打印
__C.AVFileType(_rawValue: com.apple.coreaudio-format),
__C.AVFileType(_rawValue: com.apple.m4a-audio),
__C.AVFileType(_rawValue: com.apple.m4v-video),
__C.AVFileType(_rawValue: com.apple.quicktime-movie),
__C.AVFileType(_rawValue: public.3gpp),
__C.AVFileType(_rawValue: public.mpeg-4)
iOS-音频-AVAudioSession
这里是网络上别人整理好文章, 下面列几个要点.
AVAudioSession的作用就是管理音频这一唯一硬件资源的分配,通过调优合适的AVAudioSession来适配我们的APP对于音频的功能需求。切换音频场景时候,需要相应的切换AVAudioSession。
AVAudioSession 就是用来管理多个 APP 对音频硬件设备(麦克风,扬声器)的资源使用
AVAudioSession Category 不同类型适用不同类型的 App, 共 7 种类
AVAudioSession Mode&&Options AVAudioSession Mode 定制我们的Category行为, 比如 VoIP, chat, game, 视频录制 等 AVAudioSession Options 微调Category行为, AVAudioSessionCategoryOptionMixWithOthers 支持和其他 App 混音
调优我们的Category
比如我们的 App 启动就占用音频通道, 打断了其他音乐软件的播放任务. 经过了解之后就能通过 category / mode / option 来修改为不占用其他 App 的音频通道.
1
2
3
4
5
6
7
8
9
Before: - 直接占用, 打断了其他 App 得使用
[[AVAudioSession sharedInstance] setActive: YES error: nil];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
After: - 设置为可以播放,并允许和其他 App 混音
[AVAudioSession.sharedInstance setCategory: AVAudioSessionCategoryAmbient mode:AVAudioSessionModeDefault options:AVAudioSessionCategoryOptionMixWithOthers error:NULL];
比如: 导航软件,需要后台音频, 并支持智能压低和混音其他 App.
BOOL isSuccess = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionDuckOthers error:&setCategoryError];
- 音频中断处理 其他APP或者电话会中断我们的APP音频,所以相应的我们要做出处理。 我们可以通过监听AVAudioSessionInterruptionNotification 这个 key 获取音频中断事件.
回调回来Userinfo有键值 AVAudioSessionInterruptionTypeKey: 取值AVAudioSessionInterruptionTypeBegan表示中断开始 取值AVAudioSessionInterruptionTypeEnded表示中断结束