2010-07-22 64 views
15

好吧,我有一個頻率發生器,它使用AudioTrack發送PCM數據到硬件。下面是我使用的代碼:Android AudioTrack緩衝問題

private class playSoundTask extends AsyncTask<Void, Void, Void> { 
    float frequency; 
    float increment; 
    float angle = 0; 
    short samples[] = new short[1024]; 

    @Override 
    protected void onPreExecute() { 
    int minSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);   
    track = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, 
    AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, 
    minSize, AudioTrack.MODE_STREAM); 
    track.play(); 
    } 

    @Override 
    protected Void doInBackground(Void... params) { 
    while(Main.this.isPlaying) 
    { 
    for(int i = 0; i < samples.length; i++) 
    { 
    frequency = (float)Main.this.slider.getProgress(); 
    increment = (float)(2*Math.PI) * frequency/44100; 
    samples[i] = (short)((float)Math.sin(angle)*Short.MAX_VALUE); 
    angle += increment; 
    } 

    track.write(samples, 0, samples.length); 
    } 
    return null; 
    } 
} 

頻率被綁定到一個滑動條,以及正確的值在樣本生成環被報告。當我啓動應用程序時,一切都很好。當您沿着滑動條拖動手指時,您會聽到清晰的聲音。但在大約10秒鐘後,音頻開始變得激動起來。而不是平滑的掃描,它是交錯的,並且只在每個1000Hz左右改變音調。任何想法可能會導致這種情況?

下面是情況下,所有的代碼問題出在別處:

public class Main extends Activity implements OnClickListener, OnSeekBarChangeListener { 
AudioTrack track; 
SeekBar slider; 
ImageButton playButton; 
TextView display; 

boolean isPlaying=false; 

/** Called when the activity is first created. */ 
@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.main); 

    display = (TextView) findViewById(R.id.display); 
    display.setText("5000 Hz"); 

    slider = (SeekBar) findViewById(R.id.slider); 
    slider.setMax(20000); 
    slider.setProgress(5000); 
    slider.setOnSeekBarChangeListener(this); 


    playButton = (ImageButton) findViewById(R.id.play); 
    playButton.setOnClickListener(this); 

} 

private class playSoundTask extends AsyncTask<Void, Void, Void> { 
    float frequency; 
    float increment; 
    float angle = 0; 
    short samples[] = new short[1024]; 

    @Override 
    protected void onPreExecute() { 
    int minSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);   
    track = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, 
    AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, 
    minSize, AudioTrack.MODE_STREAM); 
    track.play(); 
    } 

    @Override 
    protected Void doInBackground(Void... params) { 
    while(Main.this.isPlaying) 
    { 
    for(int i = 0; i < samples.length; i++) 
    { 
    frequency = (float)Main.this.slider.getProgress(); 
    increment = (float)(2*Math.PI) * frequency/44100; 
    samples[i] = (short)((float)Math.sin(angle)*Short.MAX_VALUE); 
    angle += increment; 
    } 

    track.write(samples, 0, samples.length); 
    } 
    return null; 
    } 
} 



@Override 
public void onProgressChanged(SeekBar seekBar, int progress, 
    boolean fromUser) { 
    display.setText(""+progress+" Hz"); 
} 

public void onClick(View v) { 
    if (isPlaying) { 
    stop(); 
    } else { 
    start(); 
    } 
} 

public void stop() { 
    isPlaying=false; 
    playButton.setImageResource(R.drawable.play); 
} 

public void start() { 
    isPlaying=true; 
    playButton.setImageResource(R.drawable.stop); 
    new playSoundTask().execute(); 
} 

@Override 
protected void onResume() { 
    super.onResume(); 

} 

@Override 
protected void onStop() { 
    super.onStop(); 
    //Store state 
    stop(); 

} 


@Override 
public void onStartTrackingTouch(SeekBar seekBar) { 
    // TODO Auto-generated method stub 

} 


@Override 
public void onStopTrackingTouch(SeekBar seekBar) { 
    // TODO Auto-generated method stub 

} 
} 
+0

不直接回答你的問題,但作爲一個附註,請注意,有一個進度條和流的已知問題:http://code.google.com/p/android/issues/detail?id=4124 雖然你的問題不是進度條本身,而是音頻流。因此,不是針對您的問題的解決方案,但是由於您使用音頻流,因此您應該知道其中存在一個或多個開放的錯誤。 – 2010-07-22 04:35:32

+0

我有完全相同的問題。緩衝區越大,越晚開始。在日誌中,我看到很多非常長的GC(GC在4200ms內清理了180個對象)。我沒有做任何分配。在DDMS分配跟蹤器中,似乎AudioTrack本身正在進行分配。不確定GC是否與跳躍有關。你有解決這個問題嗎? – 2011-02-26 11:35:49

+0

你有沒有清理過你的「audiotrack」?它可能是一個內存相關的問題 – 2014-09-08 12:49:56

回答

11

我找到了原因!

問題是AudioTrack緩衝區的大小。如果不能快速生成樣本,則樣本用完,播放暫停,並在樣本再次充足時繼續播放。

唯一的解決方案是確保您可以快速生成樣本(44100/s)。作爲一個快速修復,請嘗試將採樣率降低到22000(或增加緩衝區的大小)。

至少這是它對我的行爲 - 當我優化樣本生成程序時,跳躍消失了。 (緩衝區的大小增加會使聲音開始播放,因爲有一些預留空間會被等待 - 直到緩衝區填滿爲止。但是,如果您沒有足夠快地生成樣本,最終樣本會用完)。


哦,你應該把不變變量放在循環之外!也許可以使樣本數組變大,以便循環運行時間更長。

short samples[] = new short[4*1024]; 
... 

frequency = (float)Main.this.slider.getProgress(); 
increment = (float)(2 * Math.PI) * frequency/44100; 
for(int i = 0; i < samples.length; i++) 
{ 
    samples[i] = (short)((float)Math.sin(angle) * Short.MAX_VALUE); 
    angle += increment; 
} 

您還可以預先計算頻率爲200Hz-8000Hz的所有正弦值的值,步長爲10Hz。或者按需要做:當用戶選擇一個頻率時,使用你的方法產生樣本一段時間,並將它們保存到一個數組中。當你生成足夠的樣本來獲得一個完整的正弦波時,你可以繼續循環數組(因爲sin是一個周期函數)。實際上,聲音中可能會存在一些小的不一致,因爲您從來不會完全按照某個時段(因爲您將角度增加1,並且一個完整正弦波的長度爲sampleRate/(double)frequency樣本)。但採取正確的倍數的時期使不一致的不一致。

而且,看到http://developer.android.com/guide/practices/design/performance.html

4

如果有人碰到這個蹣跚着同樣的問題(像我一樣),該解決方案實際上已經無關,與緩衝器,它與正弦函數來做。嘗試用angle += (increment % (2.0f * (float) Math.PI));代替angle += increment。此外,爲了提高效率,請嘗試使用FloatMath.sin()而不是Math.sin()。

1

我想我已經設法解決了這個問題。

... 
samples[i] = (short)((float)Math.sin(angle)*Short.MAX_VALUE); 
angle += increment; 
angle = angle % (2.0f * (float) Math.PI); //added statement 
... 

如前所述,它不是緩衝區。它是關於角度變量,它不斷增加。過了一段時間,變量變得太大,並且不支持小步驟。

由於2 * PI後的正弦重複,我們需要取角度的模數。

希望這會有所幫助。

編輯:angle + =增量就足夠了。