15

我正在使用这种方法(如下所示)将 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)我可以得出结论,字节数组正在正确地转换为 int,反之亦然。

有人可以帮帮我吗?我在这里做错了什么?谢谢!:)

4

4 回答 4

9

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;
}
于 2014-09-25T11:33:12.303 回答
8

您确定您正在阅读 8 位单声道音频吗?否则一个字节不等于一个样本,你不能只缩放每个字节。例如,如果它是 16 位数据,您必须将每对字节解析为 16 位整数,对其进行缩放,然后将其作为两个字节写回。

于 2013-01-23T17:45:28.307 回答
7

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<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 的解决方案有两个问题:

  1. 代码运行速度有点,无法在手机上进行实时操作(浮动很慢)
  2. 它不能防止溢出,这可能会导致不良和明显的伪影

我来到了这个解决方案:

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

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

至于溢出,有两种解决方法。丑陋的一种是简单地分别用 Short.MIN_VALUE 或 Short.MAX_VALUE 替换短范围之外的值。它不会阻止剪辑,但至少它不会溢出并且伪影不那么令人不安。

但我找到了一种更好的方法,那就是应用非线性提升(也称为增益压缩)。您可以使用指数函数,而不仅仅是预先计算乘法 LUT,您可以预先计算非线性提升。实际上,该函数与 LUT 配合得很好,任何类似的函数都可以通过这种方式进行预计算。

找到一个好的 boost 函数和函数的最优参数的最好方法是尝试不同的函数一段时间,一个简单但很好的工具是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 的许多用途来说是可以接受的。

于 2018-10-06T22:35:19.230 回答
1

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

WAVE PCM 声音文件格式

于 2013-01-23T17:46:53.237 回答