Как превратить байтовый массив 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 вместе, чтобы получить ваше моно значение. Между прочим, хорошо, после суммирования слева и справа, убедиться, что числовой диапазон не превышен, так как это может создать довольно резкие искажения.