2010-07-22 17 views
15

Tamam, PCM verilerini donanıma göndermek için AudioTrack kullanan bir frekans üretecim var. İşte bunun için kullanıyorum kodu:Android AudioTrack arabelleğe yükleme sorunları

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; 
    } 
} 

frekans bir kayma çubuğu bağlıdır ve doğru değer örnek nesil döngü içinde rapor ediliyor. Uygulamayı başlattığımda her şey yolunda ve iyi. Parmağınızı kaydırma çubuğu boyunca sürüklediğinizde hoş bir süpürme sesi alırsınız. Ancak, yaklaşık 10 saniyelik bir oyun oynadıktan sonra, ses kaygılanmaya başlar. Pürüzsüz bir süpürme yerine, sarsılmış ve sadece her 1000 Hz civarında bir ton değiştirir. Buna neyin sebep olacağı konusunda bir fikrin var mı?

Burada sorun başka bir yerde yatıyor durumunda tüm kod:

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

doğrudan sorunuzu yanıtlamıyor, ancak yan not olarak, ilerleme çubuğu ve akış ile ilgili bilinen bir sorun olduğunu unutmayın: http://code.google.com/p/android/issues/detail?id=4124 Sorununuz ilerleme çubuğunun kendisiyle değil, ses akışında. Eğer ses akışı ile çalışmak beri nedenle, sorununuza bir çözünürlük, ancak, bunun bir veya daha fazla açık böcek olduğunu bilmelidir. –

+0

ben de aynı sorun var. Tampon ne kadar büyükse, daha sonra atlama başlar. günlüğüne çok uzun GC'ler bir çok (GC 4200ms 180 nesneleri temizlenmiş) bakınız. Ben herhangi bir tahsis yapıyorum. DDMS ayırma izleyicisinde, AudioTrack'in kendisinin tahsisat yaptığı görülmektedir. GC'lerin atlama ile ilgisi olup olmadığından emin değil. Bunu çözdün mü? –

+0

Hiç "AudioTrack" temizlerim? bellekle ilgili bir sorun olabilir –

cevap

11

Sebebini bulundu!

Sorun, AudioTrack'ın arabelleğinin boyutudur. Örnekleri yeterince hızlı üretemezseniz, örnekler tükenir, oynatma duraklar ve tekrar yeterli örnek olduğunda devam eder.

Tek çözüm, örnekleri yeterince hızlı oluşturabilmenizi sağlamaktır (44100/s). Hızlı bir düzeltme olarak, örnekleme oranını 22000'e (veya tamponun boyutunu artırarak) düşürmeyi deneyin.

En azından bu benim için nasıl davranıyor - örnek üretim rutinimi optimize ettiğimde, atlama gitti. Eğer yeterince hızlı örneklerini oluşturan değilse, eninde sonunda numuneler tükendi, yine tampon doldurana kadar Ama -.

(bazı rezerv bekledi ediliyor olmadığı için tampon boyutu, daha sonra oynayan ses başlangıç ​​yapar artırılması).


Oh ve döngünün dışında değişmeyen değişkenleri koymak gerekir! Ve belki de örnekler dizisini daha büyük hale getirin, böylece döngü daha uzun sürer.

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; 
} 

Ayrıca 10Hz adımla, frekanslar 200Hz-8000Hz için tüm sinüs değerlerini precompute başladı. Veya bunu talep üzerine yapın: Kullanıcı bir frekans seçtiğinde, bir süredir yönteminizi kullanarak örnekler üretin ve bunları bir diziye kaydedin. Tam sinüs dalgasına sahip olmak için yeterli örnek ürettiğinizde, dizinin üzerinden döngü yapmaya devam edebilirsiniz (çünkü günah periyodik bir işlevdir). Asla bir süreye asla vurmadığınız için seste bazı küçük tutarsızlıklar olabilir (çünkü açıyı 1 arttırıyorsunuz ve bir tam sinüs dalgasının uzunluğu sampleRate/(double)frequency örnekleridir). Ancak, dönemin sağ katsayısını almak tutarsızlıkları farkedilmez hale getirir. Ayrıca

, (benim yaptığım gibi) http://developer.android.com/guide/practices/design/performance.html

4

durumda herkes aynı sorunla bu rastlar bkz çözüm aslında tamponlar ile ilgisi yoktur, bu sinüs fonksiyonu ile bir ilgisi yoktur. angle += increment'u angle += (increment % (2.0f * (float) Math.PI)); ile değiştirmeyi deneyin. Ayrıca, verimlilik için Math.sin() yerine FloatMath.sin() kullanmayı deneyin.

1

Sanırım problemi çözmeyi başardım.

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

Daha önce belirtildiği gibi, arabelleklerle ilgili değil. Açı değişkeni ile ilgili, sürekli artar. Bir süre sonra, değişken çok büyük olur ve küçük adımları desteklemez.2 * PI sonra sinüs tekrarlar gibi

, biz açının modülünü almak gerekir.

Bu yardımcı olur umarım.

düzenlendi: + = artış açısı iş için yeterlidir.

İlgili konular