【问题标题】:How to analyze MP3 for beat/drums timestamps, trigger actions and playback at the same time (Rust)如何同时分析 MP3 的节拍/鼓时间戳、触发动作和播放 (Rust)
【发布时间】:2020-07-11 17:30:25
【问题描述】:

当播放期间 mp3 文件中的节拍或鼓声出现时,我想触发一个动作(例如让强光闪烁)。我不知道我应该采取的理论上的程序/方法。

首先我想到了在第一步中静态分析 MP3。分析的结果将是应该在哪个时间戳触发操作。然后我启动 MP3,另一个线程在特定时间启动动作。这应该很容易,因为我可以使用rodio-crate 进行播放。但是静态分析部分还是很重的。

分析算法:

我的想法是使用 minimp3-crate 从 MP3 读取原始音频数据,并使用 rustfft-crate 进行 FFT。当我从 FFT 进行频谱分析时,我可以查看高音量的深层频率,这应该是歌曲的节拍。

我尝试将 minimp3rustfft 结合起来,但我完全不知道我得到的数据的真正含义。我也无法为它编写测试。

这是我目前的方法:

use minimp3::{Decoder, Frame, Error};

use std::fs::File;
use std::sync::Arc;
use rustfft::FFTplanner;
use rustfft::num_complex::Complex;
use rustfft::num_traits::{Zero, FromPrimitive, ToPrimitive};

fn main() {
    let mut decoder = Decoder::new(File::open("08-In the end.mp3").unwrap());

    loop {
        match decoder.next_frame() {
            Ok(Frame { data, sample_rate, channels, .. }) => {
                // we only need mono data; because data is interleaved
                // data[0] is first value channel left, data[1] is first channel right, ...
                let mut mono_audio = vec![];
                for i in 0..data.len() / channels {
                    let sum = data[i] as i32 + data[i+1] as i32;
                    let avg = (sum / 2) as i16;
                    mono_audio.push(avg);
                }
                // unnormalized spectrum; now check where the beat/drums are 
                // by checking for high volume in low frequencies
                let spectrum = calc_fft(&mono_audio);
            },
            Err(Error::Eof) => break,
            Err(e) => panic!("{:?}", e),
        }
    }
}

fn calc_fft(raw_mono_audio_data: &Vec<i16>) -> Vec<i16> {
    // Perform a forward FFT of size 1234

    let len = raw_mono_audio_data.len();

    let mut input:  Vec<Complex<f32>> = vec![];
    //let mut output: Vec<Complex<f32>> = vec![Complex::zero(); 256];
    let mut spectrum: Vec<Complex<f32>> = vec![Complex::zero(); len];

    // from Vec<i16> to Vec<Complex<f32>>
    raw_mono_audio_data.iter().for_each(|val| {
        let compl = Complex::from_i16(*val).unwrap();
        input.push(compl);
    });

    let mut planner = FFTplanner::new(false);
    let fft = planner.plan_fft(len);
    fft.process(&mut input, &mut spectrum);

    // to Vec<i16>
    let mut output_i16 = vec![];
    spectrum.iter().for_each(|val| {
        if let Some(val) = val.to_i16() {
            output_i16.push(val);
        }
    });

    output_i16
}

我的问题还在于 FFT 函数没有任何可以指定 sample_rate(即 48.000kHz)的参数。我从decoder.next_frame() 得到的只是Vec&lt;i16&gt; 2304 项..

有什么想法可以实现这一目标以及我目前得到的数字实际上意味着什么?

【问题讨论】:

  • 你解决了这个问题吗?我遇到了同样的问题(不是相同的语言,而是相同的概念),而且似乎没有很好的记录
  • 是的,我做到了。我发布了这个@Maxime 的答案

标签: audio rust fft


【解决方案1】:

TL;DR:

解耦分析和音频数据准备。 (1) 读取 MP3/WAV 数据,将两个通道连接到单声道(更容易分析),从数据中获取长度为 2 次方的切片(对于 FFT;如果需要,用额外的零填充)最后( 2) 将该数据应用于板条箱spectrum_analyzer 并从代码中学习(有很好的文档)如何从 FFT 中获得某些频率的存在。

加长版

将问题分解为更小的问题/子任务。

  1. 在离散窗口中分析音频数据 => 节拍:是或否

    • “窗口”通常是一个固定大小的视图,可以看到正在进行的音频数据流
    • 在此处选择一种策略:例如低通滤波器、FFT、组合……在文献中搜索“节拍检测算法”
      • 如果您正在执行 FFT,则应始终将数据窗口扩展到 2 的下一个幂(例如用零填充)。
  2. 读取 mp3,将其转换为单声道,然后将音频样本逐步传递给分析算法。

    • 您可以使用采样率采样索引来计算时间点
    • => 将“beat: yes/no”附加到歌曲中的时间戳

分析部分应保持一般可用,以便它适用于现场音频和文件。音乐通常以 44100Hz 或 48000Hz 和 16 位分辨率离散化。所有常见的音频库都会为您提供一个接口,以通过这些属性访问来自麦克风的音频输入。如果您改为阅读 MP3 或 WAV,则音乐(音频数据)通常采用相同的格式。例如,如果您在 44100Hz 下分析长度为 2048 的窗口,则每个窗口的长度为 1/f * n == T * n == n/f == (2048/44100)s == ~46,4ms。时间窗口越短,您的节拍检测可以运行得越快,但您的准确性会越低 - 这是一个权衡:) 您的算法可以保留有关先前窗口的知识以将它们重叠以减少噪声/错误数据。

要查看解决这些子问题的现有代码,我建议使用以下 crates

使用 crate beat detector 有一个解决方案几乎可以实现这个问题的原始内容。它将实时音频输入与分析算法连接起来。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-13
    • 2013-01-19
    • 1970-01-01
    • 2017-10-12
    相关资源
    最近更新 更多