Подтвердить что ты не робот

Проблемы с буферизацией Android AudioTrack

Итак, у меня есть генератор частоты, который использует 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

 }
}
4b9b3361

Ответ 1

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

Проблема заключается в размере буфера 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

Ответ 2

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

Ответ 3

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

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

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

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

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

отредактировано: angle + = приращение достаточно для задания.