iOS Swift中简单的低延迟音频播放

时间:2016-01-08 15:01:15

标签: ios swift audio avaudioplayer low-latency

我是iOS的初学者,我正在尝试使用Swift设计一个鼓组应用程序。我用一个按钮设计了一个视图并编写了下面的代码,但它有一些问题:

  1. 当我像滚筒一样快速触摸按钮时,有些声音会丢失。
  2. 仍处于'鼓声'中,每次触摸按钮时声音都会中断,而不是让样本播放直到结束。例如,钹卷很糟糕。即使我再次触摸按钮,我也希望听到每个样品完全响起。
  3. 触摸和声音之间存在延迟。我知道AVAudioPlayer不是低延迟音频的最佳选择,但作为初学者,如果没有OpenAL中的代码示例或教程,很难学习AudioUnitSwift 。问题与此类似:Which framework should I use to play an audio file (WAV, MP3, AIFF) in iOS with low latency?
  4. 代码:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        // Enable multiple touch for the button
        for v in view.subviews {
            if v.isKindOfClass(UIButton) {
                v.multipleTouchEnabled = true
            }
        }
    
        // Init audio
        audioURL = NSBundle.mainBundle().URLForResource("snareDrum", withExtension: "wav")!
        do {
            player = try AVAudioPlayer(contentsOfURL: audioURL)
            player?.prepareToPlay()
        } catch {
            print("AVAudioPlayer Error")
        }
    }
    
    override func viewDidDisappear(animated: Bool) {
        super.viewDidDisappear(animated)
    
        player?.stop()
        player = nil
    }
    
    @IBAction func playSound(sender: UIButton) {
        player?.currentTime = 0
        player?.play()
    }
    

2 个答案:

答案 0 :(得分:5)

如果您需要极低的延迟,我发现AVAudioSession单例上有一个非常简单的解决方案(在应用启动时会自动实例化):

首先,使用此类方法获取对应用程序的AVAudioSession单例的引用:

(来自AVAudioSession Class Reference):

  

获取共享音频会话

     

宣言SWIFT

     

class func sharedInstance() -> AVAudioSession

然后,尝试将首选IO缓冲区持续时间设置为非常高  使用此实例方法缩短(例如.002):

  

设置首选的音频I / O缓冲持续时间,以秒为单位。

     

宣言SWIFT

     

func setPreferredIOBufferDuration(_ duration: NSTimeInterval) throws

     

参数

     

duration您想要的音频I / O缓冲持续时间(以秒为单位)   使用。

     

outError输入时,指向错误对象的指针。如果有错误   发生时,指针被设置为描述的NSError对象   错误。如果您不想要错误信息,请传入零。回报价值   如果请求成功,则为true,否则为false。

     

讨论

     

此方法请求更改I / O缓冲区持续时间。确定   更改是否生效,请使用IOBufferDuration属性。   有关详细信息,请参阅Configuring the Audio Session

请记住上面的注释 - IOBufferDuration属性实际是否设置为传递给func setPrefferedIOBufferDuration(_ duration: NSTimeInterval) throws方法的值,取决于函数未返回错误, 其他我不太清楚的因素。另外 - 在我的测试中 - 我发现如果你将这个值设置为一个非常低的值,那么值(或接近它的值)确实会被设置,但是当播放文件时(例如使用AVAudioPlayerNode)声音不会玩了。没有错误,只是没有声音。这显然是个问题。我还没有发现如何测试这个问题,除非注意到在实际设备上测试时听不到声音。我会调查一下。但就目前而言,我建议将首选持续时间设置为不小于.002或.0015。 .0015的值似乎适用于我正在测试的iPad Air(型号A1474)。虽然低至.0012似乎在我的iPhone 6S上运行良好。

从CPU开销角度考虑的另一件事是音频文件的格式。未压缩的格式在播放时将具有非常低的CPU开销。 Apple建议您使用CAF文件以获得最高质量和最低开销。对于压缩文件和最低开销,您应该使用IMA4压缩:

(来自iOS Multimedia Programming Guide):

  

iOS中的首选音频格式适用于未压缩(最高质量)   音频,使用16位,小端,线性PCM音频数据打包在一个   CAF文件。您可以在Mac OS X中将音频文件转换为此格式   使用afconvert命令行工具,如下所示:

/usr/bin/afconvert -f caff -d LEI16 {INPUT} {OUTPUT}

  

当您需要播放多个声音时,可以减少内存使用量   同时,使用IMA4(IMA / ADPCM)压缩。这会减少文件   大小但在解压缩期间需要最小的CPU影响。和。一样   线性PCM数据,包装CAF文件中的IMA4数据。

您也可以使用afconvert转换为IMA4:

/usr/bin/afconvert -f AIFC -d ima4 [file]

答案 1 :(得分:0)

我花了一个下午试图通过玩AVAudioPlayer&来解决这个问题。 AVAudioSession,但我无法随心所欲。 (不幸的是,这里设置IO缓冲持续时间似乎没有帮助。)我也尝试过AudioToolbox,但我发现结果表现几乎相同 - 相关用户操作之间明显可察觉的延迟和音频。

在搜索了一下互联网之后,我发现了这个:

www.rockhoppertech.com/blog/swift-avfoundation /

关于AVAudioEngine的部分结果非常有用。下面的代码是轻微的重写:

import UIKit
import AVFoundation

class ViewController: UIViewController {

var engine = AVAudioEngine()
var playerNode = AVAudioPlayerNode()
var mixerNode: AVAudioMixerNode?
var audioFile: AVAudioFile?

@IBOutlet var button: UIButton!

override func viewDidLoad() {
    super.viewDidLoad()

    engine.attach(playerNode)
    mixerNode = engine.mainMixerNode

    engine.connect(playerNode, to: mixerNode!, format: mixerNode!.outputFormat(forBus: 0))

    do {
        try engine.start()
    }

    catch let error {
        print("Error starting engine: \(error.localizedDescription)")
    }

    let url = Bundle.main.url(forResource: "click_04", withExtension: ".wav")

    do {
        try audioFile = AVAudioFile(forReading: url!)
    }

    catch let error {
        print("Error opening audio file: \(error.localizedDescription)")
    }
}

@IBAction func playSound(_ sender: Any) {

    engine.connect(playerNode, to: engine.mainMixerNode, format: audioFile?.processingFormat)
    playerNode.scheduleFile(audioFile!, at: nil, completionHandler: nil)

    if engine.isRunning{
        playerNode.play()
    } else {
        print ("engine not running")
    }
}

}

这可能不完美,因为我是一个Swift新手并且之前没有使用过AVAudioEngine。但它似乎确实有效!