В этой статье мы рассмотрим следующие темы
мы начнем с создания хука реагирования, который будет выполнять все такие операции, как startRecording, stopRecording, создание Audio Blob, обработку ошибок и т. д.
Есть еще несколько вещей, о которых нужно позаботиться, прежде чем мы перейдем к сути дела.
const VOICE_MIN_DECIBELS = -35 const DELAY_BETWEEN_DIALOGUE = 2000
Давайте назовем наш хук useAudioInput.ts, мы будем использовать API браузера, например navigator.mediaDevices.getUserMedia, MediaRecorder и AudioContext. 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) } ... }
В приведенном выше коде мы будем использовать mediaChunks в качестве переменной для хранения входного объекта и mediaRecorder для создания экземпляра нового MediaRecorder, который принимает поток в качестве входных данных из navigator.mediaDevices.getUserMedia. Далее давайте разберемся со случаями, когда 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?.();
с приведенным выше кодом мы почти закончили работу с перехватчиком, единственное, что еще предстоит сделать, это определить, перестал ли пользователь говорить или нет, мы будем использовать DELAY_BETWEEN_DIALOGUE в качестве времени ожидания, если нет ввода для 2 секунд мы будем считать, что пользователь прекратил говорить и перейдет к конечной точке речи в текст.
... 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 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.
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3