【问题标题】:Android AudioTrack buffering problemsAndroid AudioTrack 缓冲问题
【发布时间】:2010-07-22 03:30:21
【问题描述】:

好的,我有一个频率发生器,它使用 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

 }
}

【问题讨论】:

  • 不直接回答您的问题,但作为旁注,请注意进度条和流媒体存在一个已知问题:code.google.com/p/android/issues/detail?id=4124 尽管您的问题不是进度条本身而是音频流。因此,这不是解决您的问题的方法,但由于您使用的是音频流,因此您应该知道其中存在一个或多个未解决的错误。
  • 我也有同样的问题。缓冲区越大,跳跃开始的越晚。在日志中,我看到了很多非常长的 GC(GC 在 4200 毫秒内清理了 180 个对象)。我没有做任何分配。在 DDMS 分配跟踪器中,AudioTrack 本身似乎正在执行分配。不确定 GC 是否与跳跃有关。你解决了吗?
  • 你清理过你的“音轨”吗?这可能是与内存相关的问题
  • 请分享 onPostExecute 方法

标签: android audio buffer


【解决方案1】:

我找到了原因!

问题在于 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

【讨论】:

    【解决方案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 之后重复,我们需要取角度的模数。

      希望这会有所帮助。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多