这里要回答的重要问题不仅仅是声音文件的作用,而是如何确认行为。
让我们保留这个整洁的小示例程序,它带有一些额外的注释:
import numpy as np
import soundfile as sf
x = np.array([0,0.5,0.75, 1, 2]) # x.dtype is 'float64'
sf.write("x.wav", x, 1) # a wav at sampling rate 1 Hz
y, fs = sf.read("x.wav")
print(y)
WAV 可以有几种不同的采样率和数据格式(或位深度)。奇怪行为的一种可能性是 1 Hz 采样率。值得庆幸的是,在这种情况下它没有任何影响,但一般来说,避免因奇怪值引起的潜在问题是一个好主意。坚持标准采样率,直到您可以定义行为。
Soundfile 的文档本身并不是不透明的,但您确实需要做一些事情来寻找信息。对于write()
我们看到的方法
subtype (str, optional) – 查看 default_subtype() 获取默认值,查看 available_subtypes() 获取所有可能值。
然而,另一个重要的信息实际上是data
在场下
数据的数据类型不选择写入文件的数据类型。音频数据将被转换为给定的子类型。将 int 值写入浮点文件不会将值缩放为 [-1.0, 1.0)。如果您将值np.array([42], dtype='int32')
,写入subtype='FLOAT'
文件,则该文件将包含np.array([42.], dtype='float32')
.
基本上,数据类型不是由样本数据推断出来的,而是缩放到subtype
.
当我们查看时,default_subtype
我们发现 WAV 的默认值是 16 位 PCM。
棘手的一点是,soundfile 在读取信息时会做什么read
?
好的做法是使用其他东西来确认行为。如果第二种读取数据的方法报告相同的信息,那么宾果游戏,我们已经破解了。如果不是,则表明至少有一种方法正在更改数据,因此您必须尝试第三种方法(依此类推)。
读取数据并确保其未被更改的一种好方法是使用十六进制编辑器进行读取。
让我们在这一点上提醒自己,我们的输出soundfile.read()
是:
[0. 0.5 0.75 0.99996948 0.99996948]
上面的十六进制示例创建了一个文件:
52494646 2E000000 57415645 666D7420 10000000 01000100 01000000 02000000 02001000 64617461 0A000000 00000040 0060FF7F FF7F
我们知道它是 16 位样本,所以最后 10 个字节是我们感兴趣的(每个样本 2 个字节,总共 5 个样本)
16 位是有符号的,所以我们有一个摆动±2^{15}
,即 32768(学究们别担心,我会在一分钟内完成)
0000 0040 0060 FF7F FF7F
啊,但那是小端格式。所以,让我们把它翻过来,让它更容易看到
0000 4000 6000 7FFF 7FFF
每一个依次
0000
是0
,又好又容易:[0.0]
4000
是16384
, 或32768 * 0.5
:[0.5]
6000
是24576
, 或32768 * 0.75
:[0.75]
7FFF
是32767
,正是可以描述的峰值正幅度。
由于幅度被缩放到32767
,这就是读回数据时出现轻微误差的原因:32767 / 32768
等于0.99996948
(有一点舍入误差)
让我们通过将最后两个样本翻转为负来确认该行为。
import numpy as np
import soundfile as sf
x = np.array([0,0.5,0.75, -1, -2]) # x.dtype is 'float64'
sf.write("x.wav", x, 1) # a wav at sampling rate 1 Hz
y, fs = sf.read("x.wav")
print(y)
在大端格式中,我们的十六进制数据现在是
0000 4000 6000 8000 8000
8000
是-32768
一个 16 位有符号整数。
由此我们可以确认我们的数据正在被裁剪(未标准化或包装)