【问题标题】:Audio Unit Render Block in Swift?Swift 中的音频单元渲染块?
【发布时间】:2017-07-22 14:17:14
【问题描述】:

我已经用 Swift 编写了一个示例项目来试用相对较新的 Core Audio V3 API 内容。一切似乎都可以创建自定义音频单元并在进程中加载​​它。但实际的音频渲染并不顺利。我经常读到渲染代码需要使用 C 或 C++,但我也听说 Swift 速度很快,我认为我可以在其中编写一些最小的音频渲染代码。

渲染代码

override var internalRenderBlock: AUInternalRenderBlock {
    get {
        return {
            (_ actionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
            _ timeStamp: UnsafePointer<AudioTimeStamp>,
            _ frameCount: AUAudioFrameCount,
            _ outputBusNumber: Int,
            _ bufferList: UnsafeMutablePointer<AudioBufferList>,
            _ renderEvent: UnsafePointer<AURenderEvent>?,
            _ pull: AudioToolbox.AURenderPullInputBlock?) -> AUAudioUnitStatus in

            let bufferList = bufferList.pointee
            let theBuffers = bufferList.mBuffers // only one (AudioBuffer) ??

            guard let theBufferData = theBuffers.mData?.assumingMemoryBound(to: Float.self) else {
                return 1 // come up with better error?
            }

            let amountFrames = Int(frameCount)

            for frame in 0...amountFrames / 2 {
                let frame = theBufferData.advanced(by: frame)
                frame.pointee = sin(self.phase)
                self.phase += 0.0001
            }

            return noErr
        }
    }
}

听起来很糟糕

产生的声音不是我所期望的。我最初的想法是 Swift 是错误的选择。然而有趣的是,AudioToolbox 确实为这个AUAudioUnit 的呈现属性提供了一个类型别名,如下所示:

public typealias AUInternalRenderBlock = (UnsafeMutablePointer<AudioUnitRenderActionFlags>, UnsafePointer<AudioTimeStamp>, AUAudioFrameCount, Int, UnsafeMutablePointer<AudioBufferList>, UnsafePointer<AURenderEvent>?, AudioToolbox.AURenderPullInputBlock?) -> AUAudioUnitStatus

这让我相信也许可以用 Swift 编写渲染代码。

观察到的问题

但是,这里还是有一些问题。 (除了我明显缺乏 Swift 内存管理方面的能力)。

A) 尽管theBuffers 说它的mNumberOfBuffers 是2,theBuffers 最终不是一个数组,而是(AudioBuffer) 类型。我不明白括号的必要性。我找不到第二个AudioBuffer

B) 更重要的是,当我向我可以访问的AudioBuffer 写入基本的正弦波时,产生的声音会失真且不一致。这会是斯威夫特的错吗?用 Swift 编写任何音频单元渲染代码是不可能的吗?或者在这里做了一些假设以某种方式破坏了我的渲染?

终于

如果只是在 Swift 中编写这部分是不可行的,那么我希望有一些资源可以用于音频单元渲染块的 Swift 和 C 互操作。那么,返回闭包的属性是否可以用 Swift 编写,但闭包的实现会调用 C 语言?还是该属性必须简单地返回一个原型与闭包类型匹配的 C 函数?

提前致谢。

该项目的其余部分可以在here 中查看上下文。

【问题讨论】:

  • 在最近一次关于 Core Audio 的 WWDC 会议中,Apple 明确表示要在 C 中执行某些音频上下文代码,而不是 Swift 或 Obj C。(但我关于 Swift 中 V3 渲染的 github gist 似乎无论如何都可以工作。 ..目前...可能会中断)。
  • 这一点我已经很清楚了。我被公共“标题”中的 Swift 类型别名吓跑了。为什么会在那里?

标签: swift core-audio audiounit


【解决方案1】:

您听到失真声音的主要原因是 0.0001 的相位增量太小,需要 62832 个样本才能填满正弦波的一个周期——仅 0.70 赫兹! (假设您的采样率为 44100)

除了超低频正弦波之外,您还听到了大约 44100 / 512 = 86.1 Hz 的声音,因为您只填充了音频缓冲区的一半 (amountFrames / 2)。所以声音是你的音频渲染周期的近似矩形波,在大约 0.70 Hz 的幅度内缓慢变化。

我可以根据您的代码编写一个工作正弦波发生器单元:

override var internalRenderBlock: AUInternalRenderBlock {
    return { ( _, _, frameCount, _, bufferList, _, _) in
        let srate = Float(self.bus.format.sampleRate)
        var phase = self.phase

        for buffer in UnsafeMutableAudioBufferListPointer(bufferList) {
            phase = self.phase
            assert(buffer.mNumberChannels == 1, "interleaved channel not supported")

            let frames = buffer.mData!.assumingMemoryBound(to: Float.self)

            for i in 0 ..< Int(frameCount) {
                frames[i] = sin(phase)
                phase += 2 * .pi * 440 / srate // 440 Hz
                if phase > 2 * .pi {
                    phase -= 2 * .pi // to avoid floating point inaccuracy
                }
            }
        }

        self.phase = phase
        return noErr
    }
}

关于观察到的问题A,AudioBufferList是一个可变长度C结构的包装器,其中第一个字段mNumberBuffers表示缓冲区的数量(即非交错通道的数量),第二个字段是一个可变长度数组:

typedef struct AudioBufferList {
    UInt32 mNumberBuffers;
    AudioBuffer mBuffers[1];
} AudioBufferList;

这个结构的用户,在Objective-C或C++中,应该分配mNumberBuffers * sizeof(AudioBuffer)字节,这足以存储多个mBuffers。由于 C 不对数组执行边界检查,因此用户只需编写 mBuffers[1]mBuffers[2] 即可访问第二个或第三个缓冲区。

由于 Swift 没有这个变长数组的特性,Apple 提供了UnsafeMutableAudioBufferListPointer,可以像AudioBuffers 的 Swift 集合一样使用;我在上面的外部for 循环中使用了它。

最后,我尽量不要在代码的最内层循环中访问self,因为访问 Swift 或 Objective-C 对象可能会出现意外延迟,这也是 Apple 建议在 C/C++ 中编写渲染循环的原因。但是对于像这样的简单情况,我会说用 Swift 编写要容易得多,而且延迟仍然是可控的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-03
    • 1970-01-01
    • 2012-07-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多