【问题标题】:How to turn a PCM byte array into little-endian and mono?如何将 PCM 字节数组转换为 little-endian 和 mono?
【发布时间】:2021-11-20 10:45:05
【问题描述】:

我正在尝试将来自在线通信应用的音频输入 Vosk 语音识别 API。

音频以字节数组的形式出现,并采用这种音频格式PCM_SIGNED 48000.0 Hz, 16 bit, stereo, 4 bytes/frame, big-endian。 为了能够用 Vosk 处理它,它需要是 monolittle-endian

这是我目前的尝试:

        byte[] audioData = userAudio.getAudioData(1);
        short[] convertedAudio = new short[audioData.length / 2];
        ByteBuffer buffer = ByteBuffer.allocate(convertedAudio.length * Short.BYTES);
        
        // Convert to mono, I don't think I did it right though
        int j = 0;
        for (int i = 0; i < audioData.length; i += 2)
            convertedAudio[j++] = (short) (audioData[i] << 8 | audioData[i + 1] & 0xFF);

        // Convert to little endian
        buffer.order(ByteOrder.BIG_ENDIAN);
        for (short s : convertedAudio)
            buffer.putShort(s);
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        buffer.rewind();

        for (int i = 0; i < convertedAudio.length; i++)
            convertedAudio[i] = buffer.getShort();

        queue.add(convertedAudio);

【问题讨论】:

  • 我会做一些检查,因为这个运行良好的“应用程序”可能会给我发送一堆垃圾 - 然后我会检查接收到的格式和所需的格式 - 里面有东西AudioSystem - 他们是你说的吗?对此我不确定! - 那我就去做体力劳动了!!
  • @gpasch 你能详细说明一下吗? AudioSystem 中有什么“东西”?检查格式?什么体力劳动?

标签: java arrays audio byte java-audio


【解决方案1】:

当然支持签名 PCM。问题是 48000 fps 不是。我认为Java直接支持的最高帧率是44100。

至于要采取什么行动,我不知道该推荐什么。也许有可以使用的库?当然可以直接使用字节数据手动进行转换,您可以强制执行预期的数据格式。

如果需要,我可以写更多关于转换过程本身的内容(将字节组装成 PCM、操作 PCM、从 PCM 创建字节)。 VOSK 是否也期望 48000 fps?


从立体声到单声道实际上是取左右 PCM 值的总和。通常会添加一个步骤以确保不超出范围。 (如果 PCM 编码为标准化浮点数,则为 16 位范围 = -1 到 1,如果 PCM 编码为 shorts,则范围 = -32768 到 32767。)

以下代码片段是采用单个 PCM 值(有符号浮点数,标准化为 -1 和 1 之间的范围)并以小端顺序生成两个字节(16 位)的示例。数组 buffer 的类型为 float,并保存 PCM 值。数组 audioBytes 的类型为 byte

buffer[i] *= 32767;
        
audioBytes[i*2] = (byte) buffer[i];
audioBytes[i*2 + 1] = (byte)((int)buffer[i] >> 8 );

要使其成为大端序,只需交换audioBytes 的索引,或操作(byte) buffer[i](byte)((int)buffer[i] &gt;&gt; 8 )。这段代码来自AudioCue 类,这是我编写的一个类,用作增强的Clip。请参见第 1391-1394 行。

我认为您可以推断相反的过程(将传入字节转换为 PCM)。但这里有一个这样做的例子,来自代码行 391-393。在这种情况下,temp 是一个 float 数组,它将保存从字节流计算的 PCM 值。在我的代码中,该值很快会除以 32767f 以使其标准化。 (第 400 行)

temp[clipIdx++] = ( buffer[bufferIdx++] & 0xff ) | ( buffer[bufferIdx++] << 8 ) ;

对于大端,您可以颠倒&amp; 0xff&lt;&lt; 8 的顺序。

如何迭代结构取决于您的个人喜好。 IDK 我在这里选择了最佳方法。对于您的情况,我很想将 PCM 值保存在 short(范围从 -32768 到 32767)中,而不是规范化为 -1 到 1 个浮点数。如果您正在处理来自多个来源的音频数据,则规范化更有意义。但您要做的唯一处理是将左右 PCM 加在一起以获得单声道值。顺便说一下,在对左右求和之后,确保不超过数值范围是很好的——因为这会产生一些非常严重的失真。

【讨论】:

  • 我已将变量 audioFormat 中的采样率更改为 44100 Hz,但仍然出现异常。您可以在 vosk 构造函数中设置采样率,所以这应该不是问题,只是一个数字。如果您能写下转换过程,我将不胜感激。我的目标是操纵字节数组,使其变为单声道和小端。由于 vosk 的 acceptWaveform() 方法只需要一个 InputStream,我可以将操纵的字节数组转换为 ByteArrayInputStream,这应该可以工作。
  • @PhilFreihofner 感谢您更新您的答案。虽然我没有完全理解。你能向我解释你的第一个代码 sn-p 吗?您说它将它转换为 16 位小端,但是您的第二个 sn-p 会做什么呢?因为我认为第二个 sn-p 是为了把它变成小端。您的第一个 sn-p 是否将其转换为单声道? float 数组 buffer 填充了什么? byte 数组 audioBytes 在我的情况下是 queue.poll(),所以我要转换的 byte 数组? float 数组 temp 是上述问题中提到的我的音频byte 数组的副本吗?
  • 感谢您的详细问题。我进行了编辑,试图消除我之前写的内容的歧义。现在答案更清楚了吗?我正在利用我之前编写的代码,变量名与你所拥有的并不完全一致。在两个 sn-ps 中,byte[] 数组用于字节流(您从源接收的内容,发送给 Vosk 的内容),float[] 数组用于 PCM。我可能对如何使用 float[] 而不是使用 short[] 数组可能更简洁。这消除了规范化和非规范化步骤。
  • 糟糕评论在保存答案之前已更新。如果您刚才查看并没有看到更改,请再次查看。很乐意进一步澄清。总是很高兴让其他人看到并提出建议或更正!
【解决方案2】:

我遇到了同样的问题,发现这个stackoverflow 帖子将原始 pcm 字节数组转换为音频输入流。

我假设您使用的是Java Discord API (JDA),所以这是我使用 vosk 的“handleUserAudio()”函数的初始代码,以及我上面提供的链接中的代码:

                // Define audio format that vosk uses
            AudioFormat target = new AudioFormat(
                    16000, 16, 1, true, false);

            try {
                byte[] data = userAudio.getAudioData(1.0f);
                // Create audio stream that uses the target format and the byte array input stream from discord
                AudioInputStream inputStream = AudioSystem.getAudioInputStream(target,
                        new AudioInputStream(
                                new ByteArrayInputStream(data), AudioReceiveHandler.OUTPUT_FORMAT, data.length));

                // This is what was used before
//                InputStream inputStream = new ByteArrayInputStream(data);

                int nbytes;
                byte[] b = new byte[4096];
                while ((nbytes = inputStream.read(b)) >= 0) {
                    if (recognizer.acceptWaveForm(b, nbytes)) {
                        System.out.println(recognizer.getResult());
                    } else {
                        System.out.println(recognizer.getPartialResult());
                    }
                }
//                queue.add(data);
            } catch (Exception e) {
                e.printStackTrace();
            }

到目前为止,这是可行的,但是,它将所有内容都放入识别器的 '.getPartialResult()' 方法中,但至少 vosk 能够理解来自不和谐机器人的音频。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-12-09
    • 2022-06-10
    • 1970-01-01
    • 1970-01-01
    • 2017-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多