听歌识曲的原理是什么?

2,283 阅读7分钟

[toc]

引言

在任何时候听到一首正在播放的音乐时,都可以拿出手机,打开听歌识曲功能进行识别,仅仅几秒钟就可以得到识别结果。

听歌识曲使用的技术称之为音频指纹技术。

音频信号

音频信号是模拟信号,我们需要将其保存为数字信号,计算机才能够处理。

img

PCM

PCM(Pulse Code Modulation)也被称为脉冲编码调制。

PCM音频数据是未经压缩的音频采样数据裸流,它是由模拟信号经过采样、量化、编码转换成的标准的数字音频数据。

PCM的部分重要参数:

  • 采样频率:一秒内对声音信号的采集次数,44100Hz采样频率意味着每秒钟信号被分解成44100份,如果采样率高,那么媒体播放音频时会感觉信号是连续的。
  • 量化位数:用多少bit表达一次采样所采集的数据,通常有8bit、16bit、24bit和32bit等几种
  • 声道数:单声道、双声道等
  • 比特率:数据带宽,如:44.1KHz×16bit×2声道=1411.2 Kbps=176.4KB/s,即1分钟约10MB存储空间。

常见的描述:

44100HZ 16bit stereo:每秒钟有 44100 次采样,采样数据用 16 位(2字节)记录,双声道(立体声)

22050HZ 8bit mono:每秒钟有 22050 次采样,采样数据用 8 位(1字节)记录,单声道

《等你下课》的PCM:

audio_fingerprint_mp3.png

为什么是44100HZ

香农采样定理:只要采样频率大于或等于有效信号最高频率的两倍,采样值就可以包含原始信号的所有信息,被采样的信号就可以不失真地还原成原始信号。

人耳听觉对于频率响应的感知范围大致在20Hz~22kHz

WAV

WAV文件 = 文件头 + 音频数据(大部分是PCM)

没有画图,直接上代码

//pcm转wav,返回wav内存指针和wav长度
void *pcmToWav(const void *pcm, unsigned int pcmlen, unsigned int *wavlen) {
    //44字节wav头
    void *wav = malloc(pcmlen + 44);
    //wav文件多了44个字节
    *wavlen = pcmlen + 44;
    //添加wav文件头
    memcpy(wav, "RIFF", 4);
    *(int *) ((char *) wav + 4) = pcmlen + 36;
    memcpy(((char *) wav + 8), "WAVEfmt ", 8);
    *(int *) ((char *) wav + 16) = 16;
    *(short *) ((char *) wav + 20) = 1;
    *(short *) ((char *) wav + 22) = 1;
    *(int *) ((char *) wav + 24) = 8000;
    *(int *) ((char *) wav + 28) = 16000;
    *(short *) ((char *) wav + 32) = 16 / 8;
    *(short *) ((char *) wav + 34) = 16;
    strcpy((char *) ((char *) wav + 36), "data");
    *(int *) ((char *) wav + 40) = pcmlen;

    //拷贝pcm数据到wav中
    memcpy((char *) wav + 44, pcm, pcmlen);
    return wav;
}

可以看到WAV文件是由44字节的文件头加PCM数据组成。

MP3

简单的理解为PCM数据的有损压缩。

常说128K的MP3,就是1411.2 Kbps的PCM压缩后的结果。

音频指纹设计

人类听歌识曲的过程大致如下:

audio_fingerprint1.png

将上述生理过程转换为技术实现,应变为:

audio_fingerprint2.png

  1. 将音频转换为数字信号
  2. 提取音频信号中的特征
  3. 根据特征构造音频指纹
  4. 使用音频指纹进行相似度检索,得到结果

主要参考论文:

  1. PANAKO - A SCALABLE ACOUSTIC FINGERPRINTING SYSTEM HANDLING TIME-SCALE AND PITCH MODIFICATION

  2. An Industrial-Strength Audio Search Algorithm

频谱图

分帧

分帧是将不定长的音频切分成固定长度的小段。或者说把连续若干个点设为一帧。

语音信号虽然是随时间变化的,但是在一个短时间范围内(一般认为在10~30ms),其特性基本保持不变即相对稳定,即语音信号具有短时平稳性。所以可以进行“短时分析”:将语音信号分为一段一段来分析其特征参数,其中每一段称为一“帧”。

分帧一般采用交叠分段的方法,这是为了使帧与帧之前平滑过渡,保持其连续性。前一针和后一帧的交叠部分称为帧移。帧移与帧长的比值一般取为0~1/2。

audio_fingerprint4.jpg

可以去了解:频谱能量泄漏

频域处理

每个信号都可以分解为一系列正余弦波的组合。

重复的在一个小时间窗口上运用快速傅立叶变换(FFT),就得到了频谱图(spectrogram),横轴为时间,纵轴为频率,颜色越亮代表着能量(对应音量)越大。

《等你下课》频谱图:

audio_fingerprint9.png

局部最大值

假设已经得到了音频信号的时频表示:时频谱图(spectrogram),就可以开始计算局部最大值:(时间,频率)对所对应的振幅值若相比临近节点都大,那就是一个峰值。

audio_fingerprint6.png audio_fingerprint5.png

通过极大值选取,复杂的频谱图(1A)就简化成了稀疏的极大值坐标(1B)。可以将这些稀疏的坐标称为“星状图”。

在要匹配的样本中也会存在相同的星状图。如果将数据库中某首歌的星状图散乱在一个条形图上,然后将几秒样本的星状图放在一个透明的塑料板上。在条形图上滑行塑料板(有点像游标卡尺),到某个时刻的时候就会出现一件神奇的事情:当样本和数据库歌曲的正确位置对齐时,重叠的极大值就会格外多,这样就意味着样本和数据库中正确音乐的正确位置匹配上了!

构造指纹

然而,直接利用星状图来计算正确的偏移会非常缓慢。需要设计一种快速索引的方法:

核心是将多个峰值点组合在一起构成一个指纹哈希。

假设用2个点组成哈希:首先选取一个峰值点,也叫锚点(anchor point),每个峰值点都有一个与之关联的目标区域(target zone)。每个点以此与区域内的点组合,产生两个频率和一个时间差:hash:time = [f1 : f2 : t2 - t1]:t1。每个指纹都带有一个从开始到该点的时间差t1。

将hash,也就是[f1 : f2 : t2 - t1]编码为一串数字,如2493962、4400900等,就变成了指纹哈希。

audio_fingerprint7.png

audio_fingerprint8.png

目标区域与锚点的距离不宜太近,也不宜太远。

指纹匹配算法

执行搜索前,首先需要从录音样本中提取所有的哈希值和时间偏移。样本中提取的哈希都会用来和数据库中的哈希进行匹配。每一个匹配的哈希会产生一个时间对:样本中的时间和数据库中的时间。根据对应的歌曲ID将时间对分类。

每个歌曲对应的所有时间对构成一个散点图(scatterplot),如果歌曲和样本匹配,则与匹配上的特征会有相近的相对时间偏移,也即样本中提取的指纹和正确歌曲匹配上的指纹具有相同的相对时间(这个相对时间其实就是样本在原始歌曲中的起始位置)。这样就可以将搜索问题简化为散点图中寻找明显对角线的问题。

audio_fingerprint10.png

样本和正确歌曲中匹配指纹的时间满足以下关系:

其中,audio_fingerprint_tk1.png表示原始歌曲中匹配指纹的时间,audio_fingerprint_tk.png表示录音样本中对应指纹的时间。对于散点图中的每一个点(audio_fingerprint_tk1.pngaudio_fingerprint_tk.png),计算:

x = audio_fingerprint_tk1.png - audio_fingerprint_tk.png

然后计算x的直方图并获得直方图的最大值(统计相同时间差个数的最大值)。

audio_fingerprint12.png

每首歌对应的得分也即直方图的最大值。如果直方图的最大值很大,那就意味着该首歌就是要识别的正确歌曲。图2A是一个错误的匹配,图2B是对应的时间差直方图;图3A是一个正确的匹配,图3B是对应的时间差直方图。

工程落地

可以构造这样的数据库:

audio_fingerprint13.png