Swift中音频转AAC遇NSOSStatusErrorDomain -10865错误求助
错误原因排查与修复方案
错误核心原因
错误码 -10865 对应苹果音频框架的 kAudioFormatUnsupportedDataFormatError,本质是音频格式不兼容:你在使用 AVAudioEngine 时,混音节点的输出格式与要写入的AAC文件格式不匹配,导致系统无法设置音频格式抛出致命异常。
问题细节分析
- 格式不匹配:你安装混音节点Tap时使用了
audioEngine.mainMixerNode.outputFormat(forBus: 0),这个格式是系统默认的音频输出格式(通常是立体声、PCM浮点格式),但你要生成的AAC是单声道、MPEG4AAC编码格式,两者参数不兼容。 - 冗余的转换逻辑:用
AudioEngine播放+录屏的方式转换音频完全没必要,这种方式容易出现格式同步问题,代码复杂度高且不稳定。
修复方案
方案一:修复现有AudioEngine逻辑
调整Tap的格式为输出文件对应的PCM格式,确保整个音频链路格式一致:
func convertToAAC() { guard let audioFileURL = audioFileURL else { print("No audio file to convert") return } do { let file = try AVAudioFile(forReading: audioFileURL) let duration = Double(file.length) / file.fileFormat.sampleRate print("Audio file duration: \(duration) seconds") if duration == 0.0 { print("Audio file has zero duration, skipping conversion.") return } guard let buffer = AVAudioPCMBuffer(pcmFormat: file.processingFormat, frameCapacity: AVAudioFrameCount(file.length)), buffer.frameCapacity != 0 else { print("Failed to create audio buffer or buffer frame capacity is zero") return } try file.read(into: buffer) print("Audio buffer read successfully") let outputFileName = "converted.m4a" let outputFilePath = getDocumentsDirectory().appendingPathComponent(outputFileName) // 定义输出设置,同时生成对应的PCM格式供Tap使用 let outputSettings = [ AVFormatIDKey: Int(kAudioFormatMPEG4AAC), AVSampleRateKey: 44100, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] as [String : Any] guard let outputFormat = AVAudioFormat(settings: outputSettings) else { print("Failed to create output format") return } let outputFile = try AVAudioFile(forWriting: outputFilePath, settings: outputSettings, commonFormat: .pcmFormatInt16, interleaved: false) let audioEngine = AVAudioEngine() let playerNode = AVAudioPlayerNode() audioEngine.attach(playerNode) // 用buffer格式连接播放器与混音器 audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: buffer.format) audioEngine.prepare() try audioEngine.start() var isConversionComplete = false playerNode.scheduleBuffer(buffer, at: nil) { DispatchQueue.main.async { isConversionComplete = true print("Conversion to AAC completed: \(outputFilePath)") } } playerNode.play() // 使用输出文件对应的PCM格式安装Tap audioEngine.mainMixerNode.installTap(onBus: 0, bufferSize: 1024, format: outputFormat) { (buffer, time) in if !isConversionComplete { do { try outputFile.write(from: buffer) } catch { print("Error writing buffer to file: \(error.localizedDescription)") } } else { playerNode.stop() audioEngine.stop() audioEngine.mainMixerNode.removeTap(onBus: 0) } } } catch { print("Error converting audio file: \(error.localizedDescription)") } }
方案二:使用AVAssetExportSession(推荐)
直接用系统提供的导出会话转换格式,避免AudioEngine的格式兼容问题,代码简洁可靠:
func convertToAAC() { guard let audioFileURL = audioFileURL else { print("No audio file to convert") return } let outputFileName = "converted.m4a" let outputFilePath = getDocumentsDirectory().appendingPathComponent(outputFileName) // 清理已存在的输出文件 if FileManager.default.fileExists(atPath: outputFilePath.path) { try? FileManager.default.removeItem(at: outputFilePath) } let asset = AVURLAsset(url: audioFileURL) guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetAppleM4A) else { print("Failed to create export session") return } exportSession.outputURL = outputFilePath exportSession.outputFileType = .m4a exportSession.shouldOptimizeForNetworkUse = true exportSession.exportAsynchronously { DispatchQueue.main.async { switch exportSession.status { case .completed: print("Conversion to AAC completed: \(outputFilePath)") // 直接回放转换后的文件 self.playAudio(at: outputFilePath) case .failed: print("Export failed: \(exportSession.error?.localizedDescription ?? "Unknown error")") case .cancelled: print("Export cancelled") default: break } } } } // 新增简单的音频回放方法 func playAudio(at url: URL) { do { let audioPlayer = try AVAudioPlayer(contentsOf: url) audioPlayer.play() } catch { print("Failed to play audio: \(error.localizedDescription)") } }
额外注意事项
- 确保在
Info.plist中添加NSMicrophoneUsageDescription权限描述,否则音频录制会直接失败。 - 录制完成后,
audioRecorder = nil的操作是正确的,确保录音文件被完全释放,避免后续转换时文件被占用。
内容的提问来源于stack exchange,提问作者narner
相关产品推荐
相关产品推荐

