当前位置: 首页 > 面试题库 >

Android AudioTrack缓冲问题

别子实
2023-03-14
问题内容

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

 }
}

问题答案:

我找到原因了!

问题是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;
}

您还可以以10Hz为步长,对200Hz-8000Hz频率的所有正弦值进行预计算。或者按需执行此操作:当用户选择频率时,请使用您的方法生成样本一段时间,然后将其保存到数组中。当您生成足够的采样以产生一个完整的正弦波时,您可以继续在数组上循环(因为sin是周期函数)。实际上,由于您从未完全碰到一个周期,所以声音中可能会存在一些小的不一致之处(因为您将角度增加1,并且一个完整的正弦波的长度是sampleRate / (double)frequency采样)。但是,选择正确的时间段倍数会使不一致现象不明显。

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



 类似资料:
  • 我有一个使用paramiko的缓冲区的问题,我在这里发现了同样的问题,其中一个解决方案指出: 如果您只是调用.open()来获取SFTPFile实例,而不是使用.get(),那么就对该对象调用.read(),或者只是将其交给Python标准库函数shutil.copyFileObj()来下载内容。这将避免Paramiko预取缓存,并且允许您下载文件,即使它没有那么快。 如果我有:

  • 问题内容: 我在使用PHP数据对象功能时遇到了一些严重问题。我试图使用缓冲查询循环遍历一个较大的结果集(〜60k行,〜1gig),以避免获取整个结果集。 不管我做什么,脚本都只是挂在PDO :: query()上-看来查询正在无缓冲运行(为什么结果集大小的更改会“解决”问题?)。这是我的代码来重现该问题: 如果我将查询限制在一个合理的数字范围内,则可以正常运行: 我尝试过使用PDO :: MYSQ

  • 出于性能的考虑,servlet 容器允许(但不要求)缓存输出到客户端的内容。一般的,服务器是默认执行缓存,但应该允许 servlet 来指定缓存参数。 下面是 ServletResponse 接口允许 servlet 来访问和设置缓存信息的方法: getBufferSize setBufferSize isCommitted reset resetBuffer flushBuffer 不管 ser

  • 问题内容: 是缓冲还是无缓冲? 我读到这是的对象,并且是所引用的对象的类型。 而且它们都是Unbuffered的,所以为什么要刷新unbuffered …是否可以刷新unbuffered,我已经读过它们被立即写入。 问题答案: 是“标准”输出。在大多数操作系统上,终端io被缓冲,并且支持分页。 在Javadoc中, “标准”输出流。该流已经打开并且准备接受输出数据。通常,此流对应于主机环境或用户指

  • 稳定性: 2 - 稳定的 在 ECMAScript 2015 (ES6) 引入 TypedArray 之前,JavaScript 语言没有读取或操作二进制数据流的机制。 Buffer 类被引入作为 Node.js API 的一部分,使其可以在 TCP 流或文件系统操作等场景中处理二进制数据流。 TypedArray 现已被添加进 ES6 中,Buffer 类以一种更优化、更适合 Node.js 用

  • 生活和艺术一样,最美的永远是曲线。 -- 爱德华布尔沃 - 利顿 在第九章“图层时间”中,我们讨论了动画时间和CAMediaTiming协议。现在我们来看一下另一个和时间相关的机制--所谓的缓冲。Core Animation使用缓冲来使动画移动更平滑更自然,而不是看起来的那种机械和人工,在这一章我们将要研究如何对你的动画控制和自定义缓冲曲线。