2

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

音频以字节数组的形式出现,并采用这种音频格式PCM_SIGNED 48000.0 Hz, 16 bit, stereo, 4 bytes/frame, big-endian。为了能够用 Vosk 处理它,它需要是monoand little-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);
4

2 回答 2

1

我遇到了同样的问题,发现这个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 正在理解来自不和谐机器人的音频。

于 2021-12-23T18:59:58.527 回答
-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 位)的示例。数组缓冲区属于类型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] >> 8 )。这段代码来自AudioCue类,这是我编写的一个用作增强的类Clip。见第 1391-1394 行。

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

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

对于大端,您将颠倒& 0xffand的顺序<< 8

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

于 2021-09-28T22:37:49.417 回答