이 문서에서는 다음 주제를 살펴볼 것입니다.
startRecording, stopRecording, Audio Blob 생성, 오류 처리 등과 같은 모든 작업을 수행하는 반응 후크를 만드는 것부터 시작하겠습니다.
본론에 들어가기 전에 처리해야 할 일이 몇 가지 더 있습니다.
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에 있습니다.
부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.
Copyright© 2022 湘ICP备2022001581号-3