从文件中提取快速傅里叶变换数据

11
我正在构建一个工具,它应该在服务器上运行并分析音频文件。由于我所有的其他工具都是用Ruby编写的,因此我想使用Ruby来完成这项任务。但我很难找到一个好的方法来实现它。
我发现的许多例子都做了可视化和图形方面的工作,而我只需要FFT数据,没有别的要求。我需要获取音频数据并对其进行FFT处理。我的最终目标是计算一些东西,如所有频率(加权幅度)上的平均值/中位数/众数、第25百分位数和第75百分位数、BPM,以及可能一些其他的好特性,以便后来能够将类似的声音聚集在一起。
首先,我尝试使用ruby-audio和fftw3,但我从未真正使两者配合工作。文档也不好,所以我真的不知道数据是如何传递的。 接下来,我尝试使用bplay / brec,并将我的Ruby脚本限制为仅使用STDIN并对其执行FFT(仍然使用fftw3)。但我无法让bplay / brec工作,因为服务器没有声卡,我也无法直接将音频直接发送到STDOUT而不经过音频设备。

这是我最接近的:

# extracting audio from wav with ruby-audio
buf = RubyAudio::Buffer.float(1024)
RubyAudio::Sound.open(fname) do |snd|
    while snd.read(buf) != 0
        # ???
    end
end

# performing FFT on audio
def get_fft(input, window_size)
    data = input.read(window_size).unpack("s*")
    na = NArray.to_na(data)
    fft = FFTW3.fft(na).to_a[0, window_size/2]
    return fft
end

现在我卡住了,在谷歌上找不到更多好的结果。也许你们 Stack Overflow 的人可以帮我吗?

谢谢!


也许之前的讨论会有所帮助:http://stackoverflow.com/questions/2834548/ruby-play-pause-resume-aac-audio-files - fmendez
你能详细说明一下为什么你卡住了吗?请包括错误信息或者你对事物应该如何工作的理解上的空缺。 - Randall Cook
我已经添加了我的代码。在使用ruby-audio读取数据和使用fftw3提取FFT之间存在巨大的差距。请参见带有三个问号的注释。我在buf中有wav数据,但我不知道这些数据到底是什么/代表什么。里面有头文件吗?它是否被压缩/编码?等等。我想将数据传递给get_fft(几乎直接从另一个SO帖子中获取)。 - Christoffer Reijer
2个回答

10
我认为这里有两个问题。一个是获取样本,另一个是执行FFT。
要获取样本,有两个主要步骤:解码和混音。要解码WAV文件,您只需要解析头文件,以便知道如何解释样本。对于MP3文件,您需要进行完整的解码。一旦音频被解码,如果您不想单独处理立体声通道,则可能需要将其混音为单声道,因为FFT需要单个通道作为输入。如果您不介意离开Ruby,sox工具可以轻松完成此操作。例如,sox song.mp3 -b 16 song.raw channels 1应将mp3转换为纯PCM样本(即16位整数)的单声道文件。顺便说一下,快速搜索发现ruby/audio库(也许是您帖子中提到的那个)。它看起来非常不错,特别是因为它包装了libsndfile。
要执行FFT,我看到三个选项。其中一个是使用此代码片段执行FFT。虽然我不是Ruby专家,但它看起来可能还不错。第二个选项是使用NArray。它有大量的数学方法,包括FFTW,在NArray页面中间链接的一个单独模块中可用。第三个选项是编写自己的FFT代码。这不是特别复杂的算法,并且可以让您在Ruby中获得出色的数字处理经验(如果您需要)。
您可能已经知道,但FFT期望复杂输入并生成复杂输出。音频信号当然是实际的,因此输入的虚部应始终为零(a + 0*i)。由于您的输入是实数,因此输出将关于输出数组的中点对称。您可以安全地忽略上半部分。如果您想要特定频率箱中的能量(它们以线性方式间隔到采样率的一半),则需要计算复杂值的幅度(sqrt(real*real + imag*imag))。
还有一件事:因为频率零(信号的DC偏移)和奈奎斯特频率(采样率的一半)没有相位组件,所以一些FFT实现将它们放在同一个复杂箱中(通常是第一个箱的实部和虚部各一个)。您可以创建一些简单的信号(只有直流信号的所有1和交替+1,-1的奈奎斯特信号),并查看FFT输出的样子。

谢谢你的长篇回答。这基本上是我一直在思考的方式。但我一直没有能够真正将所有这些东西整合起来。我添加了一些代码,以展示当使用ruby-audio(你提供的那个)和fftw3 gem时我所达到的最远程度。 - Christoffer Reijer
1
通常当我在组合事物时遇到困难,我会从非常小的开始,一步一步地添加,添加大量的诊断代码(或在调试器中仔细检查变量),以确保事情按预期工作:我能打开文件吗?我能读取数据吗?数据格式是否符合我的期望?我能转换数据吗?它看起来还正确吗?等等。 - Randall Cook
我应该将整个音波读入数组然后再将其作为NArray馈送到FFTW3.fft中吗? - Christoffer Reijer
无论哪种方式,FFT都会提供其输入所有频率组分的能量和相位。如果您传递整首歌曲,则会获得整首歌曲的变换,就好像整首歌曲在一个瞬间播放一样。有时这很有用(例如用于测量整体频率配置文件),但更常见的是将音频分成块(称为窗口或帧),并将它们顺序传递给FFT。这会产生随时间变化的频率配置文件。 - Randall Cook
FFT通常要求其输入大小为2的幂。有时会用零填充输入以使其达到正确的大小。1024个样本的块是一个不错的选择。有时人们将窗口重叠50%,特别是如果他们计划在频域中进行滤波。对于大小为1024的情况,这意味着“跳跃大小”为512个样本。通常人们在传递给FFT之前对输入进行“窗口化”(即淡入和淡出),这可以减少FFT中的噪声和伪像。查找Hanning和Hamming窗口函数以获取更多信息。 - Randall Cook
显示剩余4条评论

10

以下是我试图实现的最终解决方案,非常感谢Randall Cook提供的有用建议。以下是在Ruby中提取wav文件的声波和FFT的代码:

require "ruby-audio"
require "fftw3"

fname = ARGV[0]
window_size = 1024
wave = Array.new
fft = Array.new(window_size/2,[])

begin
    buf = RubyAudio::Buffer.float(window_size)
    RubyAudio::Sound.open(fname) do |snd|
        while snd.read(buf) != 0
            wave.concat(buf.to_a)
            na = NArray.to_na(buf.to_a)
            fft_slice = FFTW3.fft(na).to_a[0, window_size/2]
            j=0
            fft_slice.each { |x| fft[j] << x; j+=1 }
        end
    end

rescue => err
    log.error "error reading audio file: " + err
    exit
end

# now I can work on analyzing the "fft" and "wave" arrays...

1
看起来差不多就是这样。感谢你发布了你的代码,加1个赞。我很高兴你解决了问题并且能够创建出可用的东西。顺便说一下,在Stack Overflow上表达感谢的好方法是给答案点赞和/或接受答案,如果你还没有这样做的话。 ;) - Randall Cook
我已经为你的帖子点赞,但在接受自己的答案之前还需要等待一段时间。 :) - Christoffer Reijer
@ChristofferBrodd-Reijer,你的代码可以很好地对WAV文件进行指纹识别,但是指纹太大了。你有没有找到提高速度和缩小指纹的解决方案? - Rafael Fragoso
是的,我做了。我只在歌曲开头、中间和结尾的一个小部分(3-10秒)上进行了指纹处理。这已经足够解决我的问题了。 - Christoffer Reijer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接