具体可以参考资料 :百度百科
例如:16000Hz
表示1s
中在连续信号中采集16000次
,每一次叫做一个采样点
。
具体可以参考资料:百度百科
例如:16bit
表示每一个采样点
采集2个byte
的数据,也就是2个字节
。
具体可以参考资料:百度百科
常见的声道有单声道
与立体声
,
1. 立体声有L,R
两个声道组成,我们可以在L,R
中分别填充相同的数据或者不同的数据,以达到更强的音质和可以同时在L,R
听到不同的声音,排列顺序为
L,R,L,R,L,R,L,R..............
L
,或者R
的数据,排列顺序为L,L,L,L,L,L,L,L............. 或者 R,R,R,R,R,R,R,R,R...........
例如:
采样率为16kHz
,采用位宽为16bit
,单声道
,在1分钟
中采集数据的大小为多少?
16000\*2\*60/1024/1024~=1.83MB
线性插值:百度百科
例如:8kHz转16kHz。
5.1 分析:
前面说了采样率表示每秒采多少个采样点,那么8kHz
就是1s
采8000
次,而16kHz
表示1s
采集16000
次,8kHz
转换成16kHz
,就需要在每次采的时候增加一个采样点,以达到和16kHz
的效果。
5.2 引发思考:
那么如何在每次采的时候增加一个采样点,已达到和16kHz同样的效果呢?
5.3 解决思考:
在每个音频数据前面增加一个数据,那么这个数据如何计算呢?线程插值去计算,例如下面一串音频数据:
10 20 30 40 90 67
那么通过线性差值如何计算插值呢?
例如第一个插值:(10-0)/2 +0=5
例如第一个插值:(20-10)/2 +10=15
.....
得到如下数据
5 10 15 20 …
通过如上我们便可以得出8k转16k
的公式如下:
typedef signed char k_sint8; //注意编译把char认为是有符号还是无符号。
typedef char k_int8; //注意编译把char认为是有符号还是无符号。
typedef unsigned char k_uint8;
typedef signed short int k_int16;
typedef unsigned short int k_uint16;
typedef signed int k_int32;
typedef unsigned int k_uint32;
typedef signed long k_intL32;
typedef unsigned long k_uintL32;
typedef signed long long k_intLL64;
typedef unsigned long long k_uintLL64;
typedef float k_float32;
typedef double k_double64;
static k_int16 s_sample_prev = 0;
void setpresample(k_int16 sampe)
{
s_sample_prev = sampe;
}
void convert8_16k(k_int16 *psi_buff, k_int16* psi_outbuf,k_uint32 ui_samples){
k_uint32 i,j = 2;
k_uint16 us_step = 0;
us_step = ((psi_buff[0] - s_sample_prev)) / 2; //
psi_outbuf[0] = s_sample_prev + us_step;
psi_outbuf[1] = psi_buff[0]; //us_data_pre + (us_step*3)
for(i=1;i
由以上可知,当我们传入的需要转换的psi_buff
为一段音频拆分的buf时,我们需要定义一个变量s_sample_prev
去记录上一个buf最后的值,以便下一个buf转换时用来计算,最后就是按照我们之前讲解的步骤,去一个个计算psi_outbuf
的值。
5.4 没有倍数的采样率转换
当我们采样率转换之间有倍数关系时,只要在每次采样的时候增加n个采样点或者减少n个采样点,那么当不是关系时上面的算法就失效了。使用如下算法
static void resampleData(const int16_t *sourceData, int32_t sampleRate, uint32_t srcSize, int16_t *destinationData, int32_t newSampleRate,uint32_t dstSize)
{
if (sampleRate == newSampleRate)
{
memcpy(destinationData, sourceData, srcSize * sizeof(int16_t));
return;
}
uint32_t last_pos = srcSize - 1;
//LOGDV("srcSize=%d,dstSize=%d",srcSize,dstSize);
for (uint32_t idx = 0; idx < dstSize; idx++)
{
float index = ((float) idx * sampleRate) / (newSampleRate);
uint32_t p1 = (uint32_t) index;
float coef = index - p1;
uint32_t p2 = (p1 == last_pos) ? last_pos : p1 + 1;
destinationData[idx] = (int16_t) ((1.0f - coef) * sourceData[p1] + coef * sourceData[p2]);
// LOGDV("index=%f,p1=%d,coef=%f,p2=%d",index,p1,coef,p2);
}
}
6.1 原理
前面说了采样位宽表示一个采样点采几个字节的数据,当采样位宽为16bit时,意味着每采一个采样点采2个字节的数据,当我们想转换成24bit或者8bit时,意味着每采一个采样点需要采3个或者1个字节的数据,这样我们就可以知道,
1. 如果我们是低位宽转换为高位宽
只需要在每个采样点中增加n个字节,并且为了保证原有的音频数据不失真的情况下,我们只需要在高位补0
即可。
2.如果我们是高位宽转换为低位宽
只需要在每个采样点中减少n个字节,并且为了保证原有的音频数据不失真的情况下,我们只需要把低位去除
即可。
因此得到如下代码:
static unsigned char * bitWidthConvert(unsigned char * data, int nLen, int oldBitWidth,int newBitWidth,int trackCount){
if(oldBitWidth==newBitWidth){
return data;
}
//位宽转换之后,需要改变的数据大小
int changeSize=0;
//位宽转换之后,目标数据的大小
int targetSize=0;
//源数据的采样点count
int samplerRate = (size% (oldBitWidth/8 * trackCount)) ==0 ? ( size/trackCount/ (oldBitWidth/8) ) : (int)( (float)(size/trackCount/(oldBitWidth/8)) +1 );
if(oldBitWidth > newBitWidth){
changeSize = ( (oldBitWidth-newBitWidth) /8 ) *samplerRate;
targetSize = size-changeSize;
}else{//oldBitWidth < newBitWidth
changeSize = ( (newBitWidth-oldBitWidth) /8 ) *samplerRate;
targetSize = size+changeSize;
}
//新的位宽的字节数
int newBitWidthByteCount= newBitWidth/8;
//旧的位宽的字节数
int oldBitWidthByteCount= oldBitWidth/8;
//目标数据
unsigned char * targetBuf = new unsigned char[targetSize];
//LOGDV("tempBitWidthTimes=%d,targetSize=%d,samplerRate=%d",tempBitWidthTimes,targetSize,samplerRate);
//临时的源数据的每个采样点的数据
uint32_t tempData;
//目标数据偏移量
int newBitWidthOffset=0;
int oldBitWidthOffset=0;
// uint32_t* data32bit=(uint32_t*)data;
if (oldBitWidth < newBitWidth){
for (int i = 0; i < samplerRate; ++i) {
memcpy(&tempData,data+oldBitWidthOffset, oldBitWidthByteCount);
oldBitWidthOffset+=oldBitWidthByteCount;
memcpy(targetBuf+newBitWidthOffset,&tempData, newBitWidthByteCount);
newBitWidthOffset+=newBitWidthByteCount;
}
}else{
for (int i = 0; i < samplerRate; ++i) {
memcpy(&tempData,data+oldBitWidthOffset, oldBitWidthByteCount);
//去除低位
tempData = (tempData >> (oldBitWidth-newBitWidth) );
memcpy(targetBuf+newBitWidthOffset,&tempData, newBitWidthByteCount);
newBitWidthOffset+=newBitWidthByteCount;
}
}
}
7.1原理
前面我们已经说了单声道只有L
或者R
一个声道,而双声道包含L,R
两个声道,那么
1. 双声道转换为单声道,只要取其中一个声道的数据即可,
2. 单声道转换为双声道,只要将单声道数据再复制一份到R
的位置即可。
因此得到如下代码:
static unsigned char * audioTrackConvert(unsigned char * srcTrackBuf, int srcTrackLen, int nPerSampleBytesPerTrack,bool isSingleConvertDoubleTrack)
{
int targetBuflLen=0;
int offset=0;
unsigned char * targetTrackBuf;
if(isSingleConvertDoubleTrack){//单声道转双声道
targetBuflLen== srcTrackLen * 2;
targetTrackBuf= new unsigned char[targetBuflLen];
for (int i = 0; i < srcTrackLen; i+=nPerSampleBytesPerTrack)
{
for (int j = 0; j < 2; j++)
{
memcpy(targetTrackBuf + offset,
srcTrackBuf + (i*nPerSampleBytesPerTrack), nPerSampleBytesPerTrack);
offset+=nPerSampleBytesPerTrack;
}
}
}else{//双声道转单声道
targetBuflLen== srcTrackLen /2;
targetTrackBuf= new unsigned char[targetBuflLen];
for (int i = 0; i < targetBuflLen; i+=nPerSampleBytesPerTrack)
{
memcpy(targetTrackBuf + (i*nPerSampleBytesPerTrack),
srcTrackBuf + ((i+1)*nPerSampleBytesPerTrack), nPerSampleBytesPerTrack);
}
}
return targetTrackBuf;
}
代码下载:github