如何使用AVAssetWriter导出MP3文件?解决无效文件类型UTI错误
解决macOS 10.15下AVAssetWriter无法导出MP3的问题
我完全懂你的惊讶——在macOS 10.15上用AVAssetWriter导出MP3居然报错,系统给出的支持格式列表里确实没有MP3的身影,这确实挺让人意外的。别着急,咱们来一步步解决这个问题:
问题根源
你遇到的错误已经说得很明确:AVAssetWriter本身就不支持AVFileType.mp3这个UTI。Apple在AVFoundation的这个组件中没有集成MP3编码器,所以直接用它创建MP3输出实例肯定会失败。
可行的解决方案
方案一:用AVAssetExportSession(最简单的原生方法)
虽然AVAssetWriter不行,但AVAssetExportSession在macOS 10.15上是支持MP3导出的,这是最省事的原生方案。示例代码如下:
import AVFoundation func exportToMP3(from inputURL: URL, to outputURL: URL) { let asset = AVAsset(url: inputURL) // 创建导出会话,预设可根据需求选择,比如用AVAssetExportPresetPassthrough如果源是MP3,或者用通用预设 guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetAppleM4A) else { print("无法创建导出会话") return } exportSession.outputURL = outputURL exportSession.outputFileType = .mp3 // 开始异步导出 exportSession.exportAsynchronously { DispatchQueue.main.async { switch exportSession.status { case .completed: print("MP3导出成功,路径:\(outputURL.path)") case .failed: print("导出失败:\(exportSession.error?.localizedDescription ?? "未知错误")") case .cancelled: print("导出被取消") default: break } } } }
注意:如果你的源音频是其他格式,确保AVAssetExportSession能正确编码成MP3,部分情况下可能需要先检查音频轨道的兼容性。
方案二:用AudioToolbox手动编码(自定义控制)
如果需要更精细的编码控制(比如调整比特率、采样率),可以用AudioToolbox框架的AudioConverter来手动将PCM音频数据编码成MP3。这个方法相对复杂,但灵活性更高:
import AudioToolbox func encodePCMToMP3(pcmData: Data, inputASBD: AudioStreamBasicDescription, outputURL: URL) { // 定义MP3输出格式 var outputASBD = AudioStreamBasicDescription() outputASBD.mFormatID = kAudioFormatMPEGLayer3 outputASBD.mSampleRate = inputASBD.mSampleRate outputASBD.mChannelsPerFrame = inputASBD.mChannelsPerFrame outputASBD.mBitsPerChannel = 0 // MP3不需要设置这个 outputASBD.mBytesPerPacket = 0 outputASBD.mFramesPerPacket = 1152 // MP3标准帧大小 outputASBD.mBytesPerFrame = 0 outputASBD.mFormatFlags = 0 // 创建音频转换器 var converter: AudioConverterRef? let converterStatus = AudioConverterNew(&inputASBD, &outputASBD, &converter) guard converterStatus == noErr, let audioConverter = converter else { print("创建音频转换器失败") return } // 创建输出MP3文件 let fileManager = FileManager.default if fileManager.fileExists(atPath: outputURL.path) { try? fileManager.removeItem(at: outputURL) } var outputFile: AudioFileID? let fileStatus = AudioFileCreateWithURL(outputURL as CFURL, kAudioFileMP3Type, &outputASBD, .eraseFile, &outputFile) guard fileStatus == noErr, let mp3File = outputFile else { print("创建MP3文件失败") return } // 准备PCM数据缓冲 var pcmBuffer = pcmData var packetIndex = 0 let packetsPerBuffer = 1024 // 定义输入数据回调 let inputProc: AudioConverterComplexInputDataProc = { _, ioNumberDataPackets, ioData, _, userData in guard let data = userData?.assumingMemoryBound(to: Data.self).pointee else { return -1 } let bytesPerPacket = Int(inputASBD.mBytesPerPacket) let totalPackets = data.count / bytesPerPacket let packetsToProvide = min(Int(ioNumberDataPackets.pointee), totalPackets - packetIndex) if packetsToProvide > 0 { ioData?.pointee.mNumberBuffers = 1 ioData?.pointee.mBuffers.mData = UnsafeMutableRawPointer(mutating: data.advanced(by: packetIndex * bytesPerPacket).baseAddress) ioData?.pointee.mBuffers.mDataByteSize = UInt32(packetsToProvide * bytesPerPacket) ioData?.pointee.mBuffers.mNumberChannels = inputASBD.mChannelsPerFrame ioNumberDataPackets.pointee = UInt32(packetsToProvide) packetIndex += packetsToProvide } else { ioNumberDataPackets.pointee = 0 } return noErr } // 开始转换并写入文件 var outputBuffer = AudioBufferList() var packetsWritten = 0 while true { var packets = packetsPerBuffer let status = AudioConverterFillComplexBuffer(audioConverter, inputProc, &pcmBuffer, &packets, &outputBuffer, nil) guard status == noErr, packets > 0 else { break } let writeStatus = AudioFileWritePackets(mp3File, false, outputBuffer.mBuffers.mDataByteSize, nil, UInt64(packetsWritten), &packets, outputBuffer.mBuffers.mData) if writeStatus != noErr { print("写入MP3文件失败") break } packetsWritten += packets } // 清理资源 AudioFileClose(mp3File) AudioConverterDispose(audioConverter) print("MP3编码完成") }
这个方法需要你先获取到源音频的PCM数据和对应的AudioStreamBasicDescription,适合需要自定义编码参数的场景。
方案三:集成第三方LAME库(更高灵活性)
如果原生方法满足不了你的需求,还可以集成LAME这个成熟的MP3编码库。你可以通过CocoaPods引入(比如pod 'lame'),或者手动编译集成,然后调用它的API将PCM数据编码成MP3。LAME支持更多自定义参数,比如VBR(可变比特率)编码等。
内容的提问来源于stack exchange,提问作者msmialko
相关产品推荐
相关产品推荐

