WAV系列之二:ADPCM编解码原理及代码实现-程序员宅基地

技术标签: 音频编解码  ADPCM  编解码  代码实现  PCM  

参考自:《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
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/houxiaoni01/article/details/104702570

智能推荐

基于Kepler.gl 和 Google Earth Engine 卫星数据创建拉伸多边形地图-程序员宅基地

文章浏览阅读965次,点赞18次,收藏21次。现在我们有了 2021 年和 2023 年的 NDVI 数据帧,我们需要从 2021 年的值中减去 2023 年的值以捕获 NDVI 的差异。该数据集包括像素级别的植被值,我们将编写一个自定义函数来根据红色和绿色波段的表面反射率计算 NDVI。在我的上一篇文章中,我演示了如何将单个多边形分割/镶嵌为一组大小均匀的六边形。现在我们有了植被损失数据,让我们使用 Kepler.gl 可视化每个六边形的植被损失。将地图保存为 HTML 文件,在浏览器中打开 HTML 以获得更好的视图。现在我们将调用该函数并使用、

Echarts绘制任意数据的正态分布图_echarts正态分布图-程序员宅基地

文章浏览阅读3.3k次,点赞6次,收藏5次。正态分布,又称高斯分布或钟形曲线,是统计学中最为重要和常用的分布之一。_echarts正态分布图

Android中发送短信等普通方法_android bundle.get("pdus");-程序员宅基地

文章浏览阅读217次。首先要在Mainfest.xml中加入所需要的权限:[html] view plain copyprint?uses-permission android:name="android.permission.SEND_SMS"/> uses-permission android:name="android.permission.READ_SMS"/> _android bundle.get("pdus");

2021-07-26 WSL2 的安装和联网_wsl2 联网-程序员宅基地

文章浏览阅读2.6k次。0、说明最近在学习 Data Assimilation Research Testbed (DART) 相关内容,其软件是在 Unix/Linux 操作系统下编译和运行的 ,由于我的电脑是 Windows 10 的,DART 推荐可以使用 Windows Subsystem For Linux (WSL) 来创建一个 Windows 下的 Linux 子系统。以下的内容主要介绍如何安装 WSL2,以及 WSL2 的联网。1、如何在 Windows 10 下安装WSL具体的安装流程可以在 microso_wsl2 联网

DATABASE_LINK 数据库连接_添加 database link重复的数据库链接命-程序员宅基地

文章浏览阅读1k次。DB_LINK 介绍在本机数据库orcl上创建了一个prod_link的publicdblink(使用远程主机的scott用户连接),则用sqlplus连接到本机数据库,执行select * from scott.emp@prod_link即可以将远程数据库上的scott用户下的emp表中的数据获取到。也可以在本地建一个同义词来指向scott.emp@prod_link,这样取值就方便多了..._添加 database link重复的数据库链接命

云-腾讯云-实时音视频:实时音视频(TRTC)-程序员宅基地

文章浏览阅读3.1k次。ylbtech-云-腾讯云-实时音视频:实时音视频(TRTC)支持跨终端、全平台之间互通,从零开始快速搭建实时音视频通信平台1.返回顶部 1、腾讯实时音视频(Tencent Real-Time Communication,TRTC)拥有QQ十几年来在音视频技术上的积累,致力于帮助企业快速搭建低成本、高品质音视频通讯能力的完整解决方案。..._腾讯实时音视频 分享链接

随便推点

用c语言写个日历表_农历库c语言-程序员宅基地

文章浏览阅读534次,点赞10次,收藏8次。编写一个完整的日历表需要处理许多细节,包括公历和农历之间的转换、节气、闰年等。运行程序后,会输出指定年份的日历表。注意,这个程序只是一个简单的示例,还有很多可以改进和扩展的地方,例如添加节气、节日等。_农历库c语言

FL Studio21.1.1.3750中文破解百度网盘下载地址含Crack补丁_fl studio 21 注册机-程序员宅基地

文章浏览阅读1w次,点赞28次,收藏27次。FL Studio21.1.1.3750中文破解版是最优秀、最繁荣的数字音频工作站 (DAW) 之一,日新月异。它是一款录音机和编辑器,可让您不惜一切代价制作精美的音乐作品并保存精彩的活动画廊。为方便用户,FL Studio 21提供三种不同的版本——Fruity 版、Producer 版和签名版。所有这些版本都是独一无二的,同样具有竞争力。用户可以根据自己的需要选择其中任何一种。FL Studio21.1.1.3750中文版可以说是一站式综合音乐制作单位,可以让您录制、作曲、混音和编辑音乐。_fl studio 21 注册机

冯.诺伊曼体系结构的计算机工作原理是,冯 诺依曼型计算机的工作原理是什么...-程序员宅基地

文章浏览阅读1.3k次。冯诺依曼计算机工作原理冯 诺依曼计算机工作原理的核心是 和 程序控制世界上不同型号的计算机,就其工作原理而言,一般都是认为冯 诺依曼提出了什么原理冯 诺依曼原理中,计算机硬件系统由那五大部分组成的 急急急急急急急急急急急急急急急急急急急急急急冯诺依曼结构计算机工作原理的核心冯诺依曼结构和现代计算机结构模型 转载重学计算机组成原理 一 冯 诺依曼体系结构从冯.诺依曼的存储程序工作原理及计算机的组成来..._简述冯诺依曼计算机结构及工作原理

四国军棋引擎开发(2)简单的事件驱动模型下棋-程序员宅基地

文章浏览阅读559次。这次在随机乱下的基础上加上了一些简单的处理,如进营、炸棋、吃子等功能,在和敌方棋子产生碰撞之后会获取敌方棋子大小的一些信息,目前采用的是事件驱动模型,当下完一步棋界面返回结果后会判断是否触发了相关事件,有事件发生则处理相关事件,没有事件发生则仍然是随机下棋。1.事件驱动模型首先定义一个各种事件的枚举变量,目前的事件有工兵吃子,摸暗棋,进营,明确吃子,炸棋。定义如下:enum MoveE..._军棋引擎

STL与泛型编程-第一周笔记-Geekband-程序员宅基地

文章浏览阅读85次。1, 模板观念与函数模板简单模板: template< typename T > T Function( T a, T b) {… }类模板: template struct Object{……….}; 函数模板 template< class T> inline T Function( T a, T b){……} 不可以使用不同型别的..._geekband 讲义

vb.net正则表达式html,VB.Net常用的正则表达式(实例)-程序员宅基地

文章浏览阅读158次。"^\d+$"  //非负整数(正整数 + 0)"^[0-9]*[1-9][0-9]*$"  //正整数"^((-\d+)|(0+))$"  //非正整数(负整数 + 0)"^-[0-9]*[1-9][0-9]*$"  //负整数"^-?\d+$"    //整数"^\d+(\.\d+)?$"  //非负浮点数(正浮点数 + 0)"^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0..._vb.net 正则表达式 取html中的herf