使用 Swift 构建音频录制、播放和视频格式转换应用
在这篇博客中,我们介绍如何用ffmpeg在swift上实现音频录制、音频播放、通过ffmpeg命令实现视频格式转换
- 音频录制:通过
AVAudioRecorder
实现音频录制功能。 - 音频播放:通过
AVAudioPlayer
实现录制音频的播放。 - 视频格式转换:通过
FFmpegKit
实现视频格式的转换。
这段代码展示了如何结合 iOS 的音频和视频处理框架,以及第三方库 FFmpegKit
,来构建一个功能丰富的多媒体应用。
完整代码:
import AVFoundation
import Foundation// 定义协议,用于通知录音状态的变化
protocol AudioRecorderDelegate: AnyObject {func customAudioRecorderDidFinishRecording(successfully flag: Bool)func customAudioRecorderDidEncounterError(_ error: Error)
}class AudioRecorder: NSObject {private var audioRecorder: AVAudioRecorder?private var recordingSession: AVAudioSession!weak var delegate: AudioRecorderDelegate?private var isRecording: Bool = false// 录音文件保存路径(可自定义)private var recordingFileURL: URL {// 获取 Documents 目录let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]// 创建文件名,修改扩展名为 .wavlet audioFilename = documentsPath.appendingPathComponent("recording.wav")return audioFilename}// 请求麦克风权限并设置音频会话排func requestPermissionAndSetupSession(completion: @escaping (Bool) -> Void) {recordingSession = AVAudioSession.sharedInstance()// 请求麦克风权限AVAudioSession.sharedInstance().requestRecordPermission { [unowned self] allowed inDispatchQueue.main.async {if allowed {do {// 设置音频会话类别和模式try recordingSession.setCategory(.playAndRecord, mode: .default)try recordingSession.setActive(true)completion(true)} catch {print("音频会话配置失败:\(error.localizedDescription)")self.delegate?.customAudioRecorderDidEncounterError(error)completion(false)}} else {print("麦克风权限被拒绝")completion(false)}}}}// 开始录音func startRecording() {// 设置录音参数let settings: [String: Any] = [AVFormatIDKey: Int(kAudioFormatLinearPCM), // 音频格式改为 PCMAVSampleRateKey: 44100, // 采样率AVNumberOfChannelsKey: 2, // 声道数AVLinearPCMBitDepthKey: 16, // 位深度(常用 16 位), 使用多少个二进制来存储一个采样点的样本值,位深度越高,表示振幅越精确AVLinearPCMIsBigEndianKey: false, // 是否大端字节序AVLinearPCMIsFloatKey: false, // 是否浮点型AVLinearPCMIsNonInterleaved: false, // 是否非交错]do {audioRecorder = try AVAudioRecorder(url: recordingFileURL, settings: settings)audioRecorder?.delegate = selfaudioRecorder?.record()isRecording = trueprint("开始录音")} catch {print("录音器初始化失败:\(error.localizedDescription)")delegate?.customAudioRecorderDidEncounterError(error)}}// 停止录音func stopRecording() {audioRecorder?.stop()isRecording = falseprint("停止录音")}// 判断是否正在录音func isRecordingActive() -> Bool {return isRecording}// 获取录音文件的 URLfunc getRecordingFileURL() -> URL {return recordingFileURL}
}// MARK: - AVAudioRecorderDelegateextension AudioRecorder: AVAudioRecorderDelegate {// 录音完成后的回调func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {delegate?.customAudioRecorderDidFinishRecording(successfully: flag)}// 录音发生错误的回调func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: Error?) {if let error = error {print("录音发生错误:\(error.localizedDescription)")delegate?.customAudioRecorderDidEncounterError(error)}}
}
功能概述
1. 音频录制
通过 AVAudioRecorder
实现音频录制功能,录制的音频保存为 .wav
格式。
2. 音频播放
通过 AVAudioPlayer
播放录制的音频文件。
3. 视频格式转换
通过 FFmpegKit
将视频文件从 .mp4
格式转换为另一个 .mp4
文件(可以自定义编码器和参数)。
代码分析
1. 音频录制功能
AudioRecorder 类
AudioRecorder
类封装了音频录制的逻辑,使用 AVAudioRecorder
进行录音,并通过代理通知录音状态的变化。
关键代码
-
录音文件保存路径:
private var recordingFileURL: URL {let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]let audioFilename = documentsPath.appendingPathComponent("recording.wav")return audioFilename }
录音文件保存在应用的
Documents
目录下,文件名为recording.wav
。 -
请求麦克风权限:
func requestPermissionAndSetupSession(completion: @escaping (Bool) -> Void) {recordingSession = AVAudioSession.sharedInstance()AVAudioSession.sharedInstance().requestRecordPermission { [unowned self] allowed inDispatchQueue.main.async {if allowed {do {try recordingSession.setCategory(.playAndRecord, mode: .default)try recordingSession.setActive(true)completion(true)} catch {self.delegate?.customAudioRecorderDidEncounterError(error)completion(false)}} else {completion(false)}}} }
通过
AVAudioSession
请求麦克风权限,并设置音频会话的类别为.playAndRecord
。 -
开始录音:
func startRecording() {let settings: [String: Any] = [AVFormatIDKey: Int(kAudioFormatLinearPCM),AVSampleRateKey: 44100,AVNumberOfChannelsKey: 2,AVLinearPCMBitDepthKey: 16,AVLinearPCMIsBigEndianKey: false,AVLinearPCMIsFloatKey: false,AVLinearPCMIsNonInterleaved: false,]do {audioRecorder = try AVAudioRecorder(url: recordingFileURL, settings: settings)audioRecorder?.delegate = selfaudioRecorder?.record()isRecording = true} catch {delegate?.customAudioRecorderDidEncounterError(error)} }
设置录音参数(如采样率、声道数、位深度等),并启动录音。
-
停止录音:
func stopRecording() {audioRecorder?.stop()isRecording = false }
-
代理回调:
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {delegate?.customAudioRecorderDidFinishRecording(successfully: flag) }
2. 音频播放功能
音频播放逻辑
通过 AVAudioPlayer
播放录制的 .wav
文件。
关键代码
-
播放音频:
@objc func playAudio() {let audioURL = audioRecorder.getRecordingFileURL()do {audioPlayer = try AVAudioPlayer(contentsOf: audioURL)audioPlayer?.delegate = selfaudioPlayer?.play()} catch {print("音频播放失败:\(error.localizedDescription)")} }
-
播放完成回调:
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {print("音频播放完成") }
3. 视频格式转换功能
FFmpegKit
FFmpegKit
是一个强大的多媒体处理库,支持音视频的编码、解码、转换等操作。
关键代码
-
FFmpeg 命令:
let ffmpegCommand = "\(overwriteOption) -i \"\(inputFile)\" -c:v libx264 -c:a aac \"\(outputFile)\""
该命令将输入文件转换为 H.264 视频编码和 AAC 音频编码的
.mp4
文件。 -
执行转换:
FFmpegKit.executeAsync(ffmpegCommand) { session inlet state = session?.getState()let returnCode = session?.getReturnCode()if ReturnCode.isSuccess(returnCode) {print("视频转换成功!输出文件位于:\(outputFile)")} else {if let output = session?.getAllLogsAsString() {print("转换失败,输出日志:\n\(output)")}} }
使用
FFmpegKit.executeAsync
异步执行转换命令,并通过回调处理结果。
4. 用户交互
按钮操作
-
开始录音:
@objc func startRecording() {if !audioRecorder.isRecordingActive() {audioRecorder.startRecording()} else {print("正在录音中")} }
-
停止录音:
@objc func stopRecording() {if audioRecorder.isRecordingActive() {audioRecorder.stopRecording()} else {print("当前未在录音")} }
-
播放音频:
@objc func playAudio() {let audioURL = audioRecorder.getRecordingFileURL()do {audioPlayer = try AVAudioPlayer(contentsOf: audioURL)audioPlayer?.delegate = selfaudioPlayer?.play()} catch {print("音频播放失败:\(error.localizedDescription)")} }
-
视频格式转换:
@objc func convertVideoFormat() {FFmpegKit.executeAsync(ffmpegCommand) { session inlet state = session?.getState()let returnCode = session?.getReturnCode()if ReturnCode.isSuccess(returnCode) {print("视频转换成功!输出文件位于:\(outputFile)")} else {if let output = session?.getAllLogsAsString() {print("转换失败,输出日志:\n\(output)")}}} }
疑点说明
在代码中,录音文件的保存路径使用了 .wav
扩展名,但音频格式设置为 kAudioFormatLinearPCM
。这可能会让人感到困惑,因为 .wav
通常被认为是 WAV 文件格式,而 kAudioFormatLinearPCM
是一种原始的未压缩音频格式。为了理解为什么这可以工作,我们需要了解 WAV 文件的结构和 PCM 数据的关系。
WAV 文件和 PCM 数据的关系
-
WAV 文件格式:
- WAV 文件是一种音频文件格式,它实际上是一个容器格式。
- WAV 文件的核心内容是 PCM 数据(Pulse Code Modulation,脉冲编码调制),这是一种未压缩的音频数据格式。
- 除了 PCM 数据,WAV 文件还包含一个文件头(Header),用于描述音频数据的格式(如采样率、声道数、位深度等)。
-
PCM 数据:
- PCM 是一种原始的音频数据格式,不包含任何文件头信息。
- 它只包含音频的采样值,无法单独描述音频的格式。
-
为什么可以保存为
.wav
文件:- 当你使用
AVAudioRecorder
录制音频时,即使你指定了kAudioFormatLinearPCM
,AVAudioRecorder
会自动为录音文件添加 WAV 文件头,使其成为一个合法的 WAV 文件。 - 这意味着,虽然音频数据本身是 PCM 格式,但由于文件头的存在,最终保存的文件是一个合法的 WAV 文件。
- 当你使用
代码中的行为
在你的代码中,以下设置指定了音频格式为 PCM:
AVFormatIDKey: Int(kAudioFormatLinearPCM)
但录音文件的保存路径使用了 .wav
扩展名:
let audioFilename = documentsPath.appendingPathComponent("recording.wav")
AVAudioRecorder
会根据录音设置和文件扩展名自动处理文件格式。在这种情况下,它会将录制的 PCM 数据封装为一个合法的 WAV 文件,并保存到指定路径。
注意事项
-
文件扩展名的选择:
- 虽然
.wav
是一个常见的扩展名,但它只是一个约定,真正决定文件格式的是文件内容。 - 如果你将文件扩展名改为
.pcm
,文件内容仍然是合法的 WAV 文件,只是扩展名可能会让人误解。
- 虽然
-
兼容性:
- 如果你需要与其他程序或设备共享录音文件,确保它们支持 WAV 格式。
- 如果你需要保存为纯 PCM 数据(没有文件头),你需要手动处理文件的写入。
-
自定义文件格式:
- 如果你需要更灵活的文件格式(如 MP3、AAC 等),可以更改
AVFormatIDKey
的值,并选择合适的文件扩展名。
- 如果你需要更灵活的文件格式(如 MP3、AAC 等),可以更改