2012-06-12 52 views
4

我正在構建一個相當簡單的Android應用程序(sdk revision 14:ICS),它允許用戶一次選擇兩個音頻剪輯(全部爲RIFF/WAV格式,很少-endian,簽名的PCM-16位編碼)並以各種方式組合它們以創建新的聲音。我使用該組合中的最基本的方法是如下:使用Android的AudioTrack合併聲音樣本字節會產生噪音

.... 
hMain.setBigData(hMain.getAudioTransmutation().getBigData()); //set the shared bigData 
// to the bigData in AudioTransmutation object 
hMain.getAudioProc().playWavFromByteArray(hMain.getBigData(), 22050 + (22050* 
(freqSeekSB.getProgress()/100)), 1024); //a SeekBar allows the user to adjust the freq 
//ranging from 22050 hz to 44100 hz 
.... 
public void playWavFromByteArray(byte[] audio,int sampleRate, int bufferSize){ 
    int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, 
      AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT); 
     AudioTrack at = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, 
      AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, 
      minBufferSize, AudioTrack.MODE_STREAM); 

     int i = 0; 

     at.play(); 
     at.write(audio, 0, audio.length);  
     at.stop(); 
     at.release(); 

     for(i=0;i<audio.length;i++){ 
      Log.d("me","the byte value at audio index " + i + " is " + audio[i]); 
     } 

} 

的組合和重放的結果:

//...sound samples are read in to memory as raw byte arrays elsewhere 
//...offset is currently set to 45 so as to skip the 44 byte header of basic 
//RIFF/WAV files 
... 
//Actual combination method 
public byte[] makeChimeraAll(int offset){ 
    for(int i=offset;i<bigData.length;i++){ 
     if(i < littleData.length){ 
      bigData[i] = (byte) (bigData[i] + littleData[i]); 
     } 
     else{ 
      //leave bigData alone 
     } 
    } 
    return bigData; 
} 

返回的字節陣列可接着經由AudioTrack類正是如此被播放使用上面的代碼接近我想要的(這兩個樣本在所產生的混合聲音中仍然是可辨別的),但也存在很多裂縫,爆裂聲和其他噪聲。

所以,三個問題:首先,我正確使用AudioTrack?其次,AudioTrack配置中的排序在哪裏?這些聲音本身播放得很好,聽起來幾乎就像我所期望的那樣,因此RIFF/WAV格式的小端特性似乎在某處傳達,但我不確定它在哪裏。最後,對於有符號的16位PCM編碼,我應該看到什麼字節值範圍?我期望在上面的Log.d(...)調用中看到logcat中-32768到32767之間的值,但是結果往往在-100到100的範圍內(除了一些異常值外)。也許,組合字節值超出16位範圍可能會造成噪聲?

感謝, CCJ

UPDATE:主要得益於比約內羅氏公司和威廉Coderer!我現在讀音頻數據爲short []結構,DataInputStream的字節序使用來自William的EndianInputStream(http://stackoverflow.com/questions/8028094/java-datainputstream-replacement-for-endianness)和組合方式已更改爲:

//Audio Chimera methods! 
public short[] makeChimeraAll(int offset){ 
    //bigData and littleData are each short arrays, populated elsewhere 
    int intBucket = 0; 
    for(int i=offset;i<bigData.length;i++){ 
     if(i < littleData.length){ 
      intBucket = bigData[i] + littleData[i]; 
      if(intBucket > SIGNED_SHORT_MAX){ 
       intBucket = SIGNED_SHORT_MAX; 
      } 
      else if (intBucket < SIGNED_SHORT_MIN){ 
       intBucket = SIGNED_SHORT_MIN; 
      } 
      bigData[i] = (short) intBucket; 
     } 
     else{ 
      //leave bigData alone 
     } 
    } 
    return bigData; 
} 

混合音頻輸出質量與這些改進是真棒!

回答

4

我對android音頻不熟悉,所以我不能回答你所有的問題,但我可以告訴你什麼是根本問題:逐字節地添加音頻數據將不起作用。由於它的工作原理,查看你的代碼以及它最常見的事實,我假定你有16位PCM數據。然而在任何地方,你都在處理字節。字節不適合處理音頻(除非音頻恰好是8位)

字節是aprox +/- 128.你說:「我希望看到Logcat中從-32768到32767的值從Log .d(...)調用,但是結果往往在-100到100的範圍內(除了一些以外的異常值)「那麼,當你打印一個值時,你怎麼可能去那個範圍?字節數組? 16位有符號數據的正確數據類型很短,而不是字節。如果您打印的是短值,則會看到您預期的範圍。

您必須將您的字節轉換爲短褲並將短褲求和。這會照顧你聽到的大部分噪音。既然你正在閱讀文件,但是,爲什麼要轉換?爲何不看看它關閉該文件作爲短期使用這樣的事情 http://docs.oracle.com/javase/1.4.2/docs/api/java/io/DataInputStream.html#readShort()

下一個問題是,你必須處理超出範圍的值,而不是讓他們「環繞」。最簡單的解決方案是簡單地將求和作爲整數,「剪輯」到短距離內,然後存儲剪裁的輸出。這將消除你的點擊和流行。

在僞代碼,整個過程將是這個樣子:

file1 = Open file 1 
file2 = Open file 2 
output = Open output for writing 

numSampleFrames1 = file1.readHeader() 
numSampleFrames2 = file2.readHeader() 
numSampleFrames = min(numSampleFrames1, numSampleFrames2) 
output.createHeader(numSampleFrames) 

for(int i=0; i<numSampleFrames * channels; ++i) { 
    //read data from file 1 
    int a = file1.readShort(); 
    //read data from file 2, and add it to data we read from file 1 
    a += file2.readShort(); 
    //clip into range 
    if(a > Short.MAX_VALUE) 
     a = Short.MAX_VALUE; 
    if(a < Short.MIN_VALUE) 
     a = Short.MIN_VALUE; 
    //write it to the output 
    output.writeShort((Short) a); 
} 

你會從「剪輯」的步驟變得有點扭曲,但有周圍沒有簡單的方法,裁剪是MUCH好於環繞。 (也就是說,除非你的音軌非常「熱」,並且在低頻率下很重,那麼失真不應該太明顯,如果這是一個問題,你可以做其他事情:例如乘以0.5,跳過剪輯,但是你的輸出會更安靜,這在手機上可能不是你想要的)。

+0

哎呦......感謝您指出需要使用短數組而不是字節數組;它實際上是16位的PCM,所以我不知道爲什麼我認爲逐字節的存儲和處理工作。也許是因爲我無法真正找到脈衝編碼調製在內部工作的良好解釋......您是否有任何關於瞭解數字音頻編碼/處理的低級細節的建議參考? – CCJ

+0

另外值得注意的是,由於我的RIFF/WAV音頻文件是使用little-endian字節排序進行編碼的,因此我需要使用DataInputStream的修改版本來正確讀取短值(股票java版本假設爲大端)。幸運的是,我發現了一個很好的實現必要的按位操作來完成此任務在這裏:http://stackoverflow.com/questions/8028094/java-datainputstream-replacement-for-endianness – CCJ

+0

是的,你需要處理字節序,作爲好。你可能會考慮一個類似於RandomAccessFIle的包裝器。對於參考資料,你可以從這裏開始:http://blog.bjornroche.com/2011/11/slides-from-fundamentals-of-audio.html還有一本書叫數字音頻與Java它現在已經過時,並有一些不準確的地方,但它有工作代碼,這是你不會在很多地方找到的東西。在我的第一個鏈接更多的參考。 –

相關問題