使用 Java SDK 将音频从麦克风流式传输到 IBM Watson SpeechToText Web 服务
Posted
技术标签:
【中文标题】使用 Java SDK 将音频从麦克风流式传输到 IBM Watson SpeechToText Web 服务【英文标题】:Stream audio from mic to IBM Watson SpeechToText Web service using Java SDK 【发布时间】: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有什么东西吗?
【参考方案1】:
您需要做的是将音频提供给 STT 服务,而不是作为文件,而是作为无标题的音频样本流。您只需通过 WebSocket 提供从麦克风捕获的样本。您需要将内容类型设置为“audio/pcm;rate=16000”,其中 16000 是以 Hz 为单位的采样率。如果您的采样率不同,这取决于麦克风对音频的编码方式,您将用您的值替换 16000,例如:44100、48000 等。
当提供 pcm 音频时,STT 服务不会停止识别,直到您通过 websocket 发送一个空的二进制消息来发出音频结束的信号。
丹妮
查看新版本的代码,我发现了一些问题:
1) 可以通过 websocket 发送一个空的二进制消息来完成音频的信号结束,这不是你正在做的。线条
// signal end of audio; based on WebSocketUploader.stop() source
byte[] stopData = new byte[0];
output.write(stopData);
没有做任何事情,因为它们不会导致发送空的 websocket 消息。您能否改为调用方法“WebSocketUploader.stop()”?
-
您正在以每个样本 8 位捕获音频,您应该使用 16 位以获得足够的质量。此外,您只提供几秒钟的音频,不适合测试。您能否将推送到 STT 的任何音频写入文件,然后使用 Audacity(使用导入功能)打开它?通过这种方式,您可以确保向 STT 提供的内容是良好的音频。
【讨论】:
感谢您的帮助。您的意思是:在上面的第一个示例中将.contentType(HttpMediaType.AUDIO_WAV)
替换为.contentType("audio/pcm; rate=16000")
?似乎是一个有效的内容类型,但我得到了与以前相同的行为:连接只是挂在那里,没有返回任何结果。我确实确认 audio
是一个有效的流(我可以将它捕获在一个文件中,一次一帧,然后毫无问题地传递给 STT 服务)。采样率为 16000 Hz,8 位,单通道。你能发布一个样本(甚至将它添加到发行版中)吗?似乎是一个非常常见的用例......
发送完所有块后,您需要发送一个空的二进制消息,表示音频结束,请在 python 或 ruby 中查看此示例github.com/watson-developer-cloud/…github.com/watson-developer-cloud/…Dani跨度>
丹妮,也许我误会了你。我正在使用“SpeechToText”服务——分发示例中包含的 WebSockets 包装器。由于“客户端”成员是该类的私有成员,因此我无法直接向套接字写入任何内容,并且将空缓冲区写入输入流似乎不会影响结果(如预期的那样)。您是说我不应该依赖“SpeechToText”服务,而是应该按照您所指的 Python 示例使用 WebSockets 从头开始构建它吗?如果是这样,Java 中是否有任何代码示例?再次感谢您的帮助。
另外说明,WebSocketManager(它实现了 STT 服务)似乎正在发送一个文本 STOP 消息,而不是一个零长度的二进制消息,并且是在 buildStopMessage() 中构建的,不幸的是,私人的。
你好罗伯特,发送停止消息也是有效的,相当于发送空二进制消息,所以你可以使用它。【参考方案2】:
Java SDK 有一个示例并支持该示例。
更新您的pom.xml
:
<dependency>
<groupId>com.ibm.watson.developer_cloud</groupId>
<artifactId>java-sdk</artifactId>
<version>3.3.1</version>
</dependency>
以下是如何收听麦克风的示例。
SpeechToText service = new SpeechToText();
service.setUsernameAndPassword("<username>", "<password>");
// Signed PCM AudioFormat with 16kHz, 16 bit sample size, mono
int sampleRate = 16000;
AudioFormat format = new AudioFormat(sampleRate, 16, 1, true, false);
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
if (!AudioSystem.isLineSupported(info))
System.out.println("Line not supported");
System.exit(0);
TargetDataLine line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format);
line.start();
AudioInputStream audio = new AudioInputStream(line);
RecognizeOptions options = new RecognizeOptions.Builder()
.continuous(true)
.interimResults(true)
.timestamps(true)
.wordConfidence(true)
//.inactivityTimeout(5) // use this to stop listening when the speaker pauses, i.e. for 5s
.contentType(HttpMediaType.AUDIO_RAW + "; rate=" + sampleRate)
.build();
service.recognizeUsingWebSocket(audio, options, new BaseRecognizeCallback()
@Override
public void onTranscription(SpeechResults speechResults)
System.out.println(speechResults);
);
System.out.println("Listening to your voice for the next 30s...");
Thread.sleep(30 * 1000);
// closing the WebSockets underlying InputStream will close the WebSocket itself.
line.stop();
line.close();
System.out.println("Fin.");
【讨论】:
@RobertGrzeszczuk:如果这个答案对您有帮助,请接受。它有助于其他人快速找到正确答案,而无需阅读每条信息。 此示例生成第一个“最终转录”,但之后,它只是继续生成“临时转录”,并且不再将最终标志设置为 true。如何使用连续的 websocket 流处理多个转录? 每个句子都会有一个final=true5.1.1
版本由于缺少方法和错误而无法编译。任何想法:)
谢谢,我在 Github IBM Watson Java SDK Repository 上推送了一个问题,并提供了最新版本的解决方案 :)以上是关于使用 Java SDK 将音频从麦克风流式传输到 IBM Watson SpeechToText Web 服务的主要内容,如果未能解决你的问题,请参考以下文章
我可以使用 nodejs 将麦克风音频从客户端流式传输到客户端吗?
如何通过套接字或框架将音频从 iPhone 的麦克风流式传输到 Mac/PC?