java 实时音频字节数组中的快进实现

Fast Forward implementation in Realtime Audio byte array in java

我正在使用 java 声音 API(targetDataLine 和 sourceDataLine)管理音频捕获和播放。现在假设在会议环境中,一个参与者的音频队列大小变得大于抖动大小(由于处理或网络),我想快进该参与者的音频字节以使其短于抖动大小。

如何快进该参与者的音频字节数组?

我无法在正常播放期间执行此操作,因为播放器线程只是从每个参与者的队列中提取 1 帧并将其混合以进行播放。我能得到的唯一方法是,如果我出列该参与者的超过 1 帧并混合(?)它 fast-forwarding,然后再与其他参与者混合 1 个出列帧进行播放? 在此先感谢您提供任何帮助或建议。

据我所知有两种加快播放速度的方法。在一种情况下,更快的节奏会导致音高升高。对此的编码相对容易。另一种情况下,音高保持不变,但它涉及一种处理声音颗粒(颗粒合成)的技术,并且更难解释。

对于不考虑保持相同pitch的情况,基本方案是:不是单帧推进,而是一帧+小增量推进。例如,假设在 44000 帧的过程中前进 1.1 帧就足以让您赶上进度。 (这也意味着音高增加大约是八度的 1/10。)

要推进“分数”帧,您首先必须将两个包围帧的字节转换为 PCM。然后,使用线性插值得到中间值。然后将该中间值转换回输出行的字节。

例如,如果您从帧 [0] 前进到帧 [“1.1”],您将需要知道帧 [1] 和帧 [2] 的 PCM。可以使用加权平均值计算中间值:

value = PCM[1] * 9/10 + PCM[2] * 1/10

我认为让你提前的金额逐渐变化可能会很好。采取几十帧来增加增量,并在返回正常出队时留出时间再次减少。如果您突然更改读取音频数据的速率,则可能会出现不连续的情况,您会听到咔哒声。

我使用过这个动态控制播放速度的基本方案,但是我没有针对你描述的情况使用它的经验。如果您还试图强制保持过渡平稳,那么调节变速可能会很棘手。

使用颗粒的基本思想涉及获得连续的 PCM(我不清楚语音的最佳帧数是多少,1 到 50 毫秒被认为是这种技术在合成中常用的),以及给它一个体积包络,允许您端到端混合顺序颗粒(它们必须重叠)。

我认为颗粒的包络使用了 Hann 函数或 Hamming window--但我不清楚细节,例如颗粒的重叠放置,因此它们 mix/transition顺利。我只是涉猎而已,我假设 Signal Processing 的人将是获得有关如何编写此代码的建议的最佳选择。

我找到了一个很棒的 git repo(声音库,主要用于音频播放器),它实际上完全按照我想要的方式进行控制。我可以输入整个 .wav 文件甚至音频字节数组块,在处理之后,我们可以获得加速播放体验等等。对于实时处理,我实际上在音频字节数组的每个块上都调用了它。

我找到了另一个 way/algo 来检测音频 chunk/byte 数组是否是语音,根据它的结果,我可以简单地忽略播放非语音数据包,这给了我们大约 1.5 倍的加速处理更少。

public class DTHVAD {
public static final int INITIAL_EMIN = 100;
public static final double INITIAL_DELTAJ = 1.0001;
private static boolean isFirstFrame;
private static double Emax;
private static double Emin;
private static int inactiveFrameCounter;
private static double Lamda; //
private static double DeltaJ;

static {
    initDTH();
}

private static void initDTH() {
    Emax = 0;
    Emin = 0;
    isFirstFrame = true;
    Lamda = 0.950; // range is 0.950---0.999
    DeltaJ = 1.0001;
}

public static boolean isAllSilence(short[] samples, int length) {
    boolean r = true;
    for (int l = 0; l < length; l += 80) {
        if (!isSilence(samples, l, l+80)) {
            r = false;
            break;
        }
    }
    return r;
}

public static boolean isSilence(short[] samples, int offset, int length) {

    boolean isSilenceR = false;
    long energy = energyRMSE(samples, offset, length);
    // printf("en=%ld\n",energy);

    if (isFirstFrame) {
        Emax = energy;
        Emin = INITIAL_EMIN;
        isFirstFrame = false;

    }

    if (energy > Emax) {
        Emax = energy;
    }

    if (energy < Emin) {

        if ((int) energy == 0) {
            Emin = INITIAL_EMIN;

        } else {
            Emin = energy;

        }
        DeltaJ = INITIAL_DELTAJ; // Resetting DeltaJ with initial value

    } else {
        DeltaJ = DeltaJ * 1.0001;
    }

    long thresshold = (long) ((1 - Lamda) * Emax + Lamda * Emin);
    // printf("e=%ld,Emin=%f, Emax=%f, thres=%ld\n",energy,Emin,Emax,thresshold);
    Lamda = (Emax - Emin) / Emax;

    if (energy > thresshold) {

        isSilenceR = false; // voice marking

    } else {
        isSilenceR = true; // noise marking

    }

    Emin = Emin * DeltaJ;

    return isSilenceR;
}

private static long energyRMSE(short[] samples, int offset, int length) {
    double cEnergy = 0;
    float reversOfN = (float) 1 / length;
    long step = 0;

    for (int i = offset; i < length; i++) {
        step = samples[i] * samples[i]; // x*x/N=
        // printf("step=%ld cEng=%ld\n",step,cEnergy);
        cEnergy += (long) ((float) step * reversOfN);// for length =80
        // reverseOfN=0.0125

    }
    cEnergy = Math.pow(cEnergy, 0.5);
    return (long) cEnergy;

}

}

在这里我可以将我的字节数组转换为短数组并通过

检测它是语音还是非语音

frame.silence = DTHVAD.isSilence(encodeShortBuffer, 0, shortLen);