好的,我有一个频率发生器,它使用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
}
}
答案 0 :(得分:11)
我找到了原因!
问题是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
答案 1 :(得分:4)
如果有人遇到同样的问题(就像我一样),那么解决方案实际上与缓冲区无关,它与正弦函数有关。尝试将angle += increment
替换为angle += (increment % (2.0f * (float) Math.PI));
。另外,为了提高效率,请尝试使用FloatMath.sin()而不是Math.sin()。
答案 2 :(得分:1)
我想我已经设法解决了这个问题。
...
samples[i] = (short)((float)Math.sin( angle )*Short.MAX_VALUE);
angle += increment;
angle = angle % (2.0f * (float) Math.PI); //added statement
...
如前所述,它与缓冲区无关。它是关于角度变量,它不断增加。过了一会儿,变量变得太大而且不支持很少的步骤。
正如2 * PI后正弦重复,我们需要采用角度模数。
希望这有帮助。
编辑:角度+ =增量就足够了。