【问题标题】:How to end Google Speech-to-Text streamingRecognize gracefully and get back the pending text results?如何优雅地结束 Google Speech-to-Text streamingRecognize 并取回待处理的文本结果?
【发布时间】:2021-02-10 09:17:52
【问题描述】:

我希望能够结束 Google 语音到文本流 (created with streamingRecognize),并取回待处理的 SR(语音识别)结果。

简而言之,相关的 Node.js 代码:

// create SR stream
const stream = speechClient.streamingRecognize(request);

// observe data event
const dataPromise = new Promise(resolve => stream.on('data', resolve));

// observe error event
const errorPromise = new Promise((resolve, reject) => stream.on('error', reject));

// observe finish event
const finishPromise = new Promise(resolve => stream.on('finish', resolve));

// send the audio
stream.write(audioChunk);

// for testing purposes only, give the SR stream 2 seconds to absorb the audio
await new Promise(resolve => setTimeout(resolve, 2000));

// end the SR stream gracefully, by observing the completion callback
const endPromise = util.promisify(callback => stream.end(callback))();

// a 5 seconds test timeout
const timeoutPromise = new Promise(resolve => setTimeout(resolve, 5000)); 

// finishPromise wins the race here
await Promise.race([
  dataPromise, errorPromise, finishPromise, endPromise, timeoutPromise]);

// endPromise wins the race here
await Promise.race([
  dataPromise, errorPromise, endPromise, timeoutPromise]);

// timeoutPromise wins the race here
await Promise.race([dataPromise, errorPromise, timeoutPromise]);

// I don't see any data or error events, dataPromise and errorPromise don't get settled

我的经验是 SR 流成功结束,但我没有收到任何数据事件或错误事件。 dataPromiseerrorPromise 都没有得到解决或拒绝。

如何发出音频结束信号、关闭 SR 流并仍然获得待处理的 SR 结果?

我需要坚持使用streamingRecognize API,因为我正在流式传输的音频是实时的,即使它可能会突然停止。

澄清一下,只要我继续流式传输音频,它就可以工作,我确实会收到实时 SR 结果。但是,当我发送最终的音频块并像上面那样结束流时,我没有得到我期望的最终结果。

要获得最终结果,我实际上必须再保持流静默几秒钟,这可能会增加 ST 费用。我觉得必须有更好的方法来获取它们。

更新: 看来,结束streamingRecognize 流的唯一适当时间是在data 事件时StreamingRecognitionResult.is_finaltrue。同样,我们似乎应该在data 事件被触发之前保持流式传输音频,以获得任何结果,最终的或临时的。

这对我来说似乎是一个错误,提交issue

更新:现在似乎已经确认as a bug。在修复之前,我正在寻找一种潜在的解决方法。

更新:供将来参考,here is the list 当前和以前跟踪的涉及streamingRecognize 的问题。

我希望这对于使用streamingRecognize 的人来说是一个常见问题,但很惊讶以前没有报告过。也将as a bug 提交给issuetracker.google.com

【问题讨论】:

    标签: javascript node.js async-await speech-recognition google-cloud-speech


    【解决方案1】:

    我的错 — 不出所料,这在我的代码中变成了一个模糊的竞争条件。

    我已经整理了一个按预期工作的独立示例 (gist)。它帮助我追踪问题。希望它可以帮助其他人和我未来的自己:

    // A simple streamingRecognize workflow,
    // tested with Node v15.0.1, by @noseratio
    
    import fs from 'fs';
    import path from "path";
    import url from 'url'; 
    import util from "util";
    import timers from 'timers/promises';
    import speech from '@google-cloud/speech';
    
    export {}
    
    // need a 16-bit, 16KHz raw PCM audio 
    const filename = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "sample.raw");
    const encoding = 'LINEAR16';
    const sampleRateHertz = 16000;
    const languageCode = 'en-US';
    
    const request = {
      config: {
        encoding: encoding,
        sampleRateHertz: sampleRateHertz,
        languageCode: languageCode,
      },
      interimResults: false // If you want interim results, set this to true
    };
    
    // init SpeechClient
    const client = new speech.v1p1beta1.SpeechClient();
    await client.initialize();
    
    // Stream the audio to the Google Cloud Speech API
    const stream = client.streamingRecognize(request);
    
    // log all data
    stream.on('data', data => {
      const result = data.results[0];
      console.log(`SR results, final: ${result.isFinal}, text: ${result.alternatives[0].transcript}`);
    });
    
    // log all errors
    stream.on('error', error => {
      console.warn(`SR error: ${error.message}`);
    });
    
    // observe data event
    const dataPromise = new Promise(resolve => stream.once('data', resolve));
    
    // observe error event
    const errorPromise = new Promise((resolve, reject) => stream.once('error', reject));
    
    // observe finish event
    const finishPromise = new Promise(resolve => stream.once('finish', resolve));
    
    // observe close event
    const closePromise = new Promise(resolve => stream.once('close', resolve));
    
    // we could just pipe it: 
    // fs.createReadStream(filename).pipe(stream);
    // but we want to simulate the web socket data
    
    // read RAW audio as Buffer
    const data = await fs.promises.readFile(filename, null);
    
    // simulate multiple audio chunks
    console.log("Writting...");
    const chunkSize = 4096;
    for (let i = 0; i < data.length; i += chunkSize) {
      stream.write(data.slice(i, i + chunkSize));
      await timers.setTimeout(50);
    }
    console.log("Done writing.");
    
    console.log("Before ending...");
    await util.promisify(c => stream.end(c))();
    console.log("After ending.");
    
    // race for events
    await Promise.race([
      errorPromise.catch(() => console.log("error")), 
      dataPromise.then(() => console.log("data")),
      closePromise.then(() => console.log("close")),
      finishPromise.then(() => console.log("finish"))
    ]);
    
    console.log("Destroying...");
    stream.destroy();
    console.log("Final timeout...");
    await timers.setTimeout(1000);
    console.log("Exiting.");
    

    输出:

    写... 写完了。 结束前... SR 结果,final:true,文本:这是一个测试我正在测试语音识别 This Is End 结束后。 数据 结束 破坏... 最后超时... 关闭 退出。

    要对其进行测试,需要一个 16 位/16KHz 原始 PCM 音频文件。任意 WAV 文件无法按原样工作,因为它包含带有元数据的标头。

    【讨论】:

    • 仅供参考:WAV 文件可能会工作,只要它是单声道、16 位、16kHz - 标题最终只是短暂的白噪声,任何将其解释为原始音频的东西.. . 原始文件当然要好得多,但您需要了解如何使用音频编辑器来制作。
    【解决方案2】:

    既然是bug,我不知道这是否适合你,但我用过this.recognizeStream.end();在不同的情况下多次,它的工作。但是,我的代码有点不同...

    此提要可能适合您: https://groups.google.com/g/cloud-speech-discuss/c/lPaTGmEcZQk/m/Kl4fbHK2BQAJ

    【讨论】:

    • 在你的链接之后,this is 几乎就是我在问题代码中所做的,也使用了v1beta1。可悲的是,我没有收到任何 data 事件,只有 finish。我将创建一个最小可重复的独立应用程序。
    • 是的,你是对的。这确实很烦人。可悲的是,我对 node.js 不是很熟悉,但我找到了一个有 Java 等价物的网站,所以我也理解它......cloud.google.com/translate/media/docs/… 看看处理 END_OF_SINGLE_UTTERANCE 的部分。这可能会有所帮助,但 node.js 对我来说并不是很清楚。
    • 最终,您的回答帮助我找到了代码中的错误。我会让它再挂一段时间,但赏金是你的,谢谢!
    • 我很高兴能帮上忙……不过是个棘手的问题 :)
    【解决方案3】:

    这:“我正在寻找一种潜在的解决方法。” - 您是否考虑过将 SpeechClient 作为基类进行扩展?我没有要测试的凭据,但是您可以使用自己的类从 SpeechClient 扩展,然后根据需要调用内部 close() 方法。 close() 方法关闭 SpeechClient 并解析未完成的 Promise。

    或者,您也可以Proxy SpeechClient() 并根据需要拦截/响应。但是由于您的意图是关闭它,因此以下选项可能是您的解决方法。

    const speech = require('@google-cloud/speech');
    
    class ClientProxy extends speech.SpeechClient {
      constructor() {
        super();
      }
      myCustomFunction() {
        this.close();
      }
    }
    
    const clientProxy = new ClientProxy();
    try {
      clientProxy.myCustomFunction();
    } catch (err) {
      console.log("myCustomFunction generated error: ", err);
    }
    

    【讨论】:

    • 目前我只在完成整个会话后在SpeechClient 上调用close。每次我感觉到声音时创建/关闭SpeechClient 的实例可能有点贵(在资源方面)。但是让我看看它是否解决了最初的问题,即如果我调用SpeechClient.close(),我是否会在流中获得最终的data 事件...
    • 对,那你可能需要代理streamRecognize
    • 对,那么您可能需要代理 streamRecognize - 我已经尝试过,但无济于事,包括直接使用 SpeechClient._streamRecognize(一个薄 gRPC 层 API,@ 987654332@ 本身包装)。我很惊讶以前没有人遇到过这种情况。
    • 它不适合你,你也可以通过相同的层次结构访问_streamRecognizedestroy() 方法。自定义函数应该可以调用this.streamRecognize.destroy()你试过了吗?
    • 我刚刚完成了验证:不,它没有。只要我打电话给SpeechClient.close(),流就会终止,并出现错误“UNAVAILABLE”,代码:14。这是意料之中的,但无论如何都值得缩短 - 谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多