Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AVAudioEngine recording sounds played in AVAudioPlayerNode

I need to play sounds and be able to record the melody from the buffer. But I don't understand, how to set up AVAudioSession category and/or AVAudioPlayerNode to achieve my goal. Sounds are scheduled in a player node. If I understand correctly, AVAudioRecorder records only from microphone, not music, played with AVAudioPlayerNode. So, here's my attempt: First of all I setup a session:

        NSError *error = nil;
        AVAudioSession *audioSession = [AVAudioSession sharedInstance];
        [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord
                      withOptions:AVAudioSessionCategoryOptionMixWithOthers
                            error:&error];
        if (error) {
            NSLog(@"AVAudioSession error %ld, %@", error.code, error.localizedDescription);
        }

        [audioSession setActive:YES error:&error];
        if (error) {
            NSLog(@"AVAudioSession error %ld, %@", error.code, error.localizedDescription);
        }

Set up a file for record:

    NSString* docs = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"Recording.caf"];
    NSURL* url = [NSURL fileURLWithPath:docs];

    NSError* error = nil;
    self.fileForRecording = [[AVAudioFile alloc] initForWriting:url
                                                       settings:[self.engine.inputNode inputFormatForBus:0].settings
                                                          error:&error];
    if (error) {
        NSLog(@"CREATE FILE ERROR %@", error);
    }

Then, an engine:

        self.engine = [AVAudioEngine new];
        self.player = [AVAudioPlayerNode new];

        AVAudioOutputNode *output = self.engine.outputNode;

        [self.engine attachNode:self.player];

        [self.engine connect:self.player to:output fromBus: 0 toBus: 0 format: format];
        [self.engine prepare];

And method for recording:

    - (void)startRecording {
    AVAudioFormat* recordingFormat = [self.engine.outputNode outputFormatForBus:0];

    if (recordingFormat.sampleRate > 0) {
        typeof(self) weakSelf = self;
        [self.engine.inputNode installTapOnBus:0
                                    bufferSize:1024
                                        format:recordingFormat
                                         block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
                                             NSError* error;
                                             [weakSelf.fileForRecording writeFromBuffer:buffer error:&error];
                                             NSLog(@"WRITE ERROR %@", error);
                                         }];
    }
}

I have tried to use nil as recording format when installing tap on bus, in this case block never called. I tried to use [self.engine.mainMixerNode outputFormatForBus:0]; and this generates crashes. Using self.engine.outputNode instead produces crashes too. Please help :)

like image 458
Valentin Shamardin Avatar asked Feb 04 '26 16:02

Valentin Shamardin


1 Answers

I made an empty project on Swift. My engine graph looks like this. I have 2 sounds and 2 player nodes, one for each sound. These players are connected to the engine mainMixerNode. When I want to record music from both players, I get buffer from mainMixerNode output. And this works!

class ViewController: UIViewController {
    var engine = AVAudioEngine()
    var recordingFile: AVAudioFile?
    var audioPlayer: AVAudioPlayer?
    let playerSaw = AVAudioPlayerNode()
    let playerDk = AVAudioPlayerNode()
    var bufferSaw: AVAudioPCMBuffer?
    var bufferDk: AVAudioPCMBuffer?

    override func viewDidLoad() {
        super.viewDidLoad()

        let audioSession = AVAudioSession.sharedInstance()

        do {
            try audioSession.setCategory(
                AVAudioSessionCategoryPlayAndRecord)
        } catch let error as NSError {
            print("audioSession error: \(error.localizedDescription)")
        }

        self.bufferSaw = self.createBuffer(forFileNamed: "16_saw")
        self.bufferDk = self.createBuffer(forFileNamed: "23_dk")

        if self.bufferSaw != nil &&
            self.bufferDk != nil {
            self.engine.attach(self.playerSaw)
            self.engine.attach(self.playerDk)

            let mainMixerNode = self.engine.mainMixerNode
            self.engine.connect(self.playerSaw, to:mainMixerNode, format:self.bufferSaw!.format)
            self.engine.connect(self.playerDk, to:mainMixerNode, format:self.bufferDk!.format)
            self.engine.prepare()

            do {
                try self.engine.start()
            } catch (let error) {
                print("START FAILED", error)
            }
        }
    }

    @IBAction func record(sender: AnyObject) {
        self.createRecordingFile()

        self.engine.mainMixerNode.installTap(onBus: 0,
                                         bufferSize: 1024,
                                         format: self.engine.mainMixerNode.outputFormat(forBus: 0)) { (buffer, time) -> Void in
            do {
                try self.recordingFile?.write(from: buffer)
            } catch (let error) {
                print("RECORD ERROR", error);
            }
            return
        }
    }

    @IBAction func stop(sender: AnyObject) {
        self.engine.mainMixerNode.removeTap(onBus: 0)
    }

    fileprivate func startEngineIfNotRunning() {
        if (!self.engine.isRunning) {
            do {
                try self.engine.start()
            } catch (let error) {
                print("RESTART FAILED", error)
            }
        }
    }

    @IBAction func playSaw(sender: UIButton) {
        if let buffer = self.bufferSaw {
            self.startEngineIfNotRunning()

            sender.isSelected = !sender.isSelected
            if (sender.isSelected) {
                self.playerSaw.scheduleBuffer(buffer,
                                              at: nil,
                                              options: .loops,
                                              completionHandler: nil)
                self.playerSaw.play()
            } else {
                self.playerSaw.pause()
            }
        }
    }

    @IBAction func playDk(sender: UIButton) {
        if let buffer = self.bufferDk {
            self.startEngineIfNotRunning()

            sender.isSelected = !sender.isSelected
            if (sender.isSelected) {
                self.playerDk.scheduleBuffer(buffer,
                                             at: nil,
                                             options: .loops,
                                             completionHandler: nil)
                self.playerDk.play()
            } else {
                self.playerDk.pause()
            }
        }
    }

    @IBAction func playAudio(_ sender: AnyObject) {
        if let url = self.recordingFile?.url {
            do {
                self.audioPlayer = try AVAudioPlayer(contentsOf:
                    url)
                self.audioPlayer?.prepareToPlay()
                self.audioPlayer?.play()
            } catch let error as NSError {
                print("audioPlayer error: \(error.localizedDescription)")
            }
        }
    }

    fileprivate func createRecordingFile() {
        if let dir = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true).first {
            var url = URL(fileURLWithPath: dir)
            url.appendPathComponent("my_file.caf")
            let format = self.engine.outputNode.inputFormat(forBus: 0)

            do {
                self.recordingFile = try AVAudioFile(forWriting: url, settings:format.settings)
            } catch (let error) {
                print("CREATE RECORDING FILE ERROR", error);
            }
        }
    }

    fileprivate func createBuffer(forFileNamed fileName: String) -> AVAudioPCMBuffer? {
        var res: AVAudioPCMBuffer?

        if let fileURL = Bundle.main.url(forResource: fileName, withExtension: "caf") {
            do {
                let file = try AVAudioFile(forReading: fileURL)
                res = AVAudioPCMBuffer(pcmFormat: file.processingFormat, frameCapacity:AVAudioFrameCount(file.length))
                if let _ = res {
                    do {
                        try file.read(into: res!)
                    } catch (let error) {
                        print("ERROR read file", error)
                    }
                }
            } catch (let error) {
                print("ERROR file creation", error)
            }
        }
        return res
    }
}
like image 182
Valentin Shamardin Avatar answered Feb 06 '26 05:02

Valentin Shamardin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!