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

Python中如何实现实时FFT绘图并定位、绘制及输出峰值?

实时音频频谱峰值标记实时更新解决方案

问题描述

首次使用pyaudio和matplotlib库实现麦克风输入的实时音频频域绘图,按指定间距输出峰值。程序运行无报错、帧率正常,关闭窗口可终止,但存在旧峰值标记叠加显示的问题;尝试循环内清图导致画面完全空白,已解决峰值x坐标偏移问题,需实现峰值标记的实时更新。

解决方案

核心思路是复用峰值绘图对象,而非每次循环新增绘图元素;同时避免不必要的清图操作,只更新数据而非重绘整个画布。

修改后的完整代码

import pyaudio
import wave
import struct
from scipy.fftpack import fft
from scipy.signal import find_peaks
import matplotlib.pyplot as plt
import numpy as np

class Wave:
    def __init__(self, file) -> None:
        self.CHUNK = 1024 * 4
        self.obj = wave.open(file, "r")
        self.callback_output = []
        self.data = self.obj.readframes(self.CHUNK)
        self.rate = 44100

        # 初始化PyAudio实例
        self.p = pyaudio.PyAudio()

        # 打开音频流
        self.stream = self.p.open(format=pyaudio.paInt16,
                                  channels=self.obj.getnchannels(),
                                  rate=self.rate,
                                  output=True,
                                  input=True,
                                  frames_per_buffer=self.CHUNK)
        
    def fft_plot(self, distance: float):
        x_fft = np.linspace(0, self.rate, self.CHUNK)
        fig, ax = plt.subplots()
        # 初始化频谱线
        line_fft, = ax.semilogx(x_fft, np.random.rand(self.CHUNK), "-", lw=2)
        # 初始化峰值标记(空对象,后续更新数据)
        peaks_plot, = ax.plot([], [], "x", color="red")

        # 设置坐标轴范围
        ax.set_xlim(20, self.rate / 2)
        ax.set_ylim(0, 0.5)  # 固定y轴范围,避免画面跳动

        plt.show(block=False)

        # 绑定窗口关闭事件(只绑定一次)
        def on_close(event):
            self.stream.stop_stream()
            self.stream.close()
            self.p.terminate()
            plt.close()
            exit()
        fig.canvas.mpl_connect('close_event', on_close)

        while True:
            # 读取音频数据
            data = self.stream.read(self.CHUNK)
            
            # 转换为整数数组(匹配paInt16格式)
            self.data_int = struct.unpack(str(self.CHUNK * self.obj.getnchannels()) + 'h', data)
            
            # 计算FFT并更新频谱线
            yf = fft(self.data_int)
            line_data = np.abs(yf[0:self.CHUNK]) / (128 * self.CHUNK)
            line_fft.set_ydata(line_data)

            # 查找峰值
            peaks, _ = find_peaks(line_data, distance=distance)
            # 更新峰值标记的坐标
            peaks_plot.set_xdata(x_fft[peaks])  # 使用实际频率坐标而非索引
            peaks_plot.set_ydata(line_data[peaks])

            # 更新画布
            fig.canvas.draw()
            fig.canvas.flush_events()

test_file = "C:/Users/Tam/Documents/VScode/Final Project/PrismGuitars.wav"

audio_test = Wave(test_file)
audio_test.fft_plot(2000)

关键改动说明

  1. 复用峰值绘图对象:在初始化时创建空的峰值标记对象,每次循环仅更新其坐标数据,避免重复添加绘图元素导致叠加。
  2. 修正音频数据解析:匹配paInt16格式使用'h'(短整数)解析,结合声道数计算解析长度,避免数据格式不匹配。
  3. 正确映射峰值频率:用x_fft[peaks]获取峰值对应的实际频率值,确保坐标逻辑准确。
  4. 固定y轴范围:避免频谱跳动造成的视觉干扰,可根据实际需求调整范围值。
  5. 优化窗口关闭事件:仅绑定一次关闭逻辑,同时释放音频流和PyAudio资源,避免内存泄漏。
  6. 简化初始化流程:移除不必要的初始FFT计算,直接进入实时数据处理循环。

内容的提问来源于stack exchange,提问作者TamperedAether

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 22:07:29