You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Node.js中从WAV/AIFF提取时频幅信息生成频谱图数组

在Node.js中提取音频频谱并生成目标格式数组的解决方案

首先得帮你解开那个疑惑:FFT结果长度是采样长度的一半完全是正常现象!因为我们处理的音频是实信号(只有振幅,没有虚部),FFT的计算结果会呈现共轭对称性——后半部分的频谱是前半部分的镜像,所以只需要保留前半段(从0Hz到奈奎斯特频率,也就是采样率的一半)就足够包含所有有效频率信息啦。

接下来咱们一步步实现你要的功能,从音频文件读取到最终生成[[[频率1_t1,振幅_f1t1],...],...]格式的数组:

1. 选择合适的依赖库

之前你提到浏览器用Web Audio API,Node.js里旧库废弃的问题,现在推荐几个稳定可用的工具:

  • wavefile:用来解析WAV/AIFF文件,提取采样数据、采样率等关键参数
  • fft-js:轻量的FFT实现,你已经在试用,刚好可以继续用它

先安装依赖:

npm install wavefile fft-js

2. 完整实现代码

下面是可运行的示例,我会逐段解释关键逻辑:

const WaveFile = require('wavefile').WaveFile;
const { fft, util } = require('fft-js');
const fs = require('fs');

// 配置参数
const FRAME_SIZE = 1024; // 每帧的采样点数,常用1024/2048
const HOP_SIZE = FRAME_SIZE / 2; // 帧移,通常取帧长的一半,保证时间连续性

async function generateSpectrogram(audioFilePath) {
  // 1. 读取并解析音频文件
  const buffer = fs.readFileSync(audioFilePath);
  const wav = new WaveFile(buffer);
  
  // 转换为单声道(如果是立体声的话),方便处理
  if (wav.fmt.numChannels > 1) {
    wav.toMono();
  }
  
  // 获取采样数据(转换为[-1, 1]范围的浮点数)和采样率
  const sampleRate = wav.fmt.sampleRate;
  const samples = util.toFloat32Array(wav.data.samples);
  
  // 2. 分帧处理音频
  const frames = [];
  for (let i = 0; i + FRAME_SIZE <= samples.length; i += HOP_SIZE) {
    const frame = samples.slice(i, i + FRAME_SIZE);
    // 加汉明窗,减少频谱泄漏(非常重要!)
    const windowedFrame = frame.map((sample, index) => {
      return sample * (0.54 - 0.46 * Math.cos(2 * Math.PI * index / (FRAME_SIZE - 1)));
    });
    frames.push(windowedFrame);
  }
  
  // 3. 对每帧做FFT,生成频谱数据
  const spectrogram = [];
  const freqStep = sampleRate / FRAME_SIZE; // 每个频率bin的间隔
  
  for (const frame of frames) {
    // 计算FFT
    const fftResult = fft(frame);
    // 只取前半部分(有效频谱)
    const halfFFT = fftResult.slice(0, FRAME_SIZE / 2);
    
    // 转换为[频率, 振幅]的数组
    const frameSpectrum = halfFFT.map((complex, binIndex) => {
      // 计算振幅:复数的模(绝对值),然后归一化
      const amplitude = Math.sqrt(complex[0] ** 2 + complex[1] ** 2) / (FRAME_SIZE / 2);
      // 计算当前bin对应的频率
      const frequency = binIndex * freqStep;
      return [frequency, amplitude];
    });
    
    spectrogram.push(frameSpectrum);
  }
  
  return spectrogram;
}

// 调用示例
generateSpectrogram('./test.wav')
  .then(spectrogram => {
    console.log('生成的频谱图数组:', spectrogram);
    // 这里可以把结果保存到文件或者做后续处理
  })
  .catch(err => console.error('处理出错:', err));

3. 关键逻辑解释

  • 分帧与加窗:音频是连续信号,我们需要切成固定长度的帧来分析每个时间点的频谱。加汉明窗是为了减少帧边缘的突变导致的频谱泄漏,让频率分析更准确。
  • FFT结果处理:因为实信号的对称性,我们只取前FRAME_SIZE/2个结果。每个bin对应的频率用binIndex * 采样率 / FRAME_SIZE计算,振幅是复数的模,除以FRAME_SIZE/2是为了归一化,让振幅范围更合理。
  • 单声道转换:如果是立体声文件,先转成单声道,避免处理多通道的复杂度,如果你需要保留多通道,可以单独处理每个通道的频谱。

4. 额外优化建议

  • 如果需要更高的频率分辨率,可以增大FRAME_SIZE(比如2048或4096),但时间分辨率会降低,这是频谱分析的 trade-off,根据你的需求调整。
  • 如果处理大文件,建议用流式读取,避免一次性加载全部采样数据占用过多内存。
  • 如果你需要更高效的FFT计算,可以考虑用dsp.js或者基于FFTW的node-fftw库,性能会更好。

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

火山引擎 最新活动