音頻之聲道、採樣位寬、採樣率轉換原理及其代碼實現

一、採樣率

具體可以參考資料 :百度百科
例如: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<ui_samples;i++){
        us_step = (psi_buff[i] - psi_buff[i-1]) / 2;
        psi_outbuf[j] =  psi_buff[i-1]+us_step;
        psi_outbuf[j+1] =  psi_buff[i];
        j+=2;
    }
    s_sample_prev = psi_buff[i-1];
}

由以上可知,當我們傳入的需要轉換的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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章