这里的第一次海报。我通常喜欢自己找到答案(通过研究或试错),但我很难过。
我正在尝试做什么: 我正在构建一个简单的Android音频合成器。现在,我只是实时播放正弦音,用户界面中有一个滑块,可以在用户调整时改变音调的频率。
我是如何构建的: 基本上,我有两个线程 - 工作线程和输出线程。每次调用tick()方法时,工作线程只使用正弦波数据填充缓冲区。填充缓冲区后,它会向输出线程发出警告,表明数据已准备好写入音频轨道。我使用两个线程的原因是因为audiotrack.write()阻塞,我希望工作线程能够尽快开始处理其数据(而不是等待音轨完成写入)。 UI上的滑块只是更改了工作线程中的变量,因此工作线程的tick()方法将读取频率的任何变化(通过滑块)。
什么有效: 几乎所有的;线程通信良好,回放中似乎没有任何间隙或点击。尽管缓冲区大小(感谢android),但响应能力还可以。频率变量确实会发生变化,tick()方法中缓冲区计算期间使用的中间值也会发生变化(由Log.i()验证)。
什么行不通: 出于某种原因,我似乎无法在可听频率上获得持续的变化。当我调整滑块时,频率会逐步变化,通常会变为四分之一或五分之一。从理论上讲,我应该听到1Hz的变化,但我不是。奇怪的是,似乎滑块的变化导致正弦波在谐波系列中间隔播放;但是,我可以验证频率变量是否没有捕捉到默认频率的整数倍。
我的音频曲目设置如下:
_buffSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
_audioTrackOut = new AudioTrack(AudioManager.STREAM_MUSIC, _sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, _buffSize, AudioTrack.MODE_STREAM);
正在填充工作线程的缓冲区(通过tick()):
public short[] tick()
{
short[] outBuff = new short[_outBuffSize/2]; // (buffer size in Bytes) / 2
for (int i = 0; i < _outBuffSize/2; i++)
{
outBuff[i] = (short) (Short.MAX_VALUE * ((float) Math.sin(_currentAngle)));
//Update angleIncrement, as the frequency may have changed by now
_angleIncrement = (float) (2.0f * Math.PI) * _freq / _sampleRate;
_currentAngle = _currentAngle + _angleIncrement;
}
return outBuff;
}
音频数据的写法如下:
_audioTrackOut.write(fromWorker, 0, fromWorker.length);
非常感谢任何帮助。如何才能让频率逐渐变化?我非常有信心我的tick()中的逻辑是合理的,因为Log.i()验证变量angleIncrement和currentAngle是否正确更新。
谢谢!
更新
我在这里发现了类似的问题:Android AudioTrack buffering problems 解决方案提出,必须能够为audioTrack足够快地生成样本,这是有道理的。我将采样率降低到22050Hz,并运行了一些经验测试 - 在最坏的情况下,我可以在大约6ms内填充我的缓冲区(通过tick())。这绰绰有余。在22050Hz,audioTrack给我一个2048个样本(或4096个字节)的缓冲区大小。因此,每个填充缓冲区持续约0.0928秒的音频,这比创建数据(1~6 ms)要长得多。所以,我知道我在制作样品时没有任何问题。
我还应该注意,对于应用程序生命周期的前3秒,它可以正常工作 - 滑块的平滑扫描可以在音频输出中产生平滑的扫描。在此之后,它开始变得非常不稳定(声音仅在每100Mhz变化),之后,它根本停止响应滑块输入。
我还修复了一个错误,但我认为它没有效果。 AudioTrack.getMinBufferSize()返回BYTES中允许的最小缓冲区大小,我使用这个数字作为tick()中缓冲区的长度 - 我现在使用这个数字的一半(每个样本2个字节)。
答案 0 :(得分:5)
我找到了它!
事实证明这个问题与缓冲区或线程无关。
在前几秒听起来很好,因为计算的角度相对较小。随着程序的运行和角度的增加,Math.sin(_currentAngle)开始产生不可靠的值。
因此,我将Math.sin()
替换为FloatMath.sin()
。
我也换了
_currentAngle = _currentAngle + _angleIncrement;
与
_currentAngle = ((_currentAngle + _angleIncrement) % (2.0f * (float) Math.PI));
,因此角度始终为&lt; 2 * PI。
像魅力一样!非常感谢你的帮助,praetorian机器人!