[英]Audio: Change Volume of samples in byte array
我正在使用这种方法将wav文件读取到字节数组(如下所示) 。 现在我把它存储在我的字节数组中,我想改变音量。
private byte[] getAudioFileData(final String filePath) {
byte[] data = null;
try {
final ByteArrayOutputStream baout = new ByteArrayOutputStream();
final File file = new File(filePath);
final AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(file);
byte[] buffer = new byte[4096];
int c;
while ((c = audioInputStream.read(buffer, 0, buffer.length)) != -1) {
baout.write(buffer, 0, c);
}
audioInputStream.close();
baout.close();
data = baout.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
编辑:按要求提供有关音频格式的一些信息:
PCM_SIGNED 44100.0 Hz,16位,单声道,2字节/帧,小端
从物理课开始,我记得你可以通过将正弦值乘以0到1之间的数字来改变正弦波的幅度。
编辑:更新了16位样本的代码:
private byte[] adjustVolume(byte[] audioSamples, double volume) {
byte[] array = new byte[audioSamples.length];
for (int i = 0; i < array.length; i+=2) {
// convert byte pair to int
int audioSample = (int) ((audioSamples[i+1] & 0xff) << 8) | (audioSamples[i] & 0xff);
audioSample = (int) (audioSample * volume);
// convert back
array[i] = (byte) audioSample;
array[i+1] = (byte) (audioSample >> 8);
}
return array;
}
如果我将audioSample
与volume
相乘,则声音会严重失真。 如果我不这样做,并将两个数组与Arrays.compare(array, audioSample)
进行比较Arrays.compare(array, audioSample)
我可以得出结论,正确地将字节数组转换为int Arrays.compare(array, audioSample)
。
有人可以帮帮我吗? 我在这里弄错了什么? 谢谢! :)
你确定你正在阅读8位单声道音频吗? 否则,一个字节不等于一个样本,并且您不能只缩放每个字节。 例如,如果它是16位数据,则必须将每对字节解析为16位整数,对其进行缩放,然后将其写回为两个字节。
int类型的问题,java中int的大小是4个字节,样本大小是2个字节
这个代码:
private byte[] adjustVolume(byte[] audioSamples, float volume) {
byte[] array = new byte[audioSamples.length];
for (int i = 0; i < array.length; i+=2) {
// convert byte pair to int
short buf1 = audioSamples[i+1];
short buf2 = audioSamples[i];
buf1 = (short) ((buf1 & 0xff) << 8);
buf2 = (short) (buf2 & 0xff);
short res= (short) (buf1 | buf2);
res = (short) (res * volume);
// convert back
array[i] = (byte) res;
array[i+1] = (byte) (res >> 8);
}
return array;
}
Rodion的答案是一个很好的起点,但不足以取得好成绩。
它引入了溢出,并且对于Android上的实时音频来说还不够快。
private static int N_SHORTS = 0xffff;
private static final short[] VOLUME_NORM_LUT = new short[N_SHORTS];
private static int MAX_NEGATIVE_AMPLITUDE = 0x8000;
static {
precomputeVolumeNormLUT();
}
private static void normalizeVolume(byte[] audioSamples, int start, int len) {
for (int i = start; i < start+len; i+=2) {
// convert byte pair to int
short s1 = audioSamples[i+1];
short s2 = audioSamples[i];
s1 = (short) ((s1 & 0xff) << 8);
s2 = (short) (s2 & 0xff);
short res = (short) (s1 | s2);
res = VOLUME_NORM_LUT[res+MAX_NEGATIVE_AMPLITUDE];
audioSamples[i] = (byte) res;
audioSamples[i+1] = (byte) (res >> 8);
}
}
private static void precomputeVolumeNormLUT() {
for(int s=0; s<N_SHORTS; s++) {
double v = s-MAX_NEGATIVE_AMPLITUDE;
double sign = Math.signum(v);
// Non-linear volume boost function
// fitted exponential through (0,0), (10000, 25000), (32767, 32767)
VOLUME_NORM_LUT[s]=(short)(sign*(1.240769e-22 - (-4.66022/0.0001408133)*
(1 - Math.exp(-0.0001408133*v*sign))));
}
}
这非常有效,可以很好地提升音频,没有剪辑问题,可以在Android上实时运行。
我的任务是包装一个专有的闭源TTS引擎(由客户提供),使其作为标准的Android TextToSpeechService工作。 客户抱怨音量太低,即使流量设置为最高。
我必须找到一种方法来实时提高Java的音量,同时避免剪辑和失真。
Rodion的解决方案存在两个问题 :
我来到这个解决方案:
通过为CPU交换RAM并使用查找表(LUT)可以提高计算速度 ,即为每个输入短值预先计算音量 - 增强函数值。
这样你就可以牺牲128K的RAM但完全摆脱声音处理中的浮点和乘法,这在我的情况下是一个胜利。
至于溢出 ,有两种方法可以解决这个问题。 丑陋的是简单地用Short.MIN_VALUE或Short.MAX_VALUE替换短距离之外的值。 它不会阻止剪切,但至少它不会溢出并且伪影不那么令人不安。
但我发现了一种更好的方法,即应用非线性增强 (也称为增益压缩)。 您可以使用指数函数而不是仅预先计算乘法LUT,您可以预先计算非线性增强。 实际上,该功能与LUT非常吻合,并且可以通过这种方式预先计算任何类似的功能。
找到一个好的增强功能和功能的最佳参数的最佳方法是暂时试验不同的功能,一个简单但好的工具是https://mycurvefit.com/
其中一个功能似乎很有希望,我只需做一个小修改就可以使负值以对称的方式工作。
在玩了一些参数之后,我得出的结论是,如果函数通过[0,0],[10000,25000]和[32767,32767],我将得到很好的结果。
我需要相当大的音量提升,你可能想要更加微妙。
MyCurveFit给了我这组参数:y 0 = 1.240769e-22,v 0 = -4.66022,k = 0.0001408133
在LUT中预先计算的最终boost函数如下所示:
免责声明:我不是DSP专家,我被警告说,这样的提升不适合Hi-Fi音乐等,因为它引入了音色,谐波和其他微妙的文物的变化。 但它的速度很快,并且非常适合我的目的,我认为这对于涉及语音和Lo-Fi的一般用途很多都是可以接受的。
你确定一个字节是一个样本吗? 在此格式规范中,它看起来像样本有2个字节。 并且不要忘记让标题保持不变。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.