Search code examples
swiftcore-audioavaudioengineavaudiofile

AVAudioFile.length is 0 even if file exists


I'm creating an AVAudioFile for writing sound to a sound file. If the file already exists I want to move the framePosition to the end of the file, to continue writing at the end, instead of replacing the existing file.

I did some tests, trying to read the buffer from the file into a new file, with a different URL, so that it won't overwrite the original file. I'm getting a crash when I'm trying to read the buffer into the new file:

let audioFile = try AVAudioFile(forReading: [URL to existing .caf file])
let audioFrameCount = AVAudioFrameCount(UInt32(audioFile.length))
let audioBuffer = AVAudioPCMBuffer(PCMFormat: audioFile.processingFormat, frameCapacity: audioFrameCount)

let newAudioFile = try AVAudioFile(forWriting: [another URL], settings: self.engine.mainMixerNode.outputFormatForBus(0).settings)
try newAudioFile.readIntoBuffer(audioBuffer, frameCount: audioFrameCount!) <-- CRASHES ON THIS LINE

Crash log: Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'error -50'

Man, I really hate the CoreAudio crash logs. They tell me absolutely nothing!

Isn't it possible to read data into a file that's been created for writing?

UPDATE

OK, so after some suggestions I did some changes. Basically, these are the steps I'm taking:

  1. Check if a file already exists.
  2. If it does, open it for reading and get the audio buffer.
  3. Create a new file for writing (using the same file URL)
  4. Use writeFromBuffer to write the buffer from the old file to the new file
  5. Move the framePosition of the new file to the end, so that I can continue writing/recording to it.

However, the length of the new file is 0 after I have written to it.

Here's my code:

//Check if a file already exists. If so continue to record at the end of it
var audioBuffer : AVAudioPCMBuffer!
var audioFrameCount : AVAudioFrameCount!
if (NSFileManager.defaultManager().fileExistsAtPath(self.audioRecordURL.path!)) {
    do {
        let existingAudioFile = try AVAudioFile(forReading: self.audioRecordURL)
        audioFrameCount = AVAudioFrameCount(existingAudioFile.length)
        if (audioFrameCount > 0) {
            audioBuffer = AVAudioPCMBuffer(PCMFormat: existingAudioFile.processingFormat, frameCapacity: audioFrameCount)
        }
    } catch let error as NSError {
        NSLog("Error reading buffer from file %@", error.localizedDescription)
    }
}

//Create a new file. This will replace the old file
do {
    self.audioFile = try AVAudioFile(forWriting: self.audioRecordURL, settings: self.engine.mainMixerNode.outputFormatForBus(0).settings)
} catch let error as NSError {
    NSLog("Error creating AVAudioFile %@", error.localizedDescription)
}

//Read the audio buffer from the old file into the new file
if (audioBuffer != nil) {
    do {
        try self.audioFile.writeFromBuffer(audioBuffer)
        self.audioFile.framePosition = self.audioFile.length
    } catch let error as NSError {
        NSLog("Error reading buffer into file %@", error.localizedDescription)
    }
}

By the way, the naming of the readIntoBuffer is extremely confusing to me. It sounds as if you should use that method to read a file into a buffer, but according to the documentation you should use it to read a buffer into a file? So why can't I use that method to add the buffer to my file? Why do I have to use writeFromBuffer?

UPDATE 2

So, I managed to solve it. Apparently I had to call readIntoBuffer to actually fill the buffer with data before I could use it. So I added this line

try existingAudioFile.readIntoBuffer(audioBuffer)

after

audioBuffer = AVAudioPCMBuffer(PCMFormat: existingAudioFile.processingFormat, frameCapacity: audioFrameCount)

Solution

  • Not sure if this is just a typo in the code you've provided here, and I'm not an expert in this area, but presumably you meant to have the line that crashes be:

    try audioFile.readIntoBuffer(audioBuffer, frameCount: audioFrameCount!)
    

    because it stands to reason that you can't read from a file opened for writing (newAudioFile).

    And then after filling that buffer you would want to write to the new file using writeFromBuffer https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVAudioFile_Class/#//apple_ref/occ/instm/AVAudioFile/writeFromBuffer:error: