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

从默认输入设备实时绘制频谱图失败:画面未随数据更新求助

实时滚动频谱图绘制异常排查与修复

问题描述

尝试使用matplotlib的FuncAnimation模块,基于默认音频输入设备的数据实时绘制从右向左滚动的频谱图,但图表不随缓冲区新数据更新,画面呈现异常。

原代码

from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np
import rtmixer
import math
import sounddevice as sd

FRAMES_PER_BUFFER = 512
NFFT = 4096
NOVERLAP = 128

window = 100000
downsample = 10  
channels = 1

def create_specgram(frame):
    global plotdata
    
    spec, freqs, t = plt.mlab.specgram(plotdata[:,-1], Fs=samplerate)
    xmin, xmax = np.min(t) - pad_xextent, np.max(t) + pad_xextent
    extent = xmin, xmax, freqs[0], freqs[-1]
    arr = np.flipud(10. * np.log10(spec))

    return arr, extent

def update_plot(frame):

    global plotdata

    while ringBuffer.read_available >= FRAMES_PER_BUFFER:
        read, buf1, buf2 = ringBuffer.get_read_buffers(FRAMES_PER_BUFFER)
        assert read == FRAMES_PER_BUFFER
        buffer = np.frombuffer(buf1, dtype='float32')
        buffer.shape = -1, channels
        buffer = buffer[::downsample]

        assert buffer.base.base == buf1
        shift = len(buffer)
        plotdata = np.roll(plotdata, -shift, axis=0)
        plotdata[-shift:, :] = buffer
        ringBuffer.advance_read_index(FRAMES_PER_BUFFER)

    arr, _  = create_specgram(frame)
    image.set_array(arr)
    return image,

device_info = sd.query_devices(device=None, kind='input')
samplerate = device_info['default_samplerate']

pad_xextent = (NFFT - NOVERLAP) / samplerate / 2
length = int(window * samplerate / (1000 * downsample))
plotdata = np.zeros((length, channels))

stream = rtmixer.Recorder(device=None, channels=channels, blocksize=FRAMES_PER_BUFFER,
                          latency='low', samplerate=samplerate)

ringbufferSize = 2**int(math.log2(3 * samplerate))

ringBuffer = rtmixer.RingBuffer(channels * stream.samplesize, ringbufferSize)

fig, ax = plt.subplots(figsize=(10, 5))
arr, extent = create_specgram(0)
image = plt.imshow(arr, animated=True, extent=extent, aspect='auto')
fig.colorbar(image)

ani = FuncAnimation(fig, update_plot, interval=1, blit=True, cache_frame_data=False)           

with stream:
    ringBuffer = rtmixer.RingBuffer(channels * stream.samplesize, ringbufferSize)
    action = stream.record_ringbuffer(ringBuffer)
    plt.show()

错误分析

  1. RingBuffer实例不匹配:在with stream:代码块内重新初始化了ringBuffer,导致音频流写入的是新缓冲区,而update_plot读取的是之前创建的旧缓冲区,两者完全脱节,没有数据流通。
  2. 频谱计算的数据源错误:plotdata[:,-1]索引错误,因为plotdata的形状是(length, channels),当channels=1时应该用plotdata[:,0],否则会引发索引越界或取到空数据。
  3. 未实现滚动的时间轴逻辑:每次调用create_specgram都基于全部plotdata计算频谱,时间轴范围固定在初始状态,且update_plot仅更新图像数据,未调整extent或轴范围,无法呈现滚动效果。
  4. plt.mlab.specgram的使用方式不符合滚动需求:该函数默认对整个输入数组做全局频谱分析,而非滑动窗口的实时片段分析,导致画面无法随新数据更新滚动。

修复后的代码

from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np
import rtmixer
import math
import sounddevice as sd

FRAMES_PER_BUFFER = 512
NFFT = 4096
NOVERLAP = 128
# 调整窗口长度为频谱分析所需的合适大小,避免过大的历史数据
WINDOW_DURATION = 2  # 秒
downsample = 10  
channels = 1

def create_specgram(audio_data, samplerate, time_offset):
    # 使用scipy.signal.spectrogram替代已弃用的plt.mlab.specgram
    from scipy.signal import spectrogram
    freqs, t, spec = spectrogram(
        audio_data,
        fs=samplerate,
        nperseg=NFFT,
        noverlap=NOVERLAP,
        mode='magnitude'
    )
    # 计算当前时间轴偏移,实现滚动
    t += time_offset
    # 转换为dB,加小值避免log(0)报错
    spec_db = 10 * np.log10(spec + 1e-10)
    arr = np.flipud(spec_db)
    # 固定x轴显示范围(最近WINDOW_DURATION秒)
    x_min = time_offset - WINDOW_DURATION
    x_max = time_offset
    extent = [x_min, x_max, freqs[0], freqs[-1]]
    return arr, extent, freqs

def update_plot(frame):
    global plotdata, current_time

    # 读取音频缓冲区数据
    while ringBuffer.read_available >= FRAMES_PER_BUFFER:
        read, buf1, buf2 = ringBuffer.get_read_buffers(FRAMES_PER_BUFFER)
        assert read == FRAMES_PER_BUFFER
        buffer = np.frombuffer(buf1, dtype='float32')
        buffer.shape = (-1, channels)
        buffer = buffer[::downsample]
        shift = len(buffer)
        
        # 更新滚动缓冲区
        plotdata = np.roll(plotdata, -shift, axis=0)
        plotdata[-shift:, :] = buffer
        ringBuffer.advance_read_index(FRAMES_PER_BUFFER)
        
        # 更新当前时间(按降采样后的速率计算)
        current_time += shift / (samplerate / downsample)

    # 取最新的音频数据段用于频谱分析,确保长度足够
    data_len = len(plotdata[:,0])
    analyze_data = plotdata[max(0, data_len - NFFT*2):, 0] if data_len > NFFT else plotdata[:,0]
    
    arr, extent, freqs = create_specgram(analyze_data, samplerate/downsample, current_time)
    image.set_array(arr)
    image.set_extent(extent)
    # 固定y轴范围(可选,根据需求调整)
    ax.set_ylim(freqs[0], freqs[-1])
    return image,

# 初始化音频设备
device_info = sd.query_devices(device=None, kind='input')
samplerate = device_info['default_samplerate']

# 计算缓冲区长度(降采样后的)
length = int(WINDOW_DURATION * samplerate / downsample) + NFFT
plotdata = np.zeros((length, channels))
current_time = 0

# 初始化音频流和RingBuffer
stream = rtmixer.Recorder(
    device=None,
    channels=channels,
    blocksize=FRAMES_PER_BUFFER,
    latency='low',
    samplerate=samplerate
)
ringbufferSize = 2**int(math.log2(3 * samplerate))
ringBuffer = rtmixer.RingBuffer(channels * stream.samplesize, ringbufferSize)

# 初始化图表
fig, ax = plt.subplots(figsize=(10, 5))
# 初始频谱计算
init_data = plotdata[:,0]
arr, extent, freqs = create_specgram(init_data, samplerate/downsample, current_time)
image = plt.imshow(arr, animated=True, extent=extent, aspect='auto', origin='lower')
fig.colorbar(image, label='dB')
ax.set_xlabel('Time (s)')
ax.set_ylabel('Frequency (Hz)')

# 创建动画
ani = FuncAnimation(
    fig,
    update_plot,
    interval=30,  # 调整为更合理的刷新间隔,避免过度占用资源
    blit=True,
    cache_frame_data=False
)           

# 启动音频流并显示图表
with stream:
    action = stream.record_ringbuffer(ringBuffer)
    plt.show()

修复关键点说明

  1. 统一RingBuffer实例:移除with stream:内的重复初始化,确保音频流和绘图逻辑使用同一个缓冲区。
  2. 替换频谱计算函数:用scipy.signal.spectrogram替代已弃用的plt.mlab.specgram,更稳定且功能更完整。
  3. 实现滚动时间轴:通过time_offset参数跟踪当前时间,每次更新时调整extent的x轴范围,固定显示最近一段时间的数据,模拟滚动效果。
  4. 优化缓冲区和数据读取:调整plotdata长度为适合实时分析的大小,仅取最新的片段计算频谱,避免冗余计算。
  5. 调整动画间隔:将interval从1ms改为30ms,平衡刷新速度和资源占用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 12:35:53