使用 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、深拷贝与浅拷贝的理解??? 难度系数&…

js的 encodeURI() encodeURIComponent() decodeURI() decodeURIComponent() 笔记250205

js的 encodeURI() encodeURIComponent() decodeURI() decodeURIComponent() 在JavaScript中,处理URI编码和解码的四个关键函数为:encodeURI()、encodeURIComponent()、decodeURI()和decodeURIComponent()。它们分别用于不同的场景,具体区别和…

Math Reference Notes: 符号函数

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

一文了解边缘计算

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

C语言基础系列【2】开发环境搭建

选择合适的编译器 在C语言或者C这种编译型语言开发中,编译器是必不可少的工具。它将C语言源代码转换为机器代码,使程序能够在计算机上运行。 常见的C语言编译器包括GCC(GNU Compiler Collection,GNU编译器套件)、Cla…

在 Spring Boot 项目中,bootstrap.yml 和 application.yml文件区别

在 Spring Boot 项目中,bootstrap.yml 和 application.yml 是两个常用的配置文件,它们的作用和加载顺序有所不同。以下是它们的详细说明: 1. bootstrap.yml 作用: bootstrap.yml 是 Spring Cloud 项目中的配置文件,用于…

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…

今日AI和商界事件(2025-02-05)

今日AI领域的相关事件主要包括以下几个方面&#xff1a; 一、DeepSeek引发全球关注 性能与成本优势&#xff1a; DeepSeek推出的R1模型性能出色&#xff0c;成本较低&#xff0c;在全球AI行业引发震动。该模型在数学、代码处理等方面性能优异&#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…

llama.cpp LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK2

llama.cpp LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK2 1. LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK22. LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK23. struct ggml_cgraph * build_deepseek() and struct ggml_cgraph * build_deepseek2()References 不宜吹捧中国大语言模型的同…

vite---依赖优化选项esbuildOptions详解

optimizeDeps.esbuildOptions vite.optimizeDeps.esbuildOptions 是 Vite 配置中的一个选项&#xff0c;它允许你在 Vite 启动时&#xff0c;给 esbuild&#xff08;Vite 用来处理代码转换和优化的工具&#xff09;传递额外的配置。通过这个配置项&#xff0c;你可以自定义 esb…

DNVS许可审计

在全球化商业环境中&#xff0c;合规性是企业稳定发展的关键因素。为了验证和确保企业遵循DNVS许可的合规标准&#xff0c;DNVS许可审计应运而生。本文将详细介绍DNVS许可审计的重要性、流程及其对企业的价值&#xff0c;帮助您更好地了解如何通过这一审计过程&#xff0c;引领…

因果推断与机器学习—可解释性、公平性和因果机器学习

随着人工智能技术的飞速发展,如人脸识别、自动驾驶、智能音箱和手术机器人等在社会各领域广泛应用,人工智能已成为科技革命和产业变革的核心驱动力。然而,在带来便利的同时,也引发了一系列问题: 数据统计:2016 年,基于美国食品和药物管理局(U.S. Food and Drug Adminis…

【Linux】curl命令详解

【Linux】curl命令详解 【一】curl命令介绍【1】curl命令简介【2】curl命令的基本语法【3】常用的curl命令选项【4】常用的curl命令参数 【二】curl命令示例用法【1】下载文件【2】发送 POST 请求【3】发送请求时附加头部信息【4】请求方法【5】指定用户名和密码进行身份验证【…

第二十章 存储函数

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