Android AudioTrack 缓冲问题

发布于 2024-09-11 01:51:58 字数 3806 浏览 3 评论 0原文

好的,我有一个频率发生器,它使用 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 Hz 左右改变一次音调。关于可能导致这种情况的原因有什么想法吗?

这是所有代码,以防问题出在其他地方:

    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

 }
}

Ok so I have a frequency generator which uses AudioTrack to send PCM data to the hardware. Here's the code I'm using for that:

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

The frequency is tied to a slide bar, and the correct value is being reported in the sample generation loop. Everything is fine and dandy when I start the app. When you drag your finger along the slide bar you get a nice sweeping sound. But after around 10 seconds of playing around with it, the audio starts to get jumpy. Instead of a smooth sweep, it's staggered, and only changes tone around every 1000 Hz or so. Any ideas on what could be causing this?

Here's all the code in case the problem lies somewhere else:

    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

 }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

っ左 2024-09-18 01:51:58

我找到原因了!

问题是 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/性能.html

I found the cause!

The problem is the size of the AudioTrack's buffer. If you cannot generate samples fast enough, the samples run out, the playback pauses, and continues when there are enough samples again.

The only solution is to make sure that you can generate samples fast enough (44100/s). As a fast fix, try lowering the sampling rate to 22000 (or increasing the size of the buffer).

At least this is how it behaves for me - when I optimized my sample generation routine, the jumping went away.

(Increasing the size of the buffer makes the sound start playing later, as there is some reserve being waited for - until the buffer fills. But still, if you are not generating the samples fast enough, eventually the samples run out).


Oh, and you should put invariant variables outside of the loop! And maybe make the samples array larger, so that the loop runs longer.

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

You could also precompute the values of all sines for frequencies 200Hz-8000Hz, with a step of 10Hz. Or do this on demand: when the user select a frequency, generate samples using your method for a while and also save them to an array. When you generate enough samples to have a one full sine wave, you can just keep looping over the array (since sin is a periodic function). Actually there might be some small inconsistencies in the sound as you never hit a period exactly (because you are increasing the angle by 1 and the lenght of one full sine wave is sampleRate / (double)frequency samples). But taking a right multiple of the period makes the inconsistencies unnoticeable.

Also, see http://developer.android.com/guide/practices/design/performance.html

诗化ㄋ丶相逢 2024-09-18 01:51:58

如果有人偶然发现同样的问题(就像我所做的那样),该解决方案实际上与缓冲区无关,它与正弦函数有关。尝试将 angle +=increment 替换为 angle += (increment % (2.0f * (float) Math.PI));。另外,为了提高效率,请尝试使用 FloatMath.sin() 而不是 Math.sin()。

In case anyone stumbles across this with the same problem (as I did), the solution actually has nothing to do with buffers, it has to do with the sine function. Try replacing angle += increment with angle += (increment % (2.0f * (float) Math.PI));. Also, for efficiency, try using FloatMath.sin() instead of Math.sin().

故人如初 2024-09-18 01:51:58

我想我已经成功解决了这个问题。

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

如前所述,这与缓冲区无关。它是关于角度变量的,它不断增加。一段时间后,变量变得太大并且不支持小步长。

由于正弦在 2*PI 之后重复,因此我们需要取角度模数。

希望这有帮助。

编辑:角度+=增量足以完成这项工作。

I think I've managed to solve the problem.

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

As stated before, it's not about buffers. It is about the angle variable, it increases continuously. After a while, the variable gets too large and does not support little steps.

As the sine repeats after 2*PI, we need to take the modulus of angle.

Hope this helps.

edited: angle += increment is enough for the job.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文