目录

  • 1. 源代码
    • adpcm.h
    • adpcm.c
  • 2. adpcm编解码原理
    • 1.adpcm编码原理
    • 2.adpcm解码原理
    • 注释说明
  • 3. ADPCM数据存放形式
    • 1. adpcm 数据块介绍
    • 2. 单通路pcm格式:
    • 3. 双通路pcm格式:
  • 4. 编解码代码实现

本文讲解 0x0011 /* Intel’s DVI ADPCM */的编码解码原理。本文原创,转发请标注littlezls原创。

1. 源代码

adpcm.h

#ifndef ADPCM_H
#define ADPCM_Hstruct 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 ReservedPermission 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 = state->valprev;index = state->index;step = stepsizeTable[index];bufferstep = 1;for ( ; len > 0 ; len-- ) {val = *inp++;/* Step 1 - compute difference with previous value */diff = val - valpred;sign = (diff < 0) ? 8 : 0;if ( sign ) diff = (-diff);/* Step 2 - Divide and clamp *//* Note:** This code *approximately* computes:**    delta = diff*4/step;**    vpdiff = (delta+0.5)*step/4;** but in shift step bits are dropped. The net result of this is** that even if you have fast mul/div hardware you cannot put it to** good use since the fixup would be too expensive.*/delta = 0;vpdiff = (step >> 3);if ( diff >= step ) {delta = 4;diff -= step;vpdiff += step;}step >>= 1;if ( diff >= step  ) {delta |= 2;diff -= step;vpdiff += step;}step >>= 1;if ( diff >= step ) {delta |= 1;vpdiff += step;}/* Step 3 - Update previous value */if ( sign )valpred -= vpdiff;elsevalpred += vpdiff;/* Step 4 - Clamp previous value to 16 bits */if ( valpred > 32767 )valpred = 32767;else if ( valpred < -32768 )valpred = -32768;/* Step 5 - Assemble value, update index and step values */delta |= sign;index += indexTable[delta];if ( index < 0 ) index = 0;if ( index > 88 ) index = 88;step = stepsizeTable[index];/* Step 6 - Output value if ( bufferstep ) {outputbuffer = (delta << 4) & 0xf0;} else {*outp++ = (delta & 0x0f) | outputbuffer;}*/if ( bufferstep ) {outputbuffer = delta & 0x0f;} else {*outp++ = ((delta << 4) & 0xf0) | outputbuffer;}bufferstep = !bufferstep;}/* Output last step, if needed */if ( !bufferstep )*outp++ = outputbuffer;state->valprev = valpred;state->index = index;
}void adpcm_decoder(signed char *indata, short *outdata, int len, struct adpcm_state *state)
{signed char *inp;      /* Input buffer pointer */short *outp;      /* output buffer pointer */int sign;            /* Current adpcm sign bit */int delta;          /* Current adpcm output value */int step;           /* Stepsize */int valpred;      /* Predicted value */int vpdiff;            /* Current change to valpred */int index;           /* Current step change index */int inputbuffer;     /* place to keep next 4-bit value */int bufferstep;     /* toggle between inputbuffer/input */outp = outdata;inp = (signed char *)indata;valpred = state->valprev;index = state->index;step = stepsizeTable[index];bufferstep = 0;for ( ; len > 0 ; len-- ) {/* Step 1 - get the delta value */if ( !bufferstep ) {inputbuffer = *inp++;delta = inputbuffer & 0xf;} else {delta = (inputbuffer >> 4) & 0xf;}bufferstep = !bufferstep;/* Step 2 - Find new index value (for later) */index += indexTable[delta];if ( index < 0 ) index = 0;if ( index > 88 ) index = 88;/* Step 3 - Separate sign and magnitude */sign = delta & 8;delta = delta & 7;/* Step 4 - Compute difference and new predicted value *//*** Computes 'vpdiff = (delta+0.5)*step/4', but see comment** in adpcm_coder.*/vpdiff = step >> 3;if ( delta & 4 ) vpdiff += step;if ( delta & 2 ) vpdiff += step>>1;if ( delta & 1 ) vpdiff += step>>2;if ( sign )valpred -= vpdiff;elsevalpred += vpdiff;/* Step 5 - clamp output value */if ( valpred > 32767 )valpred = 32767;else if ( valpred < -32768 )valpred = -32768;/* Step 6 - Update step value */step = stepsizeTable[index];/* Step 7 - Output value */*outp++ = valpred;}state->valprev = valpred;state->index = index;
}

2. adpcm编解码原理

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值)。为下次计算用;

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数据经过一次编码有损后,不论后面经过几次解码再编码都是数据一样,音质不会再次损失。即相对于第一次编码后,以后数据不论多少次编解码,属于无损输出。

3. ADPCM数据存放形式

本部分为adpcm数据存放说明,属于细节部分,很多代码解码出来有噪音就是因为本部分细节不对,所以需要仔细阅读。

1. adpcm 数据块介绍

adpcm数据是一个block一个block存放的,block由block header (block头) 和data 两者组成的。其中block header是一个结构,它在单声道下的定义如下:

Typedef struct
{short  sample0;    //block中第一个采样值(未压缩)
BYTE  index;     //上一个block最后一个index,第一个block的index=0;
BYTE  reserved;   //尚未使用
}MonoBlockHeader;

对于双声道,它的blockheader应该包含两个MonoBlockHeader其定义如下:

typedaf struct
{MonoBlockHeader leftbher;
MonoBlockHeader rightbher;
}StereoBlockHeader;

在解压缩时,左右声道是分开处理的,所以必须有两个MonoBlockHeader;
有了blockheader的信息后,就可以不需要知道这个block前面数据而轻松地解出本block中的压缩数据。故adpcm解码只与本block有关,与其他block无关,可以只单个解任何一个block数据。
block的大小是固定的,可以自定义,每个block含的采样数nsamples计算如下:

//
#define BLKSIZE 1024
block = BLKSIZE * channels;
//block = BLKSIZE;//ffmpeg
nsamples = (block  - 4 * channels) * 8 / (4 * channels) + 1;

例如audition软件就是采用上面的,单通路block为1024bytes,2041个samples,双通路block为2048,也是含有2041个sample。
而ffmpeg采用block =1024bytes,即不论单双通路都为1024bytes,通过公式可以算出单双通路的samples数分别为2041和1017;

2. 单通路pcm格式:

byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 byte 7 byte 8 byte 9
sample0 sample1 sample2 sample3 sample4

单通路压缩为adpcm数据为 4bytes block head + raw data:

byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 byte 7 byte 8 byte 9
sample0 index reserved data0 data1 data2 data3 data4
    其中sample1编码后存data0低4位,sample2编码后存data0高四位...

3. 双通路pcm格式:

byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 byte 7 byte 8 byte 9
sampleL0 sampleR0 sampleL1 sampleR1 sampleL2

双通路压缩为adpcm数据为 4bytes block L head + 4bytes block R head + 4bytes raw L data + 4bytes raw R data…:
adpcm双通路block head:

byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 byte 7
sample0L indexL reservedL sample0R indexR reservedR

接着双通路raw压缩数据4byte L, 4byte R …:

byte8 byte9 byte10 byte11 byte12 byte13 byte14 byte15 byte16 byte17 byte17
data0L data1L data2L data3L data0R data1R data2R data3R data4L data5L data6L

4. 编解码代码实现

需要特别留意双声道的处理和当数据不够1 block时的处理方式:
如果最后不够1block,多于1个,还按照1block进行编码,保存长度为1block。
如果最后只多余1个采样点,那就只保存blockheader,后面不用再保存了。
但在wav头告诉其实际采样点数,为将来解码还原相同的的采样点数。
代码包含了编码和解码测试用例,实现先编码再解码。欢迎交流学习
完整代码下载地址(本文只是详细说明了adpcm编解码,如果想wav文件编解码正确需要下载完整代码。
完整代码为0x0011 /* Intel’s DVI ADPCM */的编码解码代码实现。包括单双通路的处理和最后数据不是整块block的处理):
https://download.csdn.net/download/littlezls/10750913

adpcm编解码原理及其代码实现相关推荐

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

    参考自:<adpcm编解码原理及其代码实现> <ADPCM编码与解码学习笔记> <音频编码:ADPCM> 文章目录 1.PCM 1.1.采样 1.2.量化编码 2. ...

  2. 【语音编码】基于matlab ADPCM编解码(Matlab代码实现)

  3. yolov5的anchors及bbox的编解码原理

    yolov5的anchors的编解码原理 yolov5的anchors及bbox的编解码原理 1.anchor的生成 1)base anchor的生成 2)base anchor的平移和复制 2.bb ...

  4. Base64编解码原理并用Java手工实现Base64编解码

    Base64编解码原理 目前Base64已经成为网络上常见的传输8比特字节代码的编码方式之一.在做支付系统时,系统之间的报文交互都需要使用Base64对明文进行转码,然后进行签名或加密,之后再次Bas ...

  5. ADPCM编解码的使用

    网上有很多讲解Adpcm编解码的,但是就没有详细说明其是需要如何使用的. 这里就记录下是如何使用代码的,即是函数的参数需要填写什么,要注意的要点. ADPCM(Adaptive Differentia ...

  6. 哈夫曼编解码原理与实现【转载】

    1. 哈夫曼编解码原理 霍夫曼编码(Huffman Coding)是一种编码方法,霍夫曼编码是可变字长编码(VLC)的一种. 霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编 ...

  7. 一文读懂视频编解码原理

    引子 谈到视频的编解码,我们会自然地想到H.264.HEVC/H.265这些权威的视频编解码标准:谈到标准,有人觉得这个是有专门机构去研究的,我们关心应用就好:即使有兴趣读了标准和相关技术,面对更多的 ...

  8. MPEG4编解码原理

    ### Date: 2017/3/19 ### Author: SoaringLee ###Content: MPEG4 ASP编解码原理 一.MPEG4的编码原理 1 编码原理MPEG-4编码器主要 ...

  9. 一文读懂视频编解码原理[通俗易懂]

    一文读懂视频编解码原理[通俗易懂] 引子 谈到视频的编解码,我们会自然地想到H.264.HEVC/H.265这些权威的视频编解码标准:谈到标准,有人觉得这个是有专门机构去研究的,我们关心应用就好:即使 ...

最新文章

  1. 如何在Leangoo中,为企业创建项目
  2. 机器学习算法一览,应用建议与解决思路
  3. 通过WebViewJavascriptBridge实现OC与JS交互
  4. 某公司PIX 520防火墙系统和NAT的实施
  5. 【制表符\t】你不知道的制表符\t的那些事儿~
  6. 德阳计算机工程学校,德阳电子计算机工程学校2021年招生录取分数线
  7. 什么东西可以替代触屏笔?Ipad触屏笔推荐品牌
  8. 为何在网络上很难赚到钱?网络副业赚钱真的很难做吗?
  9. 分糖问题和插板法:10个相同糖果分给3个小朋友,有几种分法?
  10. IO缓冲(buffer)和高速缓存(cache)
  11. 消息循环中的TranslateMessage函数和DispatchMessage函数
  12. 系统服务器选型依据,1.2 服务器选型原则
  13. iphonex正面图_iPhoneX高清全方位图赏
  14. Robotaxi进入“大洗牌”时代,主机厂成接盘侠
  15. repo中manifest解析
  16. CSV格式的文件与EXCEL文件的区别
  17. 中国日化行业品牌营销方向与盈利前景研究报告2022版
  18. Web中间件之Apache Tomcat
  19. 【单相桥式全控整流电路求解纲要】单相桥式全控整流电路,U2=100V,负载中R=2Ω,L值极大,反电势E=60V,当a=30度时,分析电路
  20. 微信公众号文章错别字修改上限调整了?

热门文章

  1. C++性能之战(0)--Linux时间相关函数总结
  2. 关于EndNote 中文GB/T7144文献页码及括号不全的解决方法
  3. 这些聪明人为什么不来创业?聊聊投行和咨询公司的学霸们……
  4. JZOJ4727. 挺进
  5. 技术笔记:如何在win10操作系统中卸载自带的office(前提是无法在控制面板中卸载)
  6. Mac mysql数据库基本操作
  7. 平安科技有新招:游戏防沉迷更彻底
  8. CKA、CKAD、CKS、KCNA、CFCD考试
  9. Linux用户及组管理
  10. 系统升级后,电脑识别到读卡器,但识别不到SD卡的问题