用java做音乐识别软件

转载地址来自 http://www.importnew.com/21839.html

shazam 是一款用来分析/匹配音乐的应用程序。当你将它安装在手机上并用麦克风采集音源20到30秒,它就能告诉你这是首什么歌。

我第一次使用时感觉太神奇了。“它是怎么办到的!?”。甚至是今天,用了很久后,我依然觉得它有些神奇。如果我们能编写出可以带来相同感觉的程序会不会更棒呢?这是我在上周末的目标。

听着……!

先说重要的,为了获取音乐样品来分析,我们首先需要在 Java 中听取麦克风……!我从没有用 Java 实现过这个,所以我并不清楚会有多难。但结果是这很简单:

1
2
3
4
5
final AudioFormat format = getFormat(); //Fill AudioFormat with the wanted settings
DataLine.Info info = new DataLine.Info(TargetDataLine. class , format);
final TargetDataLine line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format);
line.start();

现在我们只要像普通 InputStream 那样从 TargetDataLine 中读取数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// In another thread I start:
 
OutputStream out = new ByteArrayOutputStream();
running = true ;
 
try {
     while (running) {
         int count = line.read(buffer, 0 , buffer.length);
         if (count > 0 ) {
             out.write(buffer, 0 , count);
         }
     }
     out.close();
} catch (IOException e) {
     System.err.println( "I/O problems: " + e);
     System.exit(- 1 );
}

使用这种方式会让打开麦克风和录取所有声音的操作变得非常简单!我现在正在使用的AudioFormat是:

1
2
3
4
5
6
7
8
private AudioFormat getFormat() {
     float sampleRate = 44100 ;
     int sampleSizeInBits = 8 ;
     int channels = 1 ; //mono
     boolean signed = true ;
     boolean bigEndian = true ;
     return new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian);
}

所以,现在我们获得了用 ByteArrayOutputStream 包装的录音数据,很好!第一步完成了。

麦克风数据

下个问题是分析数据,当我输出在字节数组中获取的数据时,我获得了一串长长的数据列表,像是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0
0
1
2
4
7
6
3
- 1
- 2
- 4
- 2
- 5
- 7
- 8
(etc)

额……对吗?这是声音吗?

为了让数据可视化我将输出放入Open Office形成一张线状图表:

01

 

 

 

 

 

 

 

哦是的!这有点像是’声音‘!就像是使用 Windows Sound Recorder 时看到的一样。

这种数据通常是 时间域 。但这些数据目前对我们来说基本上是无用的……如果你阅读了上面 Shazam 工作方式的文章,你会发现他们使用的是 频谱分析器 而不是直接使用时间域数据。

所以下个大问题是:我们怎么将现在的数据转换成频谱分析?

离散傅里叶变化

为了将数据转换成有用的数据我们需要应用所谓的 离散傅里叶变换 。它能将数据从时域转换成频域。

仅仅有一个问题,如果将数据转换成频域,每一位与时间有关的信息会变得松散。所以需要知道所有频率的大小,但并不清楚他们什么时候出现。

我们需要滑动窗口来解决这个问题。取出数据块(在我的例子中是 4096 字节数据)并且转换这部分信息。那么就知道了出现在 4096字节中所有频率的大小。

实现这些

我并不担心傅里叶变换,而是在谷歌搜索到了所谓的FFT(快速傅里叶转换)的代码,我调用了这个代码块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
byte audio[] = out.toByteArray();
 
final int totalSize = audio.length;
 
int amountPossible = totalSize/Harvester.CHUNK_SIZE;
 
//When turning into frequency domain we'll need complex numbers:
Complex[][] results = new Complex[amountPossible][];
 
//For all the chunks:
for ( int times = 0 ;times < amountPossible; times++) {
     Complex[] complex = new Complex[Harvester.CHUNK_SIZE];
     for ( int i = 0 ;i < Harvester.CHUNK_SIZE;i++) {
         //Put the time domain data into a complex number with imaginary part as 0:
         complex[i] = new Complex(audio[(times*Harvester.CHUNK_SIZE)+i], 0 );
     }
     //Perform FFT analysis on the chunk:
     results[times] = FFT.fft(complex);
}
 
//Done!

现在我们有了一个 double 数组,它包含了所有 Complex[] 类的数据块。这个数组包含了关于频率的数据。为了将数据可视化我决定实现一个完整的频谱分析器(只是为了保证我获得正确的数据)。我设计了这个程序来显示数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for ( int i = 0 ; i < results.length; i++) {
     int freq = 1 ;
     for ( int line = 1 ; line < size; line++) {
         // To get the magnitude of the sound at a given frequency slice
         // get the abs() from the complex number.
         // In this case I use Math.log to get a more managable number (used for color)
         double magnitude = Math.log(results[i][freq].abs()+ 1 );
 
         // The more blue in the color the more intensity for a given frequency point:
         g2d.setColor( new Color( 0 ,( int )magnitude* 10 ,( int )magnitude* 20 ));
         // Fill:
         g2d.fillRect(i*blockSizeX, (size-line)*blockSizeY,blockSizeX,blockSizeY);
 
         // I used a improviced logarithmic scale and normal scale:
         if (logModeEnabled && (Math.log10(line) * Math.log10(line)) > 1 ) {
             freq += ( int ) (Math.log10(line) * Math.log10(line));
         } else {
             freq++;
         }
     }
}

介绍一下,Aphex Twin

似乎有些跑题了,但我还想介绍这位名叫 Aphex Twin(Richard David James)的电子音乐家。他创作疯狂的电子音乐。。。。。。但一些歌曲有着有趣的特点。他大热的作品,比如 Windowlicker 中就有频谱图像。如果将这首歌看成是频谱的图像,它展示了一幅漂亮的螺旋。另一首称为“Mathematical Equation”的歌曲显示了 Twin 的脸!在这里可以发现更多信息: Bastwood - Aphex Twin’s face 。

在我的频谱分析器中播放歌曲获得下面结果:

02

 

 

 

 

 

 

 

 

不是很完美,但似乎就是Twin 的脸!

决定关键音乐的点

Shazam 算法下一步是决定歌曲中一些关键点,将这些点存储为哈希表并且尝试与数据库中超过 8 百万的歌曲匹配。这些操作被快速完成,哈希表的查找速度是 O(1)级的。这就解释了 Shazam 令人惊叹的执行力。

因为我想要在一个周末让一切运转起来(可悲的是这是我最长的注意力持续时间,随后我需要开始一个新的工程)我尽可能使我的算法简单。惊奇的是它运转起来了。

我在频谱分析每行中取出一定范围内量级最大的点。在我的例子中范围是:40—80、80—120、120—180、180—300。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//For every line of data:
 
for ( int freq = LOWER_LIMIT; freq < UPPER_LIMIT- 1 ; freq++) {
     //Get the magnitude:
     double mag = Math.log(results[freq].abs() + 1 );
 
     //Find out which range we are in:
     int index = getIndex(freq);
 
     //Save the highest magnitude and corresponding frequency:
     if (mag > highscores[index]) {
         highscores[index] = mag;
         recordPoints[index] = freq;
     }
}
 
//Write the points to a file:
for ( int i = 0 ; i < AMOUNT_OF_POINTS; i++) {
     fw.append(recordPoints[i] + "\t" );
}
fw.append( "\n" );
 
// ... snip ...
 
public static final int [] RANGE = new int [] { 40 , 80 , 120 , 180 , UPPER_LIMIT+ 1 };
 
//Find out in which range
public static int getIndex( int freq) {
     int i = 0 ;
     while (RANGE[i] < freq) i++;
         return i;
     }
}

现在录取一首歌,我们会得到一个数字列表,像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
33  56  99  121 195
30  41  84  146 199
33  51  99  133 183
33  47  94  137 193
32  41  106 161 191
33  76  95  123 185
40  68  110 134 232
30  62  88  125 194
34  57  83  121 182
34  42  89  123 182
33  56  99  121 195
30  41  84  146 199
33  51  99  133 183
33  47  94  137 193
32  41  106 161 191
33  76  95  123 185

如果我录取一首歌并将其可视化,看起来像这样:

03

 

 

 

 

 

 

 

 

(所有红点都是‘重要的点’)

把我的音乐编入索引

为了使算法就绪,我决定将我3000首歌编入索引。你可以仅仅打开mp3文件而不使用麦克风,将文件转换成正确的格式,和使用麦克风一样的方式用 AudioInputStream 读取它们。将立体声音乐转换成单声道音频比我想象的要复杂。在网上找得到的示例(需要在这里粘贴太多的代码了)需要更改一些取样。

匹配!

这个程序最重要的部分是匹配的过程。Shazam 文档显示,他们使用散列法获取匹配然后决定哪首歌是最佳匹配。

与其最后使用点集,我决定使用一串数据(例如“33、47、94、137”)作为一个散列:1370944733(在我的测试中使用3或4个点效果最佳,但是调整很困难,我每次都需要重新检索我的mp3!)

每行使用 4 个点作为哈希码的例子:

1
2
3
4
5
6
7
8
9
10
11
//Using a little bit of error-correction, damping
private static final int FUZ_FACTOR = 2 ;
 
private long hash(String line) {
     String[] p = line.split( "\t" );
     long p1 = Long.parseLong(p[ 0 ]);
     long p2 = Long.parseLong(p[ 1 ]);
     long p3 = Long.parseLong(p[ 2 ]);
     long p4 = Long.parseLong(p[ 3 ]);
     return  (p4-(p4%FUZ_FACTOR)) * 100000000 + (p3-(p3%FUZ_FACTOR)) * 100000 + (p2-(p2%FUZ_FACTOR)) * 100 + (p1-(p1%FUZ_FACTOR));
}

现在我创建两个数据集:

-一个歌曲列表,List (列表索引是歌曲ID, 字符串是歌曲名)

-哈希表的数据库:Map<Long,List>

哈希表数据库中的 long 代表着哈希表本身,包含许多 DataPoints 对象。

一个DataPoint是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private class DataPoint {
 
     private int time;
     private int songId;
 
     public DataPoint( int songId, int time) {
         this .songId = songId;
         this .time = time;
     }
 
     public int getTime() {
         return time;
     }
     public int getSongId() {
         return songId;
     }
}

现在我们已经为查找准备好了一切。首先我读取所有的歌曲并且为每个数据点形成哈希表并放进哈希数据库。

第二步是读取我们需要匹配的歌曲数据。检索获得哈希表并查看匹配数据点。

有一个问题,每个哈希表有许多匹配记录,我们怎么决定哪首歌是正确的呢……?匹配的数量?不,这是不可行的。

最重要的是计时。我们必须将时间点重叠……!但是如果我们不知道处在歌曲哪部分该怎么办呢?毕竟,我们本可以简单录取歌曲的最后和弦。

我在查看数据时发现有趣的事情,因为我们有以下的数据:

-一个录音的哈希表

-一个可能匹配的匹配哈希

-一个可能匹配的歌曲ID

-我们自己录音的现在时间

-在可能匹配中的哈希的时间。

现在我们可以用匹配哈希的时间(例如1352行)减去录音的现在时间(例如34行)。这个差和歌曲ID一起存储。起点和差值告诉我们可能处在的歌曲位置。

当我们在录音中完成所有的哈希表时,我们获得了许多歌曲ID和偏移。酷炫的是,如果你获得了许多有匹配偏移的哈希表,你也就找到了歌曲。

结果

例如,听20秒的The Kooks - Match Box,这是程序的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Done loading: 2921 songs
 
Start matching song...
 
Top 20 matches:
 
01 : 08_the_kooks_-_match_box.mp3 with 16 matches.
02 : 04 Racoon - Smoothly.mp3 with 8 matches.
03 : 05 Röyksopp - Poor Leno.mp3 with 7 matches.
04 : 07_athlete_-_yesterday_threw_everyting_a_me.mp3 with 7 matches.
05 : Flogging Molly - WMH - Dont Let Me Dia Still Wonderin.mp3 with 7 matches.
06 : coldplay - 04 - sparks.mp3 with 7 matches.
07 : Coldplay - Help Is Round The Corner (yellow b-side).mp3 with 7 matches.
08 : the arcade fire - 09 - rebellion (lies).mp3 with 7 matches.
09 : 01 -coldplay-_clocks.mp3 with 6 matches.
10 : 02 Scared Tonight.mp3 with 6 matches.
11 : 02 -radiohead-pyramid_song-ksi.mp3 with 6 matches.
12 : 03 Shadows Fall.mp3 with 6 matches.
13 : 04 Röyksopp - In Space.mp3 with 6 matches.
14 : 04 Track04.mp3 with 6 matches.
15 : 05 - Dress Up In You.mp3 with 6 matches.
16 : 05 Supergrass - Can't Get Up.mp3 with 6 matches.
17 : 05 Track05.mp3 with 6 matches.
18 : 05The Fox In The Snow.mp3 with 6 matches.
19 : 05_athlete_-_wires.mp3 with 6 matches.
20 : 06 Racoon - Feel Like Flying.mp3 with 6 matches.
 
Matching took: 259 ms
 
Final prediction: 08_the_kooks_-_match_box.mp3.song with 16 matches.

这是可行的!!

听20秒就可以匹配几乎我拥有的所有歌曲。甚至是可以在听取这个 编辑器的现场录音 40秒后匹配正确的歌曲!

再次感觉神奇!:-)

目前,这段代码不是发行版也没有运行得很完美。它只是一个周末的拼拼凑凑,更像是理论证明/算法探索。

或许,如果许多人问起它,我会整理后在某处发布。

更新:

Shazam 专利持有人的律师给我发送邮件阻止我发布代码并且要求删除博客,在这里可以看到事件原委。

  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值