使用 Swift 完成FFmpeg音频录制、播放和视频格式转换应用

使用 Swift 构建音频录制、播放和视频格式转换应用

在这篇博客中,我们介绍如何用ffmpeg在swift上实现音频录制、音频播放、通过ffmpeg命令实现视频格式转换

  1. 音频录制:通过 AVAudioRecorder 实现音频录制功能。
  2. 音频播放:通过 AVAudioPlayer 实现录制音频的播放。
  3. 视频格式转换:通过 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 数据的关系

  1. WAV 文件格式:

    • WAV 文件是一种音频文件格式,它实际上是一个容器格式。
    • WAV 文件的核心内容是 PCM 数据(Pulse Code Modulation,脉冲编码调制),这是一种未压缩的音频数据格式。
    • 除了 PCM 数据,WAV 文件还包含一个文件头(Header),用于描述音频数据的格式(如采样率、声道数、位深度等)。
  2. PCM 数据:

    • PCM 是一种原始的音频数据格式,不包含任何文件头信息。
    • 它只包含音频的采样值,无法单独描述音频的格式。
  3. 为什么可以保存为 .wav 文件:

    • 当你使用 AVAudioRecorder 录制音频时,即使你指定了 kAudioFormatLinearPCMAVAudioRecorder 会自动为录音文件添加 WAV 文件头,使其成为一个合法的 WAV 文件。
    • 这意味着,虽然音频数据本身是 PCM 格式,但由于文件头的存在,最终保存的文件是一个合法的 WAV 文件。

代码中的行为

在你的代码中,以下设置指定了音频格式为 PCM:

AVFormatIDKey: Int(kAudioFormatLinearPCM)

但录音文件的保存路径使用了 .wav 扩展名:

let audioFilename = documentsPath.appendingPathComponent("recording.wav")

AVAudioRecorder 会根据录音设置和文件扩展名自动处理文件格式。在这种情况下,它会将录制的 PCM 数据封装为一个合法的 WAV 文件,并保存到指定路径。

注意事项

  1. 文件扩展名的选择:

    • 虽然 .wav 是一个常见的扩展名,但它只是一个约定,真正决定文件格式的是文件内容。
    • 如果你将文件扩展名改为 .pcm,文件内容仍然是合法的 WAV 文件,只是扩展名可能会让人误解。
  2. 兼容性:

    • 如果你需要与其他程序或设备共享录音文件,确保它们支持 WAV 格式。
    • 如果你需要保存为纯 PCM 数据(没有文件头),你需要手动处理文件的写入。
  3. 自定义文件格式:

    • 如果你需要更灵活的文件格式(如 MP3、AAC 等),可以更改 AVFormatIDKey 的值,并选择合适的文件扩展名。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/69131.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024年12月 Scratch 图形化(二级)真题解析 中国电子学会全国青少年软件编程等级考试

202412 Scratch 图形化(二级)真题解析 中国电子学会全国青少年软件编程等级考试 一、单选题(共25题,共50分) 第 1 题 小猫初始位置和方向如下图所示,下面哪个选项能让小猫吃到老鼠?( ) A. B. …

Java 面试合集(2024版)

种自己的花,爱自己的宇宙 目录 第一章-Java基础篇 1、你是怎样理解OOP面向对象??? 难度系数:? 2、重载与重写区别??? 难度系数:? 3、接口与抽象类的区别??? 难度系数:? 4、深拷贝与浅拷贝的理解??? 难度系数&…

Math Reference Notes: 符号函数

1. 符号函数的定义 符号函数(Sign Function) sgn ( x ) \text{sgn}(x) sgn(x) 是一个将实数 ( x ) 映射为其 符号值(即正数、负数或零)的函数。 它的定义如下: sgn ( x ) { 1 如果 x > 0 0 如果 x 0 − 1 如…

一文了解边缘计算

什么是边缘计算? 我们可以通过一个最简单的例子来理解它,它就像一个司令员,身在离炮火最近的前线,汇集现场所有的实时信息,经过分析并做出决策,及时果断而不拖延。 1.什么是边缘计算? 边缘计算…

108,【8】 buuctf web [网鼎杯 2020 青龙组]AreUSerialz

进入靶场 <?php // 包含 flag.php 文件&#xff0c;通常这个文件可能包含敏感信息&#xff0c;如 flag include("flag.php");// 高亮显示当前文件的源代码&#xff0c;方便查看代码结构和逻辑 highlight_file(__FILE__);// 定义一个名为 FileHandler 的类&#x…

《redis哨兵机制》

【redis哨兵机制导读】上一节介绍了redis主从同步的机制&#xff0c;但大家有没有想过一种场景&#xff0c;比如&#xff1a;主库突然挂了&#xff0c;那么按照读写分离的设计思想&#xff0c;此时redis集群只有从库才能提供读服务&#xff0c;那么写服务该如何提供&#xff0c…

【赵渝强老师】Spark RDD的依赖关系和任务阶段

Spark RDD彼此之间会存在一定的依赖关系。依赖关系有两种不同的类型&#xff1a;窄依赖和宽依赖。 窄依赖&#xff1a;如果父RDD的每一个分区最多只被一个子RDD的分区使用&#xff0c;这样的依赖关系就是窄依赖&#xff1b;宽依赖&#xff1a;如果父RDD的每一个分区被多个子RD…

开源数据分析工具 RapidMiner

RapidMiner是一款功能强大且广泛应用的数据分析工具&#xff0c;其核心功能和特点使其成为数据科学家、商业分析师和预测建模人员的首选工具。以下是对RapidMiner的深度介绍&#xff1a; 1. 概述 RapidMiner是一款开源且全面的端到端数据科学平台&#xff0c;支持从数据准备、…

蓝桥杯备考:二维前缀和算法模板题(二维前缀和详解)

【模板】二维前缀和 这道题如果我们暴力求解的话&#xff0c;时间复杂度就是q次查询里套两层循环最差的时候要遍历整个矩阵也就是O&#xff08;q*n*m) 由题目就是10的11次方&#xff0c;超时 二维前缀和求和的公式&#xff08;创建需要用到&#xff09;f[i][j]就是从&#xf…

3-track_hacker/2018网鼎杯

3-track_hacker 打开附件 使用Wireshark打开。过滤器过滤http,看里面有没有flag.txt 发现有 得到&#xff1a;eJxLy0lMrw6NTzPMS4n3TVWsBQAz4wXi base64解密 import base64 import zlibc eJxLy0lMrw6NTzPMS4n3TVWsBQAz4wXi decoded base64.b64decode(c) result zlib.deco…

第二十章 存储函数

目录 一、概述 二、语法 三、示例 一、概述 前面章节中&#xff0c;我们详细讲解了MySQL中的存储过程&#xff0c;掌握了存储过程之后&#xff0c;学习存储函数则肥仓简单&#xff0c;存储函数其实是一种特殊的存储过程&#xff0c;也就是有返回值的存储过程。存储函数的参数…

Linux:文件系统(软硬链接)

目录 inode ext2文件系统 Block Group 超级块&#xff08;Super Block&#xff09; GDT&#xff08;Group Descriptor Table&#xff09; 块位图&#xff08;Block Bitmap&#xff09; inode位图&#xff08;Inode Bitmap&#xff09; i节点表&#xff08;inode Tabl…

java求职学习day27

数据库连接池 &DBUtils 1.数据库连接池 1.1 连接池介绍 1) 什么是连接池 实际开发中 “ 获得连接 ” 或 “ 释放资源 ” 是非常消耗系统资源的两个过程&#xff0c;为了解决此类性能问题&#xff0c;通常情况我们 采用连接池技术&#xff0c;来共享连接 Connection 。…

机器学习--2.多元线性回归

多元线性回归 1、基本概念 1.1、连续值 1.2、离散值 1.3、简单线性回归 1.4、最优解 1.5、多元线性回归 2、正规方程 2.1、最小二乘法 2.2、多元一次方程举例 2.3、矩阵转置公式与求导公式 2.4、推导正规方程0的解 2.5、凸函数判定 成年人最大的自律就是&#xff1a…

Docker 部署 ClickHouse 教程

Docker 部署 ClickHouse 教程 背景 ClickHouse 是一个开源的列式数据库管理系统&#xff08;DBMS&#xff09;&#xff0c;主要用于在线分析处理&#xff08;OLAP&#xff09;。它专为大数据的实时分析设计&#xff0c;支持高速的查询性能和高吞吐量。ClickHouse 以其高效的数…

建表注意事项(2):表约束,主键自增,序列[oracle]

没有明确写明数据库时,默认基于oracle 约束的分类 用于确保数据的完整性和一致性。约束可以分为 表级约束 和 列级约束&#xff0c;区别在于定义的位置和作用范围 复合主键约束: 主键约束中有2个或以上的字段 复合主键的列顺序会影响索引的使用&#xff0c;需谨慎设计 添加…

Google C++ Style / 谷歌C++开源风格

文章目录 前言1. 头文件1.1 自给自足的头文件1.2 #define 防护符1.3 导入你的依赖1.4 前向声明1.5 内联函数1.6 #include 的路径及顺序 2. 作用域2.1 命名空间2.2 内部链接2.3 非成员函数、静态成员函数和全局函数2.4 局部变量2.5 静态和全局变量2.6 thread_local 变量 3. 类3.…

【HTML入门】Sublime Text 4与 Phpstorm

文章目录 前言一、环境基础1.Sublime Text 42.Phpstorm(1)安装(2)启动Phpstorm(3)“启动”码 二、HTML1.HTML简介(1)什么是HTML(2)HTML版本及历史(3)HTML基本结构 2.HTML简单语法(1)HTML标签语法(2)HTML常用标签(3)表格(4)特殊字符 总结 前言 在当今的软件开发领域&#xff0c…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.20 傅里叶变换:从时域到频域的算法实现

2.20 傅里叶变换&#xff1a;从时域到频域的算法实现 目录 #mermaid-svg-zrRqIme9IEqP6JJE {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-zrRqIme9IEqP6JJE .error-icon{fill:#552222;}#mermaid-svg-zrRqIme9IEqP…

刷题记录 动态规划-7: 63. 不同路径 II

题目&#xff1a;63. 不同路径 II 难度&#xff1a;中等 给定一个 m x n 的整数数组 grid。一个机器人初始位于 左上角&#xff08;即 grid[0][0]&#xff09;。机器人尝试移动到 右下角&#xff08;即 grid[m - 1][n - 1]&#xff09;。机器人每次只能向下或者向右移动一步。…