一、MP2编码原理
1.设计思想
MP2的基本设计思想如上框图所示,可以大致分为上下2条线。上面一条线利用多相滤波器组将PCM样本变换到32个子带的频域信号,每个子带取12个样本点,再根据动态比特分配信息分别进行量化。下面一条线对输入的PCM码流做1024点FFT后,进行心理声学模型分析,并根据码率,动态分配比特。
还要注意比例因子的提取问题:对各个子带每12个样点进行一次比例因子计算。先定出12个样点中绝对值的最大值。查比例因子表中比这个最大值大的 最小值作为比例因子。用6比特表示。每帧中每个子带的三个比例因子被一起考虑,划分成特定的几种模式。根据这些模式,1个、2个或3个比例因 子和比例因子选择信息一起被传送。
2.时-频分析的矛盾
短时傅里叶变换通过时间窗内的一段信号来表示某一时刻的信号特征。在短时傅里叶变换过程中,窗的长度决定频谱图的时间分辨率和频率分辨率,窗长越长,截取的信号越长,信号越长,傅里叶变换后频率分辨率越高,时间分辨率越差;相反,窗长越短,截取的信号就越短,频率分辨率越差,时间分辨率越好。框图上支路时间分辨率高,频率分辨率低;框图下支路频率分辨率高,时间分辨率低。通过子带分析滤波器组使信号具有高的时间分辨率, 确保在短暂冲击信号情况下,编码的声音信号具有足够高的质量。下支路又可以使信号通过FFT运算具有高的频率分辨率, 因为掩蔽阈值是从功率谱密度推出来的。32个等分的子带信号并不能精确地反映人耳的听觉特性,引入FFT补偿频率分辨率不足的问题。为使上下两支路互补,上支路向下提供比例因子信息,下支路向上提供基于心理声学模型的动态比特分配。
3.心理声学模型
心理声学模型:计算信号中不可听觉感知的部分
听觉临界频带:当噪声掩蔽纯音时,起作用的是以纯音频率为中心频率的一定频带宽度内的噪声频率。如这频带内的噪声功率等于在噪声中刚能听到的该纯音的功率,则这频带就称为听觉临界频带。临界频带表征了人类最主要的听觉特性,它是在研究纯音对窄带噪声掩蔽量的规律时被发现的,在加宽噪声带宽时,最初是掩蔽量增大,但带宽超过某一定值后,掩蔽量就不再增加,这一带宽就称为临界频带。
计算过程:
(1)将样本变换到频域
(2)确定声压级别
(3)考虑安静时阈值
(4)将音频信号分解成“乐音(tones)” 和“非乐音/噪声”
部分:因为两种信号的掩蔽能力不同
(5)音调和非音调掩蔽成分消除
(6)计算单个掩蔽阈值
音调成分和非音调成分单个掩蔽阈值根据标准中给出的算法求得
(7)计算全局掩蔽阈值
同时考虑别的临界频带的影响
(8)选择子带中最小的阈值作为子带阈值
(9)计算每个子带信号掩蔽比(signal-to-mask ratio, SMR),SMR = 信号能量 / 掩蔽阈值,并将SMR传递给编码单元
4.量化编码
对每个子带进行量化,量化方式应当使得量化噪声听不见。量化时注意只需发送掩蔽水平以上的能量。
比特分配的过程:
对每个子带计算掩蔽-噪声比MNR,MNR = SNR–SMR,每一次对最高NMR的子带分配比特,使整帧和每个子带的总噪声—掩蔽比最小,循环,直到没有比特可用。
二、程序设计的整体框架
1.将输入样本变换到32个子带的频域信号
for( gr = 0; gr < 3; gr++ ) //3个块
for ( bl = 0; bl < 12; bl++ ) //每个块取12个样点
for ( ch = 0; ch < nch; ch++ )
WindowFilterSubband( &buffer[ch][gr * 12 * 32 + 32 * bl], ch,
&(*sb_sample)[ch][gr][bl][0] );
2.计算比例因子和选择比例因子信息
scale_factor_calc (*sb_sample, scalar, nch, frame.sblimit);
pick_scale (scalar, &frame, max_sc);
if (frame.actual_mode == MPG_MD_JOINT_STEREO) {
/* this way we calculate more mono than we need */
/* but it is cheap */
combine_LR (*sb_sample, *j_sample, frame.sblimit);
scale_factor_calc (j_sample, &j_scale, 1, frame.sblimit);
}
3.基于心理声学模型,计算smr
if ((glopts.quickmode == TRUE) && (++psycount % glopts.quickcount != 0)) {
/* We're using quick mode, so we're only calculating the model every
'quickcount' frames. Otherwise, just copy the old ones across */
for (ch = 0; ch < nch; ch++) {
for (sb = 0; sb < SBLIMIT; sb++)
smr[ch][sb] = smrdef[ch][sb];
}
} else {
/* calculate the psymodel */
switch (model) {
case -1:
psycho_n1 (smr, nch);
break;
case 0: /* Psy Model A */
psycho_0 (smr, nch, scalar, (FLOAT) s_freq[header.version][header.sampling_frequency] *