Python's SoundFile:soundfile.write and clipping



假设我使用Python的soundfile读取WAV文件,

import soundfile
x, fs = soundfile.read("test.wav")

阵列xfloat32max(x) = 1, min(x) = -1中。即:每个样本在xfloat32 1和1之间的数字。我做了一些操作,得到y。现在我想拯救yWAV文件。假设y现在的值大于1(和/或小于-1),我使用

soundfile.write("processed.wav", y, fs)

SoundFile如何处理超出的值?它是否做裁剪(如果y[t] > 1y[t] = 1)或归一化(将整个信号除以max(abs(y)))或其他东西?

我没有在文档中找到答案:https://pysoundfile.readthedocs.io/en/latest/#soundfile.write

import numpy as np 
import soundfile as sf
x = np.array([0,0.5,0.75, 1, 2]) 
sf.write("x.wav", x, 1)
y, fs = sf.read("x.wav")
print(y)

输出为:

[0.         0.5        0.75       0.99996948 0.99996948]

所以看起来它确实做剪辑,但我想确定。我可以控制soundfile.write如何处理超值吗?

这里要回答的重要问题不仅仅是soundfile做什么,而是如何确认行为。

让我们保留这个简洁的小示例程序,加上一些额外的注释是:

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)

WAVs可以有几种不同的采样率和数据格式(或位深度)。一个潜在的奇怪行为是1hz采样率。值得庆幸的是,在这种情况下,它没有影响,但总的来说,这是一个好主意,可以避免由奇怪的值引起的潜在问题。在您可以定义行为之前,坚持使用标准采样率。

Soundfile的文档本身不是不透明的,但您确实需要做一些追踪信息。对于write()方法,我们看到

subtype (str, optional) -默认值见default_subtype(),所有可能的值见available_subtypes()。

然而,另一个重要的信息实际上在data字段

下面

数据类型不选择写入文件的数据类型。音频数据将被转换为给定的子类型。将整型值写入浮点文件不会将值缩放为[-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

每个人依次

  • 00000,很好很容易:[0.0]
  • 4000=16384,或32768 * 0.5=[0.5]
  • 600024576,或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位有符号整数。

由此我们可以确认我们的数据正在被裁剪(没有规范化或包装)

最新更新