本文对 G.722.1 的编码器作以介绍,如有表述不当之处欢迎批评指正。欢迎任何形式的转载,但请务必注明出处。
目录
引言简介编码器1. 计算、量化和编码 MLT 的幅度包络1-1. MLT 重叠调制变换1-2. 计算、量化 MLT 的幅度包络1-3. 编码 MLT 的幅度包络2. 确定编码方法、量化和编码 MLT 的系数2-1. 产生 16 组不同的编码(分类)方法2-2. 归一化、量化和编码 MLT 系数3. 细节3-1. 符号比特3-2. 确定最终编码方法3-3. 比特流模块4. 总结引言
由于工作需要接触了一段时间的音频编解码算法 G.722.1,在此作以记录。
简介
G.722.1 是一种基于变换域编码的算法采样率: 16000hz比特率: 24kbit/s,32kbit/s变换域: MLT(Modulated Lapped Transform)帧长: 20ms变换窗长: 40ms有效编码带宽: 50~7000hz编码器
图1 G.722.1 编码器方框图上图展示了 G.722.1 的编码过程, 主要包含三个模块:
对 MLT 的幅度包络进行计算、量化和编码确定编码方法,并对 MLT 的系数进行归一化、量化和编码
下面详细分析这几个模块所做的事情。
1. 计算、量化和编码 MLT 的幅度包络
1-1. MLT 重叠调制变换
MLT 是一个严格抽样,完美重建的线形变换过程(引用自 G.722.1 官方文档中文版)此处不介绍具体公式。前一帧 320 个采样点与当前帧 320 个采样点拼接为 640 个采样点作为 MLT 变化的输入,MLT 变换输出 320 个系数。
mlt(m),0≤m<320mlt(m), 0\leq{m}<320mlt(m),0≤m<320
1-2. 计算、量化 MLT 的幅度包络
将 MLT 输出的 320 个系数平均分为 16 个区域,每个区域包含 20 个系数,对应 500hz 带宽,但编码器只对前 14 个区域也就是 0~7000hz 进行编码。
首先根据以下公式计算每个区域的幅度包络:
rms(r)=120∑j=019mlt(20r+j)mlt(20r+j),0≤r<14rms(r) = \sqrt{\frac{1}{20}\sum_{j=0}^{19}mlt(20r+j)mlt(20r+j)}, 0\leq{r}<14rms(r)=201j=0∑19mlt(20r+j)mlt(20r+j),0≤r<14
利用以下量化集合对上述 14 个区域的幅度包络进行量化,生成量化索引 rms_index(r),0≤r<14rms\_index(r),0\leq{r}<14rms_index(r),0≤r<14
2(i+22),−8≤i≤31,i∈Z2^{(\frac{i+2}{2})}, -8\leq{i}\leq{31}, i\in\mathbb Z2(2i+2),−8≤i≤31,i∈Z
进一步对第一个区域的量化索引 rms_index(0)rms\_index(0)rms_index(0) 进行限制使得
1≤rms_index(0)≤311\leq{rms\_index(0)}\leq311≤rms_index(0)≤31
具体在量化过程中,iii 的取值应该使得幅度包络 rms(r)rms(r)rms(r) 处于 2(i−0.5+22)2^{(\frac{i-0.5+2}{2})}2(2i−0.5+2) 和 2(i+0.5+22)2^{(\frac{i+0.5+2}{2})}2(2i+0.5+2) 之间。用下面的例子说明该操作:如果 rms(r)=310rms(r)=310rms(r)=310,则应该将其量化为 2(15+22)2^{(\frac{15+2}{2})}2(215+2),也即 rms_index(r)=15rms\_index(r)=15rms_index(r)=15,因为 2(15−0.5+22)≤rms(r)=310≤2(15+0.5+22)2^{(\frac{15-0.5+2}{2})}\leq{rms(r)=310}\leq{2^{(\frac{15+0.5+2}{2})}}2(215−0.5+2)≤rms(r)=310≤2(215+0.5+2)。
1-3. 编码 MLT 的幅度包络
使用 5 个 bit 来编码幅度包络的量化索引 rms_index(r)rms\_index(r)rms_index(r)。直接将第零个区域的量化索引 rms_index(0)rms\_index(0)rms_index(0) 进行传输,而其余区域传输的则是与前一个区域量化索引的差值的霍夫曼编码。
diff_rms_index(r)=rms_index(r)−rms_index(r−1),1≤r<14diff\_rms\_index(r)=rms\_index(r)-rms\_index(r-1), 1\leq{r}<{14}diff_rms_index(r)=rms_index(r)−rms_index(r−1),1≤r<14
且限定其取值范围
−12≤diff_rms_index(r)≤11-12\leq{diff\_rms\_index(r)}\leq11−12≤diff_rms_index(r)≤11
也就是说,除了第零个区域外,其余区域传输的都是 diff_rms_index(r)diff\_rms\_index(r)diff_rms_index(r) 的霍夫曼编码(通过查找两个霍夫曼表实现,一个霍夫曼表表示编码该差值需要多少 bit,另一个霍夫曼表表示把该差值具体编码成什么)。
2. 确定编码方法、量化和编码 MLT 的系数
第一部分主要是量化和编码每个区域的幅度包络,该部分主要用于确定一些量化和编码 MLT 系数相关的参数,并用这些参数对 MLT 系数进行量化。这部分相比第一部分增加了难度,笔者刚开始接触的时候看了好几遍才算看懂。笔者在这尽量用简洁的语言描述清楚该部分所做的事情。
2-1. 产生 16 组不同的编码(分类)方法
该部分根据编码完 MLT 的幅度包络所剩下的比特数以及 rms_index(r)rms\_index(r)rms_index(r) 产生 16 种不同的编码方法(或者说产生 16 种不同的参数,这些参数用于对 MLT 系数进行量化和编码)并从中选取最合适的一种编码方法(或者说选取最合适的一种参数)。下面简单介绍一下这 16 种不同的编码方法。
编码器会给 14 个区域中的每个区域都分配一个类别,共有 0~7 共 8 种可选类别。每种类别决定了该区域的量化和编码参数,并决定了量化该区域的 MLT 系数预计所需的比特数,如图2 所示。
表1 G.722.1 每一类别预计所需的比特数
由于每个区域有 8 种类别可选,因此从算术上来说应该有 8148^{14}814 种不同的编码方法,但这其中有许多编码方法是不合理的,G.722.1 编码器最终只生成 16 种不同的编码方法,并且这 16 种编码方法之间是有联系的,那就是每种编码方法和与其相邻的编码方法之间只有一个区域分配的类别不同,并且该区域的类别值只相差 1。这块可能有点绕,既有 16 种编码方法,又有 14 个区域,还有 8 种区域类别,这几个数字之间到底是什么关系哪,下面举个例子来说明一下,见表2:
表2 G.722.1 编码方法示例
表2 展示了编码器产生的 16 种编码方法之间的关系,可以看到第 2 种编码方法与第 1 种编码方法只在区域 3 的类别分配上不同,第 1 种编码方法给区域 3 分配的是类别 1,第 2 种编码方法给区域 3 分配的是类别 0,两者区域 3 的类别值只差 1。第 3 种编码方法与第 2 种编码方法也是类似的关系,依次类推,其余第 n 种编码方法与第 n-1 种编码方法也是类似的关系。通过表2,相信读者现在已经能捋清楚 16 种编码方法、14 个区域和 8 种区域类别之间的关系了。还有需要注意的一点是,最终产生的 16 种编码方法是按照所用比特数从大到小依次排列的,也就是第 0 种编码方法所用比特数最多,第 15 种编码方法所用比特数最少。
写到这,其实并没有具体提到这 16 种编码方法到底是如何根据前面所说的剩余比特数和 rms_index(r)rms\_index(r)rms_index(r) 产生的。笔者也不打算在这详说,因为这部分比较繁琐,笔者不一定能说清楚,最重要的是笔者也没太理解其中公式的具体含义……感兴趣的可以根据 G.722.1 官方文档以及源码作进一步了解。
2-2. 归一化、量化和编码 MLT 系数
该部分对类别分配为 0~6 的区域的 MLT 系数进行所谓的标量量化矢量霍夫曼编码(SQVH),而对类别分配为 7 的区域的 MLT 系数不进行编码,认为该区域没有有效的音频信息。
首先,编码器对每个区域的 MLT 系数的绝对值进行归一化和量化,产生量化索引 k(j)k(j)k(j):
k(j)=min{⌊∣x∗mlt(20r+j)∣+deadzone_rounding⌋,kmax}k(j)=min\{\lfloor|x*mlt(20r+j)|+deadzone\_rounding\rfloor,kmax\}k(j)=min{⌊∣x∗mlt(20r+j)∣+deadzone_rounding⌋,kmax}
其中
0≤j<200\leq{j}<200≤j<20x=1/(stepsize∗(rms(r)的量化值))x=1/(stepsize*(rms(r)的量化值))x=1/(stepsize∗(rms(r)的量化值))
上式中所用的参数见表3:
表3 G.722.1 SQVH 程序所用参数表1
上面展示了如何对 MLT 系数的绝对值进行归一化和量化,但为何通过这种方式来做、表3 中参数的具体值是如何得到的以及 deadzone_round 的作用是什么,笔者现在还没搞太懂。介绍完 MLT 系数的归一化,下面就介绍如何对这些归一化之后的系数进行量化和编码。
先看表3,定义了几个参数 vd\bm {vd}vd、vpr\bm {vpr}vpr 和 u\bm {u}u
表4 G.722.1 SQVH 程序所用参数表2
要理解表4 中的几个参数的具体含义就要和表3 一起对照着看。可以观察到表4 中每一行均满足以下关系:
vd∗vpr=20\bm{vd}*\bm{vpr}=20vd∗vpr=20u=(kmax+1)vd\bm{u}=(kmax+1)^{\bm{vd}}u=(kmax+1)vd
先看第一个公式中的 20 是什么意思哪,回想一下,这个 20 正是每个区域中所包含的 MLT 系数的个数。因此,vd\bm{vd}vd 和 vpr\bm{vpr}vpr 所代表的含义也就比较明显了:将每个区域的 20 个 MLT 系数,每 vd\bm{vd}vd 个组成一个矢量,总共有 vpr\bm{vpr}vpr 个矢量。类别为 0 的区域矢量数最多,总共有 10 个矢量。类别为 5、6 的区域矢量数最少,总共有 4 个矢量。
再看第二个公式,kmaxkmaxkmax 是在表3 中定义的,是指不同类别的区域的 MLT 系数归一化之后能取到的最大值。比如,类别为 0 的区域,其 MLT 系数归一化之后能取到的最大值是 13,最小值是 0,总共有 14 种可能的取值;类别为 6 的区域,其 MLT 系数归一化之后能取到的最大值是 1,最小值是 0,总共有 2 种可能的取值;kmax+1kmax+1kmax+1 就是指该类别的区域的 MLT 系数归一化之后所有可能的取值数目。因此第二个公式代表的意思就是该类别的区域的每个矢量(每个矢量共包含 vd\bm{vd}vd 个标量)所有可能取值数目。
上面讲了如何对不同类别的区域的 MLT 系数的绝对值进行归一化,并如何组合成矢量,下面就开始介绍如何对这些组合成的矢量进行编码。
将每个区域的各个矢量按照下面的公式量化为标量:
vector_index(v)=∑l=0vd−1k(v∗vd+l)(kmax+1)vd−(l+1)vector\_index(v)=\sum_{l=0}^{\bm{vd}-1}{k(v*\bm{vd}+l)(kmax+1)^{\bm{vd}-(l+1)}}vector_index(v)=l=0∑vd−1k(v∗vd+l)(kmax+1)vd−(l+1)
其中 0≤v≤vpr−10\leq{v}\leq{\bm{vpr}-1}0≤v≤vpr−1 表示区域 rrr 中的第 vvv 个矢量
再将 vector_index(v)vector\_index(v)vector_index(v) 通过查表的方式进行霍夫曼编码(此处同样有两张表,一张表表示编码该值需要多少比特,另一张表表示将该值具体编码成什么)。上述所描述的从 MLT 系数的归一化到最终的霍夫曼编码的整个过程称为标量量化矢量霍夫曼编码(SQVH) 。
至此,G.722.1 编码器的主要部分已讲的差不多了。还有一些细节需要了解。
3. 细节
3-1. 符号比特
刚才讲的是对 MLT 系数的绝对值进行归一化、量化和编码,并没提到符号位,实际上在具体传输比特的过程中,符号比特直接位于表示 vector_index(v)vector\_index(v)vector_index(v) 的比特的后面。vector_index(v)vector\_index(v)vector_index(v) 里面对应包含了多少非零值。后面就有多少个符号比特,其中正数的符号比特为 1。
3-2. 确定最终编码方法
之前提到总共有 16 种编码方法,且第 0 种编码方法使用的比特数最多,第 15 种编码方法使用的比特数最少,最终传输的过程中只选用其中最合适的一种。比如第 0 种编码方法使用了 500 个比特,第 1 种编码方法使用了 470 个比特,但每一帧最多只能用 480 个比特,所以最终选择第 1 种编码方法,多出来的 10 个比特全部置为 1;还有比如第 0~15 种编码方法所用比特数都大于 480,那么最终选取第 15 种编码方法,传输过程中只传输前 480 个比特,剩余的比特不传输。
3-3. 比特流模块
最终编码后的比特流包含三个部分:
图2 G.722.1 编码器主要比特流字段及其传输顺序
第一部分和第三部分比较清楚,第二部分就是指具体使用的哪种编码方法,因为总共有 16 种编码方法,所以用 4 个比特表示。
4. 总结
G.722.1 的编码器部分终于讲的差不多了,其中有很多细节笔者也没太搞懂,若是读者了解其中缘由,希望能指教一二。还有就是 G.722.1 在实际代码实现过程中可能不完全和文档中的一样,感兴趣的可以阅读源码了解细节。