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

如何在iOS原生端模拟Unity AudioSource.GetSpectrumData的FFT结果?

问题分析与解决方案

1. Unity频谱数组全长度的原因

Unity的GetSpectrumData返回的全长度数组是对称复制的结果。由于输入音频是实值信号,FFT的频域结果具有共轭对称性:后半部分(索引从fftSize/2到fftSize-1)完全镜像前半部分(索引0到fftSize/2-1)的有效值。Unity为了简化开发者使用,直接将前半部分复制到后半部分,所以看起来全数组都有有效值,实际上后半部分无额外信息。

2. 索引位置偏移的核心原因:采样率与FFT大小不匹配

你测试中出现的索引差异(Unity在9-11,原生FFT在4-6),本质是FFT大小或采样率与Unity不一致。频率bin的计算公式为:

每个bin的中心频率 = (采样率 × 索引) / FFT大小

以440Hz测试为例:

  • 若Unity使用1024点FFT、44100Hz采样率:每个bin频率≈44100/1024≈43.07Hz,440÷43.07≈10.22,对应索引9-11,与你的测试结果吻合。
  • 若你使用512点FFT、相同采样率:每个bin频率≈44100/512≈86.13Hz,440÷86.13≈5.11,对应索引4-6,正好是Unity索引的一半。

解决方法:

  • 将原生FFT的大小调整为与Unity完全一致(比如改成1024点)。
  • 确认原生音频库的采样率与Unity项目设置的采样率匹配(Unity默认是44100Hz或48000Hz)。

3. 数值差异的调整方向

你已经应用了汉明窗,接下来需调整幅值计算逻辑,匹配Unity的处理规则:

(1)Unity的幅值缩放规则

Unity的GetSpectrumData返回的幅值逻辑为:

  1. 对音频帧加汉明窗;
  2. 执行正向FFT;
  3. 计算每个bin的幅值(sqrt(real² + imag²));
  4. 除索引0(直流分量)和索引fftSize/2(奈奎斯特频率)外,其余幅值乘以2/FFT大小(等价于除以FFT大小/2)——这两个特殊bin没有对称的另一半,无需乘2。

你的代码对所有索引都执行sqrt(...) / fftSize * 2,和Unity逻辑几乎一致,但忽略了直流与奈奎斯特分量的特殊处理,不过这对440Hz测试影响极小。

(2)音频输入数据的归一化

确保原生音频库输出的data是归一化到[-1.0, 1.0]的浮点数据,与Unity的音频输入范围一致。如果原生数据是16位整数(范围[-32768, 32767]),需先转换为浮点:data[i] = (float)intData[i] / 32768.0f,否则数值会出现缩放偏差。

(3)FFT库的输出验证

检查ComputeFFTComplex的实现细节:

  • 确认参数true对应正向FFT;
  • 确认FFT输出的实部、虚部是否正确对应频域分量,部分库会在FFT后自动缩放,需调整系数抵消该影响。

4. 修正后的代码示例

针对1024点FFT(匹配Unity设置)的修正代码如下:

// 确保_fftSize与Unity的FFT大小一致,此处设为1024
memset(_imag, 0, _fftSize * sizeof(float));
// data必须是归一化到[-1,1]的单声道音频数据
memcpy(_real, data, _fftSize * sizeof(float));

// 应用预计算的汉明窗
for (size_t i = 0; i < _fftSize; i++) {
    _real[i] *= _window[i];
}

// 执行正向FFT
ComputeFFTComplex(_real, _imag, _logSize, true);

// 计算幅值并模拟Unity的全长度对称数组
for (size_t i = 0; i < _fftSize; i++) {
    float magnitude = sqrt(_real[i] * _real[i] + _imag[i] * _imag[i]);
    // 处理直流和奈奎斯特分量的特殊缩放
    if (i == 0 || i == _fftSize / 2) {
        _halfSpectrum[i] = magnitude / _fftSize;
    } else {
        _halfSpectrum[i] = magnitude * 2.0f / _fftSize;
    }
    // 复制前半部分到后半部分,模拟Unity的全长度数组
    if (i < _fftSize / 2) {
        _halfSpectrum[_fftSize - 1 - i] = _halfSpectrum[i];
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 02:46:06