如何分析 MP3 的 beat/drums 时间戳,同时触发动作和播放 (Rust)
How to analyze MP3 for beat/drums timestamps, trigger actions and playback at the same time (Rust)
我想在播放过程中出现 mp3 文件中的节拍或鼓时触发一个动作(例如让强光闪烁)。我不知道理论上 procedure/approach 我应该接受。
首先想到第一步静态分析MP3。分析的结果将是触发操作的时间戳。然后我启动 MP3,另一个线程在特定时间启动操作。这应该很容易,因为我可以使用 rodio
-crate 进行播放。但是静态分析部分还是比较重
分析算法:
我的想法是使用 minimp3
-crate 从 MP3 读取原始音频数据,然后使用 rustfft
-crate 进行 FFT。当我从 FFT 进行频谱分析时,我可以查看高音量的深频率位置,这应该是歌曲的节拍。
我尝试组合 minimp3
和 rustfft
但我完全不知道我得到的数据真正意味着什么..我也不能为它编写测试..
这是我目前的方法:
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<i16>
和 2304 个项目..
有什么想法可以实现吗?我目前得到的数字实际上意味着什么?
长话短说:
解耦分析和音频数据准备。 (1) 读取 MP3/WAV 数据,将两个通道连接到单声道(更容易分析),从数据中提取长度为 2 的幂的切片(对于 FFT;如果需要,用额外的零填充)和最后 (2) 将该数据应用于 crate spectrum_analyzer 并从代码(有很好的文档记录)中学习如何从 FFT 中获得某些频率的存在。
更长的版本
将问题解耦为更小的 problems/subtasks。
离散音频数据分析windows => 节拍:是或否
- a“window”通常是对正在进行的音频数据流的固定大小视图
- 在此处选择一种策略:例如低通滤波器、FFT、组合……在文献中搜索“节拍检测算法”
- 如果您正在进行 FFT,您应该将数据 window 扩展到 2 的下一个幂(例如,用零填充)。
读取mp3,将其转换为单声道,然后将音频样本逐步传递给分析算法。
- 您可以使用采样率和样本索引来计算时间点
- => 将“节拍:yes/no”附加到歌曲中的时间戳
分析部分应保持普遍可用,以便它适用于现场音频和文件。音乐通常以 44100Hz 或 48000Hz 和 16 位分辨率进行离散化。所有常见的音频库都会为您提供一个接口,以访问具有这些属性的麦克风的音频输入。如果您改为读取 MP3 或 WAV,则音乐(音频数据)通常采用相同的格式。例如,如果您在 44100Hz 处分析长度为 2048 的 windows,则每个 window 的长度为 1/f * n == T * n == n/f == (2048/44100)s == ~46,4ms
。时间 window 越短,您的节拍检测运行得越快,但您的准确度就越低 - 这是一个权衡:)
您的算法可以保留有关先前 windows 的知识以重叠它们以减少 noise/wrong 数据。
要查看解决这些子问题的现有代码,我建议使用以下 crates
- https://crates.io/crates/lowpass-filter : 简单的低通滤波器来获取数据的低频 window => (可能是) beat
- https://crates.io/crates/spectrum-analyzer:使用 FFT 对音频进行频谱分析 window 以及关于如何在存储库中完成它的出色文档
使用 crate beat detector 有一个解决方案几乎实现了这个问题的原始内容。它将实时音频输入与分析算法连接起来。
我想在播放过程中出现 mp3 文件中的节拍或鼓时触发一个动作(例如让强光闪烁)。我不知道理论上 procedure/approach 我应该接受。
首先想到第一步静态分析MP3。分析的结果将是触发操作的时间戳。然后我启动 MP3,另一个线程在特定时间启动操作。这应该很容易,因为我可以使用 rodio
-crate 进行播放。但是静态分析部分还是比较重
分析算法:
我的想法是使用 minimp3
-crate 从 MP3 读取原始音频数据,然后使用 rustfft
-crate 进行 FFT。当我从 FFT 进行频谱分析时,我可以查看高音量的深频率位置,这应该是歌曲的节拍。
我尝试组合 minimp3
和 rustfft
但我完全不知道我得到的数据真正意味着什么..我也不能为它编写测试..
这是我目前的方法:
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<i16>
和 2304 个项目..
有什么想法可以实现吗?我目前得到的数字实际上意味着什么?
长话短说:
解耦分析和音频数据准备。 (1) 读取 MP3/WAV 数据,将两个通道连接到单声道(更容易分析),从数据中提取长度为 2 的幂的切片(对于 FFT;如果需要,用额外的零填充)和最后 (2) 将该数据应用于 crate spectrum_analyzer 并从代码(有很好的文档记录)中学习如何从 FFT 中获得某些频率的存在。
更长的版本
将问题解耦为更小的 problems/subtasks。
离散音频数据分析windows => 节拍:是或否
- a“window”通常是对正在进行的音频数据流的固定大小视图
- 在此处选择一种策略:例如低通滤波器、FFT、组合……在文献中搜索“节拍检测算法”
- 如果您正在进行 FFT,您应该将数据 window 扩展到 2 的下一个幂(例如,用零填充)。
读取mp3,将其转换为单声道,然后将音频样本逐步传递给分析算法。
- 您可以使用采样率和样本索引来计算时间点
- => 将“节拍:yes/no”附加到歌曲中的时间戳
分析部分应保持普遍可用,以便它适用于现场音频和文件。音乐通常以 44100Hz 或 48000Hz 和 16 位分辨率进行离散化。所有常见的音频库都会为您提供一个接口,以访问具有这些属性的麦克风的音频输入。如果您改为读取 MP3 或 WAV,则音乐(音频数据)通常采用相同的格式。例如,如果您在 44100Hz 处分析长度为 2048 的 windows,则每个 window 的长度为 1/f * n == T * n == n/f == (2048/44100)s == ~46,4ms
。时间 window 越短,您的节拍检测运行得越快,但您的准确度就越低 - 这是一个权衡:)
您的算法可以保留有关先前 windows 的知识以重叠它们以减少 noise/wrong 数据。
要查看解决这些子问题的现有代码,我建议使用以下 crates
- https://crates.io/crates/lowpass-filter : 简单的低通滤波器来获取数据的低频 window => (可能是) beat
- https://crates.io/crates/spectrum-analyzer:使用 FFT 对音频进行频谱分析 window 以及关于如何在存储库中完成它的出色文档
使用 crate beat detector 有一个解决方案几乎实现了这个问题的原始内容。它将实时音频输入与分析算法连接起来。