في هذه المقالة سنتناول المواضيع التالية
سنبدأ بإنشاء خطاف تفاعلي سيقوم بجميع الأشياء مثل بدء التسجيل، وإيقاف التسجيل، وإنشاء ملف صوتي ثنائي كبير الحجم، ومعالجة الأخطاء وما إلى ذلك.
هناك بعض الأشياء الأخرى التي يجب الاهتمام بها قبل أن ندخل في لحم الخطاف
const VOICE_MIN_DECIBELS = -35 const DELAY_BETWEEN_DIALOGUE = 2000
دعنا نسمي الخطاف الخاص بنا باسم useAudioInput.ts، وسنستخدم واجهة برمجة تطبيقات المتصفح مثل 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 كمتغير للاحتفاظ بإدخال blob و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 إلى واجهة برمجة التطبيقات النصية، وقد أرفقت الوثائق التي أشرت إليها أثناء إنشاء جانب العقدة من الأشياء.
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 إلى النص، ويمكننا أيضًا إرسال 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