原文链接:Onset Detection Part 4: MP3 decoding and more Plotting
在本系列的第一篇文章中,我告诉您如何自己解码 MP3文件。嗯,作为一个好的运动,我扩展了框架,做了一个基于 JLayer 的漂亮的小 MP3 解码器。这是一个类似于 Wave 解码器的类:
public class MP3Decoder
{
public MP3Decoder( InputStream in );
public void readFrames( float[] samples );
}
它的工作原理与 Wave 解码器完全相同。正确的面向对象编程意味着两个解码器共享同一个接口,但我有点懒。如果你觉得里面的界面比较模糊,可以自己添加到源代码中。
我今天对框架做的另一件事是,现在我们可以设置一个标记线,这样我们就可以看到音频当前播放到的位置。下面是 Plot 类中的新方法:
public class Plot
{
...
public void setMarker( int position, Color color );
}
第一个参数指定标记线应该位于图中的x坐标。第二个是标记线的颜色。我还更改了 Plot 类的内部工作方式。图现在会不断地被重新绘制,并且几乎立即对变化做出反应。为了演示 Plot.setMarker() 方法的使用,我在 RealTimePlot 中编写了一个简单的示例。我只重构了与上一节不同的部分:
public class RealTimePlot
{
private static final int SAMPLE_WINDOW_SIZE = 1024;
private static final String FILE = "samples/sample.mp3";
public static void main( String[] argv ) throws FileNotFoundException, Exception
{
float[] samples = readInAllSamples( FILE );
Plot plot = new Plot( "Wave Plot", 1024, 512 );
plot.plot( samples, SAMPLE_WINDOW_SIZE, Color.red );
MP3Decoder decoder = new MP3Decoder( new FileInputStream( FILE ) );
AudioDevice device = new AudioDevice( );
samples = new float[SAMPLE_WINDOW_SIZE];
long startTime = 0;
while( decoder.readSamples( samples ) > 0 )
{
device.writeSamples( samples );
if( startTime == 0 )
startTime = System.nanoTime();
float elapsedTime = (System.nanoTime()-startTime)/1000000000.0f;
int position = (int)(elapsedTime * (44100/SAMPLE_WINDOW_SIZE));
plot.setMarker( position, Color.white );
Thread.sleep(15); // this is needed or else swing has no chance repainting the plot!
}
}
...
}
首先,像前面一样从 MP3 文件加载所有样本数据。然后使用单位像素 1024 个样本绘制这些样本(这是SAMPLE_WINDOW_SIZE,我们有一个 1024 个样本的窗口……)。现在,我们可以播放这个文件,并通过观察移动的标记线(从窗口顶部到底部的一个垂线,随着时间的流逝而更新)来查看当前播放的位置。
设置 mp3 解码器和音频设备现在应该很熟悉了。我们还需要一个浮点数组,用于从 MP3Decoder 中读取用于播放的样本数据。然后我们进入解码/回放循环。我们读取当前样本窗并将其写入音频设备,就像之前所做的那样。神奇的部分来了。在向设备写入第一个窗之后,我们开始测量运行时间。为此,我们必须知道播放何时开始,这是在条件 if ( startTime == 0 ) 的主体中完成的。当前时间减去开始时间除以10亿,得到播放开始后经过的时间(以秒为单位)。我们在下一行计算。在解码并编写第一个样本窗之后,我们记录开始时间,因为该窗口将音频输出与我们的绘图同步。writesamples() 方法将阻塞,直到将所有样本写入声音设备为止。因此,标记线的位置最多会慢一个像素,这对于我们目的来说是完全可以接受的。
剩下的工作就是根据经过的时间计算标记在图中的像素位置,并相应地设置标记。标记的像素位置的神奇公式是经过的时间乘以采样频率除以图中每个像素的采样数量。就是这样。如果你仔细想想,你会发现我是如何得出这个公式的。如果没有,再好好想想!剩下的是设置我们在下一行中要做的绘图的标记位置。最后,我们将解码线程休眠15毫秒,以给完成所有绘图的 Swing 线程一些时间。否则,标记线将无法平稳移动。
下面是输出结果的图像:
欢呼!实时绘制!要查看这个示例的实际操作,请通过您选择的 SVN 客户端从 http://audio-analysis.googlecode.com/svn/trunk/ 下载源代码,将项目导入 eclipse 并启动 RealTimePlot.java 示例。
下节课,我们要开始做一些数学魔术来检测 onset 。我们在这里使用的实时绘图将有助于直观地查看 onset 相对于音频播放的位置。