Python解析WAV文件识别音符时遇struct unpack需2字节缓冲区错误
解决WAV音符识别中struct.unpack的缓冲区错误
问题描述
尝试用Python从WAV文件识别音符时,触发struct.error: unpack requires a buffer of 2 bytes错误。查阅struct文档及相关资料未找到有效解决方法,曾见过类似错误但原因不同(如要求4字节、1024字节缓冲区的情况)。
完整代码
import numpy as np import math import wave import os import struct import matplotlib.pyplot as plt def note_detect(audio_file): #------------------------------------------- #here we are just storing our sound file as a numpy array #you can also use any other method to store the file as an np array file_length=audio_file.getnframes() f_s=audio_file.getframerate() #sampling frequency sound = np.zeros(file_length) #blank array for i in range(file_length) : wdata=audio_file.readframes(1) data=struct.unpack("<h",wdata) sound[i] = int(data[0]) plt.plot(sound) plt.show() sound=np.divide(sound,float(2**15)) #scaling it to 0 - 1 counter = audio_file.getnchannels() #number of channels mono/sterio #------------------------------------------- plt.plot(sound) plt.show() #fourier transformation from numpy module fourier = np.fft.fft(sound) fourier = np.absolute(fourier) imax=np.argmax(fourier[0:int(file_length/2)]) #index of max element plt.plot(fourier) plt.show() #peak detection i_begin = -1 threshold = 0.3 * fourier[imax] for i in range (0,imax+100): if fourier[i] >= threshold: if(i_begin==-1): i_begin = i if(i_begin!=-1 and fourier[i]<threshold): break i_end = i imax = np.argmax(fourier[0:i_end+100]) freq=(imax*f_s)/(file_length*counter) #formula to convert index into sound frequency #frequency database note=0 name = np.array(["C0","C#0","D0","D#0","E0","F0","F#0","G0","G#0","A0","A#0","B0","C1","C#1","D1","D#1","E1","F1","F#1","G1","G#1","A1","A#1","B1","C2","C#2","D2","D#2","E2","F2","F#2","G2","G2#","A2","A2#","B2","C3","C3#","D3","D3#","E3","F3","F3#","G3","G3#","A3","A3#","B3","C4","C4#","D4","D4#","E4","F4","F4#","G4","G4#","A4","A4#","B4","C5","C5#","D5","D5#","E5","F5","F5#","G5","G5#","A5","A5#","B5","C6","C6#","D6","D6#","E6","F6","F6#","G6","G6#","A6","A6#","B6","C7","C7#","D7","D7#","E7","F7","F7#","G7","G7#","A7","A7#","B7","C8","C8#","D8","D8#","E8","F8","F8#","G8","G8#","A8","A8#","B8","Beyond B8"]) frequencies = np.array([16.35,17.32,18.35,19.45,20.60,21.83,23.12,24.50,25.96 ,27.50 ,29.14 ,30.87 ,32.70 ,34.65 ,36.71 ,38.89 ,41.20 ,43.65 ,46.25 ,49.00 ,51.91 ,55.00 ,58.27 ,61.74 ,65.41 ,69.30 ,73.42 ,77.78 ,82.41 ,87.31 ,92.50 ,98.00 ,103.83 ,110.00 ,116.54 ,123.47 ,130.81 ,138.59 ,146.83 ,155.56 ,164.81 ,174.61 ,185.00 ,196.00 ,207.65 ,220.00 ,233.08 ,246.94 ,261.63 ,277.18 ,293.66 ,311.13 ,329.63 ,349.23 ,369.99 ,392.00 ,415.30 ,440.00 ,466.16 ,493.88 ,523.25 ,554.37 ,587.33 ,622.25 ,659.26 ,698.46 ,739.99 ,783.99 ,830.61 ,880.00 ,932.33 ,987.77 ,1046.50 ,1108.73 ,1174.66 ,1244.51 ,1318.51 ,1396.91 ,1479.98 ,1567.98 ,1661.22 ,1760.00 ,1864.66 ,1975.53 ,2093.00 ,2217.46 ,2349.32 ,2489.02 ,2637.02 ,2793.83 ,2959.96 ,3135.96 ,3322.44 ,3520.00 ,3729.31 ,3951.07 ,4186.01 ,4434.92 ,4698.64 ,4978.03 ,5274.04 ,5587.65 ,5919.91 ,6271.93 ,6644.88 ,7040.00 ,7458.62 ,7902.13,8000]) #searching for matched frequencies for i in range(0,frequencies.size-1): if(freq<frequencies[0]): note=name[0] break if(freq>frequencies[-1]): note=name[-1] break if freq>=frequencies[i] and frequencies[i+1]>=freq : if freq-frequencies[i]<(frequencies[i+1]-frequencies[i])/2 : note=name[i] else : note=name[i+1] break return note if __name__ == "__main__": path = os.getcwd() file_name = path + "\\" + "recording0.wav" audio_file = wave.open(file_name) Detected_Note = note_detect(audio_file) print("\n\tDetected Note = " + str(Detected_Note))
报错堆栈
Traceback (most recent call last): File "C:\Users\m8\Desktop\programing_stuff\python-stuff\minecraft_flute_player - 12-08-2022\app.py", line 86, in <module> Detected_Note = note_detect(audio_file) File "C:\Users\m8\Desktop\programing_stuff\python-stuff\minecraft_flute_player - 12-08-2022\app.py", line 23, in note_detect data=struct.unpack("<h",wdata) struct.error: unpack requires a buffer of 2 bytes
解决思路与方案
根本原因
你硬编码使用<h(2字节小端短整型)解析音频帧,但你的WAV文件可能不是16位PCM格式(比如是8位、32位浮点格式),或者逐帧读取时意外读到了文件末尾的空数据。另外逐帧读取的方式效率极低,还容易出现边界问题。
方案1:适配WAV实际格式,批量读取解析
先获取WAV文件的参数,动态确定struct解析格式,同时一次性读取所有帧批量解析,避免逐帧读取的问题:
def note_detect(audio_file): # 获取音频完整参数 nchannels, sampwidth, framerate, nframes, comptype, compname = audio_file.getparams() f_s = framerate # 根据采样宽度确定struct格式符:8位用b,16位用h,32位用i fmt_map = {1: "b", 2: "h", 4: "i"} fmt = "<" + fmt_map[sampwidth] # 一次性读取所有帧,批量解析 wdata = audio_file.readframes(nframes) data = struct.unpack(fmt * nframes * nchannels, wdata) # 转换为numpy数组,处理多通道 sound = np.array(data).reshape(-1, nchannels) # 立体声转单声道(取均值) if nchannels > 1: sound = sound.mean(axis=1) else: sound = sound.flatten() # 归一化到0-1区间 max_val = float(2 ** (sampwidth * 8 - 1)) sound = sound / max_val # 后续FFT、峰值检测等逻辑保持不变...
方案2:用numpy/scipy直接读取WAV(更简便)
完全可以跳过wave和struct模块,用scipy.io.wavfile.read直接读取WAV文件,自动处理格式和多通道问题:
from scipy.io import wavfile def note_detect(file_path): # 直接读取采样率和音频数据 f_s, sound = wavfile.read(file_path) # 处理多通道 if len(sound.shape) > 1: sound = sound.mean(axis=1) # 归一化到0-1区间 max_val = float(2 ** (sound.dtype.itemsize * 8 - 1)) sound = sound / max_val # 后续FFT、峰值检测等逻辑保持不变... if __name__ == "__main__": path = os.getcwd() file_name = os.path.join(path, "recording0.wav") Detected_Note = note_detect(file_name) print("\n\tDetected Note = " + str(Detected_Note))
额外优化
原代码中频率计算的file_length*counter可以替换为len(sound),因为len(sound)就是单通道的实际帧数,计算更准确。
内容的提问来源于stack exchange,提问作者somethingidk
相关产品推荐
相关产品推荐

