【问题标题】:Simple low-latency audio playback in iOS SwiftiOS Swift 中简单的低延迟音频播放
【发布时间】:2016-04-13 07:27:24
【问题描述】:

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

  1. 当我像打鼓一样快速触摸按钮时,有些声音会丢失。
  2. 仍处于“鼓滚动”状态,每次触摸按钮时声音都会中断,而不是让样本播放直到结束。例如,在钹卷中它很糟糕。即使我再次触摸该按钮,我也希望听到每个样本都完整地响起。
  3. 触摸和声音之间存在延迟。我知道AVAudioPlayer不是低延迟音频的最佳选择,但作为初学者,如果没有Swift中的代码示例或教程,很难学习OpenALAudioUnit。问题类似这样: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()
}

【问题讨论】:

  • 我认为音频单元可能是获得您正在寻找的那种低延迟行为的唯一方法,尽管如果有人告诉我我错了,我会很高兴。我正在努力学习如何在 Swift 中使用 AudioToolbox,并且很乐意分享我正在学习的内容。
  • 感谢您的回复,非常感谢您对 Swift 中的 AudioToolbox 的帮助。我们如何取得联系?通过电子邮件?
  • 你得到我的电子邮件地址了吗?我把它放在这里大约 30 分钟,然后删除它......
  • 对不起,我没听懂。你介意给我发电子邮件吗?它可以在我的网站上找到:marcos.sampaio.me(在页脚中)。我不确定 StackOverflow 是否会在此处发布。
  • 我同意@RomanSausarnes - 您没有足够控制AVAudioPlayer 的时间以在此应用程序中使用。对你来说好消息是,在 iOS7 中,Apple 在 AVFoundation 中提供了额外的 swift-friendly API,这可能会有所帮助。查看参考可能是一个很好的起点

标签: ios swift audio avaudioplayer low-latency


【解决方案1】:

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

【讨论】:

  • Starling 库做得很好,使 AVAudioEngine 更易于使用:github.com/matthewreagan/Starling
  • 该代码片段中没有任何内容表明它正在减少延迟。你能指出你发生了什么变化吗?
【解决方案2】:

如果您需要极低的延迟,我在 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 对象,该对象描述了 错误。如果您不想要错误信息,请传入 nil。返回值 如果请求成功,则为 true,否则为 false。

讨论

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


请记住上面的注释——IOBufferDuration 属性是否实际上设置为传递给func setPrefferedIOBufferDuration(_ duration: NSTimeInterval) throws 方法的值,取决于函数是否返回错误, 其他我不完全清楚的因素。另外 - 在我的测试中 - 我发现如果你将此值设置为一个极低的值,该值(或接近它的值)确实会设置,但是在播放文件时(例如使用 AVAudioPlayerNode)声音不会被播放。没有错误,只是没有声音。这显然是个问题。而且我还没有发现如何测试这个问题,除了在实际设备上测试时注意到我的耳朵没有声音。我会调查的。但就目前而言,我建议将首选持续时间设置为不少于 0.002 或 0.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数据,将IMA4数据打包成CAF文件。

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

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

【讨论】:

  • 最小 I/O 缓冲区持续时间至少为 0.005 秒(256 帧),但可能会更低,具体取决于所使用的硬件。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多