网上有很多讲解Adpcm编解码的,但是就没有详细说明其是需要如何使用的。
这里就记录下是如何使用代码的,即是函数的参数需要填写什么,要注意的要点。
ADPCM(Adaptive Differential Pulse Code Modulation),是一种针对 16bits( 或8bits或者更高) 声音波形数据的一种有损压缩算法,它将声音流中每次采样的 16bit 数据以 4bit 存储,所以压缩比 1:4.
ADPCM的音频数据则是以块的形式保存,并且有固定的格式。每一个block包含header和data两部分。它在单声道下的定义如下:
typedef struct{
short sample0; //block中第一个采样值(未压缩) 2个字节
char index; //上一个block最后一个index,第一个block的index=0;
char reserved; //尚未使用的
}MonoBlockHeader // 块的头部
前2个字节的为未压缩的原始16bit数据,第3个字节为上一个块的index,第4个字节为保留位(没有使用)。ADPCM每个数据块的解压需要前三个字节的数据作为解压函数的dec_state结构体参数输入到解压函数中。
对于双声道,它的blockheader应该包含两个MonoBlockHeader其定义如下:
typedef struct
{
MonoBlockHeader leftbher;
MonoBlockHeader rightbher;
}StereoBlockHeader;
对于双通道来说,在解压缩时,其是分开处理的,所以必须有两个MonoBlockHeader。
那么经过adpcm编码之后,其音频数据是按照一块一块数据存放的,一块的数据,其结构是
struct ADPCMBlock
{
short sample0; //原始PCM采样数据
char index;
char RESERVED;
char sampledata[252]; //这个就是进行adpcm编码之后的音频数据
};
这里的整个数据块大小是256字节,但这不是规定一定要这个值的,可以由自己来定的。
很多文章只是给了编码和解码函数,没有完整的调用过程,这里面还是有点细节要注意,要注意每个块的index 和 sample0的填充。
adpcm.h文件
/*
** adpcm.h - include file for adpcm coder.
**
** Version 1.0, 7-Jul-92.
*/
#ifndef ADPCM_H
#define ADPCM_H
#ifdef __cplusplus
extern "C" {
#endif
struct adpcm_state {
short valprev; /* Previous output value */
char index; /* Index into stepsize table */
};
//len 是采样点的个数,不是字节大小
int adpcm_coder(short* indata, char* outdata, int len, struct adpcm_state* state);
int adpcm_decoder(char* indata, short* outdata, int len, struct adpcm_state* state);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* ADPCM_H*/
adpcm.c文件
/*
** 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"
/* 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
};
int adpcm_coder(short* indata, char* outdata, int len, struct adpcm_state* state)
{
int val; /* Current input sample value */
unsigned 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 */
unsigned int outputbuffer = 0;/* place to keep previous 4-bit value */
int count = 0; /* the number of bytes encoded */
valpred = state->valprev;
index = (int)state->index;
step = stepsizeTable[index];
while (len > 0) {
/* Step 1 - compute difference with previous value */
val = *indata++;
diff = val - valpred;
if (diff < 0)
{
delta = 8;
diff = (-diff);
}
else
{
delta = 0;
}
/* 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.
*/
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;
}
/* Phil Frisbie combined steps 3 and 4 */
/* Step 3 - Update previous value */
/* Step 4 - Clamp previous value to 16 bits */
if ((delta & 8) != 0)
{
valpred -= vpdiff;
if (valpred < -32768)
valpred = -32768;
}
else
{
valpred += vpdiff;
if (valpred > 32767)
valpred = 32767;
}
/* Step 5 - Assemble value, update index and step values */
index += indexTable[delta];
if (index < 0) index = 0;
else if (index > 88) index = 88;
step = stepsizeTable[index];
/* Step 6 - Output value */
outputbuffer = (delta << 4);
/* Step 1 - compute difference with previous value */
val = *indata++;
diff = val - valpred;
if (diff < 0)
{
delta = 8;
diff = (-diff);
}
else
{
delta = 0;
}
/* 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.
*/
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;
}
/* Phil Frisbie combined steps 3 and 4 */
/* Step 3 - Update previous value */
/* Step 4 - Clamp previous value to 16 bits */
if ((delta & 8) != 0)
{
valpred -= vpdiff;
if (valpred < -32768)
valpred = -32768;
}
else
{
valpred += vpdiff;
if (valpred > 32767)
valpred = 32767;
}
/* Step 5 - Assemble value, update index and step values */
index += indexTable[delta];
if (index < 0) index = 0;
else if (index > 88) index = 88;
step = stepsizeTable[index];
/* Step 6 - Output value */
*outdata++ = (unsigned char)(delta | outputbuffer);
count++;
len -= 2;
}
state->valprev = (short)valpred;
state->index = (char)index;
return count;
}
// 解码
int adpcm_decoder(char* indata, short* outdata, int len, struct adpcm_state* state)
{
unsigned 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 */
unsigned int inputbuffer = 0;/* place to keep next 4-bit value */
int count = 0;
valpred = state->valprev;
index = (int)state->index;
step = stepsizeTable[index];
/* Loop unrolling by Phil Frisbie */
/* This assumes there are ALWAYS an even number of samples */
while (len-- > 0) {
/* Step 1 - get the delta value */
inputbuffer = (unsigned int)*indata++;
delta = (inputbuffer >> 4) & 0xf;// &0xf 防止溢出
/* Step 2 - Find new index value (for later) */
index += indexTable[delta];
if (index < 0) index = 0;
else if (index > 88) index = 88;
/* Phil Frisbie combined steps 3, 4, and 5 */
/* Step 3 - Separate sign and magnitude */
/* Step 4 - Compute difference and new predicted value */
/* Step 5 - clamp output value */
/*
** Computes 'vpdiff = (delta+0.5)*step/4', but see comment
** in adpcm_coder.
*/
vpdiff = step >> 3;
if ((delta & 4) != 0) vpdiff += step;
if ((delta & 2) != 0) vpdiff += step >> 1;
if ((delta & 1) != 0) vpdiff += step >> 2;
if ((delta & 8) != 0)
{
valpred -= vpdiff;
if (valpred < -32768)
valpred = -32768;
}
else
{
valpred += vpdiff;
if (valpred > 32767)
valpred = 32767;
}
/* Step 6 - Update step value */
step = stepsizeTable[index];
/* Step 7 - Output value */
*outdata++ = (short)valpred;
/* Step 1 - get the delta value */
delta = inputbuffer & 0xf;
/* Step 2 - Find new index value (for later) */
index += indexTable[delta];
if (index < 0) index = 0;
else if (index > 88) index = 88;
/* Phil Frisbie combined steps 3, 4, and 5 */
/* Step 3 - Separate sign and magnitude */
/* Step 4 - Compute difference and new predicted value */
/* Step 5 - clamp output value */
/*
** Computes 'vpdiff = (delta+0.5)*step/4', but see comment
** in adpcm_coder.
*/
vpdiff = step >> 3;
if ((delta & 4) != 0) vpdiff += step;
if ((delta & 2) != 0) vpdiff += step >> 1;
if ((delta & 1) != 0) vpdiff += step >> 2;
if ((delta & 8) != 0)
{
valpred -= vpdiff;
if (valpred < -32768)
valpred = -32768;
}
else
{
valpred += vpdiff;
if (valpred > 32767)
valpred = 32767;
}
/* Step 6 - Update step value */
step = stepsizeTable[index];
/* Step 7 - Output value */
*outdata++ = (short)valpred;
count += 2;
}
state->valprev = (short)valpred;
state->index = (char)index;
return count;
}
场景:现在有一pcm音频文件,需要把这个整段pcm音频进行adpcm编码,而每个adpcm块的大小是256字节。这里说的都是单通道的。双通道可能会有其他的不同。(主要是我做的是单通道的。。)
struct ADPCMBlock
{
short sample0;
char index;
char RESERVED;
char sampledata[252];
};
那么需要从pcm文件中读取数据,那是应该读取多少呢,是一次性读取完还是分次读取呢。
是需要分次读取,因为需要把pcm数据刚好可以编码成一个个adpcm块(或者就一个adpcm块)。这里我们定下来adpcm块大小是256字节,一个块中的压缩后的adpcm音频数据是252字节,压缩比是1:4,所以原始pcm数据大小是252*4=1008.
而块头部还有2字节的原始pcm数据,所以需要每次从文件读取1008+2=1010字节来进行编码。
函数:int adpcm_coder(short* indata, char* outdata, int len, struct adpcm_state* state)
我们是需要直接保存首两个字节的原始pcm数据的,所以而在使用adpcm_coder函数时候,第一个参数那里就需要从第三个字节开始进行编码的。又因为是short*类型,所以是第一个参数写reinterpret_cast
第三个参数len不是进行编码的数据长度,是需要编码的采集点个数,因为每个采集点是short类型的,所以其个数是总长度/2,而又需要去掉首两个字节的原始pcm数据,所以其是(readdatasize - 2) / 2。
可以看出,这进行编解码的数据长度大小要一定是偶数的才行。
第四个参数是adpcm_state*类型state,该函数内部会更新参数state,其主要就是更新其成员valprev和index。
要是最后的要进行编码的数据编码后不够一个块大小,也要按照一个块大小编码,保存长度为1block。
int pcmToAdpcm(const char* PcmFileName,const char* outFileName)
{
FILE* inFile = fopen(PcmFileName, "rb");
if (!inFile) {
return 0;
}
fseek(inFile, 0L, SEEK_END); //到达文件的末尾
int pcmSize = ftell(inFile);//返回给定流 stream 的当前文件位置,这样pcmSize就是文件的大小
fseek(inFile, 0L, SEEK_SET);
int totalblocks = 0; //总的adpcm块的个数
int curBlockNum = 0; //已编码的adpcm块的个数
int readUnitSize = 1010; //每次从文件读取的字节大小,
//为什么是1010呢,因为之前定了adpcm块是256字节的,一个块中的压缩后的adpcm音频数据是252字节,压缩比是1:4,所以原始pcm数据大小是252*4=1008.
//而块头部还有2字节的原始pcm数据,所以需要每次从文件读取1008+2=1010字节来进行编码
//计算出中的block的个数
if (pcmSize % readUnitSize == 0)
totalblocks = pcmSize / readUnitSize;
else
totalblocks = pcmSize / readUnitSize + 1;
FILE* outFile = fopen(outFileName, "wb");
char buf[1024];
adpcm_state adpcmState;
ADPCMBlock block;
//下面的while循环内部的是adpcm编码操作的重点
while (curBlockNum < totalblocks) {
if (curBlockNum == 0)
block.index = 0;
else
block.index = adpcmState.index;
int readdatasize = fread(buf, 1, readUnitSize, inFile); //从input_fd文件读取readUnitSize字节
block.sample0 = (static_cast(buf[1]) << 8) | buf[0]; //获取原始的2字节pcm数据
block.RESERVED = 0;
state.valprev = block.sample0;
adpcm_coder(reinterpret_cast(&buf[2]), block.sampledata, (readdatasize - 2) / 2, &adpcmState);//convert the remain 504 sample points;
fwrite(&block, 1, sizeof(block), outFile);
++curBlockNum;
}
fclose(inFile);
fclose(outFile);
}
int adpcm_decoder(char* indata, short* outdata, int len, struct adpcm_state* state)。
第一个参数事adpcm块数据,前四个字节是头部,所以第一个参数写buf+4,从第五字节开始解码。
第二个参数是解码后的pcm数据。
第三个参数是adpcm块数据的长度,这个就真的是长度的。使用的时候需要减去头部4字节,所以使用方式是readdatasize - 4。
第四个参数是adpcm_state*类型state,该函数内部会更新参数state,其主要就是更新state.index。
其函数的返回值是解码后的pcm数据点的个数,即是有多少个short数据点。所以最终解码后的pcm数据长度是count*2。
int AdpcmToPcm(const char* adpcmFileName, const char* outFileName)
{
FILE* inFile = fopen(adpcmFileName, "rb");
if (!inFile) {
std::cout << " open input file failed\n";
return 0;
}
fseek(inFile, 0L, SEEK_END); //到达文件的末尾
int pcmSize = ftell(inFile);//返回给定流 stream 的当前文件位置,这样pcmSize就是文件的大小
fseek(inFile, 0L, SEEK_SET);
int totalblocks = 0; //总的adpcm块的个数
int curBlockNum = 0; //已解码的adpcm块的个数
int blockSize = sizeof(ADPCMBlock);
//计算出中的block的个数
if (pcmSize % readUnitSize == 0)
totalblocks = pcmSize / blockSize;
else
totalblocks = pcmSize / blockSize + 1;
FILE* outFile = fopen(outFileName, "wb");
short pcmBuf[1024];
char buf[1024];
adpcm_state adpcmState;
//下面的while循环内部的是adpcm编码操作的重点
while (curBlockNum < totalblocks)
{
if (blockcnt == 0)
adpcmState.index = 0;
else
adpcmState.index = buf[2];
memset(buf, 0, sizeof(buf));
int readdatasize = fread(buf, 1, blockSize, inFile);//从文件读取adpcm块数据
pcmBuf[0] = static_cast(buf[1]) << 8 | buf[0]; //得到原始的2字节pcm数据
state.valprev = pcmBuf[0];
int count = adpcm_decoder(&buf[4], &pcmBuf[1], readdatasize - 4, &adpcmState);//返回值是采集点的个数
fwrite(pcmBuf, 1,count * 2 , outFile); //每个采集点是short类型的,总长度就是count*2
++curBlockNum;
}
fclose(inFile);
fclose(outFile);
}