数据压缩(十三)——MPEG音频编码原理及编码器调试
文章目录
- 一、MPEG音频编码原理
- 1.1 基本思想
- 1.2 心理声学模型(Psychoacoustic model)
- 1.2.1 听觉阈值
- 1.2.2 频域掩蔽
- 1.3 临界频带(Critical Band)
- 1.4 人耳听觉系统
- 1.5 掩蔽效果的加和
- 二、MPEG音频压缩编码器
- 2.1 多相滤波器组(Polyphase Filter Bank)
- 2.2 心理声学模型
- 2.3 量化和编码
- 2.3.1 比例因子的取值和编码
- 2.3.2 比特分配及编码
- 2.3.3 数据帧包装
- 三、MPEG音频编码器调试
- 3.1 理解程序设计的整体框架
- 3.2 理解感知音频编码的设计思想
- 3.2.1 两条线
- 3.2.2 时-频分析的矛盾
- 3.3 理解心理声学模型的实现过程
- 3.3.1 临界频带的概念
- 3.3.2 比例因子的计算
- 3.3.3 掩蔽值计算的思路
- 3.4 理解码率分配的实现思路
- 3.5 输出音频的采样率和目标码率
- 3.6 某个数据帧,输出
- 3.6.1 该帧所分配的比特数
- 3.6.2 该帧的比例因子
- 3.6.3 该帧的比特分配结果
- 3.6.4 输出result文件结果
一、MPEG音频编码原理
1.1 基本思想
分析信号,去掉不能被感知的部分【声音压缩算法可以确立这种特性的模型来取消更多的冗余数据】
- 子带分析滤波器组:使信号具有高的时间分辨率【短暂冲击信号情况下,编码的声音信号具有足够高的质量】
- FFT运算:使信号具有高的频率分辨率
- 比特分配:低频子带分配较多的位数【保护音调和共振峰的结构】;高频自带分配较少的位数【摩擦音和类似噪声的声音】
1.2 心理声学模型(Psychoacoustic model)
- 生理(Physiological)感知极限(传感极限)
- 心理(Psychological)感知极限(信号处理极限)
1.2.1 听觉阈值
听觉系统中存在一个听觉阈值电平,低于这个电平的声音信号就听不到
- 听觉阈值的大小随声音频率的改变而改变
- 一个人是否听到声音取决于声音的频率,以及声音的幅度是否高于这种频率下的听觉阈值。
1.2.2 频域掩蔽
听觉阈值电平是自适应的,会随听到的不同频率声音而发生变化
如果有多个频率成分的复杂信号存在,那么频谱的总掩蔽阈值与频率的关系取决于各掩蔽音的强度、频率和它们之间的距离。
1.3 临界频带(Critical Band)
临界频带是指当某个纯音被以它为中心频率、且具有一定带 宽的连续噪声所掩蔽时,如果该纯音刚好被听到时的功率等于这一频带内的噪声功率,这个带宽为临界频带宽度。
1 B a r k = { f 100 , f o r f r e q u e n c i e s f < 500 H z 9 + 4 l o g ( f 1000 ) , f o r f r e q u e n c i e s f ≥ 500 H z 1 Bark=\left\{ \begin{aligned} &\frac{f}{100},&&for frequencies&& f<500Hz \\ &9+4log(\frac{f}{1000}),&& for frequencies&& f \geq 500Hz\\ \end{aligned} \right. 1Bark=⎩⎪⎪⎨⎪⎪⎧100f,9+4log(1000f),forfrequenciesforfrequenciesf<500Hzf≥500Hz
1.4 人耳听觉系统
人类听觉系统大致等效于一个信号通过一组并联的不同中心频率的带通滤波器。
- 听音者在噪声中听某一纯音信号时,只启用中心频率与信号频率相同的那个听觉滤波器。纯音信号通过该滤波器,而噪声信号只有通带范围内的部分信号能通过,通带以外的频率成分则被抑制,只有通过该滤波器的噪声才对掩蔽起作用。
- 聆听复音时启动多个听觉滤波器。听觉能够计算各滤波器输出端的信噪比。当信噪比达到或者超过听阈因子时,即可听到该频率成分。
1.5 掩蔽效果的加和
Lutfi对多个掩蔽音同时存在时的综合掩蔽效果进行了研究: 每个掩蔽音的掩蔽效果先独立变换然后再线性相加。
F ( M A B = F ( M A ) + F ( M B ) F(M_{AB}=F(M_A)+F(M_B) F(MAB=F(MA)+F(MB) F ( M A ) = ( 1 0 M A / 10 ) p F(M_A)=(10^{M_A/10})^p F(MA)=(10MA/10)p
- 当两个信号重叠并落在一个临界频带中时,二者的掩蔽分量可以线 性相加。
- 对于复杂音频信号可将其频谱分割成一系列离散段,每段就是一个掩蔽信号。各掩蔽音互不重叠,即以一个临界带为单位。各掩蔽音的声压级则通过将对应的临界频带上的短时功率谱密度线性相加得到 。
二、MPEG音频压缩编码器
输入声音信号经过一个多相滤波器组,变换到多个子带,同时经过“心理声学模型”计算以频率为自变量的噪声掩蔽阈值。量化和编码部分用信掩比SMR决定分配给子带信号的量化位数,使量化噪声<掩蔽阈值。最后通过数据帧包装 将量化的子带样本和其它数据按照规定的帧格式组装成比特数据流。
2.1 多相滤波器组(Polyphase Filter Bank)
先分成32个相等的子带。
512个输入样本空间的FIFO。
新输入的32个样本在最低端。
多相滤波器组的缺点:
- 等带宽的滤波器组与人类听觉系统的临界频带不对应【在低频区域,单个子带会覆盖多个临界频带。】。
- 滤波器组与其逆过程不是无失真的【但滤波器组引入的误差很小,且听不到】
- 子带间频率有混叠【滤波后的相邻子带有频率混叠现象,一个子带中的信号可以影响相邻子带的输出】
2.2 心理声学模型
MPEG-1标准定义了两个模型。
心理声学模型 1:
- 计算复杂度低
- 但对假设用户听不到的部分压缩太严重
心理声学模型 2:
- 提供了适合Layer 3编码的更多特征
实习实现的模型复杂度取决于所需要的压缩因子。
2.3 量化和编码
2.3.1 比例因子的取值和编码
对各个子带每12个样点进行一次比例因子计算【查表】。
第二层中一帧对应36个子带样值,原则上要传送3个比例因子。
为了降低比例因子的传输码率,每帧中每个子带的三个比例因子被划分成特定的几种模式,根据这些模式,1个、2个或3个比例因子和比例因子选择信息(每子带2比特)一起被传送。如 果一个比例因子和下一个只有很小的差别,就只传送大的一个【这种情况在稳态信号中经常出现】。
2.3.2 比特分配及编码
- 对每个子带计算掩蔽-噪声比MNR M N R = S N R ( 信 噪 比 ) − S M R ( 信 掩 比 ) MNR=SNR(信噪比)-SMR(信掩比) MNR=SNR(信噪比)−SMR(信掩比)
- 使整个一帧和每个子带的总噪声-掩蔽比最小
【循环过程:每一次循环给获益最大的子带分配一个比特】
【第一层 1帧用4比特给每个子带的比特分配信息编码;第二层只在低频段用4比特,高频段用2比特】
比例因子的作用是充分利用量化器的量化范围, 通过比特分配和比例因子相配合,可以表示动态范围超过120dB的样本 。
2.3.3 数据帧包装
以层2为例:
- 帧头(Header):每帧开始的头32个比特,包含有同步和状态比特流信息,在所有层都相同,同步码字为12bit全1码。
- 误码检测CRC:使用一种16bit奇偶校验字,可供在比特流中作检测误码用。
- 声音数据:由比特分配表、比例因子选择信息、比例因子和子带样值组成,其中子带样值是声音数据的最大部分。
- 辅助数据:用作辅助数据比特流。
三、MPEG音频编码器调试
调试的第一步自然是跑通代码弄清楚输入参数。代码在第一次编译的时候有报错,是编译环境的问题,直接复制报错信息google一下就可以解决,在此不进行说明了。
然后运行一下代码,得到如下结果:
按照提示信息所说,必须要输入的就只有输入文件的名字,所以,如下图所示输入命令并得到结果:
3.1 理解程序设计的整体框架
通过main()函数上方的注释可以很方便的知道程序设计的框架。
/************************************************************************
*
* main
*
* PURPOSE: MPEG II Encoder with
* psychoacoustic models 1 (MUSICAM) and 2 (AT&T)//心理声学模型
*
* SEMANTICS: One overlapping frame of audio of up to 2 channels are
* processed at a time in the following order:
* (associated routines are in parentheses)//最多按以下顺序一次处理最多2个通道的一个重叠音频帧
*
* 1. Filter sliding window of data to get 32 subband
* samples per channel.
* (window_subband,filter_subband)//过滤数据滑动窗口以获取每个通道32个子带样本
*
* 2. If joint stereo mode, combine left and right channels
* for subbands above #jsbound#.//如果是联合立体声模式,请为#jsbound#以上的子带组合左右声道
* (combine_LR)
*
* 3. Calculate scalefactors for the frame, and
* also calculate scalefactor select information.//计算比例因子,并计算比例因子选择信息。
* (*_scale_factor_calc)
*
* 4. Calculate psychoacoustic masking levels using selected
* psychoacoustic model.//使用选定的心理声学模型计算心理声学掩蔽电平。
* (psycho_i, psycho_ii)
*
* 5. Perform iterative bit allocation for subbands with low
* mask_to_noise ratios using masking levels from step 4.//使用步骤4中的掩蔽电平,对具有低掩蔽比的子带执行迭代位分配。
* (*_main_bit_allocation)
*
* 6. If error protection flag is active, add redundancy for
* error protection.//若是需要,就添加CRC纠错
* (*_CRC_calc)
*
* 7. Pack bit allocation, scalefactors, and scalefactor select
*headerrmation onto bitstream.//将比特分配,比例因子和比例因子打包到比特流中。
* (*_encode_bit_alloc,*_encode_scale,transmission_pattern)
*
* 8. Quantize subbands and pack them into bitstream//量化子带并将其打包成比特流
* (*_subband_quantization, *_sample_encoding)
*
************************************************************************/
整理成有序列表如下 :
- 对滑动窗口内的音频信号进行了32子带滤波;
- 如果有立体声,就加入左右声道;
- 计算这一帧的比例因子及比例因子选择信息‘
- 使用选定的心理声学模型计算心理声学掩蔽水平;
- 使用步骤4中的掩蔽电平,对具有低掩蔽比的子带执行迭代位分配;
- 若是需要,就添加CRC纠错;
- 将比特分配,比例因子和比例因子信息打包到比特流中;
- 量化子带并将其打包成比特流
音频编码和视频编码不同,没有反馈的机制,只是一条顺序过程走下去,所以比之视频编码来的相对较为容易。
main()函数框架分析:
int main(int argc, char** argv)
{/********************************以上都是初始化,不需要过多关注***************************************************/programName = argv[0];if (argc == 1) /* no command-line args */ //如果命令行没有输入short_usage();elseparse_args(argc, argv, &frame, &model, &num_samples, original_file_name,encoded_file_name);//解析命令行参数print_config(&frame, &model, original_file_name, encoded_file_name);//输出一些配置信息/* this will load the alloc tables and do some other stuff */hdr_to_frps(&frame);//把从信息头中解压出来的信息加载nch = frame.nch;error_protection = header.error_protection;//while里面是获取的每一帧的信息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);//bit预算lg_frame = adb / 8;if (header.dab_extension) {/* in 24 kHz we always have 4 bytes */if (header.sampling_frequency == 1)header.dab_extension = 4;/* You must have one frame in memory if you are in DAB mode *//* in conformity of the norme ETS 300 401 http://www.etsi.org *//* see bitstream.c */if (frameNum == 1)minimum = lg_frame + MINIMUM;adb -= header.dab_extension * 8 + header.dab_length * 8 + 16;}{int gr, bl, ch;/* New polyphase filterCombines windowing and filtering. Ricardo Feb'03 */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) {/* 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);//计算比例因子选择信息}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 */ //心理声学模型A计算掩蔽电平psycho_0(smr, nch, scalar, (FLOAT)s_freq[header.version][header.sampling_frequency] * 1000);//心理声学模型的输出一定是smrbreak;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:/* Modified psy model 1 */psycho_3(buffer, max_sc, smr, &frame, &glopts);break;case 4:/* Modified Psycho Model 2 */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:/* Model 5 comparse model 1 and 3 */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:/* Model 6 compares model 2 and 4 */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);/* Dump the SMRs for all models */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);
}
3.2 理解感知音频编码的设计思想
3.2.1 两条线
从流程图来看,MPEG音频编码分为清晰的上下两条线。上面一条线是音频编码的主线部分,下面一条线的本质是为了帮助计算线性量化器的量化比特数。其中,下面一条线最为出彩的部分是心理声学模型的应用,它对去除冗余信息起到了极大的作用。
3.2.2 时-频分析的矛盾
要感知时域的变化,就要增加时域的分辨力,但同时就会对频域的分辨力有所限制,反之亦然,时域分辨力和频域分辨力有着天然的矛盾,我们所能做的就是这两者中取一个中间方案。
3.3 理解心理声学模型的实现过程
3.3.1 临界频带的概念
临界频带的概念在上文理论部分已经进行过说明,在此不再进行赘述。
3.3.2 比例因子的计算
在上文中已经讲解过比例因子的计算过程,下面是其实现代码:
void scale_factor_calc(double sb_sample[][3][SCALE_BLOCK][SBLIMIT],unsigned int scalar[][3][SBLIMIT], int nch,int sblimit)
{//scalar就是得到的比例因子/* Optimized to use binary search instead of linear scan through thescalefactor table; guarantees to find scalefactor in only 5jumps/comparisons and not in {0 (lin. best) to 63 (lin. worst)}.Scalefactors for subbands > sblimit are no longer computed.Uses a single sblimit-loop.Patrick De Smet Oct 1999.*/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--;) {//寻找子带中的最大值int j;unsigned int l;register double temp;unsigned int scale_fac;/* Determination of max. over each set of 12 subband samples: *//* PDS TODO: maybe this could/should ??!! be integrated into *//* the subband filtering routines? */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;}/* PDS: binary search in the scalefactor table: *//* This is the real speed up: */for (l = 16, scale_fac = 32; l; l >>= 1) {//查找比例因子表中比这个最大值大的最小值作为比例因子if (cur_max <= multiple[scale_fac])scale_fac += l;elsescale_fac -= l;}if (cur_max > multiple[scale_fac])scale_fac--;scalar[k][t][i] = scale_fac;//得到比例因子}}
}
3.3.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]; /* Smaller scale indexes mean bigger scalefactors *///较小的指标意味着较大的比例因子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 */}/* Find the minimum ATH in each subband *///在每个子带中找到最小的ATHfor (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++;}/* Find the minimum scalefactor index for each ch/sb *///找到这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];/* Oh yeah. Fudge the hell out of the SMR calculationsby combining the scalefactor table index and the min ATH in that subbandThere are probably more elegant/correct ways of combining these values,but who cares? It works pretty wellMFC Mar 03 */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];
}
3.4 理解码率分配的实现思路
比特分配的具体过程也已经在上文原理部分中有过提及,不再详述。以下是main()函数中实现码率分配的函数体定义和关键代码解释:
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)
{...//以上都是初始化,不需要过多关注/* decide on which bit allocation method to use */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 forthis frame */for (brindex = lower; brindex <= upper; brindex++) {if (bitrateindextobits[brindex] > req) {/* this method always *overestimates* the bits that are neededi.e. it will usually guess right butwhen it's wrong it'll guess a higher bitrate than actually required.e.g. on "messages from earth" track 6, the guess waswrong on 75/36341 frames. each time it guessed higher.MFC Feb 2003 */guessindex = brindex;found = TRUE;break;}}/* Just for sanity */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) {/* print out the VBR stats every 1000th frame */static int count = 0;int i;if ((count++ % 1000) == 0) {for (i = 1; i < 15; i++)fprintf(stdout, "%4i ", vbrstats[i]);fprintf(stdout, "\n");}/* Print out *every* frames bitrateindex, bits required, and bits available at this bitrate */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);}
}
通过调试可以发现,本实验使用的比特分配的函数是a_bit_allocation,以下是这个函数体的定义和关键代码解释:
int a_bit_allocation(double perm_smr[2][SBLIMIT],unsigned int scfsi[2][SBLIMIT],unsigned int bit_alloc[2][SBLIMIT], int* adb,frame_info* frame)
{int i, min_ch, min_sb, oth_ch, k, increment, scale, seli, ba;int bspl, bscf, bsel, ad, bbal = 0;double mnr[2][SBLIMIT];char used[2][SBLIMIT];int nch = frame->nch;int sblimit = frame->sblimit;int jsbound = frame->jsbound;al_table* alloc = frame->alloc;static char init = 0;static int banc = 32, berr = 0;static int sfsPerScfsi[] = { 3, 2, 1, 2 }; /* lookup # sfs per scfsi */if (!init) {init = 1;if (frame->header->error_protection)berr = 16; /* added 92-08-11 shn */}for (i = 0; i < jsbound; ++i)bbal += nch * (*alloc)[i][0].bits;for (i = jsbound; i < sblimit; ++i)bbal += (*alloc)[i][0].bits;*adb -= bbal + berr + banc;ad = *adb;for (i = 0; i < sblimit; i++)for (k = 0; k < nch; k++) {mnr[k][i] = snr[0] - perm_smr[k][i];bit_alloc[k][i] = 0;used[k][i] = 0;}bspl = bscf = bsel = 0;do {/* locate the subband with minimum SMR *///找到拥有最大MNR的子带maxmnr(mnr, used, sblimit, nch, &min_sb, &min_ch);if (min_sb > -1) { /* there was something to find *//* find increase in bit allocation in subband [min] *///计算拥有最大MNR的子带需要分配多少比特位才不是最大increment =SCALE_BLOCK * ((*alloc)[min_sb][bit_alloc[min_ch][min_sb] + 1].group *(*alloc)[min_sb][bit_alloc[min_ch][min_sb] + 1].bits);if (used[min_ch][min_sb])increment -=SCALE_BLOCK * ((*alloc)[min_sb][bit_alloc[min_ch][min_sb]].group *(*alloc)[min_sb][bit_alloc[min_ch][min_sb]].bits);/* scale factor bits required for subband [min] */oth_ch = 1 - min_ch; /* above js bound, need both chans */if (used[min_ch][min_sb])scale = seli = 0;else { /* this channel had no bits or scfs before */seli = 2;scale = 6 * sfsPerScfsi[scfsi[min_ch][min_sb]];if (nch == 2 && min_sb >= jsbound) {/* each new js sb has L+R scfsis */seli += 2;scale += 6 * sfsPerScfsi[scfsi[oth_ch][min_sb]];}}//检查剩下的可用比特位是否能够给出足够的比特位/* check to see enough bits were available for *//* increasing resolution in the minimum band */if (ad >= bspl + bscf + bsel + seli + scale + increment) {//如果够的话就给这个子带分配比特ba = ++bit_alloc[min_ch][min_sb]; /* next up alloc */bspl += increment; /* bits for subband sample */bscf += scale; /* bits for scale factor */bsel += seli; /* bits for scfsi code */used[min_ch][min_sb] = 1; /* subband has bits */mnr[min_ch][min_sb] =-perm_smr[min_ch][min_sb] + snr[(*alloc)[min_sb][ba].quant + 1];/* Check if subband has been fully allocated max bits */ //检查该子带已分配的比特数是否已经到达了最大值if (ba >= (1 << (*alloc)[min_sb][0].bits) - 1)used[min_ch][min_sb] = 2; /* don't let this sb get any more bits *///如果已分配的比特数到达了最大值,那么就不能给他分配比特了}elseused[min_ch][min_sb] = 2; /* can't increase this alloc */ //不能分配比特位if (min_sb >= jsbound && nch == 2) {/* above jsbound, alloc applies L+R */ba = bit_alloc[oth_ch][min_sb] = bit_alloc[min_ch][min_sb];used[oth_ch][min_sb] = used[min_ch][min_sb];mnr[oth_ch][min_sb] =-perm_smr[oth_ch][min_sb] + snr[(*alloc)[min_sb][ba].quant + 1];}}} while (min_sb > -1); /* until could find no channel *//* Calculate the number of bits left *///计算剩下的比特位的数目ad -= bspl + bscf + bsel;*adb = ad;for (k = 0; k < nch; k++)for (i = sblimit; i < SBLIMIT; i++)bit_alloc[k][i] = 0;return 0;
}
3.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);//采用的心理声学模型elsefprintf(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");
}
由上图可发现,音频的采样率为44.1KHz,目标码率为192Kbps。
3.6 某个数据帧,输出
在进入循环前打开文件,main()函数末尾关闭文件。
int main(int argc, char** argv)
{.../* this will load the alloc tables and do some other stuff */hdr_to_frps(&frame);//把从信息头中解压出来的信息加载nch = frame.nch;error_protection = header.error_protection;//打开输出文件FILE* result;result = fopen("result.txt", "w+");//while里面是获取的每一帧的信息while (get_audio(musicin, buffer, num_samples, nch, &header) > 0) {...}...//关闭文件fclose(result);exit(0);
}
3.6.1 该帧所分配的比特数
我们知道,在比特预算时,把计算所得的该帧总分配比特数赋给了adb,在adb获得值以后直接进行输出。
int main(int argc, char** argv)
{...adb = available_bits(&header, &glopts);//bit预算lg_frame = adb / 8;if (header.dab_extension) {/* in 24 kHz we always have 4 bytes */if (header.sampling_frequency == 1)header.dab_extension = 4;/* You must have one frame in memory if you are in DAB mode *//* in conformity of the norme ETS 300 401 http://www.etsi.org *//* see bitstream.c */if (frameNum == 1)minimum = lg_frame + MINIMUM;adb -= header.dab_extension * 8 + header.dab_length * 8 + 16;}if (frameNum == 1){//输出可用比特数fprintf(result, "可用比特数:%d\n", adb);}...
}
3.6.2 该帧的比例因子
通过调试,我们知道,实验中比例因子存放在scalar[][][]中,每个声道有SBLIMIT个子带,每个子带有3个比例因子。
值得注意的是,比例因子存放数组的定义为scalar[2][3][SBLIMIT],所以在写循环的时候需要格外注意顺序。
int main(int argc, char** argv)
{...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);//计算比例因子选择信息}if (frameNum == 1){//输出比例因子fprintf(result, "\n比例因子:\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);for (int j = 0; j < 3; j++){fprintf(result, "%d\t", scalar[k][j][i]);}fprintf(result, "\n");}}}...
}
3.6.3 该帧的比特分配结果
通过调试,我们知道,比特分配的结果存放在bit_alloc[][]中。每个声道有SBLIMIT个子带,每个子带有一个比特分配的结果。
int main(int argc, char** argv)
{...transmission_pattern(scalar, scfsi, &frame);main_bit_allocation(smr, scfsi, bit_alloc, &adb, &frame, &glopts);//进行比特分配if (frameNum == 1){//输出比特分配结果fprintf(result, "\n比特分配\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]);}}}...
}
3.6.4 输出result文件结果
至此,实验结束。
数据压缩(十三)——MPEG音频编码原理及编码器调试相关推荐
- MPEG音频编码原理及编码器调试
一 MPEG音频编码 1 基本原理 基本思想:去掉音频信号中的冗余.其中,冗余主要来自:声音信号中本身存在的冗余:不被人耳感知的部分. 1.1 MPEG-1 Audio Layer2 编码器原理 该编 ...
- 【数据压缩】MPEG音频编码实验
一.MPEG-1 Audio LayerII编码器原理 1.MPEG-1音频编码器框架图 (1)编码流程的两条线 整个编码流程分为两条线: 第一条线是主线,输入的PCM码流经过多相滤波器组分成32个子 ...
- 【实验】MPEG-1 Audio Layer II编码原理及编码器调试
目录 一.理解感知音频编码的设计思想 1.等响度曲线 2.频域掩蔽域随声压级变化曲线 二.理解心理声学模型的实现方法 1.临界频带(Critical Band) 2.掩蔽值计算的思路 三.人类听觉系统 ...
- Lab6 MPEG音频编码实验——C++代码实现
Lab6 MPEG音频编码实验--C++代码实现 一.MPEG音频编码实现框架及思路 1.MPEG音频编码实现框图: MPEG音频编码的实现主要分为上下两条线,上方红色框中的部分属于时域分析(粗细节) ...
- 实验六 MPEG音频编码
一.MPEG音频编码原理 1.1 基本思想 分析信号,去掉不能被感知的部分[声音压缩算法可以确立这种特性的模型来取消更多的冗余数据] 子带分析滤波器组:使信号具有高的时间分辨率[短暂冲击信号情况下,编 ...
- 【数据压缩】第八次作业——MPEG音频编码
MPEG音频编码实验 文章目录 MPEG音频编码实验 MPEG介绍 MPEG-1声音的主要性能 心理声学模型 人耳听觉特性 临界频带 掩蔽值 心理声学模型Ⅰ MPEG-1 编码原理 基本思想 整体框架 ...
- MPEG音频编码 基本原理和C语言代码分析
背景 MPEG(Moving Picture Experts Group)在汉语中译为活动图像专家组,特指活动影音压缩标准. MPEG 音频文件是MPEG1 标准中的声音部分,也叫MPEG 音频层,它 ...
- MPEG音频编码三十年
▲扫描图中二维码了解音视频技术大会更多信息▲ 作者:Leonardo Chiariglione 翻译:Alex 技术审校:冯建元 视 野 #011# 前言 很明显,声音信息的电子格式要早于视觉信息的电 ...
- MPEG音频编码及分析
一.MPEG音频编码思想 1.整体概述 (1)基本思想 分析信号,去掉不能被感知的部分 (2)MPEG-1声音的主要性能 输入为PCM信号,采样率为32,44.1或48kHz,输出为32kbps到38 ...
最新文章
- JSP获得客服端MAC地址
- OpenAI的GPT-3花费了1200万美元,现在放出商用API,人人皆可拿来自动生成文本、编写代码...
- 远程控制软件 TeamViewer | Windoes和Linux下teamviewer软件互连
- 学习C++之父的最新姐妹作笔记2
- 《微信小程序开发》学习情况大调查!
- Docker安装Redis(docker-compose.yml)
- 研究js特效巩固JavaScript知识
- 清北学堂模拟赛d6t4 数组异或
- java中虚拟机命令:jstack使用方法
- python3爬取网易云歌单数据清洗_如何利用Python网络爬虫爬取网易云音乐歌词
- CentOS 7.2下安装lamp环境
- 给我一篇假论文,我能骗倒半个地球
- NLP入门之综述阅读-基于深度学习的自然语言处理研究综述
- 人机交互基础教程-复习总结
- js-多个果冻按钮之当前果冻按钮弹性特效
- 汽车振动响应分析-频响函数法(附程序)
- ue4网格转地形_UE4教程:创建地形材质
- Flutter 倒计时实现
- 一、Mavlink协议
- 教育平台、刷题、技术社区
热门文章
- 【老生谈算法】matlab实现PSO算法程序求解简单的函数极值问题——PSO算法
- Linux那些事儿之我是U盘(51)光荣属于苹果,属于诺基亚,属于摩托罗拉,属于索尼爱立信
- 【Echarts】封装几个酷炫(发光)图表
- HTML5初学-第五天-作业及知识点总结
- 做软件测试,首选第三方软件测评机构
- SigAI——深入浅出聚类算法
- 二叉树的最近公共祖先、二叉搜索数的最近公共祖先
- LigerUI前端技术视频教程
- Android应用实现QQ授权登录
- 计算机组成原理3.3.5ppt,计算机组成原理_3_3.ppt