【问题标题】:How to play decoded audio data through hardware on IOS?如何在IOS上通过硬件播放解码的音频数据?
【发布时间】:2021-03-27 18:11:14
【问题描述】:

我有自己的用 C++ 编写的声音引擎,它有一些自定义功能。我的引擎中有解码+处理的 PCM 值,但我需要通过 IOS 上的库以某种方式播放它。它基于回调系统工作。第 3 方使用给定参数(例如 numberOfFrames、pointerToFillData 等)请求回调。

我对 IOS 音频非常陌生,我不知道如何完成这项工作。在 Android 上,Oboe 是执行此操作的库。

我发现了一些像novocaine 这样编写的自定义库,与我需要的非常相似,但并不完全一样。我查看了 Core Audio,但在我看来 Core Audio SDK 已被弃用。但是,包含 Core Audio 的 Audio Unit 似乎是我需要的东西。

有多种方法可以做到这一点。这就是为什么我真的可以用一个例子+建议来说明为什么你的方式是更可取的方式......

谁能用例子指导我正确的方向?

我看到了@hotpaw2 的回答,但我需要一个例子说明他的回答比其他可用选项更好的原因。

【问题讨论】:

    标签: ios core-audio


    【解决方案1】:

    iOS 音频队列 API(当前)未弃用且基于回调。

    见:https://developer.apple.com/documentation/audiotoolbox/audio_queue_services

    【讨论】:

    • 感谢您的回复。对此,我真的非常感激。以下是我心中的一些问题: 现在,我的音频引擎只有 2 个效果。我在这里可能完全错了只是一个疯狂的猜测我可以使用内置效果的苹果来为我的 PCM 数据添加效果吗?我还在这里看到了一个带有回调的 AVAudioEngine AudioUnit 示例stackoverflow.com/questions/50996398/… 你的和这个有什么区别?
    • 你也有这方面的好例子吗,我试着查了一下,但所有的例子都是从 2012 年到 2015 年的
    • @csguy 音频队列在过去 10 年没有太大变化。
    • @RhythmicFistman 我明白了,为什么人们更喜欢音频队列而不是音频单元?
    • 当延迟很重要时,我使用远程 IO 音频单元进行输出。 iOS 的 I/O 缓冲区大小与任何 API 没有明确关联,也许您可​​以配置 AQ 或 AVAudioEngine 来做正确的事情,但我认为使用 AU 的机会更大。 @hotpaw2 你确定 AudioUnits 已被弃用吗?我以为只有AUGraph API。
    【解决方案2】:

    你肯定需要一个 AVAudioEngine。

    let engine = AVAudioEngine()
    let player = AVAudioPlayerNode()
    var scheduledFrames: AVAudioFrameCount = 0
    
    init() throws {
        engine.attach(player)
        engine.prepare()
        try engine.start()
    }
    
    func addBuffer(buffer: AVAudioPCMBuffer) {
        // this will add the buffer to the end of the queue, read more in the docs
        scheduledFrames += buffer.frameLength
        player.scheduleBuffer(buffer, at: nil) {
            // buffer was played here
            scheduledFrames -= buffer.frameLength
            if scheduledFrames < 44100 { // I used around a second here, calculate depending your format
                // request new buffer
            }
        }
    }
    

    您可以像这样创建AVAudioPCMBuffer

    var data: UnsafeMutablePointer<UnsafeMutablePointer<Float>>!
    // fill your data
    let count = 100
    guard
        let format = AVAudioFormat(
            standardFormatWithSampleRate: 44100,
            channels: 1
        ),
        let buffer = AVAudioPCMBuffer(
            pcmFormat: format,
            frameCapacity: AVAudioChannelCount(count)
        )
    else {
        // throw
        return
    }
    for channel in 0..<Int(format.channelCount) {
        memcpy(buffer.floatChannelData![channel],
               data[channel],
               count * MemoryLayout<Float>.size)
    }
    

    如果需要,请将 floatChannelData 替换为 int16ChannelDataint32ChannelData

    【讨论】:

    • 几乎是我需要的。如何安排连续播放?我的意思是我需要能够连续播放我的数据,而不仅仅是一次像这里这样的缓冲区
    • 我添加了预定帧计算登录。 scheduleBuffer 回调在缓冲区播放后被调用,因此只需在队列中保留大约 1 秒的缓冲区
    • 不是我所希望的,这似乎是一种解决方法。这段代码似乎只适合播放一个缓冲区,我弄错了吗?
    • 不,您可以一次安排多个缓冲区。这更像是一个伪代码,您需要在开始播放时开始请求缓冲区,并在您安排足够时停止,并继续安排:当调用 n 缓冲区完成时,您安排缓冲区编号 n+44100。因此,您始终保持 1 秒的缓冲时间安排播放。如果您不担心内存使用情况,您可以一次安排所有缓冲区,它也可以工作。要重置缓冲区队列,您需要调用 player.stop()
    猜你喜欢
    • 2017-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-16
    • 2014-03-15
    相关资源
    最近更新 更多