ADPCM编解码的使用

网上有很多讲解Adpcm编解码的,但是就没有详细说明其是需要如何使用的。

这里就记录下是如何使用代码的,即是函数的参数需要填写什么,要注意的要点

ADPCM(Adaptive Differential Pulse Code Modulation),是一种针对 16bits( 或8bits或者更高) 声音波形数据的一种有损压缩算法,它将声音流中每次采样的 16bit 数据以 4bit 存储,所以压缩比 1:4.

ADPCM数据存放形式

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的编解码文件

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];
};
进行apdcm编码

那么需要从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<short*>(&buf[2])。

第三个参数len不是进行编码的数据长度,是需要编码的采集点个数,因为每个采集点是short类型的,所以其个数是总长度/2,而又需要去掉首两个字节的原始pcm数据,所以其是(readdatasize - 2) / 2。

可以看出,这进行编解码的数据长度大小要一定是偶数的才行。

第四个参数是adpcm_state*类型state,该函数内部会更新参数state,其主要就是更新其成员valprevindex。

要是最后的要进行编码的数据编码后不够一个块大小,也要按照一个块大小编码,保存长度为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<short>(buf[1]) << 8) | buf[0];       //获取原始的2字节pcm数据
        block.RESERVED = 0;
        state.valprev = block.sample0;
        adpcm_coder(reinterpret_cast<short*>(&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);
}
进行adpcm解码

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<short>(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);
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值