iOS Swift环境下使用sunlubo/SwiftFFmpeg库的方法及报错解决求助
SwiftFFmpeg iOS端接入适配方案(适配M1 MacBook + Xcode 13 + 1.0.5版本)
前置环境校验
- 确认Xcode 13的命令行工具已正确绑定:执行
xcode-select -p检查输出是否为/Applications/Xcode.app/Contents/Developer,若路径错误执行sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer修正 - 确认项目iOS部署目标≥11.0,该版本SwiftFFmpeg最低支持iOS 11.0
- M1设备必须开启Xcode Rosetta转译:右键Xcode->显示简介->勾选使用Rosetta打开,这是解决1.0.5版本CFFmpeg静态库架构兼容问题的核心步骤
SPM接入步骤(推荐,可解决原始SPM报错问题)
- 打开Xcode项目,进入Project配置页的Package Dependencies栏,点击+号添加依赖
- 版本选择栏精确指定版本为
1.0.5,不要选择更高版本避免兼容性问题 - 添加依赖时仅勾选
SwiftFFmpeg库绑定到主项目Target即可,无需勾选CFFmpeg - 进入Target->Build Settings,搜索
Architectures,确认Excluded Architectures中没有添加arm64配置 - 搜索
Search Paths,在Framework Search Paths中添加$(inherited),确保SPM引入的库路径被正确识别 - 清理Xcode缓存:按
Command+Shift+K清理项目,关闭Xcode后删除~/Library/Developer/Xcode/DerivedData下对应项目的缓存文件夹,重新打开Xcode编译即可
手动导入适配步骤(可解决No such module 'CFFmpeg'报错)
如果你坚持使用手动导入方式,按以下步骤调整配置即可解决模块找不到的问题:
- 拖拽SwiftFFmpeg整个源码文件夹到项目中,勾选Copy items if needed并绑定到主Target
- 进入Target->Build Settings,搜索
Swift Compiler - Search Paths,在Import Paths中添加CFFmpeg源码所在的文件夹路径,路径根据你实际存放的位置调整 - 搜索
Header Search Paths,添加FFmpeg头文件所在路径,权限设置为recursive - 进入Target->Build Phases->Compile Sources,确认所有SwiftFFmpeg的.swift文件都已添加到列表中,CFFmpeg的静态库文件已添加到Link Binary With Libraries列表
- 开启Xcode Rosetta转译,清理缓存后重新编译即可
MP4转MPEGTS示例代码
使用前需要先在需要调用的文件中引入库:import SwiftFFmpeg
转换功能实现代码如下:
func convertMP4ToMPEGTS(inputPath: String, outputPath: String) throws { // 注册所有可用格式与编解码器 AVRegister.all() // 打开输入MP4文件,读取流信息 let inputFormatCtx = try AVInputFormat.open(path: inputPath) try inputFormatCtx.findStreamInfo() // 初始化MPEGTS输出上下文 guard let outputFormat = AVOutputFormat(formatName: "mpegts") else { throw NSError(domain: "FFmpegConvert", code: -1, userInfo: [NSLocalizedDescriptionKey: "不支持mpegts格式"]) } let outputCtx = try AVFormatContext(format: outputFormat) // 音视频流映射 var streamMap: [Int: Int] = [:] var outputStreamIndex = 0 for inputStreamIndex in 0..<inputFormatCtx.streams.count { let inStream = inputFormatCtx.streams[inputStreamIndex] let codecType = inStream.codecpar.codecType guard codecType == .video || codecType == .audio else { continue } streamMap[inputStreamIndex] = outputStreamIndex outputStreamIndex += 1 // 配置输出流参数 let outStream = outputCtx.addStream() try outStream.codecpar.copy(from: inStream.codecpar) outStream.codecpar.codecTag = 0 } // 打开输出文件写入流 try outputCtx.openOutput(path: outputPath) try outputCtx.writeHeader() // 循环读写数据包(转封装无需重新编解码,速度极快) let packet = AVPacket() while try inputFormatCtx.readFrame(into: packet) { guard let mappedIndex = streamMap[Int(packet.streamIndex)] else { packet.unref() continue } packet.streamIndex = Int32(mappedIndex) // 时间基转换 let inTimebase = inputFormatCtx.streams[Int(packet.streamIndex)].timebase let outTimebase = outputCtx.streams[mappedIndex].timebase packet.rescaleTs(from: inTimebase, to: outTimebase) try outputCtx.writeFrame(packet: packet) packet.unref() } // 写入文件尾,关闭输出流 try outputCtx.writeTrailer() outputCtx.closeOutput() }
注意:转换操作属于IO密集型任务,建议放在子线程执行避免阻塞主线程,调用前请确认输入路径可读、输出路径有写入权限
内容的提问来源于stack exchange,提问作者Ankur Pachauri
相关产品推荐
相关产品推荐

