-1

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

如何快速转发该参与者的音频字节数组?

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

4

2 回答 2

2

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

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

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

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

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

我认为逐渐改变你提前的金额可能会很好。花费几十帧来增加增量,并在返回正常出队时允许时间再次下降。如果您突然改变读取音频数据的速率,则可能会引入不连续性,您会听到咔哒声。

我已经使用这个基本计划来动态控制播放速度,但我还没有在你描述的情况下使用它的经验。如果您还试图强制保持过渡平稳,则调节变速可能会很棘手。

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

我认为颗粒的信封使用了汉恩函数或汉明窗——但我不清楚细节,例如颗粒的重叠放置,以便它们顺利混合/过渡。我只是涉足,我将假设Signal Processing的人将是最好的选择,以获得有关如何编码的建议。

于 2021-11-26T01:26:36.667 回答
0

我找到了一个很棒的git repo(声波库,主要用于音频播放器),它实际上完全符合我的要求,具有如此多的控件。我可以输入整个 .wav 文件甚至是音频字节数组块,经过处理后,我们可以获得加速播放体验等等。对于实时处理,我实际上在每个音频字节数组块上调用了它。

我找到了另一种方法/算法来检测音频块/字节数组是否是语音,在取决于它的结果之后,我可以简单地忽略播放非语音数据包,这给我们带来了大约 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);

于 2021-11-29T09:40:01.360 回答