AudioKit 的 AKAudioFile 是否总是一次读取整个音频文件?

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

我的应用程序是 macOS 的音频播放器,使用 AVAudioEngine 框架在 Swift 3 中编写。

现在,我正在尝试利用 AudioKit 框架而不是直接使用 AVAudioEngine,因为它似乎是一个经过良好测试和信任的框架(AudioKit 3.7.1(不是 4.x),因为我仍在编码 Swift 3)

在我当前的音频播放器实现中(没有 AudioKit),我一次读取一小块音频数据(5 到 15 秒之间),原因很明显 - 在播放或在曲目内搜索时立即开始播放,并使内存占用量保持在合理的范围内。而且,我一直在内存中保留一个额外的 "look ahead" 缓冲区,以避免播放时出现间隙。

因此,如果我改用 AudioKit,我希望它也能做同样的事情 - 理想情况下,我可以指定要使用的缓冲区大小,它会读取给定大小的音频数据块。但是,它似乎总是一次读取整个文件。对于需要响应并保持合理内存占用的音频播放器应用程序来说,这是不可行的。

假设我播放了一个 3 小时的有声读物 MP3...将整个文件读入内存需要 5 到 10 秒。所以用户必须等待很长时间才能开始播放曲目。并且内存使用量进入 GB 范围(即整个音频文件被加载到内存中)!这对我的应用程序(或任何音频播放器应用程序)来说是不可接受的。

P.S。我查看了 AudioKit 源代码,但找不到替代的 AKAudioFile 初始值设定项、AKAudioPlayer 中的设置或另一个音频 file/player class 可以满足我的要求。

我是不是漏掉了一些显而易见的东西?我是 AudioKit 的新手。我假设这是最流行的 "high-level" MacOS 音频框架 playback/synthesis。我无法想象它的开发人员会错过这么基本的东西?还是我的期望完全被误导了?

这是我的播放器代码的本质:

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

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

} catch {
    print("Problem")
}

我建议尝试开发分支上出现的新 AKPlayer 内容。 AKAudioPlayer 的日子屈指可数了。您可以向 AKPlayer 发送一个常规的 AVAudioFile,这应该可以解决您的问题。

问题是 AKAudioPlayer(不是 AKAudioFile)只播放来自 Ram 的音频。正如 Aure 所说,要做的就是使用 AKPlayer。这目前仅在开发分支中,但默认情况下将从磁盘流式传输。

Audiokit 使用 AVAudioPlayerNode 可以直接从磁盘或缓冲区播放文件。您可以使用自己的 class 扩展 Audiokit。在这个例子中,我想用无缝循环播放磁盘中的文件。

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()

    }

}