使用 Swift 完成FFmpeg音频录制、播放和视频格式转换应用
使用 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 等),可以更改
相关文章:
使用 Swift 完成FFmpeg音频录制、播放和视频格式转换应用
使用 Swift 构建音频录制、播放和视频格式转换应用 在这篇博客中,我们介绍如何用ffmpeg在swift上实现音频录制、音频播放、通过ffmpeg命令实现视频格式转换 音频录制:通过 AVAudioRecorder 实现音频录制功能。音频播放:通过 AVAudioPlayer …...
Gitea+Gridea 创建个人博客
历史文档存档,该方法目前已经无法使用,部署方法可供参考 Gitea部分 1.关于Gitea Gitea 是一个面向开源及私有软件项目的托管平台,是全球最大的代码托管平台之一。它采用 Git 分布式版本控制系统,为开发者提供了代码托管、版本控…...
【Linux】一文带你入门了解线程和虚拟地址空间中页表映射的秘密(内附手绘底层逻辑图 通俗易懂)
绪论 每日激励:“努力去做自己该做的,但是不要期待回报,不是付出了就会有回报的,做了就不要后悔,不做才后悔。—Jack” 绪论: 本章是LInux中非常重要的线程部分,通过了解线程的基本概念&am…...
js面试some和every的区别
1.基础使用 some和every 都是数组的一个方法let num [1,2,3,4,5,6] let flag1 num.some((item,index,array)> item > 2)let flag2 num.every((item,index, array)> item > 2)1.some 遍历判断中是符合条件的值 一旦找到则不会继续迭代下去 直接返回 2.every 遍历…...
缓存类为啥使用 unordered_map 而不是 map
性能考虑: std::unordered_map 是基于哈希表实现的,而 std::map 是基于红黑树实现的。对于查找操作,std::unordered_map 的平均查找时间复杂度是 O ( 1 ) O(1) O(1),而 std::map 的查找时间复杂度是 O ( l o g n ) O(log n) O(l…...
ollama linux下载
实验室服务器(A6000)执行curl -fsSL https://ollama.com/install.sh | sh太慢了。 而sudo snap install ollama,容易爆cudalibrt.so12无法正常使用的bug。 发现 https://www.modelscope.cn/models/modelscope/ollama-linux 使用modelscope进…...
k8s服务发现有哪些方式?
在 Kubernetes 中,服务发现是指如何让应用程序在集群内互相找到并通信。Kubernetes 提供了多种服务发现的方式,适应不同的使用场景。以下是 Kubernetes 中常见的服务发现方式: 1. 环境变量(Environment Variables) 概…...
Flash Attention与Attention
原始Attention是: Flash Attention: 伪代码:4d(分别代表Q\K\V\O) Flash Attention2优化了...
vue 使用fetch-event-source 处理sse,实现ChatGpt逐字输出效果
1. 安装 npm install microsoft/fetch-event-source 2. 引用 import { fetchEventSource } from "microsoft/fetch-event-source"; 3. 使用 fetchEventSource(/api/chat, { method: POST,headers: {Content-Type: application/json,Accept: */*,Token: this.toke…...
JAVA进阶之线程
为神马有线程?这玩意儿在干嘛??? 回答这个问题,就先要知道一点点计算机的工作方式。 总所周知,计算机有五部分:输入输出、计算器、存储器、控制器。而在计算机内,CPU、内存、I/O之…...
机器学习专业毕设选题推荐合集 人工智能
目录 前言 毕设选题 开题指导建议 更多精选选题 选题帮助 最后 前言 大家好,这里是海浪学长毕设专题! 大四是整个大学期间最忙碌的时光,一边要忙着准备考研、考公、考教资或者实习为毕业后面临的升学就业做准备,一边要为毕业设计耗费大量精力。学长给大家整理…...
C++ 中的 `string` 类型:全面解析与高效操作
C 中的 string 类型:全面解析与高效操作 在 C 中,string 类型是对字符数组的高级封装,它提供了大量内置函数,使得字符串的处理变得更为简便和高效。与 C 风格的字符数组不同,string 类型不仅自动管理内存,…...
go语言中的Stringer的使用
Go 语言中的 Stringer 是一个非常有用的接口,它在标准库的 fmt 包中定义。Stringer 接口允许类型定义它们的字符串表示方式,这在格式化输出时特别有用。让我们深入了解一下: Stringer 接口定义: type Stringer interface {Strin…...
Java入门进阶
文章目录 1、常用API 1.1、Math1.2、System1.3、Object1.4、Arrays1.5、基本类型包装类 1.5.1、基本类型包装类概述1.5.2、Integer1.5.3、int和String相互转换1.5.4、自动装箱和拆箱 1.6、日期类 1.6.1、Date类1.6.2、SimpleDateFormat类 1.6.2.1、格式化(从Date到…...
【大数据技术】搭建完全分布式高可用大数据集群(Scala+Spark)
搭建完全分布式高可用大数据集群(Scala+Spark) scala-2.13.16.tgzspark-3.5.4-bin-without-hadoop.tgz注:请在阅读本篇文章前,将以上资源下载下来。 写在前面 本文主要介绍搭建完全分布式高可用集群Spark的详细步骤。 注意: 统一约定将软件安装包存放于虚拟机的/softwa…...
使用vLLM部署Qwen2.5-VL-7B-Instruct模型的详细指南
使用vLLM部署Qwen2.5-VL-7B-Instruct模型的详细指南 引言环境搭建安装vLLM安装依赖库下载模型启动vLLM服务器总结参考 引言 近年来,随着大规模语言模型(LLM)的快速发展,如何高效地进行模型推理成为了一个热门话题。vLLM作为一个专…...
AWS门店人流量数据分析项目的设计与实现
这是一个AWS的数据分析项目,关于快消公司门店手机各个门店进店人流量和各个产品柜台前逗留时间(利用IoT设备采集)和销售数据之间的统计分析,必须用到但不限于Amazon Kensis Data Stream,Spark Streaming,Sp…...
C#结合html2canvas生成切割图片并导出到PDF
目录 需求 开发运行环境 实现 生成HTML范例片断 HTML元素转BASE64 BASE64转图片 切割长图片 生成PDF文件 小结 需求 html2canvas 是一个 JavaScript 库,它可以把任意一个网页中的元素(包括整个网页)绘制到指定的 canvas 中…...
485网关数据收发测试
目录 1.UDP SERVER数据收发测试 使用产品: || ZQWL-GW1600NM 产品||【智嵌物联】智能网关型串口服务器 1.UDP SERVER数据收发测试 A(TX)连接RX B(RX)连接TX 打开1个网络调试助手,模拟用户的UDP客户端设…...
InnoDB和MyISAM的比较、水平切分和垂直切分、主从复制中涉及的三个线程、主从同步的延迟产生和解决
InnoDB和MyISAM的比较 事务支持: InnoDB支持:支持事务 (ACID 属性)。支持 Commit、Rollback 和 Savepoint 操作。适合需要事务处理的应用,例如银行系统。MyISAM:不支持事务。每次操作都是自动提交,不能回滚或中止。适合对事务要求…...
JDK9新特性
文章目录 新特性:1.模块化系统使用模块化module-info.java:exports:opens:requires:provides:uses: 2.JShell启动Jshell执行计算定义变量定义方法定义类帮助命令查看定义的变量:/var…...
基于Ubuntu2404搭建Zabbix7.2
Zabbix 搭建zabbix zabbix7.2已推出:官网 增加的新功能如下: 1.使用新的热门商品小部件全面概览指标 数据概览小部件已转换为热门项目小部件使用项目模式可以实现细粒度的项目选择利用条形图、指标和迷你图来可视化您的数据定义价值阈值以动态地可视化…...
Math Reference Notes: 符号函数
1. 符号函数的定义 符号函数(Sign Function) sgn ( x ) \text{sgn}(x) sgn(x) 是一个将实数 ( x ) 映射为其 符号值(即正数、负数或零)的函数。 它的定义如下: sgn ( x ) { 1 如果 x > 0 0 如果 x 0 − 1 如…...
【数据结构】链表应用-链表重新排序
重新排序 反转链表预期实现思路解题过程code力扣代码核心代码完整代码 总结 删除链表中间节点代码解惑 链表重新排序题目描述解题思路解题过程复杂度代码力扣代码完整代码 反转链表 预期实现 思路 你选用何种方法解题? 我选用了迭代法来反转链表。这是一种经典且高…...
学习threejs,pvr格式图片文件贴图
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️PVR贴图1.2 ☘️THREE.Mesh…...
数据库开发常识(10.6)——SQL性能判断标准及索引误区(1)
10.6. 数据库开发常识 作为一名专业数据库开发人员,不但需要掌握数据库开发相关的语法和功能实现,还要掌握专业数据库开发的常识。这样,才能在保量完成工作任务的同时,也保质的完成工作任务,避免了为应用的日后维护埋…...
2022年全国职业院校技能大赛网络系统管理赛项模块A:网络构建(样题2)-网络部分解析-附详细代码
目录 附录1:拓扑图编辑 附录2:地址规划表 1.SW1 2.SW2 3.SW3 4.SW4 5.SW5 6.SW6 7.SW7 8.R1 9.R2 10.R3 11.AC1 12.AC2 13.EG1 14.EG2 15.AP2 16.AP3 附录1:拓扑图 附录2:地址规划表...
100.7 AI量化面试题:如何利用新闻文本数据构建交易信号?
目录 0. 承前1. 解题思路1.1 数据处理维度1.2 分析模型维度1.3 信号构建维度 2. 新闻数据获取与预处理2.1 数据获取接口2.2 文本预处理 3. 情感分析与事件抽取3.1 情感分析模型3.2 事件抽取 4. 信号生成与优化4.1 信号构建4.2 信号优化 5. 策略实现与回测5.1 策略实现 6. 回答话…...
【前端】【Ts】【知识点总结】TypeScript知识总结
一、总体概述 TypeScript 是 JavaScript 的超集,主要通过静态类型检查和丰富的类型系统来提高代码的健壮性和可维护性。它涵盖了从基础数据类型到高级类型、从函数与对象的类型定义到类、接口、泛型、模块化及装饰器等众多知识点。掌握这些内容有助于编写更清晰、结…...
【前端】【Ts】TypeScript的关键知识点
一、知识点总结 (一)void 与 never 的区别 (1) void:声明函数无返回值,但可以走到 return 行。(2) never:表示函数不会走到 return 行,常用于抛异常或无限循环。 (二)字面量类型与联…...
