WAV系列之二:ADPCM编解码原理及代码实现

本文深入探讨了PCM、DPCM和ADPCM(特别是IMA-ADPCM)的编解码原理,详细介绍了ADPCM编码的三个步骤,包括计算差值、量化和更新预测值。通过IMA-ADPCM编码示例,展示了如何将16bit PCM数据压缩为4bit ADPCM数据,压缩比可达4:1。同时,文章还讲解了ADPCM数据的存储形式,包括单通路和双通路的block结构。
摘要由CSDN通过智能技术生成

参考自:《adpcm编解码原理及其代码实现》
    《ADPCM编码与解码学习笔记》
    《音频编码:ADPCM》

1、PCM

PCM (Pulse Code Modulation),脉冲编码调制。

PCM是把声音从模拟信号转化为数字信号的技术,把一个时间连续取值连续的模拟信号变换成时间离散取值离散的数字信号,模拟信号转化为数字信号需要三个步骤:采样、量化、编码。

1.1、采样

采样用一个固定的频率对模拟信号进行提取样值。

常用采样率为8KHz,16kHz,22.05kHz,32kHz,44.1kHz,48kHz,192kHz。

人耳能够感觉到的最高频率为20kHz,要满足人耳的听觉要求,根据奈奎斯特采样定律则,需要每秒进行40k次采样,即40kHz。

8Khz的采样率就可以达到人的对话程度,通常电话的采样率为8kHz/16kHz。

常见的无线电广播采样率为22.05KHz,CD采样率为44.1kHz,DVD采样率为48kHz,Hi-Res音频采样率为192kHz

1.2、量化编码

量化编码就是把采样得到的声音信号幅度转换成数字值。这个过程会产生失真,量化的精度越高失真越小。常见的量化位数为8bit,16bit,24bit。
clipboard

PCM约定俗成为无损编码,因为PCM代表了数字音频中最佳的保真水准,并不意味着PCM就能够确保信号绝对保真,PCM也只能做到最大程度的无限接近。

2、DPCM

DPCM(Differential Pulse Code Modulation),差分脉冲编码调。

PCM是不压缩的,通常数据量比较大,存储和通讯都必需付出比较大的代价,早期的通讯是不能传输那么大的数据量的,所以就要想办法把数据压缩一下,以减少带宽和存储的压力。

假设我们以8kHz的采样率,16bit量化编码,则1秒的数据量为8000 * 16 = 128000 bit 。一般音频信息都是比较连续的,不会突然很高或者突然很低,两点之间差值不会太大,所以这个差值只需要很少的几个位(比如4bit)即可表示。这样,我们只需要知道前一个点的值,又知道它与下一个点的差值,就可以计算得到下一个点了。这个差值就是所谓的Differential ,将PCM数据转成DPCM数据,数据量会小很多,如上面所说的用4bit的表示差值,则1秒的(8kHz采样率16bit量化编码) PCM数据转成DPCM则只需要大约32000bit , 压缩比大约4:1。

3、ADPCM

ADPCM (Adaptive Differential Pulse Code Modulation)、自适应差分脉冲编码调。

音频信号虽然是比较连续性的,有些差值比较小,有些差值比较大,如果差值比较大有可能用4bit表示不了,如果增大表示差值的位数(例如8bit\16bit)是可以解决这个问题,但就导致数据量变大,没起到压缩的目的,而且这种差值比较大的只是少数,大部分还是差值比较小的。

为了解决这个问题,前辈们就想出了 ADPCM,定义一个因子,用差值除以因子的值来表示两点之差,如果两点之间差值比较大,则因子也比较大。通过因子引入,可以使得DPCM编码自动适应差值比较大的数据。

ADPCM算法并没用固定标准,最经典的就是IMA ADPCM

4、IMA-ADPCM 的编解码原理

ADPCM(Adaptive Differential Pulse Code Modulation 差分脉冲编码调制)主要是针对连续的波形数据的, 保存的是相临波形的变化情况, 以达到描述整个波形的目的。本文的以IMA的ADPCM编码标准为例进行描述,IMA-ADPCM 是Intel公司首先开发的是一种主要针对16bit采样波形数据的有损压缩算法,压缩比为 4:1,它与通常的DVI-ADPCM是同一算法。 (对8bit数据压缩时是3.2:1,也有非标准的IMA-ADPCM压缩算法,可以达到5:1甚至更高的压缩比) 4:1的压缩是目前使用最多的压缩方式。结尾附adpcm编解码的源代码adpcm.h与adpcm.c。

ADPCM编码本质是一种预测编码,那么它是怎么样进行预测的呢?预测编码利用相邻的音频数据在时间上的相关性,相邻采样点的音频数据具有相似的特点。因此,经过压缩后的数据并不是音频数据本身,而是该数据的预测值与实际值之差。偏差需要量化器进行量化,假如我们对于16bit的音频数据采用16bit的量化,那么偏差与实际的数据值占据的位数一样则无法达到压缩数据的目的,如果采用4bit的量化位数,其最大的量化步数只能是16,显然是不能满足使用要求,因此ADPCM应运而生,ADPCM是一种采用变步长的量化器的预测编码算法,它的本质是根据预测值与实际的偏差范围,在量化表格中选择出合适的量化值,使预测变化的幅度保持在4bit的范围内。ADPCM的核心公式如下,其中 delta 代表为量化后的值,step 为量化步长,vpdiff 代表经过量化后有效的偏差值,vpdiff 加上本次的预测值做为下一次的运算的预测值:
在这里插入图片描述
在这里插入图片描述
整个ADPCM的编码过程分三步进行:

第一步为计算出当前实际值与预测值的偏差diffval 代表了当前数据的实际值,valpred 为当前数的预测值。delta 为量化后的带符号的有效数据为4bit的数据,其最高位代表的数据的方向,bit3为1代表负数,代表-7~7的整型数据。
在这里插入图片描述
diff 小于0, delta bit3被置1。

第二步通过index(首次编码index为0)求出step,通过diff和step求出delta。

第三步为对 diff 进行量化,简易实现不考虑计算效率的情况下完全可以直接参考上面的公式,因为是在计算机平台进行了除法运算与小数运算,该作者很巧妙的把这些运算使用与或非来实现了,提高了运算的效率,有兴趣的读者可以看看代码,学习一下这种思路。我们细看一下公式,
在这里插入图片描述
可以发现公式可以拆分为两部分实现,小数部分的量化被转换为了固定的step/8,因此节约了计算的成本。vpdiff 就是对应这部分的值。

 vpdiff = (step >> 3);

4.1、adpcm编码原理

在这里插入图片描述
编码步骤:

  1. 求出输入的pcm数据与预测的pcm数据(第一次为上一个pcm数据)的差值diff;
  2. 通过差分量化器算出delta(通过index(首次编码index为0)求出step,通过diff和step求出delta)。delta即为编码后的数据;
  3. 通过逆量化器求出vpdiff(通过求出的delta和step算出vpdiff);
  4. 求出新的预测valpred,即上次预测的valpred+vpdiff;
  5. 通过预测器(归一化),求出当前输入pcm input的预测pcm值,为下一次计算用;
  6. 量化阶调整(通过delta查表及index,计算出新的index值)。为下次计算用;

4.2、adpcm解码原理

在这里插入图片描述
解码步骤(其实解码原理就是编码的第三到六步):

  1. 通过逆量化器求出vpdiff(通过存储的delta和index,求出step,算出vpdiff);
  2. 求出新的预测valpred,即上次预测的valpred+vpdiff;
  3. 通过预测器(归一化),求出当前输入pcm input的预测pcm值,为下一次计算用。预测的pcm值即为解码后的数据;
  4. 量化阶调整(通过delta查表及index,计算出新的index值)。为下次计算用;

注释说明:

  1. 通过编码和解码的原理我们可以看出其实第一次编码的时候已经进行了解码,即预测的pcm。
  2. 因为编码再解码后输出的数据已经被量化了。根据计算公式delta = diff*4/step; vpdiff = (delta+0.5)*step/4;考虑到都是整数运算,可以推导出:pcm数据经过编码再解码生成的预测pcm数据,如果预测pcm数据再次编码所得的数据与第一次编码所得的数据是相同的。故pcm数据经过一次编码有损后,不论后面经过几次解码再编码都是数据一样,音质不会再次损失。即相对于第一次编码后,以后数据不论多少次编解码,属于无损输出。

4.3、源代码

adpcm.h

#ifndef ADPCM_H
#define ADPCM_H

struct adpcm_state
{
   
    int valprev;
    int index;
};

extern void adpcm_coder(short *indata, signed char *outdata, int len, struct adpcm_state *state);
extern void adpcm_decoder(signed char *indata, short *outdata, int len, struct adpcm_state *state);

#endif /*ADPCM_H*/

adpcm.c

/***********************************************************
Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The
Netherlands.

                        All Rights Reserved

Permission to use, copy, modify, and distribute this software and its 
documentation for any purpose and without fee is hereby granted, 
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in 
supporting documentation, and that the names of Stichting Mathematisch
Centrum or CWI not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior permission.

STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

******************************************************************/

/*
** Intel/DVI ADPCM coder/decoder.
**
** The algorithm for this coder was taken from the IMA Compatability Project
** proceedings, Vol 2, Number 2; May 1992.
**
** Version 1.2, 18-Dec-92.
**
** Change log:
** - Fixed a stupid bug, where the delta was computed as
**   stepsize*code/4 in stead of stepsize*(code+0.5)/4.
** - There was an off-by-one error causing it to pick
**   an incorrect delta once in a blue moon.
** - The NODIVMUL define has been removed. Computations are now always done
**   using shifts, adds and subtracts. It turned out that, because the standard
**   is defined using shift/add/subtract, you needed bits of fixup code
**   (because the div/mul simulation using shift/add/sub made some rounding
**   errors that real div/mul don't make) and all together the resultant code
**   ran slower than just using the shifts all the time.
** - Changed some of the variable names to be more meaningful.
*/

#include "adpcm.h"
#include <stdio.h> /*DBG*/

#ifndef __STDC__
#define signed
#endif

/* Intel ADPCM step variation table */
static int indexTable[16] = {
   
    -1, -1, -1, -1, 2, 4, 6, 8,
    -1, -1, -1, -1, 2, 4, 6, 8,
};

static int stepsizeTable[89] = {
   
    7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
    19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
    50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
    130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
    337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
    876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
    2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
    5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
    15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
};
    
void adpcm_coder(short *indata, signed char *outdata, int len, struct adpcm_state *state)
{
   
    short *inp;			/* Input buffer pointer */
    signed char *outp;		/* output buffer pointer */
    int val;			/* Current input sample value */
    int sign;			/* Current adpcm sign bit */
    int delta;			/* Current adpcm output value */
    int diff;			/* Difference between val and valprev */
    int step;			/* Stepsize */
    int valpred;		/* Predicted output value */
    int vpdiff;			/* Current change to valpred */
    int index;			/* Current step change index */
    int outputbuffer;		/* place to keep previous 4-bit value */
    int bufferstep;		/* toggle between outputbuffer/output */

    outp = (signed char *)outdata;
    inp = indata;

    valpred 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值