Android音频 - 串流正弦音发生器的奇怪行为

问题描述:

第一次在这里发布海报。我通常喜欢自己找到答案(无论是通过研究还是试验错误),但我很难在这里找到答案。Android音频 - 串流正弦音发生器的奇怪行为

我在做什么: 我正在构建一个简单的android音频合成器。现在,我只是在实时播放正弦音调,在用户界面中有一个滑块,可以在用户调整音调的同时改变音调的频率。

我如何构建它: 基本上,我有两个线程 - 工作线程和输出线程。每次调用tick()方法时,工作线程都会使用正弦波数据填充缓冲区。一旦缓冲区被填满,它就会通知输出线程数据已准备好写入音轨。我使用两个线程的原因是因为audiotrack.write()阻塞,并且我希望工作线程能够尽快开始处理其数据(而不是等待音轨完成写入)。 UI上的滑块简单地改变工作线程中的变量,以便工作线程的tick()方法读取对频率的任何更改(通过滑块)。

什么作品: 几乎所有;线程通信良好,播放中似乎没有任何间隙或点击。尽管缓冲区大小很大(谢谢安卓),但响应速度还是可以的。频率变量确实会改变,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); 

工作者线程的缓冲区被填充(通过打勾())这样:

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()验证了角度增量和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

您使用'_freq'更改什么事件?每次真的变化1Hz吗?对不起,我不清楚你的**有哪些不适用的部分。你能提供一个完整的工作代码样本或合成声音片段,以便**听到**你的问题。对于声音片段:原始声音数据格式正常 - 只需将缓冲区写入文件,而不是写入声卡。例如,16位/有符号/单声道/ 44100赫兹。 – 2012-04-15 01:16:58

+0

要更改_freq,我使用OnSeekBarChangeListener通过onProgressChanged()向_freq写入一个新的int值。不过,我也尝试过使用按钮(上/下),频率增加1赫兹,并且在许多增量之后,音调只有可听见的差异(例如,音调跳跃了大约三分之一或四分之一,而不是1Hz)。这是一个发生了什么的例子;这是当我缓慢地上下滑动滑块时,我听到的,以1Hz为增量递增频率。 (从〜200 - 〜1000Hz):http://www.sendspace.com/file/tpxuwr – Tsherr 2012-04-15 01:29:31

+0

我在网上发现了类似的问题,但其提出的解决方案没有帮助 - 请参阅上面的更新。 – Tsherr 2012-04-15 06:33:42

我找到了!

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

由于计算的角度相对较小,所以在最初的几秒内它听起来很好。随着程序运行并且角度增大,Math.sin(_currentAngle)开始产生不可靠的值。

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

我也取代 _currentAngle = _currentAngle + _angleIncrement;

_currentAngle = ((_currentAngle + _angleIncrement) % (2.0f * (float) Math.PI));,所以角度始终< 2 * PI。

工程就像一个魅力!非常感谢您的帮助,praetorian机器人!

+0

该死的,我忘了2 * Pi正常化!我已经在我自己的旧代码中看到了它,但错过了它的缺席。但我认为角度变量的溢出应该会产生点击效果,而您的示例听起来不同。无论如何,我很高兴你解决了你的问题。 – 2012-04-15 13:50:42