Получать ввод с микрофона от 2-х процессов одновременно

Я работаю над распознаванием речи Java, используя sphynx4, и в настоящее время у меня есть проблема.

У меня есть приложение, которое распознает вход микрофона, используя класс LiveSpeechRecognizer Sphynx4, который отлично работает. Проблема в том, что после того, как я добавил класс, который также слушает микрофон, преобразует и визуализирует вывод.

По отдельности оба класса работают нормально. Но при объединении в одном приложении я получаю сообщение об ошибке:

LineUnavailableException: строка с форматом PCM_SIGNED 44100,0 Гц, 8 бит, моно, 1 байт/кадр, не поддерживается.

Я проверил проблему, и, похоже, она вызвана одновременным доступом к микрофону. У меня была идея использовать StreamSpeechRecognizer вместо Live, но мне не удалось получить поток с микрофонного входа. Пробовал AudioInputStream для этой цели.

Не могли бы вы предложить, как я могу настроить свой код, чтобы оба: SpeechRecognition и Oscilloscope использовали микрофон одновременно?

Заранее спасибо.

УПД:

Это моя попытка разделить вход микрофона для использования в обоих приложениях.

....
     byte[] data = new byte[dataCaptureSize];   
            line.read(data, 0, data.length);

            ByteArrayOutputStream out = new ByteArrayOutputStream();
            out.write(data);
            byte audioData[] = out.toByteArray();
            InputStream byteArrayInputStream = new ByteArrayInputStream(audioData);
            AudioInputStream audioInputStream = new AudioInputStream(byteArrayInputStream,
                    inputFormat,
                    audioData.length / inputFormat.getFrameSize());
....

Вот как я конвертирую его во входной поток, который затем передается в StreamSpeechRecognizer, а массив байтов преобразуется с помощью быстрого преобразования Фурье и передается на график. Это не работает, так как он просто постоянно останавливает график, поэтому отображаемые данные не являются фактическими.

Я попытался запустить распознавание в отдельном потоке, но это совсем не увеличило производительность.

Мой код разделения на потоки приведен ниже:

Thread recognitionThread = new Thread(new RecognitionThread(configuration,data));
    recognitionThread.join();
    recognitionThread.run();

UPD 2: Вход с микрофона. Приведенный выше AudioInputStream передается в StreamSpeechRecognizer:

StreamSpeechRecognizer nRecognizer = new StreamSpeechRecognizer(configuration); nRecognizer.startRecognition(audioStream);

А массив байтов передается преобразованным с помощью БПФ и передается в граф: ` double[] arr = FastFourierTransform.TransformRealPart(data);

for (int i = 0; i < arr.length; i++) { 
    series1.getData().add(new XYChart.Data<>(i*22050/(arr.length), arr[i]));

`


person Andrey Hrynyuk    schedule 06.05.2020    source источник
comment
Первый оператор line.read исходит от микрофона? Если это так, предполагается, что будет несколько чтений, пока линия микрофона открыта, но вы, похоже, создаете новые ByteArrayOutputStream, InputStream и AudioInputStream при каждом чтении. Я не могу сказать, как эти потоки используются из показанного кода, но логически данные микрофона должны постепенно добавляться к ним, если они, в свою очередь, считываются в другом месте другими разделами программы, которую вы пытаетесь реализовать. В чем смысл разделения треда? Можно заполнить несколько потоков в одном потоке.   -  person Phil Freihofner    schedule 12.05.2020
comment
Просто думал, что будет быстрее. Поток просто передается методу StreamSpeechRecognizer recognize()   -  person Andrey Hrynyuk    schedule 13.05.2020
comment
Да. линия с микрофона. Добавлен код того, как он используется.   -  person Andrey Hrynyuk    schedule 13.05.2020


Ответы (1)


Вот правдоподобный подход для рассмотрения.

Во-первых, напишите свой собственный микрофонный ридер. (Есть учебные пособия о том, как это сделать.) Затем переупакуйте эти данные в виде двух параллельных строк, которые могут читать другие приложения.

Другой подход состоит в том, чтобы проверить, включена ли какая-либо функция "сквозной передачи" в каком-либо приложении.

РЕДАКТИРОВАТЬ: добавлено для уточнения

Этот пример кода утилиты звукозаписи Java открывается a TargetDataLine к микрофону и сохраняет данные с него в массив (строки 69, 70). Вместо того, чтобы хранить данные в массиве, я предлагаю вам создать два объекта SourceDataLine и записать данные в каждый из них.

recordBytes = new ByteArrayOutputStream();
secondStreamBytes = new ByteArrayOutputStream();

isRunning = true;

while (isRunning) {
    bytesRead = audioLine.read(buffer, 0, buffer.length);
    recordBytes.write(buffer, 0, bytesRead);
    secondStreamBytes.write(buffer, 0, bytesRead);
}

Надеюсь, не будет слишком сложно понять, как настроить ваши две программы для чтения с созданных строк, а не с линии микрофона. Я не могу дать руководство о том, как это сделать.

РЕДАКТИРОВАТЬ 2: Я бы хотел, чтобы некоторые другие люди присоединились. Я немного не в себе, чтобы делать что-то необычное с потоками. И код, который вы даете, настолько минимален, что я до сих пор не понимаю, что происходит или как все связано.

FWTW: (1) Являются ли данные, которые вы добавляете в «серию 1», потоковыми данными? Если да, можете ли вы добавить строку в этот цикл for и записать те же данные в поток, потребляемый другим классом? (Это был бы способ использования данных микрофона «последовательно», а не «параллельно».)

(2) Потоки данных часто включают код, который блокируется или выполняется с разной скоростью из-за непредсказуемого способа переключения процессора между задачами. Таким образом, если вы напишете «разветвитель» (как я пытался проиллюстрировать, изменив код чтения микрофона, который я связал ранее), может возникнуть ситуация, когда код будет работать только настолько быстро, насколько медленнее из двух «разветвителей» в данный момент времени. момент. Возможно, вам придется включить некоторую буферизацию и использовать отдельные потоки для двух получателей данных микрофона.

Недавно я написал свой первый код буферизации для ситуации, когда линия чтения с микрофона отправляет поток в функцию микширования звука в другом потоке. Я написал это всего несколько недель назад, и это первый раз, когда я столкнулся с попыткой запустить поток через потоки с барьером потока, поэтому я не знаю, является ли идея, к которой я пришел, лучшим способом сделать такого рода вещи . Но ему удается стабильно подавать сигнал от микрофона к микшеру, без пропаданий и потерь.

Считыватель микрофона считывает буфер данных, затем добавляет этот byte[] буфер в ConcurrentLinkedQueue<Byte[]>.

Из другого потока код микширования аудио опрашивает ConcurrentLinkedQueue для данных.

Я немного поэкспериментировал и в настоящее время размер буфера byte[] составляет 512 байт, а ConcurrentLinkedQueue настроен на хранение до 12 «буферов», прежде чем он начнет выбрасывать самые старые буферы (структура FIFO). Кажется, этих небольших буферов достаточно, чтобы приспособиться, когда код обработки микрофона временно опережает микшер.

ConcurrentLinkedQueue имеет встроенные средства, позволяющие добавлять и опрашивать два потока одновременно, не вызывая исключения. Является ли это чем-то, что вы должны написать, чтобы помочь без руки, и каким может быть лучший размер буфера, я не могу сказать. Может быть, гораздо больший буфер с меньшим количеством буферов, хранящихся в очереди, был бы лучше.

Может быть, кто-то еще взвесит свое мнение, или, может быть, стоит поэкспериментировать с этим предложением и опробовать его.

Во всяком случае, это лучшее, что я могу сделать, учитывая мой ограниченный опыт в этом. Я надеюсь, что вы сможете что-то придумать.

person Phil Freihofner    schedule 07.05.2020
comment
Не совсем уверен, что понимаю тебя. Проблема в том, что у меня есть микрофонный ридер, и он работает нормально. Но для другого процесса — преобразования речи в текст — микрофонный ридер упакован внутри банки и срабатывает при вызове метода распознавания. Я не могу повлиять на него или передать ему байты. - person Andrey Hrynyuk; 08.05.2020
comment
Я добавлю к ответу, чтобы уточнить. - person Phil Freihofner; 08.05.2020
comment
Спасибо за разъяснение. Проблема в том, что это почти тот же код, который используется для передачи данных на график сигнала в моем втором приложении. Если бы я получил SourceDataLine, все еще неясно, как я могу передать его экземпляру класса LiveSpeechRecognizer из приложения распознавания. - person Andrey Hrynyuk; 09.05.2020
comment
Да, вам понадобится в ОБОИХ приложениях либо способ настроить, какая строка используется для ввода, либо иметь возможность выполнить небольшую перекодировку, позволяющую указать строку ввода. Похоже, вы можете сделать это с помощью программы построения графиков сигналов, но не LiveSpeechRecognizer. - person Phil Freihofner; 09.05.2020
comment
Только что сделал быстрый облет сфинкса. Я не вижу, где ввод происходит в коде. Но у них есть форум на sourceforge. Кто-то там должен быть в состоянии дать вам знать, где переписывание должно произойти. Возможно, они даже уже предусмотрели это. - person Phil Freihofner; 09.05.2020
comment
Это верно. Я не могу переписать файл LiveSpeechRecognizer. По крайней мере, мне не удалось найти способ. Я пытался использовать «StreamSpeechRecognizer», но для этого требуется входной поток. В любом случае я могу получить его со входа микрофона, а не из файла? - person Andrey Hrynyuk; 10.05.2020
comment
Я запутался. Обе функции из одной программы? Если вы сможете определить точку в каждом из них, на которую ссылается входной поток, я мог бы помочь в дальнейшем. Но я снова призываю вас также проконсультироваться с создателями программы. - person Phil Freihofner; 10.05.2020
comment
По сути, в настоящее время у меня есть программа, которая строит график, используя линию передачи данных с микрофонного входа. У меня также есть приложение для распознавания речи, которое представляет собой демонстрационное приложение с веб-сайта. Он вызывает метод распознавания из классов LiveSpeechRecognizer, и это в основном все. Я также нашел еще один класс — StreamSpeechRecognizer. Я нашел пример использования, когда входной поток создавался из файла и передавался в качестве параметра. Итак, основная идея, которую я пытался достичь, заключалась в том, чтобы получить входной поток не из файла, а из первого приложения, которое у меня есть, и передать его в качестве параметра. - person Andrey Hrynyuk; 10.05.2020
comment
Вы написали код для графического дисплея? Если это так, то именно здесь вы используете описанную мной концепцию сплиттера. Связь между двумя приложениями, вероятно, означает создание вывода сетевого сокета как часть первого приложения (приложение для отображения графики) и копирование данных, считанных с микрофона, во второй поток, который выводится через сетевой сокет. В учебнике по StreamSpeechRecognizer говорится, что возможно получение InputStream из NetworkSocket. - person Phil Freihofner; 11.05.2020
comment
Идея состоит в том, чтобы получить одно приложение в конце. Так что, я думаю, коммуникативную часть можно пропустить. Я уже пытался передать AudioInputStream в StreamSpeechRecognizer из части с графиком, но это не сработало. Может быть, я что-то не так делаю. - person Andrey Hrynyuk; 11.05.2020
comment
Готово. Надеюсь, вы поймете мое объяснение. - person Andrey Hrynyuk; 12.05.2020
comment
Добавлено к моему ответу. - person Phil Freihofner; 13.05.2020
comment
Ну, у тебя наверняка больше опыта, чем у меня. И я бы сказал, что ваши комментарии очень информативны. Я уже много экспериментировал с этими классами, но предлагаемое вами решение не входит в число тех, которые я пробовал. Не могу сказать, что все понял, но попробую. Что касается исходного кода, я могу отправить его вам по почте, если хотите. - person Andrey Hrynyuk; 13.05.2020
comment
В данный момент я немного завален другими обязательствами (например, мне нужно пройти финал в ближайшие несколько дней). Возможно, я мог бы посмотреть на следующей неделе, уделив проблеме полчаса или около того. Я хотел бы, чтобы вы четко определили точки ввода, я не хочу тратить время на их поиск. А пока продолжайте экспериментировать. Я знаю, что много раз в программировании мне приходилось делать шаг назад и тратить некоторое время на более глубокое изучение темы, прежде чем вернуться к ней. Это может иметь место для вас здесь. Если вы можете, возможно, просто попробуйте написать простую программу, которая выполняет только простую сквозную передачу или разделение, в качестве трамплина. - person Phil Freihofner; 13.05.2020