2012-04-15 78 views
8

第一次在這裏發佈海報。我通常喜歡自己找到答案(無論是通過研究還是試驗錯誤),但我很難在這裏找到答案。Android音頻 - 串流正弦音發生器的奇怪行爲

我在做什麼: 我正在構建一個簡單的android音頻合成器。現在,我只是在實時播放正弦音調,在用戶界面中有一個滑塊,可以在用戶調整音調的同時改變音調的頻率。

我如何構建它: 基本上,我有兩個線程 - 工作線程和輸出線程。每次調用tick()方法時,工作線程都會使用正弦波數據填充緩衝區。一旦緩衝區被填滿,它就會通知輸出線程數據已準備好寫入音軌。我使用兩個線程的原因是因爲audiotrack.write()阻塞,並且我希望工作線程能夠儘快開始處理其數據(而不是等待音軌完成寫入)。 UI上的滑塊簡單地改變工作線程中的變量,以便工作線程的tick()方法讀取對頻率的任何更改(通過滑塊)。

什麼作品: 幾乎所有;線程通信良好,播放中似乎沒有任何間隙或點擊。儘管緩衝區大小很大(謝謝安卓),但響應速度還是可以的。頻率變量確實會改變,tick()方法中的緩衝區計算過程中使用的中間值也會改變(由Log.i()驗證)。

什麼不工作: 出於某種原因,我似乎無法獲得可聽頻率的連續變化。當我調整滑塊時,頻率會逐步改變,通常爲四分之一或五分之一。理論上講,我應該聽到1Hz的變化,但我不是。奇怪的是,似乎對滑塊的改變導致正弦波在諧波序列中通過間隔播放;但是,我可以驗證頻率變量不是捕捉到默認頻率的整數倍。

我的音軌被設置爲這樣:

_buffSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT); 
_audioTrackOut = new AudioTrack(AudioManager.STREAM_MUSIC, _sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, _buffSize, AudioTrack.MODE_STREAM); 

工作者線程的緩衝區被填充(通過打勾())這樣:

public short[] tick() 
{ 
    short[] outBuff = new short[_outBuffSize/2]; // (buffer size in Bytes)/2 
    for (int i = 0; i < _outBuffSize/2; i++) 
    { 
     outBuff[i] = (short) (Short.MAX_VALUE * ((float) Math.sin(_currentAngle))); 

     //Update angleIncrement, as the frequency may have changed by now 
     _angleIncrement = (float) (2.0f * Math.PI) * _freq/_sampleRate; 
     _currentAngle = _currentAngle + _angleIncrement;  
    } 
    return outBuff;  
} 

音頻數據被寫入像這個:

_audioTrackOut.write(fromWorker, 0, fromWorker.length); 

任何幫助將不勝感激。我怎樣才能獲得更頻繁的頻率變化?我非常確信我的tick()中的邏輯是正確的,因爲Log.i()驗證了角度增量和currentAngle變量正在被正確更新。

謝謝!

更新:

我在這裏找到了類似的問題:Android AudioTrack buffering problems 解決方案提出了一個必須能夠生產樣品足夠快的audioTrack,這是有道理的。我將採樣率降低到22050Hz,並進行了一些經驗性測試 - 在最差的情況下,我可以在大約6ms內填充緩衝區(通過tick())。這是綽綽有餘的。在22050Hz,audioTrack給我一個2048個樣本(或4096個字節)的緩衝區大小。所以,每個填充的緩衝區持續〜0。0928秒的音頻,這比創建數據所用的時間長(1〜6 ms)。所以,我知道我生產樣品的速度不會有任何問題。

我還應該注意,對於應用程序生命週期的前3秒,它可以正常工作 - 滑塊的平滑掃描會在音頻輸出中產生平滑掃描。在此之後,它開始變得非常波濤洶涌(聲音每100Mhz只發生一次變化),之後,它根本不會響應滑塊輸入。

我也修復了一個錯誤,但我認爲它沒有效果。 AudioTrack.getMinBufferSize()返回BYTES中允許的最小緩衝區大小,我使用這個數字作爲tick()中緩衝區的長度 - 現在我使用這個數字的一​​半(每個樣本2字節)。

+0

您使用'_freq'更改什麼事件?每次真的變化1Hz嗎?對不起,我不清楚你的**有哪些不適用的部分。你能提供一個完整的工作代碼樣本或合成聲音片段,以便**聽到**你的問題。對於聲音片段:原始聲音數據格式正常 - 只需將緩衝區寫入文件,而不是寫入聲卡。例如,16位/有符號/單聲道/ 44100赫茲。 – 2012-04-15 01:16:58

+0

要更改_freq,我使用OnSeekBarChangeListener通過onProgressChanged()向_freq寫入一個新的int值。不過,我也嘗試過使用按鈕(上/下),頻率增加1赫茲,並且在許多增量之後,音調只有可聽見的差異(例如,音調跳躍了大約三分之一或四分之一,而不是1Hz)。這是一個發生了什麼的例子;這是當我緩慢地上下滑動滑塊時,我聽到的,以1Hz爲增量遞增頻率。 (從〜200 - 〜1000Hz):http://www.sendspace.com/file/tpxuwr – Tsherr 2012-04-15 01:29:31

+0

我在網上發現了類似的問題,但其提出的解決方案沒有幫助 - 請參閱上面的更新。 – Tsherr 2012-04-15 06:33:42

回答

4

我找到了!

事實證明,問題與緩衝區或線程無關。

由於計算的角度相對較小,所以在最初的幾秒內它聽起來很好。隨着程序運行並且角度增大,Math.sin(_currentAngle)開始產生不可靠的值。

因此,我用FloatMath.sin()替換Math.sin()

我也取代 _currentAngle = _currentAngle + _angleIncrement;

_currentAngle = ((_currentAngle + _angleIncrement) % (2.0f * (float) Math.PI));,所以角度始終< 2 * PI。

工程就像一個魅力!非常感謝您的幫助,praetorian機器人!

+0

該死的,我忘了2 * Pi正常化!我已經在我自己的舊代碼中看到了它,但錯過了它的缺席。但我認爲角度變量的溢出應該會產生點擊效果,而您的示例聽起來不同。無論如何,我很高興你解決了你的問題。 – 2012-04-15 13:50:42