15个版本 (7个稳定)
4.2.0 | 2024年1月17日 |
---|---|
4.1.0 | 2023年5月22日 |
4.0.0 | 2023年4月19日 |
3.2.0 | 2023年3月22日 |
0.3.1 |
|
#29 in 音频
每月下载量206
用于 18 个crate(16个直接使用)
96KB
1.5K SLoC
音频处理器特性
音频处理器类型和音频缓冲区类型的特性。可能会大幅更改。
简介
这很大程度上是基于探索的,我仍在寻找最佳Rust类型API来表达。
我认为我已经找到了一个处理AudioBuffer
和基本FXAudioProcessor
的好抽象,但还有更多东西需要挖掘。
动机
为处理音频的结构提供通用特性会很好。
这些可能是音频处理之上的1或多个抽象,使得高级系统可以操作音频处理节点。
在核心,音频处理可能被定义为函数
fn process_audio(_input: &mut [f32]) { /* ... */ }
input
将是你输入/输出缓冲区,你可以在其上执行可变操作。然而,这并没有编码几个音频处理问题,如
- 缓冲区不总是
f32
,它可能是f64
- 缓冲区通道数没有表达
- 多通道数据的缓冲区布局没有表达(例如,交错数据或其他方式)
- 采样率没有表达
- 干/湿配置没有表达
- MIDI及其他问题没有表达
我们希望有一些抽象可以解决这些问题。不考虑外部系统问题(如MIDI、状态和干/湿),一个基本的音频处理器特性可以解决缓冲区/样本转换问题。
AudioBuffer
提供了一个AudioBuffer
特性。它提供了一个抽象来获取缓冲区的大小并修改它。
《AudioBuffer》特质可以包裹不同布局或所有权的样本,然而,建议使用《AudioBuffer::frame》、《AudioBuffer::frame_mut》、《AudioBuffer::slice》和《AudioBuffer::slice_mut》来处理样本。
原因在于使用《slice》迭代器比遍历数字范围并调用《AudioBuffer::get》更高效。
遗憾的是,在这个crate中还没有提供一种统一优化的方法来迭代具有不同布局的缓冲区。
我认为可以有一种通道迭代器,它将包裹切片迭代器。通道数量应该较低,这样在读取帧时丢失一些优化不应成问题。
请注意,10-20倍的减速是基于一个非常简单的“增益”工作负载。实际上这可能会成为一个问题。
在我的电脑上,使用《get/set》函数大约需要每512个样本600纳秒的开销。
使用不检查边界的《_unchecked》版本,开销约为300纳秒,而使用《frames/slice》则没有开销。
相比之下,我当前的混音到VST缓冲区转换实现将CPAL转换为VST大约需要1.15微秒,从VST转换回CPAL大约需要1.1微秒。
这意味着在转换每个512个样本块时大约会花费2微秒,而迭代则会导致300-600纳秒的减速。
这让我想到,可能更好的做法是让《AudioProcessor》只公开一个《process_sample(&mut self, sample: f32)》函数,这样它们就与《AudioBuffer》没有任何联系。
AudioProcessor
《AudioProcessor》特质仅有两个方法
pub trait AudioProcessor {
type SampleType;
fn prepare(&mut self, _settings: AudioProcessorSettings) {}
fn process<BufferType: AudioBuffer<SampleType = Self::SampleType>>(
&mut self,
data: &mut BufferType,
);
}
它提供了一个准备回调,其中会提供通道和采样率配置,以及一个处理回调,其中提供了一个通用的《AudioBuffer》。
设计说明
SampleType关联类型
《SampleType》作为关联类型提供给《AudioBuffer》和《AudioProcessor》特质。这使得实现者可以在他们的处理器中使用通用的《SampleType》类型。
例如,这是在这个crate中《SilenceAudioProcessor》的实现,它应该适用于任何《num::Float》类型和任何《AudioBuffer》实现
pub struct SilenceAudioProcessor<SampleType>(PhantomData<SampleType>);
impl<SampleType: num::Float + Send> AudioProcessor for SilenceAudioProcessor<SampleType> {
type SampleType = SampleType;
fn process<BufferType: AudioBuffer<SampleType = Self::SampleType>>(
&mut self,
output: &mut BufferType,
) {
for sample_index in 0..output.num_samples() {
for channel_index in 0..output.num_channels() {
output.set(
channel_index,
sample_index,
<BufferType as AudioBuffer>::SampleType::zero(),
);
}
}
}
}
待办事项
MIDI特质- 更丰富的应用程序API
- 状态管理指南,使用后台引用计数垃圾收集器与不可变的“状态句柄”引用(同时仍然允许处理器的内部状态可变)
- 为所有特质实现自动实现VST API
- 为所有特质实现自动实现LV2 API
- 为所有特质实现自动实现一个基于“独立”《cpal》的应用程序(请参阅此存储库中的《audio-processor-standalone》)
- 音频图实现
- GUI支持
- 测试工具
缓冲区性能
在简单的增益基准测试中,使用AudioBuffer::get
API的性能在VecAudioBuffer
上比在Vec
上差10-20倍。
其他需要测量的内容
- 在小型窗口大小上测量缓冲区转换时间
- 与
AudioBuffer
抽象的额外开销进行比较
- 与
- 与非平凡处理器(非增益)的大范围开销进行比较
其他思考
- 哪种布局更高效?
- 音频处理器应该公开一个逐样本处理的
process
函数吗?也许这更容易优化
许可证
MIT
依赖项
~0.9–3MB
~63K SLoC