这两天调ST的一颗PDM MIC芯片,PDM转PCM是完成了,但是PCM数据量太大了不适合传输,所以在尝试ADPCM压缩。网上找了很久关于PCM压缩到ADPCM以及ADPCM解压缩到PCM的文章,很多都讲压缩的实现过程,关键是很多像我一样的喜欢拿来主义的并不感兴趣。经过几天的摸索,大概理清了一些东西,顺手记下来。
正文之前还是简单说一下PCM头跟ADPCM头。实际音频采样回来都是裸的PCM数据,加上一个头就是可以播放的wav格式文件。ADPCM有很多种,有5bit压缩的,更多的是4bit压缩,也有3bit压缩的,我只了解了其中的4bit压缩。废话不多说,还是上代码。因为测试代码是在Qt creator上面写的,有一些数据类型是Qt平台定义的,应该很好辨识。
struct PCMHeaderInfo
{
char RIFF[4];
quint32 filesize;
char WAVE[4];
char FMT[4];
quint32 format;
quint16 fmttag;
quint16 channels;
quint32 samplerate;
quint32 byterate;
quint16 blockalign;
quint16 bitspersample;
char DATA[4];
quint32 datalength;
};
struct ADPCMHeaderInfo
{
char RIFF[4];
quint32 filesize;
char WAVE[4];
char FMT[4];
quint32 format;
quint16 fmttag;
quint16 channels;
quint32 samplerate;
quint32 byterate;
quint16 blockalign;
quint16 bitspersample;
quint16 blockinfo[2];
char DATA[4];
quint32 sampledatabytes;
};
可以看出pcm的头跟adpcm的头很相似。
RIFF[4]: 恒定的’R’,‘I’,‘F’,‘F’;
filesize: 文件大小,wav文件的总大小 - 8 ;
WAVE[4]: 恒定的’W’,‘A’,‘V’,‘E’;
FMT[4]: 恒定的’f’,‘m’,‘t’,’ ‘;最后一个空格不要忘了;
format: PCM文件是0x00000010,ADPCM文件是0x00000014(此处可能有其他情况,因为ADPCM有很多标准;);
fmttag: PCM文件是0x0001,ADPCM文件是0x0011(也有可能是其他的);
channels: 音频的通道数,单声道就是01,双声道就是02;
samplerate: 音频的采样率,常见的8k,16k,22.05k,32k,44.1k,48k等,就是对应的数值8000,16000…
byterate:应该可以叫码率吧,值是(采样率 * 通道数)* 采样位数 / 8;
blockalign: PCM文件是bitspersample / 2,ADPCM我使用的是256也就是0x0100;后面会说这256字节的具体含义;
bitspersample :每个采样点的位数。常见的16位,就是说前段采样AD的位数是16位,一次采样产生16bit的数据;
blockinfo[2] :ADPCM特有的, blockinfo[0]恒定的0x0002,blockinfo[1] = (blockalign - 4) * 2 + 1;
DATA[4] :恒定的’d’,‘a’,‘t’,‘a’;
sampledatabytes :总的采样字节数(文件总大小取出头信息),即wav文件的总大小 - 44(PCM)或者-48(ADPCM) ;
大概头的一些简要说明就先说这么多,因为我的测试音频都是单声道的,双声道的可能有些不一样,不能全部照搬。
接下去说一下ADPCMde block信息。之前定了ADPCM一个block大小256字节,怎么定义这256字节?
struct ADPCMBlock
{
qint16 sample0;
quint8 index;
quint8 RESERVED;
quint8 sampledata[252];
};
前两个字节是该block第一个为压缩过的采样数据,其实是个short型,占两个字节,中间一个字节是上一个block的index,具体作用查看adpcm.c中的源码。下一个字节保留没用,之后的252字节就是该block剩余的采样数据。这样的话,因为采用的是4bit压缩,一个16bit的PCM数据就被压缩到4bit,这样252字节ADPCM数据就是504个PCM数据的压缩,再加上最开始没有被压缩的一个PCM数据,一个blcok就包含了505个PCM数据。
接下去就是压缩算法,找的gayhub上面的一个92年的,应该比较成熟。其实就一个adpcm.h跟adpcm.c文件,里面就两个api,一个编码的一个解码的,很简单。但是实际输出的音频文件就很大的噪声,问了一下别人说PCM直接转ADPCM是会有噪音,所以那些软件都做过其他处理,后来我用Au测试生成的文件也有一些噪音,3bit压缩噪音还是很明显,4bit压缩就要好一些。
/*
** 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, unsigned 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(unsigned 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);
/* 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;
}
其余的一些操作就是读写文件,添加头信息这些。
struct adpcm_state state =
{
0,
0
};
#pragma pack(1)
struct PCMHeaderInfo pcmheader_t;
#pragma pack()
#pragma pack(1)
struct ADPCMHeaderInfo adpcmheader_t;
#pragma pack()
#pragma pack(1)
struct ADPCMBlock block;
#pragma pack()
上面是一些数据的定义,那几个结构体记得1byte对齐,不然后面写文件的时候可能会有问题。还有一个state结构体,类型说明是在adpcm.h文件,其实就是记录的第一个没有被压缩的采样数据以及上一个block的index,文件里面的压缩跟解压缩的api里面需要这个结构体,正好我们在连续转换的时候可以直接将这个结构体做参数传进去,结构体成员的内容会自动刷新的。
void MainWindow::getFileName()
{
fileDialog = new QFileDialog(this);
fileDialog->setAcceptMode(QFileDialog::AcceptOpen);
fileDialog->setFileMode(QFileDialog::AnyFile);
filefull = fileDialog->getOpenFileName(this,"Open wav or dat file",".","*.wav *.dat");
fileinfo = QFileInfo(filefull);
ui->filenameLineEdit->setText(fileinfo.fileName());
}
void MainWindow::initHeaderInfo(struct PCMHeaderInfo *header,quint32 samplerate,quint16 databits)
{
header->RIFF[0] = 'R';
header->RIFF[1] = 'I';
header->RIFF[2] = 'F';
header->RIFF[3] = 'F';
header->filesize = 0;
header->WAVE[0] = 'W';
header->WAVE[1] = 'A';
header->WAVE[2] = 'V';
header->WAVE[3] = 'E';
header->FMT[0] = 'f';
header->FMT[1] = 'm';
header->FMT[2] = 't';
header->FMT[3] = ' ';
header->format = 0x00000010;
header->fmttag = 0x01;
header->channels = 0x01;
header->samplerate = samplerate;
header->byterate = (samplerate * header->channels * databits / 8);
header->blockalign = databits / 8;
header->bitspersample = databits;
header->DATA[0] = 'd';
header->DATA[1] = 'a';
header->DATA[2] = 't';
header->DATA[3] = 'a';
header->datalength = 0;
}
void MainWindow::initHeaderInfo(struct ADPCMHeaderInfo *header,quint32 samplerate,quint16 databits)
{
header->RIFF[0] = 'R';
header->RIFF[1] = 'I';
header->RIFF[2] = 'F';
header->RIFF[3] = 'F';
header->filesize = 0;
header->WAVE[0] = 'W';
header->WAVE[1] = 'A';
header->WAVE[2] = 'V';
header->WAVE[3] = 'E';
header->FMT[0] = 'f';
header->FMT[1] = 'm';
header->FMT[2] = 't';
header->FMT[3] = ' ';
header->format = 0x00000014;
header->fmttag = 0x0011;
header->channels = 0x01;
header->samplerate = samplerate;
header->byterate = (samplerate * header->channels * databits / 8);
header->blockalign = 256;
header->bitspersample = databits;
header->blockinfo[1] = (header->blockalign - 4) * 2 + 1;
header->blockinfo[0] = 0x0002;
header->DATA[0] = 'd';
header->DATA[1] = 'a';
header->DATA[2] = 't';
header->DATA[3] = 'a';
header->sampledatabytes = 0;
}
这边的三个函数分别是获取要操作的文件名以及路径这些,初始化PCM的头或者ADPCM的头。有一些内容目前未知,所以先写的0,到后来转换完成之后再填写。
void MainWindow::convertAdpcmToPcm(QFile *pcmfile,QFile* adpcmfile)
{
qint16 pcmbuffer[505] = {0};
quint8 filebuffer[256] = {0};
quint32 adpcmfilesize = 0;
quint32 totalreadsize = 0;
quint32 readdatasize = 0;
quint32 convertsize = 0;
quint32 totalwritesize = 0;
quint32 totalblocks = 0;
quint32 blockcnt = 0;
if(adpcmfile->open(QIODevice::ReadOnly) == false)
{
QMessageBox::warning(NULL,"Error","ADPCM File Open Failed!",QMessageBox::Yes);
adpcmfile->close();
return;
}
if(pcmfile->open(QIODevice::WriteOnly) == false)
{
QMessageBox::warning(NULL,"Error","PCM File Open Failed!",QMessageBox::Yes);
pcmfile->close();
return;
}
QDataStream in(adpcmfile);
QDataStream out(pcmfile);
in.readRawData(reinterpret_cast<char *>(filebuffer),48);
memcpy(&adpcmheader_t,filebuffer,48);
if(adpcmheader_t.fmttag != 0x0011)
{
QMessageBox::information(NULL,"Error","File is Not a ADPCM File!",QMessageBox::Yes,QMessageBox::Yes);
pcmfile->close();
adpcmfile->close();
return;
}
if (adpcmheader_t.bitspersample != 4)
{
QMessageBox::information(NULL,"Error","Only Support 4bit ADPCM File!",QMessageBox::Yes,QMessageBox::Yes);
pcmfile->close();
adpcmfile->close();
return;
}
if(adpcmheader_t.channels != 0x01)
{
QMessageBox::information(NULL,"Error","Only Support 1 Channel Audio File!",QMessageBox::Yes,QMessageBox::Yes);
pcmfile->close();
adpcmfile->close();
return;
}
initHeaderInfo(&pcmheader_t,adpcmheader_t.samplerate,16);
out.writeRawData(reinterpret_cast<char *>(&pcmheader_t),sizeof(pcmheader_t));
totalwritesize += sizeof(pcmheader_t);
adpcmfilesize = adpcmfile->size();
if((adpcmfilesize - sizeof(adpcmheader_t)) % (adpcmheader_t.blockalign))
totalblocks = (adpcmfilesize - sizeof(adpcmheader_t)) / (adpcmheader_t.blockalign) + 1;
else
totalblocks = (adpcmfilesize - sizeof(adpcmheader_t)) / (adpcmheader_t.blockalign);
memset(&state,0,sizeof(state));
while(blockcnt < totalblocks)
{
readdatasize = in.readRawData(reinterpret_cast<char *>(filebuffer),256);
totalreadsize += readdatasize;
pcmbuffer[0] = static_cast<short>(filebuffer[1]) << 8 | filebuffer[0];
if(blockcnt == 0)
state.index = 0;
else
state.index = filebuffer[2];
convertsize = adpcm_decoder(&filebuffer[4],&pcmbuffer[1],readdatasize - 4,&state);
out.writeRawData(reinterpret_cast<char *>(pcmbuffer),sizeof(pcmbuffer));
totalwritesize += sizeof(pcmbuffer);
ui->playerProgressBar->setValue((totalreadsize * 1.0f) / adpcmfilesize * 100);
blockcnt++;
}
ui->playerProgressBar->setValue(100);
pcmheader_t.filesize = totalwritesize - 8;
pcmheader_t.datalength = totalwritesize - 44;
pcmfile->seek(0);
out.writeRawData(reinterpret_cast<char *>(&pcmheader_t),44); //update file size and sample size before close file
pcmfile->close();
adpcmfile->close();
QMessageBox::information(NULL,"Success!","Convert Success!!",QMessageBox::Yes,QMessageBox::Yes);
ui->playerProgressBar->setValue(0);
}
这个函数就是将adpcm文件解压缩成pcm文件。
void MainWindow::convertPcmToAdpcm(QFile *pcmfile,QFile* adpcmfile)
{
quint8 filebuffer[1010] = {0};
quint32 pcmfilesize = 0;
quint32 totalreadsize = 0;
quint32 totalwritesize = 0;
quint32 readdatasize = 0;
quint32 convertsize = 0;
quint32 totalblocks = 0;
quint32 blockcnt = 0;
quint32 tempcnt = 0;
if(pcmfile->open(QIODevice::ReadOnly) == false)
{
QMessageBox::warning(NULL,"Error","PCM File Open Failed!",QMessageBox::Yes);
pcmfile->close();
return;
}
if(adpcmfile->open(QIODevice::WriteOnly) == false)
{
QMessageBox::warning(NULL,"Error","ADPCM File Open Failed!",QMessageBox::Yes);
adpcmfile->close();
return;
}
QDataStream in(pcmfile);
QDataStream out(adpcmfile);
tempcnt = in.readRawData(reinterpret_cast<char *>(filebuffer), sizeof(pcmheader_t)); //skip 44bytes header info
memcpy(&pcmheader_t,filebuffer,44);
if(pcmheader_t.format != 0x0010)
{
QMessageBox::information(NULL,"Error","File is Not a RAW PCM File!",QMessageBox::Yes,QMessageBox::Yes);
pcmfile->close();
adpcmfile->close();
return;
}
if (pcmheader_t.bitspersample != 16)
{
QMessageBox::information(NULL,"Error","Only Support 16bit PCM File!",QMessageBox::Yes,QMessageBox::Yes);
pcmfile->close();
adpcmfile->close();
return;
}
if(pcmheader_t.channels != 0x01)
{
QMessageBox::information(NULL,"Error","Only Support 1 Channel Audio File!",QMessageBox::Yes,QMessageBox::Yes);
pcmfile->close();
adpcmfile->close();
return;
}
initHeaderInfo(&adpcmheader_t,pcmheader_t.samplerate,4); //only support 4bit compress
tempcnt = out.writeRawData(reinterpret_cast<char *>(&adpcmheader_t), sizeof(adpcmheader_t)); //write 48 bytes adpcm header
totalwritesize += tempcnt;
pcmfilesize = pcmfile->size();
if((pcmfilesize - sizeof(pcmheader_t)) % 1010)
totalblocks = (pcmfilesize - sizeof(pcmheader_t)) / 1010 + 1; //get total adpcm blocks
else
totalblocks = (pcmfilesize - sizeof(pcmheader_t)) / 1010;
while(blockcnt < totalblocks)
{
if (blockcnt == 0)
block.index = 0;
else
block.index = state.index; //get block index
readdatasize = in.readRawData(reinterpret_cast<char *>(filebuffer),1010); // read 1010 byte pcm data(505 sample points)
totalreadsize += readdatasize; //update total read size
block.sample0 = (static_cast<short>(filebuffer[1]) << 8) | filebuffer[0]; //get first sample point of current block;
block.RESERVED = 0;
state.valprev = block.sample0;
convertsize = adpcm_coder(reinterpret_cast<short *>(&filebuffer[2]),block.sampledata,(readdatasize - 2) / 2,&state);//convert the remain 504 sample points;
tempcnt = out.writeRawData(reinterpret_cast<char *>(&block), sizeof(block)); //write 256 bytes block data
totalwritesize += tempcnt;
ui->playerProgressBar->setValue((totalreadsize * 1.0f) / pcmfilesize * 100);
blockcnt++;
}
ui->playerProgressBar->setValue(100);
adpcmheader_t.filesize = totalwritesize - 8;
adpcmheader_t.sampledatabytes = totalwritesize - 48;
adpcmfile->seek(0);
out.writeRawData(reinterpret_cast<char *>(&adpcmheader_t),48); //update file size and sample size before close file
pcmfile->close();
adpcmfile->close();
QMessageBox::information(NULL,"Success!","Convert Success!!",QMessageBox::Yes,QMessageBox::Yes);
ui->playerProgressBar->setValue(0);
}
跟上一个函数相反的操作。
完整的工程文件在下面这个链接,工程是用的Qt creator创建的。
http://download.csdn.net/download/u013910954/10042543