この記事では、次のトピックについて説明します
まず、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<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) } ... }
上記のコードでは、入力 BLOB を保持する変数として mediaChunks を使用し、navigator.mediaDevices.getUserMedia からの入力としてストリームを受け取る新しい MediaRecorder のインスタンスを持つために 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 を使用します。秒後、ユーザーが話すのをやめ、Speech to Text エンドポイントに到達すると想定します。
... 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() ... }
上記のコードでは、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 })
2 番目の部分は、Google speech to text 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!'); });
この記事では、Google speech to text エンドポイントへのオーディオ コンテンツまたは BLOB の送信について説明しましたが、コンテンツの代わりに BLOB URI を送信することもできます。唯一の変更はペイロードです
// sending url as part of audio object to speech to text api ... audio: {url: audioUrl} or audio: {content: audioBlob} ...
記事に関連するコードは Github にあります。
以上がGoogle Speech to Text による音声からテキストへの入力の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。