You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 08:48:21