在这篇博客中,我们介绍如何用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]
// 创建文件名,修改扩展名为 .wav
let audioFilename = documentsPath.appendingPathComponent("recording.wav")
return audioFilename
}
// 请求麦克风权限并设置音频会话排
func requestPermissionAndSetupSession(completion: @escaping (Bool) -> Void) {
recordingSession = AVAudioSession.sharedInstance()
// 请求麦克风权限
AVAudioSession.sharedInstance().requestRecordPermission { [unowned self] allowed in
DispatchQueue.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), // 音频格式改为 PCM
AVSampleRateKey: 44100, // 采样率
AVNumberOfChannelsKey: 2, // 声道数
AVLinearPCMBitDepthKey: 16, // 位深度(常用 16 位), 使用多少个二进制来存储一个采样点的样本值,位深度越高,表示振幅越精确
AVLinearPCMIsBigEndianKey: false, // 是否大端字节序
AVLinearPCMIsFloatKey: false, // 是否浮点型
AVLinearPCMIsNonInterleaved: false, // 是否非交错
]
do {
audioRecorder = try AVAudioRecorder(url: recordingFileURL, settings: settings)
audioRecorder?.delegate = self
audioRecorder?.record()
isRecording = true
print("开始录音")
} catch {
print("录音器初始化失败:\(error.localizedDescription)")
delegate?.customAudioRecorderDidEncounterError(error)
}
}
// 停止录音
func stopRecording() {
audioRecorder?.stop()
isRecording = false
print("停止录音")
}
// 判断是否正在录音
func isRecordingActive() -> Bool {
return isRecording
}
// 获取录音文件的 URL
func getRecordingFileURL() -> URL {
return recordingFileURL
}
}
// MARK: - AVAudioRecorderDelegate
extension 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)
}
}
}
通过 AVAudioRecorder
实现音频录制功能,录制的音频保存为 .wav
格式。
通过 AVAudioPlayer
播放录制的音频文件。
通过 FFmpegKit
将视频文件从 .mp4
格式转换为另一个 .mp4
文件(可以自定义编码器和参数)。
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 in
DispatchQueue.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 = self
audioRecorder?.record()
isRecording = true
} catch {
delegate?.customAudioRecorderDidEncounterError(error)
}
}
设置录音参数(如采样率、声道数、位深度等),并启动录音。
停止录音:
func stopRecording() {
audioRecorder?.stop()
isRecording = false
}
代理回调:
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
delegate?.customAudioRecorderDidFinishRecording(successfully: flag)
}
通过 AVAudioPlayer
播放录制的 .wav
文件。
播放音频:
@objc func playAudio() {
let audioURL = audioRecorder.getRecordingFileURL()
do {
audioPlayer = try AVAudioPlayer(contentsOf: audioURL)
audioPlayer?.delegate = self
audioPlayer?.play()
} catch {
print("音频播放失败:\(error.localizedDescription)")
}
}
播放完成回调:
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
print("音频播放完成")
}
FFmpegKit
是一个强大的多媒体处理库,支持音视频的编码、解码、转换等操作。
FFmpeg 命令:
let ffmpegCommand = "\(overwriteOption) -i \"\(inputFile)\" -c:v libx264 -c:a aac \"\(outputFile)\""
该命令将输入文件转换为 H.264 视频编码和 AAC 音频编码的 .mp4
文件。
执行转换:
FFmpegKit.executeAsync(ffmpegCommand) { session in
let state = session?.getState()
let returnCode = session?.getReturnCode()
if ReturnCode.isSuccess(returnCode) {
print("视频转换成功!输出文件位于:\(outputFile)")
} else {
if let output = session?.getAllLogsAsString() {
print("转换失败,输出日志:\n\(output)")
}
}
}
使用 FFmpegKit.executeAsync
异步执行转换命令,并通过回调处理结果。
开始录音:
@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 = self
audioPlayer?.play()
} catch {
print("音频播放失败:\(error.localizedDescription)")
}
}
视频格式转换:
@objc func convertVideoFormat() {
FFmpegKit.executeAsync(ffmpegCommand) { session in
let 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
文件:
AVAudioRecorder
录制音频时,即使你指定了 kAudioFormatLinearPCM
,AVAudioRecorder
会自动为录音文件添加 WAV 文件头,使其成为一个合法的 WAV 文件。在你的代码中,以下设置指定了音频格式为 PCM:
AVFormatIDKey: Int(kAudioFormatLinearPCM)
但录音文件的保存路径使用了 .wav
扩展名:
let audioFilename = documentsPath.appendingPathComponent("recording.wav")
AVAudioRecorder
会根据录音设置和文件扩展名自动处理文件格式。在这种情况下,它会将录制的 PCM 数据封装为一个合法的 WAV 文件,并保存到指定路径。
文件扩展名的选择:
.wav
是一个常见的扩展名,但它只是一个约定,真正决定文件格式的是文件内容。.pcm
,文件内容仍然是合法的 WAV 文件,只是扩展名可能会让人误解。兼容性:
自定义文件格式:
AVFormatIDKey
的值,并选择合适的文件扩展名。