gpt4 book ai didi

javascript - 如何通过 socket.io 将实时音频从浏览器流式传输到 Google Cloud Speech?

转载 作者:行者123 更新时间:2023-12-01 08:22:33 27 4
gpt4 key购买 nike

我有一个基于 React 的应用程序的情况,我有一个输入,我也想允许语音输入。我可以让它只与 Chrome 和 Firefox 兼容,所以我正在考虑使用 getUserMedia .我知道我将使用 Google Cloud 的 Speech to Text API。但是,我有一些警告:

  • 我希望它能够实时流式传输我的音频数据,而不仅仅是在我完成录制时。这意味着我发现的许多解决方案都不能很好地工作,因为仅保存文件然后将其发送到 Google Cloud Speech 是不够的。
  • 我不相信我的前端有我的 Google Cloud API 信息。相反,我已经在具有我的凭据的后端运行了一项服务,并且我想将音频(实时)流式传输到该后端,然后从该后端流到 Google Cloud,然后将更新发送到我的成绩单他们回到前端。
  • 我已经使用 socket.io 连接到该后端服务,并且我想完全通过套接字来管理它,而不必使用 Binary.js 或类似的东西。

  • 似乎没有一个很好的教程来说明如何做到这一点。我该怎么办?

    最佳答案

    首先,信用到期:我的大量解决方案是通过引用 vin-ni 的 Google-Cloud-Speech-Node-Socket-Playground project 创建的。 .然而,我不得不为我的 React 应用程序调整它,所以我分享了我所做的一些更改。

    我这里的解决方案由四个部分组成,两个在前端,两个在后端。

    我的前端解决方案分为两部分:

  • 用于访问我的麦克风的实用程序文件,将音频流式传输到后面
    end,从后端取回数据,分别运行一个回调函数
    从后端接收数据的时间,然后清理
    在完成流式传输或后端抛出时在其自身之后
    一个错误。
  • 一个包裹我的 React 的麦克风组件
    功能。

  • 我的后端解决方案分为两部分:
  • 处理实际语音识别流的实用程序文件
  • 我的 main.js文件

  • (这些不需要以任何方式分开;我们的 main.js 文件没有它就已经是一个庞然大物了。)

    我的大部分代码都将被摘录,但我的实用程序将完整显示,因为我在涉及的所有阶段都遇到了很多问题。我的前端实用程序文件如下所示:
    // Stream Audio
    let bufferSize = 2048,
    AudioContext,
    context,
    processor,
    input,
    globalStream;

    //audioStream constraints
    const constraints = {
    audio: true,
    video: false
    };

    let AudioStreamer = {
    /**
    * @param {function} onData Callback to run on data each time it's received
    * @param {function} onError Callback to run on an error if one is emitted.
    */
    initRecording: function(onData, onError) {
    socket.emit('startGoogleCloudStream', {
    config: {
    encoding: 'LINEAR16',
    sampleRateHertz: 16000,
    languageCode: 'en-US',
    profanityFilter: false,
    enableWordTimeOffsets: true
    },
    interimResults: true // If you want interim results, set this to true
    }); //init socket Google Speech Connection
    AudioContext = window.AudioContext || window.webkitAudioContext;
    context = new AudioContext();
    processor = context.createScriptProcessor(bufferSize, 1, 1);
    processor.connect(context.destination);
    context.resume();

    var handleSuccess = function (stream) {
    globalStream = stream;
    input = context.createMediaStreamSource(stream);
    input.connect(processor);

    processor.onaudioprocess = function (e) {
    microphoneProcess(e);
    };
    };

    navigator.mediaDevices.getUserMedia(constraints)
    .then(handleSuccess);

    // Bind the data handler callback
    if(onData) {
    socket.on('speechData', (data) => {
    onData(data);
    });
    }

    socket.on('googleCloudStreamError', (error) => {
    if(onError) {
    onError('error');
    }
    // We don't want to emit another end stream event
    closeAll();
    });
    },

    stopRecording: function() {
    socket.emit('endGoogleCloudStream', '');
    closeAll();
    }
    }

    export default AudioStreamer;

    // Helper functions
    /**
    * Processes microphone data into a data stream
    *
    * @param {object} e Input from the microphone
    */
    function microphoneProcess(e) {
    var left = e.inputBuffer.getChannelData(0);
    var left16 = convertFloat32ToInt16(left);
    socket.emit('binaryAudioData', left16);
    }

    /**
    * Converts a buffer from float32 to int16. Necessary for streaming.
    * sampleRateHertz of 1600.
    *
    * @param {object} buffer Buffer being converted
    */
    function convertFloat32ToInt16(buffer) {
    let l = buffer.length;
    let buf = new Int16Array(l / 3);

    while (l--) {
    if (l % 3 === 0) {
    buf[l / 3] = buffer[l] * 0xFFFF;
    }
    }
    return buf.buffer
    }

    /**
    * Stops recording and closes everything down. Runs on error or on stop.
    */
    function closeAll() {
    // Clear the listeners (prevents issue if opening and closing repeatedly)
    socket.off('speechData');
    socket.off('googleCloudStreamError');
    let tracks = globalStream ? globalStream.getTracks() : null;
    let track = tracks ? tracks[0] : null;
    if(track) {
    track.stop();
    }

    if(processor) {
    if(input) {
    try {
    input.disconnect(processor);
    } catch(error) {
    console.warn('Attempt to disconnect input failed.')
    }
    }
    processor.disconnect(context.destination);
    }
    if(context) {
    context.close().then(function () {
    input = null;
    processor = null;
    context = null;
    AudioContext = null;
    });
    }
    }

    这段代码的主要亮点(除了 getUserMedia 配置,它本身有点冒险)是 onaudioprocess发出的处理器回调 speechData将数据转换为 Int16 后将事件与数据一起发送到套接字。我在上面的链接引用中的主要更改是替换所有功能以使用回调函数(由我的 React 组件使用)实际更新 DOM,并添加一些源代码中未包含的错误处理。

    然后,我只需使用以下命令就可以在我的 React 组件中访问它:
    onStart() {
    this.setState({
    recording: true
    });
    if(this.props.onStart) {
    this.props.onStart();
    }
    speechToTextUtils.initRecording((data) => {
    if(this.props.onUpdate) {
    this.props.onUpdate(data);
    }
    }, (error) => {
    console.error('Error when recording', error);
    this.setState({recording: false});
    // No further action needed, as this already closes itself on error
    });
    }

    onStop() {
    this.setState({recording: false});
    speechToTextUtils.stopRecording();
    if(this.props.onStop) {
    this.props.onStop();
    }
    }

    (我将我的实际数据处理程序作为 Prop 传递给该组件)。

    然后在后端,我的服务处理了 main.js 中的三个主要事件:
    // Start the stream
    socket.on('startGoogleCloudStream', function(request) {
    speechToTextUtils.startRecognitionStream(socket, GCSServiceAccount, request);
    });
    // Receive audio data
    socket.on('binaryAudioData', function(data) {
    speechToTextUtils.receiveData(data);
    });

    // End the audio stream
    socket.on('endGoogleCloudStream', function() {
    speechToTextUtils.stopRecognitionStream();
    });

    我的 speechToTextUtils 然后看起来像:
    // Google Cloud
    const speech = require('@google-cloud/speech');
    let speechClient = null;

    let recognizeStream = null;

    module.exports = {
    /**
    * @param {object} client A socket client on which to emit events
    * @param {object} GCSServiceAccount The credentials for our google cloud API access
    * @param {object} request A request object of the form expected by streamingRecognize. Variable keys and setup.
    */
    startRecognitionStream: function (client, GCSServiceAccount, request) {
    if(!speechClient) {
    speechClient = new speech.SpeechClient({
    projectId: 'Insert your project ID here',
    credentials: GCSServiceAccount
    }); // Creates a client
    }
    recognizeStream = speechClient.streamingRecognize(request)
    .on('error', (err) => {
    console.error('Error when processing audio: ' + (err && err.code ? 'Code: ' + err.code + ' ' : '') + (err && err.details ? err.details : ''));
    client.emit('googleCloudStreamError', err);
    this.stopRecognitionStream();
    })
    .on('data', (data) => {
    client.emit('speechData', data);

    // if end of utterance, let's restart stream
    // this is a small hack. After 65 seconds of silence, the stream will still throw an error for speech length limit
    if (data.results[0] && data.results[0].isFinal) {
    this.stopRecognitionStream();
    this.startRecognitionStream(client, GCSServiceAccount, request);
    // console.log('restarted stream serverside');
    }
    });
    },
    /**
    * Closes the recognize stream and wipes it
    */
    stopRecognitionStream: function () {
    if (recognizeStream) {
    recognizeStream.end();
    }
    recognizeStream = null;
    },
    /**
    * Receives streaming data and writes it to the recognizeStream for transcription
    *
    * @param {Buffer} data A section of audio data
    */
    receiveData: function (data) {
    if (recognizeStream) {
    recognizeStream.write(data);
    }
    }
    };

    (同样,您并不严格需要此 util 文件,您当然可以将 speechClient 作为 const 放在文件顶部,具体取决于您获取凭据的方式;这正是我实现它的方式。)

    最后,这应该足以让你开始做这件事。我鼓励您在重用或修改它之前尽最大努力理解此代码,因为它可能无法为您“开箱即用”,但与我发现的所有其他来源不同,这至少应该让您开始涉及项目的各个阶段。我希望这个答案能防止其他人像我一样遭受痛苦。

    关于javascript - 如何通过 socket.io 将实时音频从浏览器流式传输到 Google Cloud Speech?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50976084/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com