如何使用连接到AVAudioEngine混音器的节点同时播放缓冲区中的多种声音 [英] How to play multiple sounds from buffer simultaneously using nodes connected to AVAudioEngine's mixer

查看:139
本文介绍了如何使用连接到AVAudioEngine混音器的节点同时播放缓冲区中的多种声音的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在制作一个适用于iOS的基本音乐应用,其中按音符会播放相应的声音.我试图将存储在缓冲区中的多种声音以最小的延迟同时播放.但是,我只能在任何时候播放一种声音.

I am making a basic music app for iOS, where pressing notes causes the corresponding sound to play. I am trying to get multiple sounds stored in buffers to play simultaneously with minimal latency. However, I can only get one sound to play at any time.

最初,我使用多个AVAudioPlayer对象设置声音,然后将声音分配给每个播放器.虽然它确实可以同时播放多个声音,但似乎无法同时启动两个声音(似乎在第一个声音开始后稍稍延迟了第二个声音).此外,如果我以非常快的速度按下音符,似乎引擎无法跟上,并且在我按下较后的音符之后,以后的声音会很好地发出.

I initially set up my sounds using multiple AVAudioPlayer objects, assigning a sound to each player. While it did play multiple sounds simultaneously, it didn't seem like it was capable of starting two sounds at the same time (it seemed like it would delay the second sound just slightly after the first sound was started). Furthermore, if I pressed notes at a very fast rate, it seemed like the engine couldn't keep up, and later sounds would start well after I had pressed the later notes.

我正在尝试解决此问题,从我所做的研究来看,似乎最好使用AVAudioEngine播放声音,在该方法中,我可以在一系列缓冲区中设置声音,然后使用它们从这些缓冲区播放.

I am trying to solve this problem, and from the research I have done, it seems like using the AVAudioEngine to play sounds would be the best method, where I can set up the sounds in an array of buffers, and then have them play back from those buffers.

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的混音器上将每个缓冲区和播放器分配给了自己的总线,但是播放一种声音仍然会停止播放其他任何声音.

Each sound should be playing without interrupting the other sounds, only interrupting its own sound when the sounds is played again. However, despite setting up multiple buffers and players, and assigning each one to its own Bus on the audioEngine's mixer, playing one sound still stops any other sounds from playing.

此外,尽管省略.interrupts确实可以防止声音停止其他声音,但是这些声音直到当前播放的声音完成后才会播放.这意味着,如果我先播放note1,然后播放note2,然后播放note3,则将播放note1,而note2仅在note1完成后播放,note3仅在note2完成后播放.

Furthermore, while leaving out .interrupts does prevent sounds from stopping other sounds, these sounds won't play until the sound that is currently playing completes. This means that if I play note1, then note2, then note3, note1 will play, while note2 will only play after note1 finishes, and note3 will only play after note2 finishes.

我能够在不使用playSound函数中以下代码的中断的情况下,将audioFilePlayer再次重置为开头.

I was able to get the audioFilePlayer to reset to the beginning again without using interrupt with the following code in the playSound function.

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

这仍然让我想出如何同时播放这些声音,因为播放另一种声音仍会停止当前正在播放的声音.

This still leaves me with figuring out how to play these sounds simultaneously, since playing another sound will still stop the currently playing sound.

我找到了解决问题的方法.我的答案在下面.

推荐答案

事实证明,使用.interrupt选项并不是问题所在(实际上,实际上,这是重新启动声音的最佳方法.根据我的经验,因为在重启过程中没有明显的停顿,不像stop()函数).阻止多个声音同时播放的实际问题是此特定代码行.

It turns out that having the .interrupt option wasn't the issue (in fact, this actually turned out to be the best way to restart the sound that was playing in my experience, as there was no noticeable pause during the restart, unlike the stop() function). The actual problem that was preventing multiple sounds from playing simultaneously was this particular line of code.

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

这里发生的是,为数组的每个项目分配了完全相同的AVAudioPlayerNode值,因此它们都有效地共享了相同的AVAudioPlayerNode.结果,AVAudioPlayerNode函数正在影响数组中的所有项目,而不仅仅是指定的项目.为了解决此问题并为每个项目赋予不同的AVAudioPlayerNode值,我最终更改了上面的行,以使其开始时是类型为AVAudioPlayerNode的空数组.

What happened here was that each item of the array was being assigned the exact same AVAudioPlayerNode value, so they were all effectively sharing the same AVAudioPlayerNode. As a result, the AVAudioPlayerNode functions were affecting all of the items in the array, instead of just the specified item. To fix this and give each item a different AVAudioPlayerNode value, I ended up changing the above line so that it starts as an empty array of type AVAudioPlayerNode instead.

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

然后,我在viewDidLoad()函数的第二个for循环的开始处,在该数组中添加了新行,以添加新的AVAudioPlayerNode.

I then added a new line to append to this array a new AVAudioPlayerNode at the beginning inside of the second for-loop of the viewDidLoad() function.

// 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值.播放声音或重新开始声音不会再中断当前正在播放的其他声音.现在,我可以同时播放任何音符,并且在音符按下和播放之间没有任何明显的延迟.

This gave each item in the array a different AVAudioPlayerNode value. Playing a sound or restarting a sound no longer interrupts the other sounds that are currently being played. I can now play any of the notes simultaneously and without any noticeable latency between note press and playback.

这篇关于如何使用连接到AVAudioEngine混音器的节点同时播放缓冲区中的多种声音的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆