EMIPLIB庫分析三 MIPWAVInput

在之前的EMIPLIB庫分析三文章中分析過MIPWAVInput類。之前那篇文章因爲只是探尋EMIPLIB的執行框架,所以對一些細節未做過多地分析。因此,遺留下了很多不瞭解的實現代碼。MIPWAVInput類內就有很多這樣的代碼。

首先,初始化MIPWAVInput時調用open方法使用了最後兩個參數的缺省值。最後一個參數的缺省值指示代碼爲MIPWAVInput申請一塊空間,此空間爲一數組存儲浮點數,並生成一個MIPRawFloatAudioMessage消息。同時,因爲open函數最後一個參數使用了缺省值的影響,MIPWAVInput的push函數內會使用MIPWAVReader的浮點數版本的readFrames:bool MIPWAVReader::readFrames(float *buffer, int numFrames, int *numFramesRead)。疑問是,爲何確定讀取語音數據時強制進行浮點數轉換?確定使用浮點數的源頭來自MIPWAVInput的init函數。也就是說這項設置來自客戶端代碼。看到示例代碼顯示的就是使用浮點數。如果由客戶端代碼決定是否使用浮點數,那麼該依據什麼來做出這項決定呢?還是說,其實是否用浮點數還是無符號16位整型無所謂。測試結果表明,open函數的最後一個參數是true或者false對同一個wav文件而言沒有區別,在對端都可以聽到正確的語音。是不是因爲浮點數佔用空間更多,可以提供對原始語音更精確地回放?代碼中關於這個參數的相關說明或註釋基本沒有,所以也無從考證。

第二處疑問是每個採樣數據取出後的轉換過程。浮點數版本的readFrames函數內調用fread API從wav文件內讀取特定個數字節後,會對每個字節進行浮點數轉換操作。這裏爲了簡化起見,只考慮wav文件內單個採樣字節數是2的情形。 即,下面代碼中else部分代碼。

<p>for (int i = 0 ; i < num ; i++)
{
 for (int j = 0 ; j < m_channels ; j++)
 {
  if (m_bytesPerSample == 1)
  {
    ...
  }
  else
  {
   uint32_t x = 0;

   if ((m_pFrameBuffer[byteBufPos + m_bytesPerSample - 1] & 0x80) == 0x80)
    x = m_negStartVal; 
    int shiftNum = 0;
   for (int k = 0 ; k < m_bytesPerSample ; k++, shiftNum += 8, byteBufPos++)
    x |= ((uint32_t)(m_pFrameBuffer[byteBufPos])) << shiftNum;</p><p>   int32_t y = *((int32_t *)(&x));</p><p>   buffer[floatBufPos] = ((float)y)*m_scale;
  }  
  floatBufPos++;
 }
}</p>

上述代碼顯示,一個採樣數據中的低八位放置在一個無符號32位整型數據中的最低八位,一個採樣數據中的高八位放置在一個無符號32位整型數據中的倒數第二個低八位,無符號32位整型數據中的高十六位都置成1,得到的32位無符號整型數據再乘以m_scale。m_scale值是在解析wav文件頭時依據wav文件內的參數信息計算得到的。從計算公式可以看出,m_scale的值主要依據每採樣位數指標。

m_scale = (float)(2.0/((float)(((uint64_t)1) << bitsPerSample)));

現在用一個具體的數據代入這個公式看看能得到什麼值。之前爲了簡化分析單個採樣是二個字節的情形,也就是說單個採樣十六位。針對這個公式而言,bitsPerSample就是16。uint64_t類型其實就是unsigned __int64,無符號64位整型。將無符號64位整型的1左移16位。也就是最低十六位都是零,原來的最低位移到了第十七位,現在第十六位是1。左移操作其實就是乘,左移十六位相當於乘以整型數字16。再用這個浮點數16作爲分母,浮點數2.0作爲分子,最終的結果就是m_scale的值。即,現在m_scale的值是浮點數0.125。

上述計算m_scale的公式是第三處疑問。一,爲何使用無符號64位整型的1,而不是無符號16或者32位整型的1。二、爲何用左移操作而不是乘法操作符。三、爲何分子的值是2.0,而不是其他值。

暫且將m_scale計算公式中的疑問放一旁,先回到採樣數據轉換那個過程。現在知道了,m_scale的值是0.125。即,最後的結果要乘以0.125然後再賦值給readFrames的輸入參數之一buffer數組的某個位置。乘以0.125其實就是除以8。賦給buffer數組的float值,爲何要除以8。這樣的計算公式完全沒頭緒,不知爲何如此。

這三處疑問應該都與語音數據格式有關。這促使我想知道語音源頭的格式是什麼。之前做分析時,沒考慮這些,現在也沒任何關於這類信息的相關代碼。因此我又看了一遍MIPWAVReader的open函數。果然在解析wav文件頭時有相關的代碼。

if (!(fmtData[0] == 1 && fmtData[1] == 0))
{
	fclose(f);
	setErrorString(MIPWAVREADER_ERRSTR_NOTPCMDATA);
	return false;
}

代碼顯示,emiplib庫只支持源數據格式是PCM的wav文件。也就是說,後續的轉換和處理都是基於PCM數據作爲基礎數據。

依據示例中建立處理鏈的代碼,知道下一個MIPComponent是MIPSamplingRateConverter。以下是這個類的註釋說明。

/** Converts sampling rate and number of channels of raw audio messages.
 *  This component accepts incoming floating point or 16 bit signed integer raw audio 
 *  messages and produces
 *  similar messages with a specific sampling rate and number of channels set during
 *  initialization.
 */

MIPSamplingRateConverter可以接收浮點數或者16位有符號整型數據表示的原始語音數據。這段文字也許說明了採用浮點數或者16位整型無本質的區別,可能還是因爲處理後數據的精度原因。但還是無法解釋爲何要將原始的16位PCM數據除以8。

由於這也是格式轉換,讓人容易想到另外兩個開源軟件:sox和audacity。這兩個程序都是格式轉換的利器,只不過運行方式不同。sox是控制檯操作方式,audacity是個窗口程序。sox的關於自身實現方式的文檔中有這麼一句:

SoX’s formats and effects operate with an internal sample format of signed 32-bit integer.

sox在內部的處理都是基於無符號32位整型數據表示的採樣數據。這句話隱含的意思是,無論提供給sox的原始採樣數據以何種方式表示,sox都會將其轉換成無符號32位整型。似乎與我之前的一個假設相呼應:採用浮點數或者16位整型無本質的區別。在任何處理引擎的內部,sox、audacity或者emiplib,都會選擇一種或者兩種數據類型來表示採樣數據。在引擎內部,這種類型是個中間類型,引入了這個中間類型讓引擎可以針對某種格式的語音只實現中間類型至目標類型的轉換器,簡化了引擎的實現。這個結論來自於自身對這個問題的思考,並沒有任何其他的文字可以佐證我的想法。

 


 

 

發佈了82 篇原創文章 · 獲贊 2 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章