Проблемы с буферизацией Android AudioTrack - PullRequest
15 голосов
/ 22 июля 2010

Хорошо, у меня есть генератор частоты, который использует AudioTrack для отправки данных PCM на оборудование. Вот код, который я использую для этого:

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 секунд игры с ним, звук начинает нервничать. Вместо плавной развертки, она находится в шахматном порядке и изменяет тон примерно каждые 1000 Гц или около того. Любые идеи о том, что может быть причиной этого?

Вот весь код на случай, если проблема лежит где-то еще:

    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

 }
}

Ответы [ 3 ]

11 голосов
/ 26 февраля 2011

Я нашел причину!

Проблема в размере буфера AudioTrack. Если вы не можете сгенерировать сэмплы достаточно быстро, сэмплы заканчиваются, воспроизведение приостанавливается и продолжается, когда снова достаточно сэмплов.

Единственное решение - убедиться, что вы можете генерировать образцы достаточно быстро (44100 / с). В качестве быстрого исправления попробуйте снизить частоту дискретизации до 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;
}

Вы также можете предварительно вычислить значения всех синусов для частот от 200 Гц до 8000 Гц с шагом 10 Гц. Или сделайте это по требованию: когда пользователь выбирает частоту, на некоторое время генерируйте выборки, используя ваш метод, а также сохраняйте их в массив. Когда вы генерируете достаточно сэмплов, чтобы получить одну полную синусоидальную волну, вы можете просто продолжать цикл по массиву (поскольку sin - периодическая функция). На самом деле в звуке могут быть небольшие несоответствия, так как вы никогда не достигаете точного периода (потому что вы увеличиваете угол на 1, а длина одной полной синусоидальной волны составляет sampleRate / (double)frequency сэмплов). Но выбор правильного кратного периода делает несоответствия незаметными.

Также см. http://developer.android.com/guide/practices/design/performance.html

4 голосов
/ 15 апреля 2012

Если кто-то сталкивается с этой же проблемой (как я), решение на самом деле не имеет ничего общего с буферами, оно связано с функцией синуса. Попробуйте заменить angle += increment на angle += (increment % (2.0f * (float) Math.PI));. Также, для эффективности, попробуйте использовать FloatMath.sin () вместо Math.sin ().

1 голос
/ 11 апреля 2013

Думаю, мне удалось решить проблему.

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

Как уже говорилось, речь идет не о буферах. Речь идет о переменной угла, она постоянно увеличивается. Через некоторое время переменная становится слишком большой и не поддерживает маленькие шаги.

Поскольку синус повторяется после 2 * PI, нам нужно взять модуль угла.

Надеюсь, это поможет.

отредактировано: угол + = приращения достаточно для работы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...