user9108262
user9108262

Reputation:

Does AudioKit's AKAudioFile always read the entire audio file at once?

My app is an audio player for macOS, written in Swift 3 using the AVAudioEngine framework.

Now, I'm trying to leverage the AudioKit framework instead of directly using AVAudioEngine, since it seems like a well-tested and trusted framework (AudioKit 3.7.1 (not 4.x) because I'm still coding in Swift 3)

In my current audio player implementation (without AudioKit), I read small chunks of audio data at a time (between 5 and 15 seconds), for the obvious reasons - immediate start of playback when playing or seeking within a track, and keeping the memory footprint reasonably small. And, I keep one extra "look ahead" buffer in memory at all times, to avoid gaps in playback.

So, if I switch to using AudioKit, I expect it to be able to do the same - ideally, I can specify a buffer size to use, and it will read chunks of audio data of that given size. However, it seems like it always reads the entire file at once. That is not feasible for an audio player application that needs to be responsive and keep a reasonable memory footprint.

Let's say I play a 3 hour audiobook MP3 ... it takes between 5 and 10 seconds to read the whole file into memory. So the user has to wait that long for the track to begin playing. And the memory usage goes into the GB range (i.e. the whole audio file is loaded into memory) ! This is not acceptable for my app (or any audio player app).

P.S. I have looked through the AudioKit source code, and could not find an alternative AKAudioFile initializer, a setting in AKAudioPlayer, or another audio file/player class that does what I want.

Have I missed something totally obvious ? I'm a total beginner with AudioKit. I was under the assumption that this is the most popular "high-level" MacOS framework for audio playback/synthesis. I can't conceive that its developers would have missed something this fundamental ? Or am I totally misguided in my expectation ?

Here's the essence of my player code:

do {
    let akFile = try AKAudioFile(forReading: track.file)
    akPlayer = try AKAudioPlayer(file: akFile)

    AudioKit.output = akPlayer
    AudioKit.start()
    akPlayer.start()

} catch {
    print("Problem")
}

Upvotes: 1

Views: 1385

Answers (3)

Mark
Mark

Reputation: 84

Audiokit is using AVAudioPlayerNode which can play a file directly from disk or a buffer. You can extend Audiokit with your own class. In this example I wanted to play a file from disk with seamless looping.

import Foundation
import AudioKit

open class Xplayer: AKNode {

    var url : URL? = nil

    let playerNode = AVAudioPlayerNode()
    private var mixer = AVAudioMixerNode()
    var inFile : AKAudioFile

    var isLooping : Bool = true


    public init(audioFile: AKAudioFile)
    {
        self.inFile = audioFile
                AudioKit.engine.attach(playerNode)
                AudioKit.engine.attach(mixer)
                AudioKit.engine.connect(playerNode, to: mixer)
                super.init(avAudioNode: mixer, attach: false)
    }


        func Prepare()
    {

        let frames = inFile.length

        var offsetFrame: Int64 = 0

        self.isLooping = true

        let sampleRate = playerNode.outputFormat(forBus: 0).sampleRate
        var segmentTime : AVAudioFramePosition = 0
        var segmentCompletion : AVAudioNodeCompletionHandler!
        segmentCompletion = {
            if (self.isLooping == true) {    // Need to set false before stopping sound else delays start
            segmentTime += frames - offsetFrame
            self.playerNode.scheduleFile(self.inFile, at: AVAudioTime(sampleTime: segmentTime, atRate: sampleRate), completionHandler: segmentCompletion)
            offsetFrame = 0
            }
        }


        offsetFrame = 0

        let frameCount = frames - offsetFrame

        playerNode.scheduleSegment(inFile,
                                   startingFrame: AVAudioFramePosition(offsetFrame),
                                   frameCount: AVAudioFrameCount(frameCount),
                                   at: nil,
            completionHandler: segmentCompletion)

    }

    func Play()
    {
    Prepare()
    playerNode.play()

    }

    func Stop()
    {
        isLooping = false
        playerNode.stop()
    }


    func Change_sound(audioFile: AKAudioFile)
    {
        Stop()
        self.inFile = audioFile
        Play()

    }

} 

Upvotes: 1

Ryan Francesconi
Ryan Francesconi

Reputation: 1006

The problem is that AKAudioPlayer (not AKAudioFile) plays audio from Ram only. The thing to do is as Aure says and use AKPlayer. This is currently only in the development branch but will stream from disk by default.

Upvotes: 2

Aurelius Prochazka
Aurelius Prochazka

Reputation: 4573

I'd recommend trying out the new AKPlayer stuff happening on the develop branch. AKAudioPlayer's days are numbered. You can send the AKPlayer a regular AVAudioFile and that should solve your issues.

Upvotes: 2

Related Questions