Как превратить байтовый массив PCM в AudioInputStream в Java

Я пытаюсь передать звук из приложения для онлайн-коммуникации в API распознавания речи Vosk.

Аудио поступает в виде массива байтов и с этим аудиоформатом PCM_SIGNED 48000.0 Hz, 16 bit, stereo, 4 bytes/frame, big-endian. Чтобы можно было обрабатывать его с помощью Vosk, он должен быть mono а также little-endian.

Я пробовал превратить массив байтов в ByteArrayInputStream а затем в, чтобы преобразовать его в другой AudioInputStream с желаемым форматом, указанным с помощью переменной audioFormat. (попробуйте с ресурсами)

Код:

      private void analyseAudio() {
        // Desired audio format
        //var audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 48000, 16, 1, 4, 48000, false);
        var audioFormat = new AudioFormat(48000.0f, 16, 1, true, false);
        int bytesRead;
        byte[] bytes = new byte[4096];

        // queue.poll() returns said byte array from the app
        try (InputStream audioStream = AudioSystem.getAudioInputStream(audioFormat, AudioSystem.getAudioInputStream(new ByteArrayInputStream(queue.poll())))) {
            while ((bytesRead = audioStream.read(bytes)) >= 0) {
                // Feed the bytes into the vosk api
                // acceptWaveForm(byte[] data, int len)
                recognizer.acceptWaveForm(bytes, bytesRead);
            }
        } catch (IOException | UnsupportedAudioFileException e) {
            e.printStackTrace();
        }
    }

Это не сработало. У меня следующее исключение:

      javax.sound.sampled.UnsupportedAudioFileException: Stream of unsupported format
at java.desktop/javax.sound.sampled.AudioSystem.getAudioInputStream(AudioSystem.java:1014)
at me.moeux.oratio.commands.SpeakCommand$SpeechRecognizer.analyseAudio(SpeakCommand.java:101)
at me.moeux.oratio.commands.SpeakCommand$SpeechRecognizer.handleUserAudio(SpeakCommand.java:85)
at net.dv8tion.jda.internal.audio.AudioConnection.lambda$setupReceiveThread$3(AudioConnection.java:425)
at java.base/java.lang.Thread.run(Thread.java:831)

Полагаю, PCM не поддерживается? Как мне превратить массив байтов аудио в моно и с прямым порядком байтов, чтобы передать его в recognizer.acceptWave() метод?

2 ответа

У меня была такая же проблема, и я нашел этот пост stackoverflow , который преобразует необработанный массив байтов PCM в поток ввода звука.

Я предполагаю, что вы используете Java Discord API (JDA) , поэтому вот мой исходный код для функции «handleUserAudio()», которая использует vosk, и код в приведенной выше ссылке:

                      // 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 понимает звук, исходящий от бота Discord.

Подписанный PCM, безусловно, поддерживается. Проблема в том, что 48000 fps нет. Я думаю, что максимальная частота кадров, поддерживаемая Java напрямую, составляет 44100.

Что касается того, что предпринять, я не уверен, что порекомендовать. Может быть, есть библиотеки, которые можно использовать? Конечно, можно выполнять преобразования вручную напрямую с байтовыми данными, когда вы применяете ожидаемые форматы данных.

Я могу написать немного больше о самом процессе преобразования (сборка байтов в PCM, управление PCM, создание байтов из PCM), если потребуется. VOSK тоже ожидает 48000 кадров в секунду?


Следующий фрагмент кода предназначен для преобразования одного кадра PCM (подписанного, нормализованного, с плавающей запятой) в 16-битный, с прямым порядком байтов. Буфер массива имеет тип и содержит значения PCM. Массив audioBytes имеет тип byte.

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

Чтобы сделать его прямым порядком байтов, просто поменяйте местами индексы audioBytes. Этот код из класса , взятнаписанного мнойAudioCue класса, который функционирует как расширенный Clip. См. Строки 1391-1394.

Я думаю, вы можете экстраполировать обратный процесс. Но вот код из строк 391–393. В этом случае температура равна floatмассив, который получит значения PCM. Вскоре значение будет разделено на 32767f, чтобы оно нормализовалось. (строка 400)

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

Для прямого порядка байтов вы должны изменить порядок & 0xff а также << 8.

То, как вы перебираете структуры, зависит от ваших личных предпочтений. IDK, что я здесь выбрал оптимальные методы. В вашей ситуации у меня возникло бы искушение считать PCM shortв диапазоне от -32768 до 32767 вместо нормализации до -1 к 1 с плавающей запятой. Нормализация имеет больше смысла, если вы обрабатываете аудиоданные из нескольких источников. Но единственная обработка, которую вы собираетесь выполнить, - это сложить левый и правый PCM вместе, чтобы получить ваше моно значение. Между прочим, хорошо, после суммирования слева и справа, убедиться, что числовой диапазон не превышен, так как это может создать довольно резкие искажения.

Другие вопросы по тегам