当前位置: 首页 > 知识库问答 >
问题:

谷歌云语音到文本在某些IDEVICE上无法正确转录流式音频

越望
2023-03-14

过去几周,我使用实时流音频实现了谷歌云语音到文本API。虽然一开始一切看起来都很好,但最近我在更多的设备上测试了该产品,发现在某些想法上存在一些奇怪的不规则之处。首先,以下是相关代码:

前端(反应组件)

constructor(props) {
  super(props);
  this.audio = props.audio;
  this.socket = new SocketClient();
  this.bufferSize = 2048;
}

/**
* Initializes the users microphone and the audio stream.
*
* @return {void}
*/
startAudioStream = async () => {
  const AudioContext = window.AudioContext || window.webkitAudioContext;
  this.audioCtx = new AudioContext();
  this.processor = this.audioCtx.createScriptProcessor(this.bufferSize, 1, 1);
  this.processor.connect(this.audioCtx.destination);
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  /* Debug through instant playback:
  this.audio.srcObject = stream;
  this.audio.play();
  return; */

  this.globalStream = stream;
  this.audioCtx.resume();
  this.input = this.audioCtx.createMediaStreamSource(stream);
  this.input.connect(this.processor);

  this.processor.onaudioprocess = (e) => {
    this.microphoneProcess(e);
  };
  this.setState({ streaming: true });
}

/**
 * Processes microphone input and passes it to the server via the open socket connection.
 *
 * @param {AudioProcessingEvent} e
 * @return {void}
 */
microphoneProcess = (e) => {
  const { speaking, askingForConfirmation, askingForErrorConfirmation } = this.state;
  const left = e.inputBuffer.getChannelData(0);
  const left16 = Helpers.downsampleBuffer(left, 44100, 16000);
  if (speaking === false) {
    this.socket.emit('stream', {
      audio: left16,
      context: askingForConfirmation || askingForErrorConfirmation ? 'zip_code_yes_no' : 'zip_code',
      speechContext: askingForConfirmation || askingForErrorConfirmation ? ['ja', 'nein', 'ne', 'nö', 'falsch', 'neu', 'korrektur', 'korrigieren', 'stopp', 'halt', 'neu'] : ['$OPERAND'],
    });
  }
}

助手(下采样缓冲器)

/**
 * Downsamples a given audio buffer from sampleRate to outSampleRate.
 * @param {Array} buffer The audio buffer to downsample.
 * @param {number} sampleRate The original sample rate.
 * @param {number} outSampleRate The new sample rate.
 * @return {Array} The downsampled audio buffer.
 */
static downsampleBuffer(buffer, sampleRate, outSampleRate) {
  if (outSampleRate === sampleRate) {
    return buffer;
  }
  if (outSampleRate > sampleRate) {
    throw new Error('Downsampling rate show be smaller than original sample rate');
  }
  const sampleRateRatio = sampleRate / outSampleRate;
  const newLength = Math.round(buffer.length / sampleRateRatio);
  const result = new Int16Array(newLength);
  let offsetResult = 0;
  let offsetBuffer = 0;
  while (offsetResult < result.length) {
    const nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);
    let accum = 0;
    let count = 0;
    for (let i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) {
      accum += buffer[i];
      count++;
    }

    result[offsetResult] = Math.min(1, accum / count) * 0x7FFF;
    offsetResult++;
    offsetBuffer = nextOffsetBuffer;
  }
  return result.buffer;
}

后端(套接字服务器)

io.on('connection', (socket) => {
  logger.debug('New client connected');
  const speechClient = new SpeechService(socket);

  socket.on('stream', (data) => {
    const audioData = data.audio;
    const context = data.context;
    const speechContext = data.speechContext;
    speechClient.transcribe(audioData, context, speechContext);
  });
});

后端(语音客户端/转录功能,将数据发送到GCloud)

async transcribe(data, context, speechContext, isFile = false) {
  if (!this.recognizeStream) {
    logger.debug('Initiating new Google Cloud Speech client...');
    let waitingForMoreData = false;
    // Create new stream to the Google Speech client
    this.recognizeStream = this.speechClient
      .streamingRecognize({
        config: {
          encoding: 'LINEAR16',
          sampleRateHertz: 16000,
          languageCode: 'de-DE',
          speechContexts: speechContext ? [{ phrases: speechContext }] : undefined,
        },
        interimResults: false,
        singleUtterance: true,
      })
      .on('error', (error) => {
        if (error.code === 11) {
          this.recognizeStream.destroy();
          this.recognizeStream = null;
          return;
        }
        this.socket.emit('error');
        this.recognizeStream.destroy();
        this.recognizeStream = null;
        logger.error(`Received error from Google Cloud Speech client: ${error.message}`);
      })
      .on('data', async (gdata) => {
        if ((!gdata.results || !gdata.results[0]) && gdata.speechEventType === 'END_OF_SINGLE_UTTERANCE') {
          logger.debug('Received END_OF_SINGLE_UTTERANCE - waiting 300ms for more data before restarting stream');
          waitingForMoreData = true;
          setTimeout(() => {
            if (waitingForMoreData === true) {
              // User was silent for too long - restart stream
              this.recognizeStream.destroy();
              this.recognizeStream = null;
            }
          }, 300);
          return;
        }
        waitingForMoreData = false;
        const transcription = gdata.results[0].alternatives[0].transcript;
        logger.debug(`Transcription: ${transcription}`);

        // Emit transcription and MP3 file of answer
        this.socket.emit('transcription', transcription);
        const filename = await ttsClient.getAnswerFromTranscription(transcription, 'fairy', context); // TODO-Final: Dynamic character
        if (filename !== null) this.socket.emit('speech', `${config.publicScheme}://${config.publicHost}:${config.publicPort}/${filename}`);

        // Restart stream
        if (this.recognizeStream) this.recognizeStream.destroy();
        this.recognizeStream = null;
      });
  }
  // eslint-disable-next-line security/detect-non-literal-fs-filename
  if (isFile === true) fs.createReadStream(data).pipe(this.recognizeStream);
  else this.recognizeStream.write(data);
}

现在,在我测试的设备中,行为差异很大。我最初是在iMac 2017上使用谷歌Chrome作为浏览器开发的。工作起来很有魅力。然后,在iPhone11专业版和iPad Air 4上进行了测试,无论是Safari还是全屏网络应用。同样,工作起来很有魅力。

后来,我尝试了iPad Pro 12.9”2017。突然,谷歌云有时根本不返回转录,有时它会返回一些只使用非常幻想的东西,听起来像实际口语文本的东西。iPad 5和iPhone6 Plus上的相同行为。

我真的不知道接下来该怎么做。至少到目前为止,我读到的是,iPhone6s(不幸的是,我对iPad一无所知)的硬件采样率从44.1khz更改为48khz。所以我想,可能就是这样了,在代码中到处都是采样率,没有成功。此外,我注意到我的带有谷歌Chrome的iMac也在44.1khz上运行,就像转录不起作用的“旧”iPad一样。同样,新的iPad在48khz上运行——这里一切正常。所以这不可能。

我还注意到:当我将一些Airpod连接到“坏”的设备并将其用作音频输入时,一切都会恢复正常。因此,这一定与这些设备的内部麦克风的处理有关。我只是不知道到底是什么。

谁能把我引向正确的方向?在音频和麦克风方面,这几代设备之间发生了哪些变化?

更新1:我现在已经实现了一个快速功能,可以使用node-wav将流PCM数据从前端写入后端的文件。我想,我现在越来越近了——在语音识别变得疯狂的设备上,我听起来像一只花栗鼠(音调极高)。我还注意到二进制音频数据的流动速度比一切正常的设备慢得多。所以这可能与样本/码率、编码或其他有关。不幸的是,我不是音频专家,所以不知道下一步该怎么办。

更新2:在经历了大量的试验结束错误之后,我发现如果我在Google Cloud中将采样率设置为大约9500到10000,那么一切都会正常进行。当我将此设置为节点wav文件输出的采样率时,听起来也不错。如果我再次将“传出”采样率重置为GCloud至16000,并将音频输入从前端的44100降低至25000左右,而不是16000左右(请参阅“microphoneProcess”功能中的“frontend(React Component)”),它也会起作用。因此,样本率差异中似乎存在某种约0.6的因素。然而,我仍然不知道这种行为是从哪里来的:iMac上的Chrome和iPad上的Safari都有音频上下文。44100的采样器。因此,当我在代码中将它们的样本减少到16000时,我认为两者都应该工作,而只有iMac工作。iPad内部的采样率似乎有所不同?

共有1个答案

云镜
2023-03-14

经过大量的尝试和错误,我找到了问题(和解决方案)。似乎“较旧”的iDevice机型(如2017款iPad Pro)有一些奇怪的特性,即自动将麦克风采样率调整为播放音频的速率。尽管这些设备的硬件采样率设置为44.1khz,但只要播放一些音频,采样率就会改变。这可以通过以下方式观察到:

const audioCtx = new webkitAudioContext();
console.log(`Current sample rate: ${audioCtx.sampleRate}`); // 44100
const audio = new Audio();
audio.src = 'some_audio.mp3';
await audio.play();
console.log(`Current sample rate: ${audioCtx.sampleRate}`); // Sample rate of the played audio

在我的例子中,在打开语音转录套接字之前,我播放了一些从谷歌文本到语音的合成语音。这些声音文件的采样率为24khz,正是Google Cloud接收到我的音频输入的采样率。

因此,解决方案是——无论如何我都应该这样做——将所有内容的采样率降低到16khz(参见问题中的助手函数),但不是从硬编码的44.1khz,而是从音频上下文的当前采样率。因此,我将microhoneprocess()函数更改如下:

const left = e.inputBuffer.getChannelData(0);
const left16 = Helpers.downsampleBuffer(left, this.audioCtx.sampleRate, 16000);

结论:不要相信Safari的页面加载采样率。它可能会改变。

 类似资料:
  • 我正在使用谷歌云语音到文本AP,并试图转录长音频文件。但是,无法检测到桶中的音频文件。我得到一个错误,说明:IOError:[Errno 2]没有这样的文件或目录: transcribe_gcs(gcs_uri):

  • 我正在使用spyder IDE进行我的语音到文本转换项目,该项目使用谷歌语音到文本api。我在代码中集成了谷歌语音和文本,出现了类似“配额耗尽”的错误。在谷歌控制台的仪表板上,我可以看到可用的配额。 > 错误 文件"C:\Program Data\Anaconda3\lib\site-包\speech_recognition__init__. py",第932行,在recognize_google

  • 我在尝试使用谷歌云语音库时出错。 这可能是关于我的依赖项。在添加Firebase身份验证依赖项之前,库是无缝工作的。所以它可能是关于endpoint-管理-控制-应用引擎-所有依赖项。 Stackdriver日志: 这是类的唯一方法: 项目的依赖关系:

  • 我想用nodejs和googlespeech-to-text api制作一个实时转录应用程序。 我正在使用RecordRTC和socket。io将音频块获取到后端服务器。目前,我正在录制1秒长的块,并且转录工作正常,但它没有将其视为流,而是在处理每个块后发送响应。这意味着我要收回半句话,谷歌无法利用上下文帮助自己识别演讲。 我的问题是,如何让谷歌将我的块视为一个连续的流。或者是否有其他解决方案可以

  • Android谷歌语音转文本SDK,语音录制由SDK控制。我需要将其设置为手动按钮,用于启动和停止语音录制,以便将语音转换为文本。例如:当单击按钮开始语音识别时,它会继续录制音频,直到单击停止按钮。但在android SDK中,它会自动停止录制并将录制的音频传递给处理。

  • 我正在尝试从扬声器转录音频 我正在将声音从扬声器传送到节点。js文件(https://askubuntu.com/a/850174) 这是我的抄本。js公司 但谷歌云语音到文本在1分钟内对流媒体识别有一个限制。所以我有一个错误“超过了允许的最大流持续时间65秒” 如何将流拆分为以静默为拆分器的块,或拆分为持续30秒的块?