使用AVAudioConverter转换音频出现杂音且播放缓慢问题求助
问题修复:AVAudioConverter音频转换后播放缓慢、有杂音
你的代码在使用AVAudioConverter进行采样率和格式转换时,存在两个核心错误,直接导致了播放缓慢、杂音问题:
- 输出缓冲区容量固定为1024帧,未考虑采样率转换后的帧数量变化,引发数据截断或重复
- 输入块逻辑错误,反复返回同一个输入缓冲区,未正确处理转换器的数据流需求
修复后的完整代码
import SwiftUI import AVFoundation struct ConvertAudio: View { @State var inputFormat = "\n\n\n" @State var outputFormat = "\n\n\n" @State var documentsDirectory : URL? var body: some View { ZStack { Color.black .ignoresSafeArea() VStack{ Spacer() Text(inputFormat) .foregroundStyle(.yellow) Spacer() Button { convert() } label: { Text("Convert 44 to 16") .font(.title) .foregroundStyle(.white) .frame(width: 300, height: 75) .background(.gray) .cornerRadius(10) } Spacer() Text(outputFormat) .foregroundStyle(.green) Spacer() } } .onAppear{ documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first } } func convert() { guard let inputUrl = Bundle.main.url(forResource: "44input", withExtension: "wav") else { return } Task { let outputSettings: [String: Any] = [ AVFormatIDKey: kAudioFormatLinearPCM, AVSampleRateKey: 16000.0, AVNumberOfChannelsKey: 1, AVLinearPCMBitDepthKey: 16, AVLinearPCMIsNonInterleaved: false, AVLinearPCMIsFloatKey: false, AVLinearPCMIsBigEndianKey: false ] // 读取输入格式信息 let asset = AVURLAsset(url: inputUrl) do { let tracks = try await asset.load(.tracks) if let track = tracks.first(where: { $0.mediaType == .audio }) { let formatDescriptions = try await track.load(.formatDescriptions) if let formatDescription = formatDescriptions.first, let asbdPointer = CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription) { let asbd = asbdPointer.pointee inputFormat = "Sample Rate: \(asbd.mSampleRate)\n" + "Channels: \(asbd.mChannelsPerFrame)\n" + "Format ID: \(asbd.mFormatID)\n" + "Bits per Channel: \(asbd.mBitsPerChannel)" } } } catch { print("Error loading asset format: \(error.localizedDescription)") return } do { let inputFile = try AVAudioFile(forReading: inputUrl) guard let documentsDirectory else { return } let outputURL = documentsDirectory.appendingPathComponent("16test.wav") // 删除已存在的输出文件,避免追加写入 if FileManager.default.fileExists(atPath: outputURL.path) { try FileManager.default.removeItem(at: outputURL) } let outputFile = try AVAudioFile(forWriting: outputURL, settings: outputSettings) guard let converter = AVAudioConverter(from: inputFile.processingFormat, to: outputFile.processingFormat) else { print("Failed to create converter") return } var isInputFinished = false let inputBlock: AVAudioConverterInputBlock = { _, outStatus in guard !isInputFinished else { outStatus.pointee = .endOfData return nil } let inputBuffer = AVAudioPCMBuffer(pcmFormat: inputFile.processingFormat, frameCapacity: 1024)! do { try inputFile.read(into: inputBuffer) if inputBuffer.frameLength == 0 { isInputFinished = true outStatus.pointee = .endOfData return nil } outStatus.pointee = .haveData return inputBuffer } catch { print("Error reading input audio: \(error.localizedDescription)") isInputFinished = true outStatus.pointee = .endOfData return nil } } // 循环处理转换,直到输入结束 while !isInputFinished { // 动态计算所需输出缓冲区容量 let inputFrameCount = 1024 let outputFrameCount = converter.requiredOutputFrameCount(forInputFrameCount: AVAudioFrameCount(inputFrameCount)) guard let outputBuffer = AVAudioPCMBuffer(pcmFormat: outputFile.processingFormat, frameCapacity: outputFrameCount) else { print("Failed to create output buffer") break } var error: NSError? let status = converter.convert(to: outputBuffer, error: &error, withInputFrom: inputBlock) switch status { case .haveData: try outputFile.write(from: outputBuffer) case .endOfData: isInputFinished = true case .error: if let error = error { print("Conversion error: \(error.localizedDescription)") } break default: break } } print("Conversion complete") // 读取输出格式信息 let outputAsset = AVURLAsset(url: outputURL) do { let tracks = try await outputAsset.load(.tracks) if let track = tracks.first(where: { $0.mediaType == .audio }) { let formatDescriptions = try await track.load(.formatDescriptions) if let formatDescription = formatDescriptions.first, let asbdPointer = CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription) { let asbd = asbdPointer.pointee outputFormat = "Sample Rate: \(asbd.mSampleRate)\n" + "Channels: \(asbd.mChannelsPerFrame)\n" + "Format ID: \(asbd.mFormatID)\n" + "Bits per Channel: \(asbd.mBitsPerChannel)" } } } catch { print("Error loading output asset format: \(error.localizedDescription)") } } catch { print("Audio conversion error: \(error.localizedDescription)") } } } }
关键改动说明
动态计算输出缓冲区容量:
使用converter.requiredOutputFrameCount(forInputFrameCount:)根据输入帧数量计算转换后的输出帧数量,确保缓冲区能完全容纳转换后的数据,避免截断或重复。修正输入块逻辑:
将输入块移到循环外,统一处理数据读取和状态标记,在输入结束时返回.endOfData状态,让转换器正确识别数据流终点。处理文件覆盖:
在创建输出文件前删除已存在的同名文件,避免追加写入导致的音频异常。完善转换状态处理:
对convert方法的返回状态进行判断,分别处理有数据、结束、错误三种情况,确保转换流程稳定。
内容的提问来源于stack exchange,提问作者Bartender1382
相关产品推荐
相关产品推荐

