音频之声道、采样位宽、采样率转换原理及其代码实现

一、采样率

具体可以参考资料 :百度百科
例如:16000Hz 表示1s中在连续信号中采集16000次,每一次叫做一个采样点


二、采样位宽(位数)

具体可以参考资料:百度百科
例如:16bit 表示每一个采样点采集2个byte的数据,也就是2个字节


三、声道

具体可以参考资料:百度百科
常见的声道有单声道立体声

1. 立体声L,R两个声道组成,我们可以在L,R中分别填充相同的数据或者不同的数据,以达到更强的音质和可以同时在L,R听到不同的声音,排列顺序为

L,R,L,R,L,R,L,R..............
  1. 而单声道通常只有一个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就是1s8000次,而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

你可能感兴趣的:(音频相关)