「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > Google Speech to Text による音声からテキストへの入力

Google Speech to Text による音声からテキストへの入力

2024 年 11 月 1 日に公開
ブラウズ:828

Audio to Text Input via Google Speech to Text

この記事では、次のトピックについて検討します

  1. navigator.mediaDevices.getUserMedia ブラウザ API
  2. Google 音声認識 API

まず、startRecording、stopRecording、オーディオ Blob の作成、エラー処理などのすべての処理を行う反応フックを作成します。

本題に入る前に、他に注意すべきことがいくつかあります

  1. それを超えると対話が入力としてみなされる最小デシベル、例: -35db (単なる乱数)
  2. ユーザーが入力を停止したことを示す一時停止の長さ (例: 2000ms)
const VOICE_MIN_DECIBELS = -35
const DELAY_BETWEEN_DIALOGUE = 2000

フックに useAudioInput.ts という名前を付けます。navigator.mediaDevices.getUserMedia、MediaRecorder、AudioContext などのブラウザ API を使用することになります。 AudioContext は、入力オーディオが入力としてみなされるために必要な最小デシベルより高いかどうかを識別するのに役立ちます。そのため、次の変数とプロパティから始めます

const defaultConfig = {
    audio: true
};

type Payload = Blob;

type Config = {
    audio: boolean;
    timeSlice?: number
    timeInMillisToStopRecording?: number
    onStop: () => void;
    onDataReceived: (payload: Payload) => void
};

export const useAudioInput = (config: Config = defaultConfig) => {
    const mediaChunks = useRef([]);
    const [isRecording, setIsRecording] = useState(false);
    const mediaRecorder = useRef(null);
    const [error, setError] = useState(null);
    let requestId: number;
    let timer: ReturnType;

    const createBlob = () => {
      const [chunk] = mediaChunks.current;
      const blobProperty = { type: chunk.type };
      return new Blob(mediaChunks.current, blobProperty)
    }
  ...
}

上記のコードでは、入力 BLOB を保持する変数として mediaChunks を使用し、navigator.mediaDevices.getUserMedia からの入力としてストリームを受け取る新しい MediaRecorder のインスタンスを持つために mediaRecorder を使用します。次に、getUserMedia が利用できない場合に対処しましょう

...
useEffect(() => {
        if(!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
            const notAvailable = new Error('Your browser does not support Audio Input')
            setError(notAvailable)
        }

    },[]);
...

setupMediaRecorder、setupAudioContext、onRecordingStart、onRecordingActive、startRecording、stopRecording などのさまざまな関数で構成されるフックの実際の機能の作成を開始します。

const onRecordingStart = () => mediaChunks.current = [];

const onRecordingActive = useCallback(({data}: BlobEvent) => {
        if(data) {
            mediaChunks.current.push(data);
            config?.onDataReceived?.(createBlob())
        }
    },[config]);

const startTimer = () => {
        timer = setTimeout(() => {
            stopRecording();
        }, config.timeInMillisToStopRecording)
    };

const setupMediaRecorder = ({stream}:{stream: MediaStream}) => {
        mediaRecorder.current = new MediaRecorder(stream)
        mediaRecorder.current.ondataavailable = onRecordingActive
        mediaRecorder.current.onstop = onRecordingStop
        mediaRecorder.current.onstart = onRecordingStart
        mediaRecorder.current.start(config.timeSlice)

    };

 const setupAudioContext = ({stream}:{stream: MediaStream}) => {
        const audioContext = new AudioContext();
        const audioStreamSource = audioContext.createMediaStreamSource(stream);
        const analyser = audioContext.createAnalyser();

        analyser.minDecibels = VOICE_MIN_DECIBELS;

        audioStreamSource.connect(analyser);
        const bufferLength = analyser.frequencyBinCount;
        const domainData = new Uint8Array(bufferLength)

        return {
            domainData,
            bufferLength,
            analyser
        }
    };

const startRecording = async () => {
        setIsRecording(true);

        await navigator.mediaDevices
            .getUserMedia({
                audio: config.audio
            })
            .then((stream) => {
                setupMediaRecorder({stream});
                if(config.timeSlice) {
                    const { domainData, analyser, bufferLength } = setupAudioContext({ stream });
                    startTimer()
                }
            })
            .catch(e => {
                setError(e);
                setIsRecording(false)
            })
    };



    const stopRecording = () => {
        mediaRecorder.current?.stop();

        clearTimeout(timer);
        window.cancelAnimationFrame(requestId);

        setIsRecording(false);
        onRecordingStop()
    };

    const createBlob = () => {
        const [chunk] = mediaChunks.current;
        const blobProperty = { type: chunk.type };
        return new Blob(mediaChunks.current, blobProperty)
    }

    const onRecordingStop = () => config?.onStop?.();

上記のコードでフックはほぼ完了しています。唯一の保留中のことは、ユーザーが話し始めたかどうかを識別することです。2 に対する入力がない場合、待機時間として DELAY_BETWEEN_DIALOGUE を使用します。秒後、ユーザーが話すのをやめ、音声からテキストへのエンドポイントに到達すると想定します。

...
const detectSound = ({ 
        recording,
        analyser,
        bufferLength,
        domainData
    }: {
        recording: boolean
        analyser: AnalyserNode
        bufferLength: number
        domainData: Uint8Array
    }) => {
        let lastDetectedTime = performance.now();
        let anySoundDetected = false;

        const compute = () => {
            if (!recording) {
                return;
            }

            const currentTime = performance.now();

            const timeBetweenTwoDialog =
                anySoundDetected === true && currentTime - lastDetectedTime > DELAY_BETWEEN_DIALOGUE;

            if (timeBetweenTwoDialog) {
                stopRecording();

                return;
            }

            analyser.getByteFrequencyData(domainData);

            for (let i = 0; i  0) {
                    anySoundDetected = true;
                    lastDetectedTime = performance.now();
                }
            }

            requestId = window.requestAnimationFrame(compute);
        };

        compute();

    }
...

const startRecording = async () => {
 ... 
  detectSound()
 ... 
}

上記のコードでは、requestAnimationFrame を使用してユーザーのオーディオ入力を検出しています。これでフックが完了し、さまざまな場所でフックの使用を開始できるようになります。

例えば

  const onDataReceived = async (data: BodyInit) => {
    const rawResponse = await fetch('https://backend-endpoint', {
      method: 'POST',
      body: data
    });
    const response = await rawResponse.json();

    setText(response)
  };

  const { isRecording, startRecording, error } = useAudioInput({
    audio: true,
    timeInMillisToStopRecording: 2000,
    timeSlice: 400,
    onDataReceived
  })

2 番目の部分は、Google speech to text API と通信できるノード サーバーを接続することです。ノード側の作成時に参照したドキュメントを添付しました。
https://codelabs.developers.google.com/codelabs/cloud-speech-text-node.

// demo node server which connects with google speech to text api endpoint

const express = require('express');
const cors = require('cors');

const speech = require('@google-cloud/speech');

const client = new speech.SpeechClient();

async function convert(audioBlob) {
  const request = {
    config: {
      encoding: 'WEBM_OPUS', // Ensure this matches the format of the audio being sent
      sampleRateHertz: 48000, // This should match the sample rate of your recording
      languageCode: 'en-US'
    },
    audio: {
      content: audioBlob
    }
  };

  const [response] = await client.recognize(request);

  const transcription = response.results
    .map(result => result.alternatives[0].transcript)
    .join('\n');
  return transcription;
}

const app = express();

app.use(cors())
app.use(express.json());

app.post('/upload', express.raw({ type: '*/*' }), async (req, res) => {
    const audioBlob = req.body;

    const response = await convert(audioBlob);

    res.json(response);
});

app.listen(4000,'0.0.0.0', () => {
  console.log('Example app listening on port 4000!');
});


この記事では、音声コンテンツまたは blob を Google speech to text エンドポイントに送信する方法について説明しましたが、コンテンツの代わりに blob URI を送信することもできます。唯一の変更はペイロードです

// sending url as part of audio object to speech to text api 
...
audio: {url: audioUrl} or audio: {content: audioBlob}
...

記事に関連するコードはGithubにあります。

リリースステートメント この記事は次の場所に転載されています: https://dev.to/shubhadip/audio-to-text-input-via-google-speech-to-text-4ob0?1 侵害がある場合は、[email protected] までご連絡ください。それを削除するには
最新のチュートリアル もっと>

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3