Java Android音频-流正弦音频发生器奇数行为

Java Android音频-流正弦音频发生器奇数行为,java,android,audio,synthesis,Java,Android,Audio,Synthesis,第一次在这里张贴海报。我通常喜欢自己找到答案(无论是通过研究还是反复试验),但我在这里被难住了 我想做什么: 我正在构建一个简单的android音频合成器。现在,我只是在实时播放正弦音调,用户界面中有一个滑块,可以随着用户的调整改变音调的频率 我是如何构建它的: 基本上,我有两个线程——一个工作线程和一个输出线程。每次调用其tick()方法时,工作线程都会用正弦波数据填充缓冲区。一旦缓冲区被填满,它会提醒输出线程数据已准备好写入音频曲目。我之所以使用两个线程是因为audiotrack.write

第一次在这里张贴海报。我通常喜欢自己找到答案(无论是通过研究还是反复试验),但我在这里被难住了

我想做什么: 我正在构建一个简单的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()),如下所示:

任何帮助都将不胜感激。我怎样才能在频率上获得更多的渐变?我非常确信我在tick()中的逻辑是正确的,因为Log.I()验证变量angleIncrement和currentAngle是否正确更新

谢谢大家!

更新:

我在这里发现了一个类似的问题: 该解决方案提出,必须能够以足够快的速度制作音频曲目的样本,这是有意义的。我将采样率降低到22050Hz,并进行了一些实证测试——在最坏的情况下,我可以在大约6ms内填充缓冲区(通过tick()。这已经足够了。在22050Hz时,audioTrack为我提供了2048个样本(或4096字节)的缓冲区大小。因此,每个填充的缓冲区持续约0.0928秒的音频,这比创建数据所需的时间(1~6毫秒)要长得多。所以,我知道我没有任何问题,生产样品足够快

我还应该注意到,在应用程序生命周期的前3秒钟,它工作得很好-滑动条的平滑扫描会在音频输出中产生平滑扫描。在这之后,它开始变得非常起伏(声音只会每100Mhz变化一次),然后,它就完全停止响应滑块输入


我还修复了一个bug,但我认为它没有效果。AudioTrack.getMinBufferSize()返回允许的最小缓冲区大小(字节),我在tick()中使用了这个数字作为缓冲区的长度-我现在使用这个数字的一半(每个样本2个字节)。

我找到了它

事实证明,问题与缓冲区或线程无关

由于计算的角度相对较小,所以在前几秒钟听起来不错。随着程序的运行和角度的增大,Math.sin(_currentAngle)开始产生不可靠的值

因此,我将
Math.sin()
替换为
FloatMath.sin()

我也换了
\u currentAngle=\u currentAngle+\u angleIncrement

_currentAngle=((_currentAngle+_angleIncrement)%(2.0f*(float)Math.PI)),因此角度始终小于2*PI


工作起来很有魅力!非常感谢你的帮助,裁判机器人

您使用什么事件来更改频率?它真的每次改变1Hz吗?对不起,我不太明白你的“什么不起作用”部分。您能否提供完整的工作代码示例或合成的声音片段,以便能够听到您的问题。对于声音片段:原始声音数据格式正常-只需将缓冲区写入文件,而不是声卡。例如,16位/signed/mono/44100 Hz。要更改_-freq,我使用一个OnSeekBarChangeListener,它通过onProgressChanged()将一个新的int值写入_-freq。然而,我也尝试过使用按钮(向上/向下)将频率增加1Hz,在多次增加之后,音高只有可听的差别(例如,声音在音高上跳跃约3或4,而不是1Hz)。这是一个正在发生的事情的例子;这是当我缓慢地上下滑动滑块,以1Hz的增量增加频率时听到的。(从~200-~1000Hz):我在网上发现了一个类似的问题,但其建议的解决方案没有帮助-请参阅上面的更新。使用您的设置在设备上返回AudioTrack.getMinBufferSize()。使用mono、16位PCM和22050的采样率,getMinBufferSize()返回4096。在对我的原始帖子的最后一次编辑中,我提到在使用示例填充缓冲区时,我更新了我的代码,将这个数字除以2(因为我需要2048条短裤来填充4096字节的缓冲区)。该死,我忘记了2*Pi规范化!我在自己的旧代码中看到过它,但在您的代码中没有看到。但我认为角度变量的溢出应该会产生点击的效果,而您的示例听起来就不同了。任何
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);