使用Python过滤WAV文件

20

最近我成功地用Python构建了一个系统,可以记录、绘制和播放音频wav文件。现在,我正在尝试在录制和开始绘制并输出文件到扬声器之间加入一些滤波和音频混合。然而,我不知道从哪里开始。现在,我需要读入初始的wav文件,应用一个低通滤波器,然后将新滤波的数据重新打包到一个新的wav文件中。下面是我用来绘制初始数据的代码,当我录制完它后。

import matplotlib.pyplot as plt
import numpy as np
import wave
import sys

spf = wave.open('wavfile.wav','r')

#Extract Raw Audio from Wav File
signal = spf.readframes(-1)
signal = np.fromstring(signal, 'Int16')

plt.figure(1)
plt.title('Signal Wave...')
plt.plot(signal)

这是我用来生成测试单音频文件的代码:

import numpy as np
import wave
import struct

freq = 440.0
data_size = 40000
fname = "High_A.wav"
frate = 11025.0  
amp = 64000.0    

sine_list_x = []
for x in range(data_size):
    sine_list_x.append(np.sin(2*np.pi*freq*(x/frate)))

wav_file = wave.open(fname, "w")

nchannels = 1
sampwidth = 2
framerate = int(frate)
nframes = data_size
comptype = "NONE"
compname = "not compressed"

wav_file.setparams((nchannels, sampwidth, framerate, nframes,
comptype, compname))

for s in sine_list_x:
    wav_file.writeframes(struct.pack('h', int(s*amp/2)))

wav_file.close()

我不太确定如何应用所说的音频滤波器并重新打包,如果您能提供任何帮助和/或建议,将不胜感激。


2
你有尝试看一下 scipylfilter 吗? - dpwilson
2
不要使用for循环来生成正弦波,你需要像这样使用sine_signal = np.sin(2*np.pi*freq*(np.arange(data_size)/frate)),然后使用wav_file.writeframes((sine_signal*amp/2).astype('h').tostring()) - dpwe
2个回答

58

第一步:您需要哪种音频滤波器?

选择滤波带

接下来的步骤中,我假设您需要一个低通滤波器

选择截止频率

截止频率是信号被衰减-3dB的频率。

您的示例信号为440Hz,因此让我们选择一个截止频率400Hz。然后,您的440Hz信号将被低通400Hz滤波器衰减(超过-3dB)。

选择您的滤波器类型

根据这个StackOverflow答案

滤波器设计超出了Stack Overflow的范围——那是一个DSP问题,而不是编程问题。滤波器设计由任何DSP教材涵盖——去你的图书馆。我喜欢Proakis和Manolakis的数字信号处理。(Ifeachor和Jervis的数字信号处理也不错。)

为了进入一个简单的例子,我建议使用移动平均滤波器(用于简单的低通滤波器)。

请参见移动平均

数学上,移动平均是卷积的一种类型,因此它可以被视为信号处理中使用的低通滤波器的一个示例。

这个移动平均低通滤波器是一个基本的滤波器,它非常容易使用和理解。

移动平均的参数是窗口长度

移动平均 窗口长度和 截止频率 之间的关系需要一些数学知识,并在这里进行了解释。

代码将会是:

import math

sampleRate = 11025.0 
cutOffFrequency = 400.0
freqRatio = cutOffFrequency / sampleRate

N = int(math.sqrt(0.196201 + freqRatio**2) / freqRatio)

因此,在本示例中,窗口长度将为12

第二步:编写过滤器

手动移动平均值

参见如何在Python中创建移动平均值的详细讨论

Alleo提供的解决方案为

def running_mean(x, windowSize):
   cumsum = numpy.cumsum(numpy.insert(x, 0, 0)) 
   return (cumsum[windowSize:] - cumsum[:-windowSize]) / windowSize 

filtered = running_mean(signal, N)

使用 lfilter

另外,正如dpwilson所建议的那样,我们也可以使用 lfilter。

win = numpy.ones(N)
win *= 1.0/N
filtered = scipy.signal.lfilter(win, [1], signal).astype(channels.dtype)

第三步:让我们把所有东西放在一起

import matplotlib.pyplot as plt
import numpy as np
import wave
import sys
import math
import contextlib

fname = 'test.wav'
outname = 'filtered.wav'

cutOffFrequency = 400.0

# from https://dev59.com/g2Yr5IYBdhLWcg3wYZKD
def running_mean(x, windowSize):
  cumsum = np.cumsum(np.insert(x, 0, 0)) 
  return (cumsum[windowSize:] - cumsum[:-windowSize]) / windowSize

# from https://dev59.com/DUvSa4cB1Zd3GeqPhtvE#2227174
def interpret_wav(raw_bytes, n_frames, n_channels, sample_width, interleaved = True):

    if sample_width == 1:
        dtype = np.uint8 # unsigned char
    elif sample_width == 2:
        dtype = np.int16 # signed 2-byte short
    else:
        raise ValueError("Only supports 8 and 16 bit audio formats.")

    channels = np.fromstring(raw_bytes, dtype=dtype)

    if interleaved:
        # channels are interleaved, i.e. sample N of channel M follows sample N of channel M-1 in raw data
        channels.shape = (n_frames, n_channels)
        channels = channels.T
    else:
        # channels are not interleaved. All samples from channel M occur before all samples from channel M-1
        channels.shape = (n_channels, n_frames)

    return channels

with contextlib.closing(wave.open(fname,'rb')) as spf:
    sampleRate = spf.getframerate()
    ampWidth = spf.getsampwidth()
    nChannels = spf.getnchannels()
    nFrames = spf.getnframes()

    # Extract Raw Audio from multi-channel Wav File
    signal = spf.readframes(nFrames*nChannels)
    spf.close()
    channels = interpret_wav(signal, nFrames, nChannels, ampWidth, True)

    # get window size
    # from http://dsp.stackexchange.com/questions/9966/what-is-the-cut-off-frequency-of-a-moving-average-filter
    freqRatio = (cutOffFrequency/sampleRate)
    N = int(math.sqrt(0.196196 + freqRatio**2)/freqRatio)

    # Use moviung average (only on first channel)
    filtered = running_mean(channels[0], N).astype(channels.dtype)

    wav_file = wave.open(outname, "w")
    wav_file.setparams((1, ampWidth, sampleRate, nFrames, spf.getcomptype(), spf.getcompname()))
    wav_file.writeframes(filtered.tobytes('C'))
    wav_file.close()

嗨,我认为你的代码意外地变成了一个高通滤波器。我在wav音频上使用它,结果去掉了低音。我需要低通和高通滤波器,所以你能指出需要改变的部分吗? - naman1901
我确认在我的端上它作为低通滤波器正在工作。 你能分享一下你使用的wav文件吗? - piercus
抱歉,我的错。它作为低通滤波器运行。显然,低音比前景更高频。 - naman1901
win, [1], signal 中的 [1] 是什么意思? - user13107
2
@user13107 请参考 https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.lfilter.html#scipy-signal-lfilter 。在 nfilter 中,我们使用更通用的 IIR 或 FIR 滤波器概念。 这是一个多项式定义的滤波器,根据方程 a[0]*y[n] = b[0]*x[n] + b[1]*x[n-1] + ... + b[M]*x[n-M] - a[1]*y[n-1] - ... - a[N]*y[n-N]。我们使用 b = win, a = [1], x = signal,然后方程变为 1*y[n] = win[0]*signal[n] + win[1]*signal[n-1] + ... + win[M]*signal[n-M]。这就像用大锤砸开坚果,但它确实有效! - piercus
显示剩余3条评论

2

sox库可用于静态噪声消除。

我发现这个代码片段有一些有用的命令示例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接