代码之家  ›  专栏  ›  技术社区  ›  msampaio

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

  •  5
  • msampaio  · 技术社区  · 9 年前

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

    1. 当我像鼓点一样快速地按下按钮时,一些声音会消失。
    2. 还是在“鼓点”中,每次按下按钮时声音都会中断,而不是让样本一直播放到结束。例如,在钹演奏中就很糟糕。我希望听到每一个样本都能完整地发出声音,即使我再次按下按钮。
    3. 触摸和声音之间存在延迟。我知道 AVAudioPlayer 不是低延迟音频的最佳选择,但作为初学者,很难学习 OpenAL , AudioUnit 没有代码示例或教程 Swift 问题与此类似: Which framework should I use to play an audio file (WAV, MP3, AIFF) in iOS with low latency? .

    代码:

    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 回复  |  直到 7 年前
        1
  •  7
  •   nastynate13    9 年前

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

    首先,使用以下类方法获取对应用程序的AVAudioSession singleton的引用:

    (来自 AVAudioSession Class Reference ) :

    获取共享音频会话

    声明SWIFT

    class func sharedInstance() -> AVAudioSession

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

    设置首选音频I/O缓冲区持续时间(秒)。

    声明SWIFT

    func setPreferredIOBufferDuration(_ duration: NSTimeInterval) throws

    参数

    duration 所需的音频I/O缓冲区持续时间(秒) 使用。

    outError 在输入时,指向错误对象的指针。如果出现错误 发生时,指针被设置为描述 错误如果不需要错误信息,请输入nil。返回值 如果请求成功,则为true,否则为false。

    讨论

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


    请记住上面的注释- IOBufferDuration 属性是 事实上 设置为传递到 func setPrefferedIOBufferDuration(_ duration: NSTimeInterval) throws 方法取决于函数不返回错误, 我不完全清楚的其他因素。此外,在我的测试中,我发现如果将该值设置为极低的值,该值(或接近该值的值)确实会被设置,但在播放文件(例如使用AVAudioPlayerNode)时,声音将不会被播放。没有错误,只是没有声音。这显然是个问题。我还没有发现如何测试这个问题,除了在实际设备上测试时注意到没有声音击中我的耳朵。我会调查的。但就目前而言,我建议将首选持续时间设置为不小于.002或.0015。.0015的值似乎适用于我正在测试的iPad Air(A1474型)。而在我的iPhone 6S上,.0012的值似乎很适用。

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

    (来自 iOS Multimedia Programming Guide ) :

    iOS中未压缩的首选音频格式(最高质量) 音频,使用封装在 CAF文件。您可以在Mac OS X中将音频文件转换为该格式 使用afconvert命令行工具,如下所示:

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

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

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

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

        2
  •  3
  •   CKP78    7 年前

    我花了一个下午的时间试图通过玩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。不过,它似乎确实管用!