I am trying to convert a file to a different format using AVAudioConverter.
Using ChatGPT to guide me, as I haven't worked with audio before, I came up with the code below. It's the closest I have gotten, and while it creates the file with my voice , the file is slower and has noise.
Can't seem to get any further to nail down where I am going wrong, or what is missing.
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")
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 readingError: Error? = nil // To track potential read errors
while readingError == nil { // Loop as long as no read error occurred
let inputBuffer = AVAudioPCMBuffer(pcmFormat: inputFile.processingFormat, frameCapacity: 1024)!
do {
try inputFile.read(into: inputBuffer)
if inputBuffer.frameLength == 0 {
break // EOF reached successfully
}
let outputBuffer = AVAudioPCMBuffer(pcmFormat: outputFile.processingFormat, frameCapacity: 1024)!
let inputBlock: AVAudioConverterInputBlock = { _, outStatus in
outStatus.pointee = .haveData
return inputBuffer
}
try converter.convert(to: outputBuffer, error: nil, withInputFrom: inputBlock)
try outputFile.write(from: outputBuffer)
} catch {
print("Error during audio reading: \(error.localizedDescription)")
readingError = error // Set the error to exit the loop
}
}
print("Conversion complete")
// Get and display the output file's format
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)")
}
}
}
}
Your AVAudioConverterInputBlock
is returning the same buffer over and over which creates a funky, but undesirable effect. You need to return each buffer only once, like this:
let outputBuffer = AVAudioPCMBuffer(pcmFormat: outputFile.processingFormat, frameCapacity: 1024)!
var haveData = true
let inputBlock: AVAudioConverterInputBlock = { _, outStatus in
if haveData {
outStatus.pointee = .haveData
haveData = false
return inputBuffer
} else {
outStatus.pointee = .noDataNow
return nil
}
}