0

I am trying to save the buffer from my installTap to a file. I do it in chunks of 10 so I'll get a bigger file. When I try to play the written file (from the simulator's directory) QuickTime says that it's not compatible.

I have examined the bad m4a file and a working one. There are a lot of zero's in the bad file at the beginning followed by a lot of data. However both files appears to have the same header.

A lot of people mention that I have to nil the AudioFile, but:

audioFile = nil

is not a valid syntax, nor can I file a close method in AudioFile.

Here's the complete code, edited into one working file:

import UIKit
import AVFoundation

class ViewController: UIViewController {
    let audioEngine = AVAudioEngine()
    var audioFile = AVAudioFile()
    var x = 0


    override func viewDidLoad() {
        super.viewDidLoad()
        record()
        // Do any additional setup after loading the view.
    }

    func makeFile(format: AVAudioFormat) {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
        do {
            _ = try FileManager.default.contentsOfDirectory(at: paths!, includingPropertiesForKeys: nil)
        } catch { print ("error")}
        let destinationPath = paths!.appendingPathComponent("audioT.m4a")
        print ("\(destinationPath)")
        do {
            audioFile = try AVAudioFile(forWriting: destinationPath,
                                            settings: format.settings)

            print ("file created")
        } catch { print ("error creating file")}
    }
    
    func record(){
        let node = audioEngine.inputNode
        let recordingFormat = node.inputFormat(forBus: 0)
        makeFile(format: recordingFormat)
        
        
        node.installTap(onBus: 0, bufferSize: 8192, format: recordingFormat, block: { [self]
            
            (buffer, _) in
            do {
                try audioFile.write(from: buffer);
            print ("buffer filled");
                x += 1;
                print("wrote \(x)")
                if x > 9 {
                    endThis()
                }
            } catch {return};})
        
        audioEngine.prepare()
        do {
            try audioEngine.start()
        } catch let error {
            print ("oh catch")
        }
    }
    
    func endThis(){
        audioEngine.stop()
        audioEngine.inputNode.removeTap(onBus: 0)
    }
}

1 Answers1

0

You need to let your AVAudioFile go out of scope (nil it at some point), that's how you call AVAudioFile's close() method, which presumably finishes writing out header information.

Rhythmic Fistman
  • 34,352
  • 5
  • 87
  • 159
  • if this answer is correct then this is a duplicate of https://stackoverflow.com/q/42660090/22147 but this question has a nice hexdump of good and bad data and also references classic southern hemisphere rock, so I'd leave it open – Rhythmic Fistman Jan 07 '22 at 13:47
  • I reedited the OP so it's all in one working file. But I can't seem to find a way to nil the audioFile pointer, nor close it. – oldprogrammer84 Jan 09 '22 at 20:28
  • You need to make your `AVAudioFile` optional - then you can nil it. – Rhythmic Fistman Jan 09 '22 at 21:35
  • You're answer is correct. Before I answer it correct I wanted to make sure you see this. Does it matter "when" I nil it? Should I stop the engine first? Works either way, one must be more right? Such a kludgy way (would a close method be so awful?) – oldprogrammer84 Jan 10 '22 at 05:49
  • There's thread safety to consider with the likely async tap callback, but you can probably get away with making using a `weak` reference to the audio file in your tap callback and nilling whenever you like, e.g. in `endThis()` – Rhythmic Fistman Jan 10 '22 at 11:01