"일꾼이 일을 잘하려면 먼저 도구를 갈고 닦아야 한다." - 공자, 『논어』.
첫 장 > 프로그램 작성 > Google Speech to Text를 통한 오디오-텍스트 입력

Google Speech to Text를 통한 오디오-텍스트 입력

2024-11-01에 게시됨
검색:844

Audio to Text Input via Google Speech to Text

이 문서에서는 다음 주제를 살펴볼 것입니다.

  1. navigator.mediaDevices.getUserMedia 브라우저 API
  2. Google 음성을 텍스트 API로 변환

startRecording, stopRecording, Audio 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)
    }
  ...
}

위 코드에서는 mediaChunk를 변수로 사용하여 입력 blob을 보관하고 mediaRecorder를 사용하여 navigator.mediaDevices.getUserMedia에서 스트림을 입력으로 사용하는 새 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
  })

두 번째 부분은 Google 음성과 텍스트 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 음성으로 텍스트 끝점으로 보내는 방법을 다루었습니다. 또한 콘텐츠 대신 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