如何从WAV文件中获取WAV样本?

5

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

请问有人可以告诉我如何做吗?

4个回答

13

标准库的wave模块是关键: 在你的代码顶部导入import wave之后,wave.open('the.wav', 'r')会返回一个"wave read"对象,你可以使用.readframes方法读取帧(frame),该方法返回一系列字节(byte)样本(sample)...以任何格式呈现在波形文件中(你可以使用.getnchannels方法确定分解帧成样本时相关的两个参数,用于通道数和每个样本的字节数.getsampwidth)。

将字节串转换为数字序列的最佳方式是使用array模块,分别使用'B', 'H', 'L'类型表示每个样本的1、2、4个字节(在Python 32位版本中; 您可以使用数组对象的itemsize值进行双重检查)。 如果您有不同的样本宽度,则需要切割字节串(适当地填充每个小片段的字节值为0),并改用struct模块(但这更加笨拙和缓慢,所以如果可以,请使用array)。


当我尝试使用.getsamplewidth时,它给了我一个值2,意味着2个字节.. 当我尝试使用.readframes(1)应该返回1帧,然后它为我返回了“/x03/x16”这样的内容 我猜这是2个字节,那么这是否意味着1帧只有1个样本.. getnchannels有什么用?? 我想单独从每个帧中取样本,并将它们表示为整数,我该怎么做?? - kaki
1
@kaki,在每一帧中,都有来自每个声道的第一个采样,然后是每个声道的第二个采样,依此类推。因此,除非您的声音是单声道,即仅有1个声道,否则您必须决定如何处理声道(跳过所有但一个,平均它们等等)。假设是单声道(Mono),最简单的是,使用 x = array.array('h', w.getframes(1)) 将在 x 中给出一个包含所有采样的整数数组,正如您所要求的一样(h 而不是 H:它们是有符号的)。如果是立体声(Stereo),也就是2个声道,那么 x 的偶数索引就是左声道采样。顺便提一下,这是小端序。 - Alex Martelli
顺便说一句,https://ccrma.stanford.edu/courses/422/projects/WaveFormat/上的格式文档并不使用“帧”的概念,而是使用“块”和“子块” ,但最后当然结果差不多。;-) - Alex Martelli
@kaki,不用谢 - 但请考虑接受帮助了你的答案(通过点击问题左侧的勾选标记),因为这是真正基本的SO礼仪! - Alex Martelli
@AlexMartelli 对于这个问题抱歉打扰了,但我正在尝试使用 .readframes(1) 并且我需要将两个样本拆分为两个单独的变量(基本上将第一个左样本放入变量X中,将第一个右样本放入变量Y中),但我不知道如何做到这一点,有什么建议吗? - MarcusJ

2
以下是从Wave文件中读取样本的函数(已在单声道和立体声下进行过测试):

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模块。


你能告诉我如何获取浮点数数组的样本,而不使用scipy吗? - kaki

0

在阅读示例后(例如使用wave模块,更多细节这里),您可能希望将值范围缩放为-1到1之间(这是音频信号的约定)。

在这种情况下,您可以添加:

# scale to -1.0 -- 1.0
max_nb_bit = float(2**(nb_bits-1))  
samples = signal_int / (max_nb_bit + 1.0) 

使用nb_bits表示位深度,signal_int表示整数值。


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