"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Entrada de audio a texto a través de Google Speech to Text

Entrada de audio a texto a través de Google Speech to Text

Publicado el 2024-11-01
Navegar:227

Audio to Text Input via Google Speech to Text

En este artículo analizaremos los siguientes temas

  1. navigator.mediaDevices.getUserMedia API del navegador
  2. Api de voz a texto de Google

comenzaremos creando un gancho de reacción que hará todas las cosas como iniciar la grabación, detener la grabación, crear Audio Blob, manejo de errores, etc.

Hay algunas otras cosas de las que debemos ocuparnos antes de entrar en el meollo del anzuelo

  1. Decibelios mínimos por encima del cual consideraríamos un diálogo como entrada, por ejemplo, -35 dB (solo un número aleatorio)
  2. ¿Cuánto tiempo debe durar la pausa que indicaría que el usuario ha detenido la entrada, por ejemplo, 2000 ms?
const VOICE_MIN_DECIBELS = -35
const DELAY_BETWEEN_DIALOGUE = 2000

Llamemos a nuestro enlace useAudioInput.ts. Estaríamos usando las API del navegador como navigator.mediaDevices.getUserMedia, MediaRecorder y AudioContext. AudioContext nos ayudará a identificar si el audio de entrada es superior al decibelio mínimo requerido para que se considere entrada, por lo que comenzaríamos con las siguientes variables y accesorios

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

En el código anterior usaríamos mediaChunks como variable para contener el blob de entrada y mediaRecorder para tener una instancia del nuevo MediaRecorder que toma la transmisión como entrada desde navigator.mediaDevices.getUserMedia. A continuación, ocupémonos de los casos en los que getUserMedia no está disponible

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

    },[]);
...

comenzaremos a escribir la funcionalidad real del enlace, que constará de varias funciones como setupMediaRecorder, setupAudioContext, onRecordingStart, onRecordingActive, startRecording, stopRecording, etc.

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

con el código anterior ya casi terminamos con el gancho, lo único pendiente es identificar si el usuario ha dejado de hablar o no, usaríamos DELAY_BETWEEN_DIALOGUE como el tiempo que esperaríamos, si no hay entrada para 2 segundos, asumiremos que el usuario dejó de hablar y accederá al punto final de voz a texto.

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

en el código anterior estamos usando requestAnimationFrame para detectar la entrada de audio del usuario, con esto hemos terminado con el gancho y ahora podemos comenzar a usarlo en varios lugares.

p.ej

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

La segunda parte es conectar un servidor de nodo que pueda comunicarse con la API de voz a texto de Google. He adjuntado la documentación a la que me referí mientras creaba el lado del nodo.
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!');
});


en este artículo he cubierto el envío de contenido de audio o blob al punto final de voz a texto de Google, también podemos enviar un uri de blob en lugar de contenido, el único cambio será la carga útil

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

El código relacionado con el artículo está presente en Github.

Declaración de liberación Este artículo se reproduce en: https://dev.to/shubhadip/audio-to-text-input-via-google-speech-to-text-4ob0?1 Si hay alguna infracción, comuníquese con [email protected] para borrarlo
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3