如何从WAV文件获取采样?
我想知道怎么从一个 .wav 文件中提取样本,以便对两个 .wav 文件进行窗口连接。
有没有人能告诉我该怎么做?
4 个回答
这里有一个函数,可以用来从一个波形文件(wave file)中读取样本(samples),这个函数已经在单声道和立体声上测试过了。
def read_samples(wave_file, nb_frames):
frame_data = wave_file.readframes(nb_frames)
if frame_data:
sample_width = wave_file.getsampwidth()
nb_samples = len(frame_data) // sample_width
format = {1:"%db", 2:"<%dh", 4:"<%dl"}[sample_width] % nb_samples
return struct.unpack(format, frame_data)
else:
return ()
接下来是一个完整的脚本,它可以对多个 .wav
文件进行窗口混合或者连接。所有输入的文件需要有相同的参数,比如声道数量和样本宽度。
import argparse
import itertools
import struct
import sys
import wave
def _struct_format(sample_width, nb_samples):
return {1:"%db", 2:"<%dh", 4:"<%dl"}[sample_width] % nb_samples
def _mix_samples(samples):
return sum(samples)//len(samples)
def read_samples(wave_file, nb_frames):
frame_data = wave_file.readframes(nb_frames)
if frame_data:
sample_width = wave_file.getsampwidth()
nb_samples = len(frame_data) // sample_width
format = _struct_format(sample_width, nb_samples)
return struct.unpack(format, frame_data)
else:
return ()
def write_samples(wave_file, samples, sample_width):
format = _struct_format(sample_width, len(samples))
frame_data = struct.pack(format, *samples)
wave_file.writeframes(frame_data)
def compatible_input_wave_files(input_wave_files):
nchannels, sampwidth, framerate, nframes, comptype, compname = input_wave_files[0].getparams()
for input_wave_file in input_wave_files[1:]:
nc,sw,fr,nf,ct,cn = input_wave_file.getparams()
if (nc,sw,fr,ct,cn) != (nchannels, sampwidth, framerate, comptype, compname):
return False
return True
def mix_wave_files(output_wave_file, input_wave_files, buffer_size):
output_wave_file.setparams(input_wave_files[0].getparams())
sampwidth = input_wave_files[0].getsampwidth()
max_nb_frames = max([input_wave_file.getnframes() for input_wave_file in input_wave_files])
for frame_window in xrange(max_nb_frames // buffer_size + 1):
all_samples = [read_samples(wave_file, buffer_size) for wave_file in input_wave_files]
mixed_samples = [_mix_samples(samples) for samples in itertools.izip_longest(*all_samples, fillvalue=0)]
write_samples(output_wave_file, mixed_samples, sampwidth)
def concatenate_wave_files(output_wave_file, input_wave_files, buffer_size):
output_wave_file.setparams(input_wave_files[0].getparams())
sampwidth = input_wave_files[0].getsampwidth()
for input_wave_file in input_wave_files:
nb_frames = input_wave_file.getnframes()
for frame_window in xrange(nb_frames // buffer_size + 1):
samples = read_samples(input_wave_file, buffer_size)
if samples:
write_samples(output_wave_file, samples, sampwidth)
def argument_parser():
parser = argparse.ArgumentParser(description='Mix or concatenate multiple .wav files')
parser.add_argument('command', choices = ("mix", "concat"), help='command')
parser.add_argument('output_file', help='ouput .wav file')
parser.add_argument('input_files', metavar="input_file", help='input .wav files', nargs="+")
parser.add_argument('--buffer_size', type=int, help='nb of frames to read per iteration', default=1000)
return parser
if __name__ == '__main__':
args = argument_parser().parse_args()
input_wave_files = [wave.open(name,"rb") for name in args.input_files]
if not compatible_input_wave_files(input_wave_files):
print "ERROR: mixed wave files must have the same params."
sys.exit(2)
output_wave_file = wave.open(args.output_file, "wb")
if args.command == "mix":
mix_wave_files(output_wave_file, input_wave_files, args.buffer_size)
elif args.command == "concat":
concatenate_wave_files(output_wave_file, input_wave_files, args.buffer_size)
output_wave_file.close()
for input_wave_file in input_wave_files:
input_wave_file.close()
你可以使用 wave
这个模块。首先,你需要读取一些元数据,比如采样大小或者声道数量。通过 readframes()
方法,你可以读取样本,但它们会以字节字符串的形式出现。根据样本的格式,你需要使用 struct.unpack()
来把它们转换成样本。
另外,如果你想要将样本作为浮点数的数组,可以使用 SciPy 的 io.wavfile
模块。
标准库中的 wave 模块是关键:首先在代码顶部加上 import wave
,然后使用 wave.open('the.wav', 'r')
可以打开一个 WAV 文件,这样就得到了一个可以读取音频数据的“波形读取”对象。接着,你可以用 .readframes
方法来读取音频帧,这个方法会返回一串字节,这些字节就是音频样本,具体格式取决于 WAV 文件的设置。你可以用 .getnchannels
方法来获取通道数,用 .getsampwidth
方法来获取每个样本的字节数。
将这串字节转换成数字值的最佳方法是使用 array
模块,样本的类型可以是 'B'
(1字节)、'H'
(2字节)、'L'
(4字节),这取决于你使用的 Python 版本(32位)。你可以通过查看数组对象的 itemsize
值来确认这一点。如果你的样本宽度与 array
提供的不同,你就需要把字节串切割成小块(每块前面加上适当数量的零字节),然后使用 struct 模块来处理(不过这样会比较麻烦和慢,所以如果可以的话,还是建议使用 array
)。