【发布时间】:2016-09-10 23:34:24
【问题描述】:
尝试使用 Java SDK 将连续音频流从麦克风直接发送到 IBM Watson SpeechToText Web 服务。随分发 (RecognizeUsingWebSocketsExample) 提供的示例之一显示了如何将 .WAV 格式的文件流式传输到服务。但是,.WAV 文件需要提前指定它们的长度,因此一次只将一个缓冲区附加到文件的简单方法是不可行的。
似乎SpeechToText.recognizeUsingWebSocket 可以接受一个流,但给它一个AudioInputStream 的实例似乎并没有这样做,似乎连接已建立,但即使RecognizeOptions.interimResults(true) 也没有返回转录本。
public class RecognizeUsingWebSocketsExample {
private static CountDownLatch lock = new CountDownLatch(1);
public static void main(String[] args) throws FileNotFoundException, InterruptedException {
SpeechToText service = new SpeechToText();
service.setUsernameAndPassword("<username>", "<password>");
AudioInputStream audio = null;
try {
final AudioFormat format = new AudioFormat(16000, 16, 1, true, false);
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
TargetDataLine line;
line = (TargetDataLine)AudioSystem.getLine(info);
line.open(format);
line.start();
audio = new AudioInputStream(line);
} catch (LineUnavailableException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
RecognizeOptions options = new RecognizeOptions.Builder()
.continuous(true)
.interimResults(true)
.contentType(HttpMediaType.AUDIO_WAV)
.build();
service.recognizeUsingWebSocket(audio, options, new BaseRecognizeCallback() {
@Override
public void onTranscription(SpeechResults speechResults) {
System.out.println(speechResults);
if (speechResults.isFinal())
lock.countDown();
}
});
lock.await(1, TimeUnit.MINUTES);
}
}
任何帮助将不胜感激。
-rg
以下是基于德国人评论的更新(谢谢)。
我能够使用javaFlacEncode 将来自麦克风的 WAV 流转换为 FLAC 流并将其保存到临时文件中。与创建时固定大小的 WAV 音频文件不同,FLAC 文件可以轻松附加。
WAV_audioInputStream = new AudioInputStream(line);
FileInputStream FLAC_audioInputStream = new FileInputStream(tempFile);
StreamConfiguration streamConfiguration = new StreamConfiguration();
streamConfiguration.setSampleRate(16000);
streamConfiguration.setBitsPerSample(8);
streamConfiguration.setChannelCount(1);
flacEncoder = new FLACEncoder();
flacOutputStream = new FLACFileOutputStream(tempFile); // write to temp disk file
flacEncoder.setStreamConfiguration(streamConfiguration);
flacEncoder.setOutputStream(flacOutputStream);
flacEncoder.openFLACStream();
...
// convert data
int frameLength = 16000;
int[] intBuffer = new int[frameLength];
byte[] byteBuffer = new byte[frameLength];
while (true) {
int count = WAV_audioInputStream.read(byteBuffer, 0, frameLength);
for (int j1=0;j1<count;j1++)
intBuffer[j1] = byteBuffer[j1];
flacEncoder.addSamples(intBuffer, count);
flacEncoder.encodeSamples(count, false); // 'false' means non-final frame
}
flacEncoder.encodeSamples(flacEncoder.samplesAvailableToEncode(), true); // final frame
WAV_audioInputStream.close();
flacOutputStream.close();
FLAC_audioInputStream.close();
添加任意数量的帧后,可以毫无问题地分析生成的文件(使用curl 或recognizeUsingWebSocket())。但是,recognizeUsingWebSocket() 将在到达 FLAC 文件末尾时立即返回最终结果,即使文件的最后一帧可能不是最终帧(即在 encodeSamples(count, false) 之后)。
我希望recognizeUsingWebSocket() 阻塞直到最后一帧被写入文件。实际上,这意味着分析在第一帧之后停止,因为分析第一帧比收集第二帧花费的时间更少,因此返回结果时,就到达了文件末尾。
这是在 Java 中实现来自麦克风的流式音频的正确方法吗?似乎是一个常见的用例。
这是对RecognizeUsingWebSocketsExample 的修改,其中包含了 Daniel 在下面的一些建议。它使用 PCM 内容类型(作为 String 传递,连同帧大小),并尝试发出音频流结束的信号,尽管不是很成功。
和以前一样,建立了连接,但从未调用识别回调。关闭流似乎也不被解释为音频的结束。我一定是在这里误会了什么......
public static void main(String[] args) throws IOException, LineUnavailableException, InterruptedException {
final PipedOutputStream output = new PipedOutputStream();
final PipedInputStream input = new PipedInputStream(output);
final AudioFormat format = new AudioFormat(16000, 8, 1, true, false);
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
final TargetDataLine line = (TargetDataLine)AudioSystem.getLine(info);
line.open(format);
line.start();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
final int MAX_FRAMES = 2;
byte buffer[] = new byte[16000];
for(int j1=0;j1<MAX_FRAMES;j1++) { // read two frames from microphone
int count = line.read(buffer, 0, buffer.length);
System.out.println("Read audio frame from line: " + count);
output.write(buffer, 0, buffer.length);
System.out.println("Written audio frame to pipe: " + count);
}
/** no need to fake end-of-audio; StopMessage will be sent
* automatically by SDK once the pipe is drained (see WebSocketManager)
// signal end of audio; based on WebSocketUploader.stop() source
byte[] stopData = new byte[0];
output.write(stopData);
**/
} catch (IOException e) {
}
}
});
thread1.start();
final CountDownLatch lock = new CountDownLatch(1);
SpeechToText service = new SpeechToText();
service.setUsernameAndPassword("<username>", "<password>");
RecognizeOptions options = new RecognizeOptions.Builder()
.continuous(true)
.interimResults(false)
.contentType("audio/pcm; rate=16000")
.build();
service.recognizeUsingWebSocket(input, options, new BaseRecognizeCallback() {
@Override
public void onConnected() {
System.out.println("Connected.");
}
@Override
public void onTranscription(SpeechResults speechResults) {
System.out.println("Received results.");
System.out.println(speechResults);
if (speechResults.isFinal())
lock.countDown();
}
});
System.out.println("Waiting for STT callback ... ");
lock.await(5, TimeUnit.SECONDS);
line.stop();
System.out.println("Done waiting for STT callback.");
}
Dani,我检测了 WebSocketManager 的源代码(随 SDK 提供)并将对 sendMessage() 的调用替换为显式 StopMessage 有效负载,如下所示:
/**
* Send input steam.
*
* @param inputStream the input stream
* @throws IOException Signals that an I/O exception has occurred.
*/
private void sendInputSteam(InputStream inputStream) throws IOException {
int cumulative = 0;
byte[] buffer = new byte[FOUR_KB];
int read;
while ((read = inputStream.read(buffer)) > 0) {
cumulative += read;
if (read == FOUR_KB) {
socket.sendMessage(RequestBody.create(WebSocket.BINARY, buffer));
} else {
System.out.println("completed sending " + cumulative/16000 + " frames over socket");
socket.sendMessage(RequestBody.create(WebSocket.BINARY, Arrays.copyOfRange(buffer, 0, read))); // partial buffer write
System.out.println("signaling end of audio");
socket.sendMessage(RequestBody.create(WebSocket.TEXT, buildStopMessage().toString())); // end of audio signal
}
}
inputStream.close();
}
sendMessage() 选项(发送 0 长度的二进制内容或发送停止文本消息)似乎都不起作用。调用者代码与上面没有变化。结果输出是:
Waiting for STT callback ...
Connected.
Read audio frame from line: 16000
Written audio frame to pipe: 16000
Read audio frame from line: 16000
Written audio frame to pipe: 16000
completed sending 2 frames over socket
onFailure: java.net.SocketException: Software caused connection abort: socket write error
修订:实际上,永远不会到达音频结束呼叫。将最后一个(部分)缓冲区写入套接字时引发异常。
为什么会中止连接?这通常发生在对等端关闭连接时。
至于第 2 点):在现阶段,这些中的任何一个都重要吗?似乎根本没有开始识别过程......音频是有效的(我将流写入磁盘,并且能够通过从文件流式传输来识别它,正如我在上面指出的那样)。
另外,在进一步查看WebSocketManager 源代码时,onMessage() 已经从return 立即从sendInputSteam() 发送StopMessage(即,当音频流或上例中的管道时,排水管),因此无需显式调用它。该问题肯定在音频数据传输完成之前发生。无论PipedInputStream 或AudioInputStream 是否作为输入传递,行为都是相同的。两种情况下发送二进制数据时都会抛出异常。
【问题讨论】:
-
q) 您是否成功处理了 wav 文件?确定您可以移至麦克风 2) HttpMediaType.AUDIO_WAV 在那里看起来很可疑
-
1) 是的,流式传输 .wav 文件就可以了。 2)眼睛好,但没有雪茄。我已经尝试过 HttpMediaType 支持的所有 4 种 AUDIO 格式(FLAC、OGG、RAW、WAV),但它们的行为方式都相同——建立了连接,但没有返回任何转录本。
-
您不能使用 WAVE,因为如果您正在流式传输音频,您事先不知道大小。您需要从麦克风(通常是 WAVE)中获取字节数组并将其转换为 FLAC,然后将其发送到
RecognizeOptions。 -
德语,谢谢,有帮助。我能够创建一个 FLAC 音频文件并逐帧附加到它,音频来自麦克风。可以对生成的文件进行整体分析(例如,使用 curl 或 identifyUsingWebSocket())。但是,我无法从麦克风流式传输 - 例程一旦到达文件末尾就会返回最终结果,即使最后一帧尚未写入它(我希望如果最后一帧它应该阻塞不是最终的)。我会用详细信息更新问题。
-
对于来自麦克风的连续流音频的语音识别,特别是对于简短的陈述,似乎是一个更好的替代方案,它是使用基于会话的(有状态的)POST 作为多部分发送数据。来自麦克风的音频帧可以写成单独的文件(例如,每个文件一帧)并单独提交。我已经看到了一些对 Python 实现的引用和一些(不工作的)cURL 示例。 Java有什么东西吗?
标签: java speech-to-text ibm-watson