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 编码

Download history 203/week @ 2024-04-28 243/week @ 2024-05-05 152/week @ 2024-05-12 94/week @ 2024-05-19 47/week @ 2024-05-26 123/week @ 2024-06-02 175/week @ 2024-06-09 305/week @ 2024-06-16 145/week @ 2024-06-23 77/week @ 2024-06-30 56/week @ 2024-07-07 74/week @ 2024-07-14 180/week @ 2024-07-21 186/week @ 2024-07-28 323/week @ 2024-08-04 71/week @ 2024-08-11

766 downloads per month
Used in 4 crates

MIT license

200KB
3.5K SLoC

WaveRS - Wav 文件读取/写入器

Crates.io Documentation crate-pywavers-img crate-pywavers-docrs-img crate-pywavers-readdocs-img

WaveRs(发音为wavers)是一个用Rust编写的Wav文件读取/写入器,旨在快速且易于使用。WaveRs还通过PyWaveRs包在Python中可用。

入门 · 基准


入门

本节提供了WaveRs提供的功能快速概述,帮助您快速入门。WaveRs允许用户读取、写入以及在不同类型的采样音频之间进行转换,目前支持i16i32f32f64。现在还有对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::framesWav::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_ndarrayas_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