Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Write array of floats to a wav audio file in swift

I have this flow now: i record audio with AudioEngine, send it to an audio processing library and get an audio buffer back, then i have a strong will to write it to a wav file but i'm totally confused how to do that in swift.

I've tried this snippet from another stackoverflow answer but it writes an empty and corrupted file.( load a pcm into a AVAudioPCMBuffer )

//get data from library

var len : CLong = 0
let res: UnsafePointer<Double> = getData(CLong(), &len )
let bufferPointer: UnsafeBufferPointer = UnsafeBufferPointer(start: res, count: len)

//tranform it to Data

let arrayDouble = Array(bufferPointer)
let arrayFloats = arrayDouble.map{Float($0)}
let data = try Data(buffer: bufferPointer)

//attempt to write in file
    do {
        let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 16000, channels: 2, interleaved: false)

        var buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(data.count))
        buffer.floatChannelData!.pointee.withMemoryRebound(to: UInt8.self, capacity: data.count) {
            let stream = OutputStream(toBuffer: $0, capacity: data.count)
            stream.open()
            _ = data.withUnsafeBytes {
                stream.write($0, maxLength: data.count)
            }
            stream.close()
        }
        //settings are from AudioEngine.inputNode!.inputFormat(forBus: 0).settings

        var audioFile = try AVAudioFile(forWriting: url, settings: settings)
        try audioFile.write(from: buffer)

    } catch let error as NSError {
        print("ERROR HERE", error.localizedDescription)
    }

So, i guess i do this transform of floatChannelData wrong or everything wrong. Any suggestions or pointers where to read about it would be great!

like image 582
zo_chu Avatar asked Feb 11 '17 17:02

zo_chu


2 Answers

With a great colleague help we've managed to get it to work. Apparently, AudioPCMBuffer after filling also needs to be notified about it's new size. Also i was using totally wrong formats.

Here is the code:

let SAMPLE_RATE =  Float64(16000.0)

let outputFormatSettings = [
    AVFormatIDKey:kAudioFormatLinearPCM,
    AVLinearPCMBitDepthKey:32,
    AVLinearPCMIsFloatKey: true,
    //  AVLinearPCMIsBigEndianKey: false,
    AVSampleRateKey: SAMPLE_RATE,
    AVNumberOfChannelsKey: 1
    ] as [String : Any]

let audioFile = try? AVAudioFile(forWriting: url, settings: outputFormatSettings, commonFormat: AVAudioCommonFormat.pcmFormatFloat32, interleaved: true)

let bufferFormat = AVAudioFormat(settings: outputFormatSettings)

let outputBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: AVAudioFrameCount(buff.count))

// i had my samples in doubles, so convert then write

for i in 0..<buff.count {
    outputBuffer.floatChannelData!.pointee[i] = Float( buff[i] )
}
outputBuffer.frameLength = AVAudioFrameCount( buff.count )

do{
    try audioFile?.write(from: outputBuffer)

} catch let error as NSError {
    print("error:", error.localizedDescription)
}
like image 122
zo_chu Avatar answered Oct 24 '22 02:10

zo_chu


Update for Swift 5

This is an update for writing array of floats to a wav audio file in swift 5. The function can be used as the following saveWav([channel1, channel2])

func saveWav(_ buf: [[Float]]) {
    if let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 44100, channels: 2, interleaved: false) {
        let pcmBuf = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(buf[0].count))
        memcpy(pcmBuf?.floatChannelData?[0], buf[0], 4 * buf[0].count)
        memcpy(pcmBuf?.floatChannelData?[1], buf[1], 4 * buf[1].count)
        pcmBuf?.frameLength = UInt32(buf[0].count)

        let fileManager = FileManager.default
        do {
            let documentDirectory = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)
            try FileManager.default.createDirectory(atPath: documentDirectory.path, withIntermediateDirectories: true, attributes: nil)
            let fileURL = documentDirectory.appendingPathComponent("out.wav")
            print(fileURL.path)
            let audioFile = try AVAudioFile(forWriting: fileURL, settings: format.settings)
            try audioFile.write(from: pcmBuf!)
        } catch {
            print(error)
        }
    }
}

To make sure the above function works properly, use the following function that converts an audio file to an array of floats, and save it back to an audio file with saveWav

do {
    guard let url = Bundle.main.url(forResource: "audio_example", withExtension: "wav") else { return }
    let file = try AVAudioFile(forReading: url)
    if let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: file.fileFormat.sampleRate, channels: file.fileFormat.channelCount, interleaved: false), let buf = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(file.length)) {

        try file.read(into: buf)
        guard let floatChannelData = buf.floatChannelData else { return }
        let frameLength = Int(buf.frameLength)
        // we convert audio using audio pcm buffer to arrays of floats with two channels
        let channel1 = Array(UnsafeBufferPointer(start:floatChannelData[0], count:frameLength))
        let channel2 = Array(UnsafeBufferPointer(start:floatChannelData[1], count:frameLength))
        // we save the audio back using saveWave function
        saveWav([channel1,channel2])
    }
} catch {
    print("Audio Error: \(error)")
}
like image 38
Ibrahim Avatar answered Oct 24 '22 03:10

Ibrahim