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);
我正在使用 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);