16 releases (stable)
1.4.3 | 2024年6月20日 |
---|---|
1.4.1 | 2024年4月25日 |
1.4.0 | 2024年3月15日 |
1.1.1 | 2023年11月8日 |
0.3.0 | 2023年5月26日 |
#140 in 编码
766 downloads per month
Used in 4 crates
200KB
3.5K SLoC
WaveRS - Wav 文件读取/写入器
WaveRs(发音为wavers)是一个用Rust编写的Wav文件读取/写入器,旨在快速且易于使用。WaveRs还通过PyWaveRs包在Python中可用。
入门 · 基准
入门
本节提供了WaveRs提供的功能快速概述,帮助您快速入门。WaveRs允许用户读取、写入以及在不同类型的采样音频之间进行转换,目前支持i16
、i32
、f32
和f64
。现在还有对i24
的实验性支持。
有关项目和Wav文件的更多详细信息,请参阅下面的WaveRs 项目部分。有关WaveRs提供的功能的更详细信息的说明,请参阅文档。
读取
use wavers::{Wav, read};
use std::path::Path;
fn main() {
let fp = "path/to/wav.wav";
// creates a Wav file struct, does not read the audio data. Just the header information.
let wav: Wav<i16> = Wav::from_path(fp).unwrap();
let samples: Samples<i16> = wav.read().unwrap();
// or to read the audio data directly
let (samples, sample_rate): (Samples<i16>, i32) = read::<i16, _>(fp).unwrap();
// samples can be derefed to a slice of samples
let samples: &[i16] = &samples;
}
转换
use wavers::{Wav, read, ConvertTo};
use std::path::Path;
fn main() {
// Two ways of converted a wav file
let fp: "./path/to/i16_encoded_wav.wav";
let wav: Wav<f32> = Wav::from_path(fp).unwrap();
// conversion happens automatically when you read
let samples: &[f32] = &wav.read().unwrap();
// or read and then call the convert function on the samples.
let (samples, sample_rate): (Samples<i16>, i32) = read::<i16, _>(fp).unwrap();
let samples: &[f32] = &samples.convert();
}
写入
use wavers::Wav;
use std::path::Path;
fn main() {
let fp: &Path = &Path::new("path/to/wav.wav");
let out_fp: &Path = &Path::new("out/path/to/wav.wav");
// two main ways, read and write as the type when reading
let wav: Wav<i16> = Wav::from_path(fp).unwrap();
wav.write(out_fp).unwrap();
// or read, convert, and write
let (samples, sample_rate): (Samples<i16>,i32) = read::<i16, _>(fp).unwrap();
let sample_rate = wav.sample_rate();
let n_channels = wav.n_channels();
let samples: &[f32] = &samples.convert();
write(out_fp, samples, sample_rate, n_channels).unwrap();
}
迭代
WaveRs
提供两种主要的迭代方法:帧迭代和通道迭代。这些可以通过使用Wav::frames
和Wav::channels
函数分别执行。两种方法都返回对Wav文件中样本的迭代器。`frames`方法返回对Wav文件帧的迭代器,其中帧是每个通道的单个样本。`channels`方法返回对Wav文件通道的迭代器,其中通道是单个通道的所有样本。
use wavers::Wav;
fn main() {
let wav = Wav::from_path("path/to/two_channel.wav").unwrap();
for frame in wav.frames() {
assert_eq!(frame.len(), 2, "The frame should have two samples since the wav file has two channels");
// do something with the frame
}
for channel in wav.channels() {
// do something with the channel
assert_eq!(channel.len(), wav.n_samples() / wav.n_channels(), "The channel should have the same number of samples as the wav file divided by the number of channels");
}
}
Wav 工具
use wavers::wav_spec;
fn main() {
let fp = "path/to/wav.wav";
let wav: Wav<i16> = Wav::from_path(fp).unwrap();
let sample_rate = wav.sample_rate();
let n_channels = wav.n_channels();
let duration = wav.duration();
let encoding = wav.encoding();
let (duration, header) = wav_spec(fp).unwrap();
}
查看wav_inspect,这是一个简单的命令行工具,用于检查Wav文件的头部。
特性
以下部分描述了WaveRs包中可用的特性。
Ndarray
ndarray功能用于提供函数,允许将wav文件以ndarray
2-D数组(样本 x 通道)的形式读取。提供了两个函数,分别是into_ndarray
和as_ndarray
。这两个函数都从样本中创建一个Array2
,并与音频的采样率一起返回。into_ndarray
会消耗输入的Wav
结构,而as_ndarray
则不会。
use wavers::{read, Wav, AsNdarray, IntoNdarray};
use ndarray::{Array2, CowArray2};
fn main() {
let fp = "path/to/wav.wav";
let wav: Wav<i16> = Wav::from_path(fp).unwrap();
// does not consume the wav file struct
let (i16_array, sample_rate): (Array2<i16>, i32) = wav.as_ndarray().unwrap();
// consumes the wav file struct
let (i16_array, sample_rate): (Array2<i16>, i32) = wav.into_ndarray().unwrap();
// convert the array to samples.
let samples: Samples<i16> = Samples::from(i16_array);
}
PyO3
pyo3
功能用于提供与Python的互操作性,特别是针对PyWavers项目(在Python中使用Wavers)。
WaveRs项目
决定编写Wavers时,有几个激励因素。首先,我的博士研究涉及大量音频处理,我几乎一直在使用Python处理wav文件。Python是一种非常出色的语言,但我一直对其基线内存使用等问题有所困扰。其次,我对学习更底层的语言感兴趣,但并不介意C/C++(再次),Rust引起了我的注意。第三,我必须完成一个语音和音频处理模块,这涉及到一个项目。所有这些因素加在一起,使我开始这个项目,并为Wavers的MVP设定了截止日期和目标。
Rust的音频处理库数量有限,专门针对wav格式的更是寥寥无几。目前,最受欢迎的wav读取/写入crate是ruuda的hound。hound目前被用于其他Rust音频库,如Rodio的wav文件读取/写入器,但hound的最后更新是在2022年9月。尽管如此,Rust最通用的音频处理库是CPAL。CPAL(跨平台音频库)是一个纯Rust的音频输入和输出低级库。最后,还有Symphonia,另一个通用的媒体编码/解码crate,用于Rust。Symphonia支持读取和写入wav文件。CPAL和Symphonia都是非常底层的,并不提供WaveRs所追求的易用性。
项目目标
Wav文件解剖
波形文件格式是一种广泛支持的数字音频存储格式。Wav文件使用资源交换文件格式(RIFF)文件结构,并将文件中的数据组织为块。每个块都包含有关其类型和大小的信息,并且可以很容易地被不理解特定块类型的软件跳过。
除了RIFF块之外,剩余的块没有保证的顺序,只有fmt和data块是保证存在的。这意味着,而不是为解码块定义特定结构,块必须通过在wav文件中搜索并读取块ID和块大小字段来发现。然后,如果需要该块,则根据块格式读取该块,如果不需要,则跳过该块的大小。
以下描述了Wavers支持的块类型。随着时间的推移和库的成熟,计划扩展支持的块数量。
RIFF
总字节数 = 8 + 4个块名 + 剩余文件大小的字节数(减去8字节用于块ID和大小)。
字节序列描述 | 长度(字节) | 偏移量(字节) | 描述 |
---|---|---|---|
块ID | 4 | 0 | 字符字符串"RIFF" |
大小 | 4 | 4 | 块的字节数量 |
RIFF类型ID | 4 | 8 | 字符字符串"WAVE" |
块 | $x$ | 12 | 其他块 |
fmt
总字节数 = 16 + 4个块大小 + 4个块名
字节序列描述 | 长度(字节) | 偏移量(字节) | 描述 |
---|---|---|---|
块ID | 4 | 0 | 字符字符串"fmt "(注意空格!) |
大小 | 4 | 4 | 块的字节数量 |
压缩代码 | 2 | 6 | 表示wav文件的格式,例如PCM = 1和IEEE浮点数 = 3 |
声道数 | 2 | 8 | wav文件中的声道数 |
采样率 | 4 | 12 | 音频采样的速率 |
字节数率 | 4 | 16 | 每秒字节数 |
块对齐 | 2 | 18 | 数据的最小原子单元 |
每样本位数 | 2 | 20 | 每样本的位数,例如PCM_16有16位 |
数据块
总字节数 = 4个用于块名称 + 4个用于大小,然后是$x$个数据字节数。
字节序列描述 | 长度(字节) | 偏移量(字节) | 描述 |
---|---|---|---|
块ID | 4 | 0 | 字符串"数据" |
大小 | 4 | 4 | 数据的大小(以字节为单位) |
数据 | $x$ | 8 | 编码的音频数据 |
基准测试
以下基准测试使用Criterion
录制,每个基准测试都是在从GenSpeech数据集选取的小型wav文件数据集上运行的。持续时间在约7秒到15秒之间,每个文件都编码为PCM_16。以下结果是加载数据集中所有wav文件所需的时间。因此,每文件的时间是总时间除以数据集中文件的数量。数据集包含10个文件。基准测试中存在一些可疑的异常,需要进行进一步调查。基准测试是在以下配置的台式PC上运行的
- CPU:第13代英特尔® 酷睿™ i7-13700KF
- RAM:32Gb DDR4
- 存储:1Tb SSD
Hound与Wavers - 原生i16
基准测试 | 名称 | 最小时间 | 平均时间 | 最大时间 |
---|---|---|---|---|
Hound与Wavers - 原生i16 | Hound读取i16 | 7.4417 ms | 7.4441 ms | 7.4466 ms |
Hound与Wavers - 原生i16 | Wavers读取i16 | 122.42 µs | 122.56 µs | 122.72 µs |
Hound与Wavers - 原生i16 | Hound写入i16 | 2.1900 ms | 2.2506 ms | 2.3201ms |
Hound与Wavers - 原生i16 | Wavers写入i16 | 5.9484 ms | 6.2091 ms | 6.5018 ms |
读取
基准测试 | 名称 | 最小时间 | 平均时间 | 最大时间 |
---|---|---|---|---|
读取 | 原生i16 - 读取 | 121.28 µs | 121.36 µs | 121.44 µs |
读取 | 原生i16 - 读取Wav文件 | 121.56 µs | 121.79 µs | 122.08 µs |
读取 | 原生i16 As f32 | 287.63 µs | 287.78 µs | 287.97 µs |
写入
基准测试 | 名称 | 最小时间 | 平均时间 | 最大时间 |
---|---|---|---|---|
写入 | 切片 - 原生i16 | 5.9484 ms | 6.2091 ms | 6.5018 ms |
写入 | 切片 - 原生i16 As f32 | 30.271 ms | 33.773 ms | 37.509 ms |
写入 | 写入原生f32 | 11.286 ms | 11.948 ms | 12.648 ms |
依赖项
~0.5–9.5MB
~84K SLoC