音频:更改字节数组中的样本量

我正在使用这种方法将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); } return array; } 

如果我将audioSamplevolume相乘,则声音会严重失真。 如果我不这样做,并将两个数组与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; } 

你确定一个字节是一个样本吗? 在此格式规范中,它看起来像样本有2个字节。 并且不要忘记让标题保持不变。

WAVE PCM声音文件格式

Rodion的答案是一个很好的起点,但不足以取得好成绩。

它引入了溢出,并且对于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 

这非常有效,可以很好地提升音频,没有剪切问题,可以在Android上实时运行。

我是怎么到那儿的

我的任务是包装一个专有的闭源TTS引擎(由客户提供),使其作为标准的Android TextToSpeechService工作。 客户抱怨音量太低,即使流量设置为最高。

我必须找到一种方法来实时提高Java的音量,同时避免剪辑和失真。

Rodion的解决方案存在两个问题

  1. 对于手机上的实时操作,代码运行有点太慢 (浮点慢)
  2. 它不会阻止溢出 ,这可能会导致错误和明显的伪影

我来到这个解决方案:

通过为CPU交换RAM并使用查找表(LUT)可以提高计算速度 ,即为每个输入短值预先计算音量 - 增强函数值。

这样你就可以牺牲128K的RAM但完全摆脱声音处理中的浮点和乘法,这在我的情况下是一个胜利。

至于溢出 ,有两种方法可以解决这个问题。 丑陋的是简单地用Short.MIN_VALUE或Short.MAX_VALUE替换短距离之外的值。 它不会阻止剪切,但至少它不会溢出,并且伪像不那么令人不安。

但我发现了一种更好的方法,即应用非线性增强 (也称为增益压缩)。 您可以使用指数函数而不是仅预先计算乘法LUT,您可以预先计算非线性增强。 实际上,该function与LUT非常吻合,并且可以通过这种方式预先计算任何类似的function。

找到一个好的增强function和function的最佳参数的最佳方法是暂时试验不同的function,一个简单但好的工具是https://mycurvefit.com/

其中一个function似乎很有希望,我只需做一个小修改就可以使负值以对称的方式工作。

在玩了一些参数后,我得出的结论是,如果函数通过[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的一般用途很多都是可以接受的。