Microsoft ADPCM 编码解码算法
Microsoft ADPCM 编码解码算法
因为种种原因,最近需要把原始的wav文件压缩成ADPCM格式。但是网上几乎搜不到相关的中文资料。花了相当长的时间,七拼八凑的从一些文章中得到了些信息,终于搞定了它。为了方便遇到跟我一样麻烦的人,我决定把它详细的写下来。
1. 关于DPCM
DPCM是differential pulse code modulation的缩写,也就是差分脉冲编码调制的意思。他的主要思想是通过已知的数据预测下一个数据,然后传递预测值与实际值之间的差值。具体的细节可以在很多信号处理相关的书上找到。
一般的DPCM编码器都是采用的线性预测。假设传递的数据是X1,X2,...Xn,而下一个数据,Xn+1还是未知。可以通过前面的X1,X2,...Xn的加权和来预测Xn+1,也就是
Xn+1 = ∑(Ai*Xi),其中i属于1...n
为了简化计算,大部分编码的实现只取前两项,也就是,Xn+1 = a*Xn + b*Xn-1, 现在,最主要的事情就是如何对a,b进行取值,才能使得Xn+1的误差最小。
如果假设 x~i 是预测值,xi是实际值,那么,∑(x~i-xi)^2 最小的时候,a,b就是最优的。设 F=∑(X~i-Xi)^2,因为 X~i = a*X~i-1 + b*X~i-2,可以得出,F是关于a,b的二元函数.也就是 F=f(a,b) 。可以分别对a和b求偏导数,求出它的极值点。
f<sub>a</sub>(a,b) = 0 ;
f<sub>b</sub>(a,b) = 0 ;
可以得到
a * ∑(Xi-1)^2 + b * ∑(Xi-1)*(Xi-2) = ∑Xi*Xi-1
a * ∑(Xi-1)*(Xi-2) + b * ∑(Xi-2)^2 = ∑Xi*Xi-2
如果设
alpha = ∑(Xi-1)^2
beta = ∑(Xi-1)*(Xi-2)
gama = ∑(Xi-2)^2
m = ∑Xi*Xi-1
n = ∑Xi*Xi-2
上面的式子就可以写成
a*alpha + b*beta = m
a*beta + b*gama = n
算出alpha,beta,gama,m,n以后,a和b的值就可以计算出来了,实际上我们只需要一个循环遍历前n个数就能把它们都求出来。
2. ADPCM的思想
如果直接使用DPCM进行编码的话,是得不到什么压缩的效率的。缘故是,需要传输或保存的是预测值后的值与实际值之间的差值,这与原来的数据占用同样的空间。
为了满足我们原始的压缩数据的动机,你可以对这些差值进行各种各样的编码。因为,大部分情况下,差值都是像1,1,1,2,3,5,5,5之类的数。可以对它们进行通常的游程编码或者huffman编码,运气好的话能够得到很大的压缩比。
这样做会有一个很大的弊端。因为有些数据可能之间的联系会呈线性或者某种连续函数的性质。但是大部分情况下,数据的分布还是有一定的离散性的。当数据之间出现很大的跳跃的时候,这种方法就显得很苍白无力了。
我们可以这么做,每次对得到的差值用一个随着差值大小变化的数来除。这样就可以随着差值的变化,不断调整比例因子。这样出现较大的跳跃时也能把我们要存储的差值限定在一个较小的范围之内。
如果你现在有些迷惑,没事,我们换种方式来说明一下。
假设差值是 diff,也就是 diff = X~i - Xi,那么,diff就有可能变动很大,如果引入一个不断变化的因子iDelta,那么,diff' = diff / iDelta,而对于iDelta,每当diff变大的时候,他就变大比较大,当diff变得比较小的时候,他就相应的减小。这样,我们的diff'就能保持相对的稳定了。通过iDelta的引入,可以使得我们的DPCM编码自动的适应数据间大幅度的跳跃。这就是自适应脉冲编码调制,ADPCM的主要思想。
你现在可能会想,iDelta到底怎么变化,才能自动的匹配diff的变化? 一种可行的方法就是,把它定义为diff的一个函数,这个函数根据不同的diff的值的大小取不同大小的值。通常我们会做一个iDelta值的表,通过diff作为索引,这样,就可以根据不同的diff值,iDelta就可以作相应的变化了。