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

如何通过socket将实时音频从浏览器传输到Google云语音。伊奥?

沈飞跃
2023-03-14

我有一个基于React的应用程序,我有一个输入,我想允许语音输入。我可以让它只与Chrome和Firefox兼容,所以我考虑使用getUserMedia。我知道我将使用谷歌云的语音到文本API。然而,我有几个注意事项:

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

似乎没有任何地方有关于如何做到这一点的好教程。我该怎么办?

共有1个答案

狄珂
2023-03-14

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

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

我的前端解决方案由两部分组成:

  1. 一个实用文件,用于访问我的麦克风、将音频流到后端、从后端检索数据、每次从后端接收到数据时运行回调函数,然后在完成流式处理或后端出错时自行清理

我的后端解决方案由两部分组成:

  1. 处理实际语音识别流的实用文件

(这些文件不需要以任何方式分开;没有它,我们的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回调演讲稿数据事件在将其转换为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();
    }
}

(我传入了我的实际数据处理程序作为这个组件的道具)。

然后在后端,我的服务在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();
            });

我的演讲稿是这样的:

// 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放在文件的顶部,具体取决于您获取凭据的方式;这正是我实现它的方式。)

最后,这应该足以让您开始这方面的工作。我鼓励您在重用或修改这段代码之前尽最大努力理解它,因为它可能无法“开箱即用”地为您工作,但与我找到的所有其他来源不同,这应该至少可以让您开始项目的所有相关阶段。我希望这个答案将防止其他人像我一样遭受痛苦。

 类似资料:
  • 问题内容: 我想将录制的音频从浏览器实时流传输到服务器并播放。该服务器最终将成为播放这些音频流的嵌入式设备。 到目前为止,我已经成功地录制了音频并将其编码为WAVE文件,并使用网络音频API并按照本教程在浏览器上播放。 现在,我有了.WAV编码的Blob流。我试图找到通过Web套接字连接将其流式传输到Node.js后端并使用npm模块播放它们的方法。但是我没有运气。 有人知道我应该遵循的任何资源或

  • 问题内容: 我目前正在尝试将直播麦克风音频从Android设备流式传输到Java程序。我首先在两个Android设备之间发送实时音频,以确认我的方法正确。在接收设备上几乎没有任何延迟地可以完美地听到音频。接下来,我将相同的音频流发送到一个小型Java程序,并验证了数据也已正确发送到此处。现在,我想要做的是对这些数据进行编码,并以某种方式在运行Java程序的服务器上对其进行回放。我宁愿在使用HTML

  • 我正试图创建一个呼叫录音机应用程序,然而,我的音频来源时,它的麦克风工作正常,但它不能捕捉第二个声音。 如果我更改为VOICE_CALL,我的应用程序关闭。 我在PlayStore上看到了数百个类似的应用程序,并想知道它们能够记录通话双方的秘密是什么。

  • 如果有人能把我引向正确的方向,我会很感激,这样我就知道从哪里开始了。

  • 我正在开发一个用于实时翻译的Python应用程序。我需要实时识别语音:当用户说话时,它会自动将这段音频发送到谷歌语音API并返回文本。因此,我希望在说话时立即显示已识别的文本。 我发现了流式语音识别,但似乎我仍然需要先录制完整的语音,然后将其发送到服务器。此外,没有关于如何在Python中使用它的示例 是否可以使用Google Speech API执行此操作?

  • 我正在构建一个应用程序,它使用语音命令来执行某些功能。我这里有一些代码 然而,这种方法需要通过点击按钮来激活。有没有办法通过语音命令启动语音识别器?就像现在的谷歌一样,你可以说“Ok Google”,然后它就会打开语音识别器活动并监听命令? 谢谢。