如何使用连接到AVAudioEngine混音器的节点同时播放缓冲区中的多个声音



我正在为iOS制作一个基本的音乐应用,在其中按音符会导致相应的声音播放。我正在尝试获得多种声音中存储在缓冲区中,以同时播放最小的延迟。但是,我只能随时播放一个声音。

我最初使用多个avaudioplayer对象设置声音,为每个播放器分配声音。虽然它确实同时播放多种声音,但似乎并没有能够同时启动两种声音(似乎在第一个声音开始后会稍微延迟第二种声音(。此外,如果我以非常快速的速度按音符,则似乎无法保持发动机,然后在我按下后来的音符之后,声音就会很好。

我正在尝试解决这个问题,从我所做的研究中,似乎使用avaudioengine播放声音是最好的方法他们从这些缓冲区中播放。

class ViewController: UIViewController
{
    // Main Audio Engine and it's corresponding mixer
    var audioEngine: AVAudioEngine = AVAudioEngine()
    var mainMixer = AVAudioMixerNode()
    // One AVAudioPlayerNode per note
    var audioFilePlayer: [AVAudioPlayerNode] = Array(repeating: AVAudioPlayerNode(), count: 7)
    // Array of filepaths
    let noteFilePath: [String] = [
    Bundle.main.path(forResource: "note1", ofType: "wav")!, 
    Bundle.main.path(forResource: "note2", ofType: "wav")!, 
    Bundle.main.path(forResource: "note3", ofType: "wav")!]
    // Array to store the note URLs
    var noteFileURL = [URL]()
    // One audio file per note
    var noteAudioFile = [AVAudioFile]()
    // One audio buffer per note
    var noteAudioFileBuffer = [AVAudioPCMBuffer]()
    override func viewDidLoad()
    {
        super.viewDidLoad()
        do
        {
            // For each note, read the note URL into an AVAudioFile,
            // setup the AVAudioPCMBuffer using data read from the file,
            // and read the AVAudioFile into the corresponding buffer
            for i in 0...2
            {
                noteFileURL.append(URL(fileURLWithPath: noteFilePath[i]))
                // Read the corresponding url into the audio file
                try noteAudioFile.append(AVAudioFile(forReading: noteFileURL[i]))
                // Read data from the audio file, and store it in the correct buffer
                let noteAudioFormat = noteAudioFile[i].processingFormat
                let noteAudioFrameCount = UInt32(noteAudioFile[i].length)
                noteAudioFileBuffer.append(AVAudioPCMBuffer(pcmFormat: noteAudioFormat, frameCapacity: noteAudioFrameCount)!)
                // Read the audio file into the buffer
                try noteAudioFile[i].read(into: noteAudioFileBuffer[i])
            }
           mainMixer = audioEngine.mainMixerNode
            // For each note, attach the corresponding node to the audioEngine, and connect the node to the audioEngine's mixer.
            for i in 0...2
            {
                audioEngine.attach(audioFilePlayer[i])
                audioEngine.connect(audioFilePlayer[i], to: mainMixer, fromBus: 0, toBus: i, format: noteAudioFileBuffer[i].format)
            }
            // Start the audio engine
            try audioEngine.start()
            // Setup the audio session to play sound in the app, and activate the audio session
            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.soloAmbient)
            try AVAudioSession.sharedInstance().setMode(AVAudioSession.Mode.default)
            try AVAudioSession.sharedInstance().setActive(true)            
        }
        catch let error
        {
            print(error.localizedDescription)
        }
    }
    func playSound(senderTag: Int)
    {
        let sound: Int = senderTag - 1
        // Set up the corresponding audio player to play its sound.
        audioFilePlayer[sound].scheduleBuffer(noteAudioFileBuffer[sound], at: nil, options: .interrupts, completionHandler: nil)
        audioFilePlayer[sound].play()
    }

每个声音都应该在不中断其他声音的情况下播放,只有在再次播放声音时会打断自己的声音。但是,尽管设置了多个缓冲区和播放器,并在AudioEngine的搅拌机上分配了自己的公共汽车,但播放一种声音仍然可以阻止其他任何声音。

此外,何时抛弃。中断确实可以阻止声音停止其他声音,直到当前播放的声音完成后,这些声音才会播放。这意味着,如果我播放Note1,然后Note2,然后Note3,Note1将播放,而Note2只会在Note1完成后播放,而Note3仅在Note2完成后才播放。

编辑:我能够使AudioFilePlayer再次重置为开始,而无需在弹奏函数中使用以下代码。

if audioFilePlayer[sound].isPlaying == true
        {
            audioFilePlayer[sound].stop()
        }
        audioFilePlayer[sound].scheduleBuffer(noteAudioFileBuffer[sound], at: nil, completionHandler: nil)
        audioFilePlayer[sound].play()

这仍然让我弄清楚如何同时播放这些声音,因为播放另一种声音仍然会停止当前播放的声音。

编辑2:我找到了解决问题的解决方案。我的答案在下面。

,事实证明,有。中断选项不是问题(实际上,这实际上是重新启动在我的经验中播放的声音的最佳方法,因为在重新启动过程中没有明显的停顿,与stop((函数不同(。这是防止多种声音同时播放的实际问题是这条特定的代码。

// One AVAudioPlayerNode per note
    var audioFilePlayer: [AVAudioPlayerNode] = Array(repeating: AVAudioPlayerNode(), count: 7)

这里发生的事情是分配了数组的每个项目完全相同的Avaudioplayernode值,因此它们都有效地共享了相同的Avaudioplayernode。结果,avaudioplayernode函数正在影响数组中的所有项目,而不仅仅是指定的项目。为了解决此问题并给每个项目一个不同的Avaudioplayernode值,我最终更改了上面的行,以便它以一个空的Avaudioplayernode的空数开始。

// One AVAudioPlayerNode per note
    var audioFilePlayer = [AVAudioPlayerNode]()

i然后添加了一条新行以将其附加到此数组中,一个新的Avaudioplayernode在ViewDidload((函数的第二个循环的开头。

// For each note, attach the corresponding node to the audioEngine, and connect the node to the audioEngine's mixer.
for i in 0...6
{
    audioFilePlayer.append(AVAudioPlayerNode())
    // audioEngine code
}

这给了数组中的每个项目一个不同的avaudioplayernode值。播放声音或重新启动声音不再会打断当前正在播放的其他声音。我现在可以同时播放任何注释,并且在Note Press和Ploback之间没有任何明显的延迟。

最新更新