„Wenn ein Arbeiter seine Arbeit gut machen will, muss er zuerst seine Werkzeuge schärfen.“ – Konfuzius, „Die Gespräche des Konfuzius. Lu Linggong“
Titelseite > Programmierung > Audio-zu-Text-Eingabe über Google Speech to Text

Audio-zu-Text-Eingabe über Google Speech to Text

Veröffentlicht am 01.11.2024
Durchsuche:387

Audio to Text Input via Google Speech to Text

In diesem Artikel werden wir uns mit folgenden Themen befassen

  1. navigator.mediaDevices.getUserMedia-Browser-API
  2. Google Speech to Text API

Wir beginnen mit der Erstellung eines React-Hooks, der alle Dinge wie startRecording, stopRecording, Audio-Blob erstellen, Fehlerbehandlung usw. erledigt.

Es gibt noch ein paar andere Dinge, um die wir uns kümmern müssen, bevor wir uns an die Sache machen

  1. Mindestdezibel, oberhalb dessen wir einen Dialog als Eingabe betrachten würden, z. B. -35 dB (nur eine Zufallszahl)
  2. Wie lang sollte die Pause sein, die anzeigt, dass der Benutzer die Eingabe gestoppt hat, z. B. 2000 ms?
const VOICE_MIN_DECIBELS = -35
const DELAY_BETWEEN_DIALOGUE = 2000

Nennen wir unseren Hook useAudioInput.ts. Wir würden die Browser-APIs wie navigator.mediaDevices.getUserMedia, MediaRecorder und AudioContext verwenden. Mithilfe von AudioContext können wir ermitteln, ob das Eingangsaudio höher ist als das Mindestdezibel, das erforderlich ist, damit es als Eingabe betrachtet wird. Daher würden wir mit den folgenden Variablen und Requisiten beginnen

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)
    }
  ...
}

Im obigen Code würden wir mediaChunks als Variable verwenden, um den Eingabeblob zu speichern, und mediaRecorder, um eine Instanz des neuen MediaRecorder zu haben, der den Stream als Eingabe von navigator.mediaDevices.getUserMedia übernimmt. Als nächstes kümmern wir uns um Fälle, in denen getUserMedia nicht verfügbar ist

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

    },[]);
...

Wir beginnen mit dem Schreiben der eigentlichen Funktionalität des Hooks, die aus verschiedenen Funktionen wie setupMediaRecorder, setupAudioContext, onRecordingStart, onRecordingActive, startRecording, stopRecording usw. besteht.

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?.();

Mit dem obigen Code sind wir mit dem Hook fast fertig. Das einzige, was noch aussteht, ist festzustellen, ob der Benutzer aufgehört hat zu sprechen oder nicht. Wir würden DELAY_BETWEEN_DIALOGUE als die Zeit verwenden, auf die wir warten würden, wenn für 2 keine Eingabe erfolgt Sekunden gehen wir davon aus, dass der Benutzer aufgehört hat zu sprechen und den Endpunkt „Speech-to-Text“ erreicht.

...
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()
 ... 
}

Im obigen Code verwenden wir requestAnimationFrame, um die Audioeingabe des Benutzers zu erkennen. Damit sind wir mit dem Hook fertig und können nun beginnen, den Hook an verschiedenen Stellen zu verwenden.

z.B

  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
  })

Der zweite Teil besteht darin, einen Knotenserver zu verkabeln, der mit Google Speech to Text API kommunizieren kann. Ich habe die Dokumentation angehängt, auf die ich beim Erstellen der Knotenseite verwiesen habe.
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!');
});


In diesem Artikel habe ich das Senden von Audioinhalten oder Blobs an den Google Speech to Text-Endpunkt behandelt. Wir können auch eine Blob-URI anstelle von Inhalten senden. Die einzige Änderung ist die Nutzlast.

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

Der Code zum Artikel ist in Github vorhanden.

Freigabeerklärung Dieser Artikel ist abgedruckt unter: https://dev.to/shubhadip/audio-to-text-input-via-google-speech-to-text-4ob0?1 Bei Verstößen wenden Sie sich bitte an [email protected] um es zu löschen
Neuestes Tutorial Mehr>

Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.

Copyright© 2022 湘ICP备2022001581号-3