Android的AudioTrack缓冲问题 [英] Android AudioTrack buffering problems

查看:171
本文介绍了Android的AudioTrack缓冲问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好了,所以我有一个频率发生器,它使用AudioTrack为PCM数据发送到硬件。这里的code,我使用的是:

 私有类playSoundTask扩展的AsyncTask<虚空,虚空,虚空> {
  浮动频率;
  浮动增量;
  浮动角= 0;
  短样本[] =新的短[1024];

  @覆盖
  在preExecute保护无效(){
   INT minSize属性= AudioTrack.getMinBufferSize(44100,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT);
   轨道=新AudioTrack(AudioManager.STREAM_MUSIC,44100,
     AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT,
     minSize属性,AudioTrack.MODE_STREAM);
   track.play();
  }

  @覆盖
  保护无效doInBackground(虚空...... PARAMS){
   而(Main.this.isPlaying)
   {
    的for(int i = 0; I< samples.length;我++)
    {
     频率=(浮点)Main.this.slider.getProgress();
     增量=(浮点)(2 * Math.PI)*频率/ 44100;
     样本[I] =(短)((浮点)Math.sin(角度)* Short.MAX_VALUE);
     角+ =增量;
    }

    track.write(样品,0,samples.length);
   }
   返回null;
  }
 }
 

频率被绑定到一个滑动条,以及正确的值被报道的样本生成循环。一切都很好,很正常,当我启动应用程序。当您将您的手指沿滑动条,你得到一个不错的扫地声。但与它玩弄大约10秒后,将音频开始得到跳动。取而代之的是流畅的扫描,它的交错,而只是改变身边的每一个1000赫兹左右的语气。什么可能会造成这个任何想法?

下面是所有code的情况下,问题出在别的地方:

 公共类主要活动扩展实现OnClickListener,OnSeekBarChangeListener {
 AudioTrack轨道;
 搜索栏滑块;
 的ImageButton为playButton;
 TextView的显示;

 布尔IsPlaying模块= FALSE;

 / **第一次创建活动时调用。 * /
 @覆盖
 公共无效的onCreate(包savedInstanceState){
  super.onCreate(savedInstanceState);
  的setContentView(R.layout.main);

  显示器=(TextView中)findViewById(R.id.display);
  display.setText(5000赫兹);

  滑动=(搜索栏)findViewById(R.id.slider);
  slider.setMax(20000);
  slider.setProgress(5000);
  slider.setOnSeekBarChangeListener(本);


  为playButton =(的ImageButton)findViewById(R.id.play);
  playButton.setOnClickListener(本);

 }

 私有类playSoundTask扩展的AsyncTask<虚空,虚空,虚空> {
  浮动频率;
  浮动增量;
  浮动角= 0;
  短样本[] =新的短[1024];

  @覆盖
  在preExecute保护无效(){
   INT minSize属性= AudioTrack.getMinBufferSize(44100,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT);
   轨道=新AudioTrack(AudioManager.STREAM_MUSIC,44100,
     AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT,
     minSize属性,AudioTrack.MODE_STREAM);
   track.play();
  }

  @覆盖
  保护无效doInBackground(虚空...... PARAMS){
   而(Main.this.isPlaying)
   {
    的for(int i = 0; I< samples.length;我++)
    {
     频率=(浮点)Main.this.slider.getProgress();
     增量=(浮点)(2 * Math.PI)*频率/ 44100;
     样本[I] =(短)((浮点)Math.sin(角度)* Short.MAX_VALUE);
     角+ =增量;
    }

    track.write(样品,0,samples.length);
   }
   返回null;
  }
 }



 @覆盖
 公共无效onProgressChanged(搜索栏搜索栏,INT进步,
   布尔FROMUSER){
  display.setText(+进度+赫兹);
 }

 公共无效的onClick(视图v){
  如果(IsPlaying模块){
   停止();
  } 其他 {
   开始();
  }
 }

 公共无效停止(){
  IsPlaying模块= FALSE;
  playButton.setImageResource(R.drawable.play);
 }

 公共无效启动(){
  IsPlaying模块= TRUE;
  playButton.setImageResource(R.drawable.stop);
  新playSoundTask()执行()。
 }

 @覆盖
 保护无效onResume(){
  super.onResume();

 }

 @覆盖
 保护无效的onStop(){
  super.onStop();
  //存储状态
  停止();

 }


 @覆盖
 公共无效onStartTrackingTouch(搜索栏搜索栏){
  // TODO自动生成方法存根

 }


 @覆盖
 公共无效onStopTrackingTouch(搜索栏搜索栏){
  // TODO自动生成方法存根

 }
}
 

解决方案

我找到了原因!

问题是AudioTrack的缓冲区的大小。如果不能产生样本不够快,样品用完,播放暂停,并继续在那里了足够的样本。

唯一的解决办法是,以确保您可以生成样本足够快(44100 /秒)。作为一个快速修复,尽量降低采样率22000(或增加缓冲区的大小)。

至少,这是它的行为对我来说 - 当我优化我的样本生成程序,跳走了

(增加缓冲区的大小,使声音开始后播放,因为有一些储备正在等待 - 直到缓冲区填满但尽管如此,如果你不生成样品速度不够快,最终样品用完)


哦,你应该把循环的外部变量不变!及也许使样品阵列较大,从而使循环运行更长的时间。

 短样本[] =新的短[4 * 1024];
...

频率=(浮点)Main.this.slider.getProgress();
增量=(浮点)(2 * Math.PI)*频率/ 44100;
的for(int i = 0; I< samples.length;我++)
{
   样本[I] =(短)((浮点)Math.sin(角度)* Short.MAX_VALUE);
   角+ =增量;
}
 

您也可以precompute所有正弦值的频率为200Hz-8000Hz的,具有为10Hz的步骤。还是做需求:当用户选择频率,生成用你的方法了一段时间的样品,并将它们保存到一个数组。当你产生足够的样本有一个完整的正弦波,你可以不断循环在数组(因为单是一个周期性的功能)。其实有可能是一些小的不一致的声音,你从来没有打过一段时间的准确位置(因为你增加了1角和一个完整的正弦波的lenght是采样率/(双)频样本)。但服用时间的权利多,使矛盾不显着。

此外,请参阅 http://developer.android.com/guide/practices /design/performance.html

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

 }
}

解决方案

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

这篇关于Android的AudioTrack缓冲问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆