使用 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();

添加任意数量的帧后,可以毫无问题地分析生成的文件(使用curlrecognizeUsingWebSocket())。但是,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(即,当音频流或上例中的管道时,排水管),因此无需显式调用它。该问题肯定在音频数据传输完成之前发生。无论PipedInputStreamAudioInputStream 是否作为输入传递,行为都是相同的。两种情况下发送二进制数据时都会抛出异常。

【问题讨论】:

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=true 5.1.1 版本由于缺少方法和错误而无法编译。任何想法:) 谢谢,我在 Github IBM Watson Java SDK Repository 上推送了一个问题,并提供了最新版本的解决方案 :)

以上是关于使用 Java SDK 将音频从麦克风流式传输到 IBM Watson SpeechToText Web 服务的主要内容,如果未能解决你的问题,请参考以下文章

我可以使用 nodejs 将麦克风音频从客户端流式传输到客户端吗?

将实时 Android 音频流式传输到服务器

将音频从 Mac 上的麦克风流式传输到 iPhone

如何通过套接字或框架将音频从 iPhone 的麦克风流式传输到 Mac/PC?

将麦克风从客户端浏览器流式传输到远程服务器,并将音频实时传递到 ffmpeg 以与第二个视频源结合

用于从 iphone 麦克风流式传输音频的多点连接