一、採樣率
具體可以參考資料 :百度百科
例如: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<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