0

我有这个方法:

public double sineWave(double t) 
{
   return amplitude==0?0:Math.sin(t * frequency * Math.PI*2 + phase) * amplitude;
}

它由另一个类中的另一个方法调用以生成简单正弦波的样本,然后将其添加到缓冲区中以发送到声卡。t是时候了。出于某种原因,应用程序调用此方法的次数越多,它变得越慢。这没有任何意义,15 秒后它的速度足以使用我的 CPU 的完整内核并使音频结结巴巴。

我 100% 确定是这段代码,因为如果我用 return 0 替换它,运行它所需的时间(用 测量System.nanotime())是恒定的。

为什么会这样?我能做些什么来解决这个问题吗?

4

4 回答 4

5

从这里的信息 - 虽然不清楚缓冲区有多大,但每次迭代都会增加 t 。假设您的频率非常高,则每次迭代都会增加 Sin() 参数。检查参数是否不断增加到非常高的值。一个快速而肮脏的测试表明,Sin 的性能下降 -

public class SinTest {
  public static void main(String args[]) {
    long angle = Long.parseLong(args[0]);
    long startTime = System.nanoTime();
    for(long l=0L; l<=1000000L; l++) {
      Math.sin(angle);
    }
    long estimatedTime = System.nanoTime() - startTime;
    System.out.println(estimatedTime);
  }
}

$ java SinTest 100000
29181000
$ java SinTest 10000000
138598000
于 2013-01-13T21:21:35.807 回答
0

请不要给分,所以解决方案将给出@mk的答案:

public double sineWave(double t) 
{
    final double TAU = Math.PI *2;
    double a = t * frequency;
    a -= (long)a;
    return amplitude==0?0:Math.sin(a * TAU + phase) * amplitude;
}
于 2013-01-13T21:30:27.677 回答
0

我用查找表解决了这个问题:

private static final int LUT_SIZE=1000000;
private static double[] sineLookupTable=new double[(int)(Math.PI*2*LUT_SIZE)];
static{
    for(double i=0;i<sineLookupTable.length;i++){
        sineLookupTable[(int)i]=Math.sin(i/(double)LUT_SIZE);
    }

}
private static double sinLUT(double t){
    return sineLookupTable[(int) (((long) Math.floor((t%Math.PI*2)*LUT_SIZE))%sineLookupTable.length)];
}
public double sineWave(double t) {
    return amplitude==0?0:sinLUT(t * frequency * Math.PI*2 + phase) * amplitude;
}

它工作......有点,唯一的问题是我在高频上有很多失真。您可以建议一些插值方法吗?

于 2013-01-14T07:44:19.487 回答
0

Java 框架的当前版本将尝试对参数进行 mod-reduce,以Math.sin使用数学上完美的值 2π,而不是 value Math.PI*2。对于像您这样的代码,这意味着与使用与乘法中使用的相同比例因子(即Math.PI*2)执行模归约相比,代码将花费更长的时间并且产生的结果准确度更低。为了获得良好的准确性和速度,您应该在进行乘法之前执行模减少,使用类似的方法:

double thisSpin = t * frequency;
thisSpin -= (thisSpin - Math.Floor(thisSpin)) * 8.0; // value of 0-7.9999=one rotation
switch((int)(thisSpin*8.0))
{
  case 0: return  Math.sin(  thisSpin   * (Math.PI/4.0));
  case 1: return  Math.cos((2-thisSpin) * (Math.PI/4.0));
  case 2: return  Math.cos((thisSpin-2) * (Math.PI/4.0));
  case 3: return  Math.sin((4-thisSpin) * (Math.PI/4.0));
  case 4: return -Math.sin((thisSpin-4) * (Math.PI/4.0));
  case 5: return -Math.cos((6-thisSpin) * (Math.PI/4.0));
  case 6: return -Math.cos((thisSpin-6) * (Math.PI/4.0));
  case 7: return -Math.sin((8-thisSpin) * (Math.PI/4.0));
  default: return 0; // Shouldn't be possible, but thisSpin==8 would be congruent to 0
}

这将确保既不sin也不cos使用大于 π/4 的参数,根据文档,这是 Java 切换到使用缓慢且适得其反的范围缩小的点。

于 2014-06-07T17:43:00.327 回答