In diesem Artikel werden wir uns mit folgenden Themen befassen
Wir beginnen mit der Erstellung eines Reaktions-Hooks, der alle Aufgaben erledigt, wie Aufnahme starten, Aufnahme stoppen, Audio-Blob erstellen, Fehlerbehandlung usw.
Es gibt noch ein paar andere Dinge, um die wir uns kümmern müssen, bevor wir uns an die Arbeit machen
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 die Mindestdezibel, die erforderlich ist, damit es als Eingabe betrachtet wird. Daher beginnen wir mit den folgenden Variablen und Requisiten
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<Blob[]>([]); const [isRecording, setIsRecording] = useState(false); const mediaRecorder = useRef<MediaRecorder | null>(null); const [error, setError] = useState<Error| null>(null); let requestId: number; let timer: ReturnType<typeof setTimeout>; 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 Sprach-zu-Text-Endpunkt 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 < bufferLength; i += 1) { if (domainData[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.
Das obige ist der detaillierte Inhalt vonAudio-zu-Text-Eingabe über Google Speech to Text. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!