如何从WAV文件获取采样?

5 投票
4 回答
13993 浏览
提问于 2025-04-16 00:05

我想知道怎么从一个 .wav 文件中提取样本,以便对两个 .wav 文件进行窗口连接。

有没有人能告诉我该怎么做?

4 个回答

2

这里有一个函数,可以用来从一个波形文件(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()
2

你可以使用 wave 这个模块。首先,你需要读取一些元数据,比如采样大小或者声道数量。通过 readframes() 方法,你可以读取样本,但它们会以字节字符串的形式出现。根据样本的格式,你需要使用 struct.unpack() 来把它们转换成样本。

另外,如果你想要将样本作为浮点数的数组,可以使用 SciPy 的 io.wavfile 模块。

13

标准库中的 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)。

撰写回答