我在读一个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赫兹,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)
我可以得出结论,字节数组被转换到正确int和周围的其他方法。
任何人可以帮助我吗? 什么我会收到什么错在这里? 谢谢! :)
你确定你正在阅读的8位单声道音频? 否则,一个字节不等于一个样品,你可以不只是规模的每个字节。 例如,如果它是你必须分析每对字节作为16位整数的16位数据,即规模,然后将它写回为两个字节。
问题在整型,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;
}
通过罗迪答案是一个很好的起点,但它不足以得到好的结果。
它引入了溢出的速度不够快在Android上实时音频。
TL; DR:我涉及LUT和增益压缩改进的解决方案
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中的体积实时,同时避免削波和失真。
有两个问题与罗迪的解决方案:
- 代码正在运行有点太慢了实时操作在手机上(浮动慢)
- 它不会阻止溢出 ,这可能会导致不好的和显着的文物
我来到这个解决方案:
计算速度可通过交易为CPU RAM和使用的查找表(LUT),即能够提高预先计算对于每个输入值短的体积升压函数值在那里。
这样,你牺牲的RAM 128K,但无害化处理过程中摆脱了浮点和乘法的完全,这在我的情况下,是一个双赢。
至于溢出 ,周围有此两种方式。 丑一种是简单地替换短程分别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最终提升功能,以预先计算如下:
免责声明:我不是一个DSP专家,有人警告我说,像这样的提升是不适合的Hi-Fi音乐,这样,因为它引入了在音色的变化,谐波和其他微妙的文物。 但它的速度快,工作得非常好我的目的,我认为这是可以接受的涉及语音和Lo-Fi无线的东西,在一般的多种用途。
你确定一个字节是一个样本? 在这种格式规范,它看起来像一个样品有2个byttes。 而且不要忘了让头保持不变。
WAVE PCM格式的音效档