一、MPEG音频编码原理
思想原理:利用人耳的听觉特性,去除掉无法被人👂感知的音频部分。
基本框架:最重要的是理解这种图以及”带着镣铐跳舞“这句话的含义。
MPEG-2音频编码器架构:
-
多项滤波器(32个子带):MPEG将输入的PCM音频码流变换为32个子带的频域信号,分解的思想其实就是子带编码思想(将原始信号分解为若干个子频带,对其分别进行编码,利用下采样能够降低传送数据的比特率,同时还能保持较小的失真范围。
-
比例因子提取:输入的1152个样本=32x12x3,32是指分解成的32个子带,在经过一次分解后需要等12次,相当于1组由12个取样点组成,先找出12个样点中绝对值的最大值作为公共比例因子;接着等待三组,从三组中抽出最大的比例因子进行量化(减小运算量)
(以上为时域部分要点)
(下面是频域部分要点)
-
心理声学模型:利用人耳的掩蔽特性,结合临界频带对输入的数据进行过滤,之保持频率最高的部分。
-
输入FFT(频域信号)以及比例因子
-
输出SMR(子带信号掩蔽比)
-
-
动态比特分配:根据心理声学模型的计算结果,为每个自带信号分配比特数。
- 输入比特预算和SMR
- 输出每个自带对应的量化数
1、心理声学模型构建
主要思想: 人耳的听觉系统中存在一个听觉阈值电平,低于这个电平的声音信号就听不到。
- 听觉阈值的大小随声音频率的改变而改变。
- 一个人是否听到声音取决于声音的频率,以及声音的幅度是否高于这种频率下的听觉阈值
- 人耳具有听觉掩蔽特性,人耳感知的听觉阈值电平是自适应的,会随听到的不同频率声音而发生变化
①听觉阈值:
-
两个声音响度级相同,但强度不一定相同,还与频率有关(图中密密麻麻的线,属于等响度曲线,同一条线上的响度级相同。观察同一条曲线上的两点,其强度不一定相同)
-
声压级越高,等响度曲线趋于平坦
②频域掩蔽:
- 听觉阈值电平是自适应的,会随听到的不同频率声音而发生变化
- 如果有多个频率成分的复杂信号存在,那么频谱的总掩蔽阈值与频率的关系取决于各掩蔽音的强度、频率和它们之间的距离。
③临界频带
含义:当某个纯音被以它为中心频率、且具有一定带宽的连续噪声所掩蔽时,如果该纯音刚好被听到时的功率等于这一频带内的噪声功率,这个带宽称之为临界频带宽度。
通常认为从20Hz到16kHz有25个临界频带,单位为bark,1 Bark = 一个临界频带的宽度:
④人耳听觉系统
主要思想:等效于一个信号通过一组并联的不同中心频率的带通滤波器。
- 中心频率与信号频率相同的滤波器具有最大响应;中心频率偏离信号频率较多的滤波器不会产生响应。
- 在0Hz到20KHz频率范围内由25个重叠的带通滤波器组成的滤波器组。
- 听音者在噪声中听某一纯音信号时,只启用中心频率与信号频率相同的那个听觉滤波器,纯音信号通过该滤波器,而噪声信号只有通带范围内的部分信号能通过,通带以外的频率成分则被抑制,只有通过该滤波器的噪声才对掩蔽起作用。
⑤掩蔽音的关系
掩蔽音与被掩蔽音的组合方式有四种,即它们分别可以是乐音信号(弦信号,Tone)或窄带噪声(noise)。乐音信号和窄带噪声信号作为掩蔽音时产生的掩蔽效果有很大不同。
- 噪声掩蔽纯音时,只有以纯音频率为中心的,一定频带宽度内的噪声能量起掩蔽作用,超出该频带的噪声能量无掩蔽效应,称掩蔽的临界带宽。掩蔽说明了频率选择性的极限
- 掩蔽纯音信号,理论上使用带宽范围等于其临界带宽1/3倍频程的窄带噪声,这是因为掩蔽的临界带宽稍窄,听起来接近于纯音,患者常常混淆纯音与掩蔽噪声,而以纯音频率为中心频率的1/3倍频程噪声,宽度略大于临界带宽,同样可以起到很好的掩蔽效果。但实际应用中根据IEC645规定,临床听力计上常使用约4/10倍频程的窄带噪声
回顾声学模型:
上面一条线:通过子带分析滤波器组使信号具有高的时间分辨率,确保在短暂冲击信号情况下,编码的声音信号具有足够高的质量。
下面一条线:通过FFT运算具有高的频率分辨率。
上面一条线是音频编码的主线部分,下面一条线的本质是为了帮助计算线性量化器的量化比特数。其中,下面一条线最为出彩的部分是心理声学模型的应用,它对去除冗余信息起到了极大的作用。
时域-频域矛盾
:举一个超级简单易懂的例子,不知道对不对,但是很好理解:
输入1152个样本,如果音频采样率是48kHz,对应的时间窗口就是24ms,观察一帧图像也就是按照24ms为单位。
反观频域:频率分辨率就是1/24,时间窗口越长(样本数越多),时域分辨率月底,而频域分辨率却越高。增大频域分辨力的同时时域分辨力必然会相应减小。
⑥动态比特分配
目的:使帧和每个子带的噪声-掩蔽比最小
- 在调整到固定的码率之前,首先需要确定可用于样值编码的有效比特数,这个数值取决于比例因子、比例因子选择信息、比特分配信息以及辅助数据所需比特数。
- 计算噪掩蔽的公式NMR=信掩比SMR-信噪比SNR(NMR表示波形误差和预测感知误差的关系,当NMR为负值时不需要考虑)
- 首先假设SNR=0,按照比特预算的比特数,优先考虑NMR值最大的子带。
- 每分配1bit就会降低6dB,依次循环~直到分配bit数=0或者所有子带的NMR都降低至0。
再次举一个超级简单的例子for example:
二、编码器设计分析
输入声音信号经过一个多相滤波器组,变换到多个子带,等待多组提取共同的比例因子,同时进行频率分析, 经过“心理声学模型” 计算以频率为自变量的噪声掩蔽阈值。量化和编码部分用信掩比SMR决定分配给子带信号的量化位数,使量化噪声小于掩蔽阈值。最后通过数据帧包装 将量化的子带样本和其它数据按照规定的帧格式组装成比特数据流。
首先我们查看命令行参数(命令行参数什么都不设置,输出,查看要求格式)
表示这里需要设置输入文件的名称以及输出文件的名称
下一步关注main函数:
int main(int argc, char** argv)
{
/********************************参数初始化***************************************************/
programName = argv[0];//argv[0]表示输入文件
if (argc == 1)
short_usage();
else
parse_args(argc, argv, &frame, &model, &num_samples, original_file_name,
encoded_file_name);//解析命令行参数
print_config(&frame, &model, original_file_name, encoded_file_name);//输出配置信息
hdr_to_frps(&frame);//加载信息头中解压出来的信息
nch = frame.nch;
error_protection = header.error_protection;
//按帧获取信息
while (get_audio(musicin, buffer, num_samples, nch, &header) > 0) {
if (glopts.verbosity > 1)
if (++frameNum % 10 == 0)
fprintf(stderr, "[%4u]\r", frameNum);
fflush(stderr);
win_buf[0] = &buffer[0][0];
win_buf[1] = &buffer[1][0];
adb = available_bits(&header, &glopts);//在码率分配前首先要计算码率预算,把计算所得的该帧总分配比特数赋给了adb,在adb获得值以后直接进行输出。
lg_frame = adb / 8;
if (header.dab_extension) {
if (header.sampling_frequency == 1)
header.dab_extension = 4;
if (frameNum == 1)
minimum = lg_frame + MINIMUM;
adb -= header.dab_extension * 8 + header.dab_length * 8 + 16;
}
{
int gr, bl, ch;
for (gr = 0; gr < 3; gr++)
for (bl = 0; bl < 12; bl++)
for (ch = 0; ch < nch; ch++)
WindowFilterSubband(&buffer[ch][gr * 12 * 32 + 32 * bl], ch,
&(*sb_sample)[ch][gr][bl][0]);//多相滤波器组
}
scale_factor_calc(*sb_sample, scalar, nch, frame.sblimit);//计算比例因子
pick_scale(scalar, &frame, max_sc);//选择其中最大的比例因子
if (frame.actual_mode == MPG_MD_JOINT_STEREO) {
combine_LR(*sb_sample, *j_sample, frame.sblimit);
scale_factor_calc(j_sample, &j_scale, 1, frame.sblimit);//计算比例因子选择信息
}
if ((glopts.quickmode == TRUE) && (++psycount % glopts.quickcount != 0)) {
for (ch = 0; ch < nch; ch++) {
for (sb = 0; sb < SBLIMIT; sb++)
smr[ch][sb] = smrdef[ch][sb];
}
}
else
{
//根据心理声学模型计算掩蔽电平
switch (model) {
case -1:
psycho_n1(smr, nch);
break;
case 0://心理声学模型A计算掩蔽电平
psycho_0(smr, nch, scalar, (FLOAT)s_freq[header.version][header.sampling_frequency] * 1000);//注意这里 输入比例因子,fft,输出SMR
break;
case 1:
psycho_1(buffer, max_sc, smr, &frame);
break;
case 2:
for (ch = 0; ch < nch; ch++) {
psycho_2(&buffer[ch][0], &sam[ch][0], ch, &smr[ch][0], //snr32,
(FLOAT)s_freq[header.version][header.sampling_frequency] *
1000, &glopts);
}
break;
case 3:
psycho_3(buffer, max_sc, smr, &frame, &glopts);
break;
case 4:
for (ch = 0; ch < nch; ch++) {
psycho_4(&buffer[ch][0], &sam[ch][0], ch, &smr[ch][0], // snr32,
(FLOAT)s_freq[header.version][header.sampling_frequency] *
1000, &glopts);
}
break;
case 5:
psycho_1(buffer, max_sc, smr, &frame);
fprintf(stdout, "1 ");
smr_dump(smr, nch);
psycho_3(buffer, max_sc, smr, &frame, &glopts);
fprintf(stdout, "3 ");
smr_dump(smr, nch);
break;
case 6:
for (ch = 0; ch < nch; ch++)
psycho_2(&buffer[ch][0], &sam[ch][0], ch, &smr[ch][0], //snr32,
(FLOAT)s_freq[header.version][header.sampling_frequency] *
1000, &glopts);
fprintf(stdout, "2 ");
smr_dump(smr, nch);
for (ch = 0; ch < nch; ch++)
psycho_4(&buffer[ch][0], &sam[ch][0], ch, &smr[ch][0], // snr32,
(FLOAT)s_freq[header.version][header.sampling_frequency] *
1000, &glopts);
fprintf(stdout, "4 ");
smr_dump(smr, nch);
break;
case 7:
fprintf(stdout, "Frame: %i\n", frameNum);
psycho_1(buffer, max_sc, smr, &frame);
fprintf(stdout, "1");
smr_dump(smr, nch);
psycho_3(buffer, max_sc, smr, &frame, &glopts);
fprintf(stdout, "3");
smr_dump(smr, nch);
for (ch = 0; ch < nch; ch++)
psycho_2(&buffer[ch][0], &sam[ch][0], ch, &smr[ch][0], //snr32,
(FLOAT)s_freq[header.version][header.sampling_frequency] *
1000, &glopts);
fprintf(stdout, "2");
smr_dump(smr, nch);
for (ch = 0; ch < nch; ch++)
psycho_4(&buffer[ch][0], &sam[ch][0], ch, &smr[ch][0], // snr32,
(FLOAT)s_freq[header.version][header.sampling_frequency] *
1000, &glopts);
fprintf(stdout, "4");
smr_dump(smr, nch);
break;
case 8:
/* Compare 0 and 4 */
psycho_n1(smr, nch);
fprintf(stdout, "0");
smr_dump(smr, nch);
for (ch = 0; ch < nch; ch++)
psycho_4(&buffer[ch][0], &sam[ch][0], ch, &smr[ch][0], // snr32,
(FLOAT)s_freq[header.version][header.sampling_frequency] *
1000, &glopts);
fprintf(stdout, "4");
smr_dump(smr, nch);
break;
default:
fprintf(stderr, "Invalid psy model specification: %i\n", model);
exit(0);
}
if (glopts.quickmode == TRUE)
/* copy the smr values and reuse them later */
for (ch = 0; ch < nch; ch++) {
for (sb = 0; sb < SBLIMIT; sb++)
smrdef[ch][sb] = smr[ch][sb];
}
if (glopts.verbosity > 4)
smr_dump(smr, nch);
}
transmission_pattern(scalar, scfsi, &frame);
main_bit_allocation(smr, scfsi, bit_alloc, &adb, &frame, &glopts);//进行比特分配
if (error_protection)
CRC_calc(&frame, bit_alloc, scfsi, &crc);//如果需要就添加CRC纠错
encode_info(&frame, &bs);
if (error_protection)
encode_CRC(crc, &bs);
encode_bit_alloc(bit_alloc, &frame, &bs);//将比特分配打包到比特流中
encode_scale(bit_alloc, scfsi, scalar, &frame, &bs);//将比例因子打包到比特流中
subband_quantization(scalar, *sb_sample, j_scale, *j_sample, bit_alloc,
*subband, &frame);//量化子带
sample_encoding(*subband, bit_alloc, &frame, &bs);//将子带打包到比特流中
...
exit (0);
}
着装关注几个非常重要的模块:比例因子提取、心理声学模型、动态比特分配
1、比例因子提取:
void scale_factor_calc (double sb_sample[][3][SCALE_BLOCK][SBLIMIT],
unsigned int scalar[][3][SBLIMIT], int nch,
int sblimit)
{
int k, t;
/* Using '--' loops to avoid possible "cmp value + bne/beq" compiler */
/* inefficiencies. Below loops should compile to "bne/beq" only code */
for (k = nch; k--;)
for (t = 3; t--;) {
int i;
for (i = sblimit; i--;) {//12个一组寻找子带中的最大值
int j;
unsigned int l;
register double temp;
unsigned int scale_fac;
register double cur_max = fabs (sb_sample[k][t][SCALE_BLOCK - 1][i]);
for (j = SCALE_BLOCK - 1; j--;) {
if ((temp = fabs (sb_sample[k][t][j][i])) > cur_max)
cur_max = temp;
}
for (l = 16, scale_fac = 32; l; l >>= 1) {//查找比例因子表中比这个最大值大的最小值作为比例因子
if (cur_max <= multiple[scale_fac])
scale_fac += l;
else
scale_fac -= l;
}
if (cur_max > multiple[scale_fac])
scale_fac--;
scalar[k][t][i] = scale_fac;//得到比例因子
}
}
}
2、选取最大的比例因子
void pick_scale (unsigned int scalar[2][3][SBLIMIT], frame_info * frame,
double max_sc[2][SBLIMIT])
{
int i, j, k, max;
int nch = frame->nch;
int sblimit = frame->sblimit;
for (k = 0; k < nch; k++)
for (i = 0; i < sblimit; max_sc[k][i] = multiple[max], i++)
for (j = 1, max = scalar[k][0][i]; j < 3; j++)
if (max > scalar[k][j][i])//循环找最大的比例因子
max = scalar[k][j][i];
for (i = sblimit; i < SBLIMIT; i++)
max_sc[0][i] = max_sc[1][i] = 1E-20;
}
3、心理声学模型(这里以我们使用的A模型分析)
void psycho_0(double SMR[2][SBLIMIT], int nch, unsigned int scalar[2][3][SBLIMIT], FLOAT sfreq) {
int ch, sb, gr;
int minscaleindex[2][SBLIMIT];//较小的指标意味着较大的比例因子
static FLOAT ath_min[SBLIMIT];
int i;
static int init = 0;
if (!init) {
FLOAT freqperline = sfreq / 1024.0;
for (sb = 0; sb < SBLIMIT; sb++) {
ath_min[sb] = 1000; /* set it huge */
}
//在每个子带中找到最小的ATH
for (i = 0; i < 512; i++) {
FLOAT thisfreq = i * freqperline;
FLOAT ath_val = ATH_dB(thisfreq, 0);
if (ath_val < ath_min[i >> 4])
ath_min[i >> 4] = ath_val;
}
init++;
}
//找到这32个子带中最小的比例因子
for (ch = 0; ch < nch; ch++)
for (sb = 0; sb < SBLIMIT; sb++)
minscaleindex[ch][sb] = scalar[ch][0][sb];
for (ch = 0; ch < nch; ch++)
for (gr = 1; gr < 3; gr++)
for (sb = 0; sb < SBLIMIT; sb++)
if (minscaleindex[ch][sb] > scalar[ch][gr][sb])
minscaleindex[ch][sb] = scalar[ch][gr][sb];
for (ch = 0; ch < nch; ch++)
for (sb = 0; sb < SBLIMIT; sb++)
SMR[ch][sb] = 2.0 * (30.0 - minscaleindex[ch][sb]) - ath_min[sb];
}
4、动态码率分配
void main_bit_allocation(double perm_smr[2][SBLIMIT],
unsigned int scfsi[2][SBLIMIT],
unsigned int bit_alloc[2][SBLIMIT], int* adb,
frame_info* frame, options* glopts)
{
...
//选择动态分配码率模式
if (glopts->vbr == FALSE) {
/* Just do the old bit allocation method */
noisy_sbs = a_bit_allocation(perm_smr, scfsi, bit_alloc, adb, frame);
}
else {
/* do the VBR bit allocation method *//动态分配码率
frame->header->bitrate_index = lower;
*adb = available_bits(frame->header, glopts);
{
int brindex;
int found = FALSE;
/* Work out how many bits are needed for there to be no noise (ie all MNR > 0.0 + VBRLEVEL) */计算如果全部消除噪声情况下所需要的比特数
int req =
VBR_bits_for_nonoise(perm_smr, scfsi, frame, glopts->vbrlevel);
/* Look up this value in the bitrateindextobits table to find what bitrate we should use for
this frame */在bitrateindextobits表中查找对此帧使用的比特率
for (brindex = lower; brindex <= upper; brindex++) {
if (bitrateindextobits[brindex] > req) {
guessindex = brindex;
found = TRUE;
break;
}
}
if (found == FALSE)
guessindex = upper;
}
frame->header->bitrate_index = guessindex;
*adb = available_bits(frame->header, glopts);
/* update the statistics */
vbrstats[frame->header->bitrate_index]++;
if (glopts->verbosity > 2) {
static int count = 0;
int i;
if ((count++ % 1000) == 0) {
for (i = 1; i < 15; i++)
fprintf(stdout, "%4i ", vbrstats[i]);
fprintf(stdout, "\n");
}
if (glopts->verbosity > 5)
fprintf(stdout,
"> bitrate index %2i has %i bits available to encode the %i bits\n",
frame->header->bitrate_index, *adb,
VBR_bits_for_nonoise(perm_smr, scfsi, frame,
glopts->vbrlevel));
}
noisy_sbs =
VBR_bit_allocation(perm_smr, scfsi, bit_alloc, adb, frame, glopts);
}
}
5、输出音频的采样率和目标码率
void print_config(frame_info* frame, int* psy, char* inPath,
char* outPath)
{
frame_header* header = frame->header;
if (glopts.verbosity == 0)
return;
fprintf(stderr, "--------------------------------------------\n");
fprintf(stderr, "Input File : '%s' %.1f kHz\n",
(strcmp(inPath, "-") ? inPath : "stdin"),
s_freq[header->version][header->sampling_frequency]);//输入文件路径和音频采样率
fprintf(stderr, "Output File: '%s'\n",
(strcmp(outPath, "-") ? outPath : "stdout"));//输出文件路径
fprintf(stderr, "%d kbps ", bitrate[header->version][header->bitrate_index]);//音频采样率
fprintf(stderr, "%s ", version_names[header->version]);
if (header->mode != MPG_MD_JOINT_STEREO)
fprintf(stderr, "Layer II %s Psycho model=%d (Mode_Extension=%d)\n",
mode_names[header->mode], *psy, header->mode_ext);//采用的心理声学模型
else
fprintf(stderr, "Layer II %s Psy model %d \n", mode_names[header->mode],
*psy);
fprintf(stderr, "[De-emph:%s\tCopyright:%s\tOriginal:%s\tCRC:%s]\n",
((header->emphasis) ? "On" : "Off"),
((header->copyright) ? "Yes" : "No"),
((header->original) ? "Yes" : "No"),
((header->error_protection) ? "On" : "Off"));
fprintf(stderr, "[Padding:%s\tByte-swap:%s\tChanswap:%s\tDAB:%s]\n",
((glopts.usepadbit) ? "Normal" : "Off"),
((glopts.byteswap) ? "On" : "Off"),
((glopts.channelswap) ? "On" : "Off"),
((glopts.dab) ? "On" : "Off"));
if (glopts.vbr == TRUE)
fprintf(stderr, "VBR Enabled. Using MNR boost of %f\n", glopts.vbrlevel);
fprintf(stderr, "ATH adjustment %f\n", glopts.athlevel);
fprintf(stderr, "--------------------------------------------\n");
}
最终得到输出结果
输入文件名:test.wav 音频的采样率:44.1KHz
输出文件名:test.mp2 音频的目标码率为192Kbps
在main函数中加入代码输出比特分配结果
if (frameNum == 1)
{
FILE* result;
result = fopen("result.txt", "w");
// 输出比例因子
fprintf(result, "比例因子//\n");
for (int k = 0; k < nch; k++) // 每个声道单独输出
{
fprintf(result, "声道%d\n", k + 1);
for (sb = 0; sb < frame.sblimit; sb++) // 每个子带
{
fprintf(result, "子带[%d]:\t", sb + 1);
for (int gr = 0; gr < 3; gr++) {
fprintf(result, "%2d\t", scalar[k][gr][sb]);
}
fprintf(result, "\n");
}
}
fprintf(result, "\n");
//输出比特分配结果
fprintf(result, "比特分配//\n");
for (int k = 0; k < nch; k++)//每个声道单独输出
{
fprintf(result, "声道%d:\n", k + 1);
for (int i = 0; i < SBLIMIT; i++)//每个子带有一个比特分配结果
{
fprintf(result, "子带%d:\t", i);
fprintf(result, "%d\n", bit_alloc[k][i]);
}
}
fclose(result);
}
三、分析乐音、噪声、混合音三个音频文件
1、噪声
输入文件名:nosie.wav 音频的采样率:48KHz
输出文件名:nosie.mp2 音频的目标码率为192Kbps
2、test乐音
输入文件名:test.wav 音频的采样率:44.1KHz
输出文件名:test.mp2 音频的目标码率为192Kbps
3、mix混合音
输入文件名:mix.wav 音频的采样率:48KHz
输出文件名:mix.mp2 音频的目标码率为192Kbps
3、修改程序~输出比例因子、比特预算、比特分配结果
if (frameNum == 1)//输出第一帧为例
{
FILE* result;
result = fopen("result.txt", "w");
// 输出比例因子
fprintf(result, "比例因子//\n");
for (int k = 0; k < nch; k++) // 每个声道单独输出
{
fprintf(result, "声道%d\n", k + 1);
for (sb = 0; sb < frame.sblimit; sb++) // 每个子带
{
fprintf(result, "子带[%d]:\t", sb + 1);
for (int gr = 0; gr < 3; gr++) {
fprintf(result, "%2d\t", scalar[k][gr][sb]);
}
fprintf(result, "\n");
}
}
fprintf(result, "\n");
//输出比特分配结果
fprintf(result, "比特分配//\n");
for (int k = 0; k < nch; k++)//每个声道单独输出
{
fprintf(result, "声道%d:\n", k + 1);
for (int i = 0; i < SBLIMIT; i++)//每个子带有一个比特分配结果
{
fprintf(result, "子带%d:\t", i);
fprintf(result, "%d\n", bit_alloc[k][i]);
}
}
//输出比特预算
fprintf(result, "比特预算//\n");
fprintf(result, "%d bits\n", adb);
}
4、查看result结果:
噪音 | 乐音 | 混合音 | |
---|---|---|---|
比例因子选择 | |||
比特分配 | |||
比特预算 |
总结
- MPEG的关键思想——掌握“时频两条线”(主线是时域线,最终的量化还是在时域线里进行,频域相当于只是利用生理特性限制条件)
- 声学心理模型中,首先需要计算比特预算,将比特预算和频域共同输入声学模型,得到SMR(每个子带的信号-掩蔽比)