该站点上还有其他问题和答案表明,要创建回声或延迟效果,您只需添加一个音频样本和过去存储的音频样本。因此,我有以下 Java 类:
public class DelayAMod extends AudioMod {
private int delay = 500;
private float decay = 0.1f;
private boolean feedback = false;
private int delaySamples;
private short[] samples;
private int rrPointer;
@Override
public void init() {
this.setDelay(this.delay);
this.samples = new short[44100];
this.rrPointer = 0;
}
public void setDecay(final float decay) {
this.decay = Math.max(0.0f, Math.min(decay, 0.99f));
}
public void setDelay(final int msDelay) {
this.delay = msDelay;
this.delaySamples = 44100 / (1000/this.delay);
System.out.println("Delay samples:"+this.delaySamples);
}
@Override
public short process(short sample) {
System.out.println("Got:"+sample);
if (this.feedback) {
//Delay should feed back into the loop:
sample = (this.samples[this.rrPointer] = this.apply(sample));
} else {
//No feedback - store base data, then add echo:
this.samples[this.rrPointer] = sample;
sample = this.apply(sample);
}
++this.rrPointer;
if (this.rrPointer >= this.samples.length) {
this.rrPointer = 0;
}
System.out.println("Returning:"+sample);
return sample;
}
private short apply(short sample) {
int loc = this.rrPointer - this.delaySamples;
if (loc < 0) {
loc += this.samples.length;
}
System.out.println("Found:"+this.samples[loc]+" at "+loc);
System.out.println("Adding:"+(this.samples[loc] * this.decay));
return (short)Math.max(Short.MIN_VALUE, Math.min(sample + (int)(this.samples[loc] * this.decay), (int)Short.MAX_VALUE));
}
}
它一次从输入流中接受一个 16 位样本,找到一个较早的样本,并将它们相应地相加。然而,输出只是可怕的嘈杂静态,尤其是当衰减提高到实际上会导致任何明显结果的水平时。将衰减降低到 0.01 几乎无法让原始音频通过,但此时肯定没有回声。
基本故障排除事实:
- 如果跳过此处理,音频流听起来不错。
- 如果衰减为 0(无需添加),音频流听起来不错。
- 存储的样本确实以正确的顺序和正确的位置存储和访问。
- 存储的样本正在衰减并正确添加到输入样本中。
- 调用
process()
toreturn sample
的所有数字正是我对这个算法的期望,即使在这个类之外仍然如此。
问题似乎来自于简单地将带符号的短路相加,由此产生的波形绝对是一场灾难。我已经在很多地方看到了这种特定的方法——C#、C++,甚至在微控制器上——那么为什么它在这里失败得这么难呢?
编辑:看来我一直在做这完全错误的事情。我不知道是 FFmpeg/avconv 还是其他因素,但我在这里没有使用正常的 PCM 信号。通过波形图,以及对音调发生器的失败尝试和结果分析,我确定这是某种版本的差分脉冲编码调制;音高是由一个样本到下一个样本的变化决定的,将纯正弦波上的预期“音量”乘数减半实际上会降低音高并使音量保持不变。(在非正弦序列上使用音量乘数会产生与此回声算法相同的静态。)因为此算法和其他 DSP 算法旨在用于线性脉冲编码调制,我将需要一些方法来首先获得正确的音频流。