पेश है Eleven v4पेश है Eleven v4, हमारा अब तक का सबसे भावपूर्ण मॉडल। 12 अक्टूबर तक Creator+ के साथ 3 गुना क्रेडिट शामिल हैं

कंटेंट पर जाएं

रीयल-टाइम स्पीच टू टेक्स्ट 200ms से कम में: एक आर्किटेक्चर गाइड

प्रकाशित
आखिरी बार अपडेट किया गया

सुनेंइस आर्टिकल को सुनें

रियल-टाइम स्पीच टू टेक्स्ट (STT) व्यक्ति के बोलते समय ऑडियो को सक्रिय रूप से ट्रांसक्राइब करता है और कुछ सौ मिलीसेकंड में शब्दों को टेक्स्ट के रूप में लौटाता है। लेकिन STT लेटेंसी कम रखना मॉडल के साथ-साथ आर्किटेक्चर की भी समस्या है। इंजीनियरों को ट्रांसपोर्ट, चंकिंग, एंड-पॉइंटिंग और कैप्चर पाथ की योजना बनानी होती है, जिनमें से हर एक लेटेंसी बढ़ाता है। इनमें से किसी एक की अक्षमता आपका 200ms बजट खत्म कर सकती है।

यह गाइड आपको ट्रांसपोर्ट लेयर से ऊपर तक रियल-टाइम स्पीच टू टेक्स्ट पाइपलाइन बनाने के लिए एक व्यावहारिक सिस्टम देता है। हम Scribe v2 Realtime को आधार बनाएंगे, जो लगभग 150ms की मॉडल लेटेंसी में आंशिक ट्रांसक्रिप्शन देता है, 90+ भाषाओं को सपोर्ट करता है, PCM (8kHz-48kHz) और mu-law ऑडियो स्वीकार करता है, और वॉइस एक्टिविटी डिटेक्शन के साथ सेगमेंट फाइनलाइज़ करने के लिए मैन्युअल कमिट कंट्रोल देता है।

हम देखेंगे कि ऑडियो सर्वर तक कैसे पहुंचता है, हाइपोथिसिस कमिटेड टेक्स्ट में कैसे बदलती हैं, इन-स्ट्रीम फीचर्स की क्या लागत है और ऑडियो को सही तरह कैप्चर व फ़ॉरवर्ड कैसे करें।

सारांश

  • रियल-टाइम स्पीच टू टेक्स्ट सिस्टम बनाने के लिए आर्किटेक्चर को बारीकी से ट्यून करना पड़ता है, ताकि पूरी पाइपलाइन में लेटेंसी कम रहे।
  • ज़्यादातर पाइपलाइनों के लिए WebSocket सही डिफ़ॉल्ट है, हालांकि WebRTC अधिक जटिल होने के बावजूद कई लाभ देता है।
  • वॉइस एक्टिविटी डिटेक्शन हैंड्स-फ़्री सेगमेंटेशन संभालता है, जबकि मैन्युअल कमिट आपके ऐप्लिकेशन को टर्न खत्म होने की जानकारी होने पर ओवरराइड देता है।
  • पार्शियल अस्थायी होते हैं और फाइनल कमिटेड होते हैं, इसलिए उन्हें अलग तरह से रेंडर करना चाहिए। 
  • लगभग 100ms के छोटे PCM चंक पहले पार्शियल तक की लेटेंसी कम करते हैं।

रियल-टाइम स्पीच टू टेक्स्ट के लिए WebSocket बनाम WebRTC

ट्रांसक्रिप्शन शुरू होने से पहले, ऑडियो को स्रोत से रिकग्नाइज़र तक जाना होता है। चुना गया चैनल आगे की हर चीज़ के लिए न्यूनतम लेटेंसी तय करता है। ऑडियो को ट्रांसक्रिप्शन लेयर तक पहुंचाने के दो व्यावहारिक विकल्प हैं।

WebSocket, TCP पर चलने वाला लंबे समय तक सक्रिय, क्रमबद्ध, भरोसेमंद और द्विदिश चैनल है। आप कनेक्शन खोलते हैं, बाइनरी ऑडियो फ़्रेम भेजते हैं और ट्रांसक्रिप्ट इवेंट वापस पढ़ते हैं। यह क्लाइंट और सर्वर, दोनों पर सरल है, HTTPS की अनुमति देने वाले कॉर्पोरेट प्रॉक्सी और फ़ायरवॉल से गुजरता है, और हर ब्राउज़र व सर्वर रनटाइम इसे सपोर्ट करता है। 

WebSocket की सीमा यह है कि यह TCP पर चलता है। कोई पैकेट खोने पर TCP उसे दोबारा भेजता है और गैप भरने तक बाद के डेटा को रोकता है। अच्छे नेटवर्क में यह दिखता नहीं है। पैकेट लॉस होने पर हेड-ऑफ़-लाइन ब्लॉकिंग होती है: ऑडियो थोड़ी देर रुकता है और फिर एक साथ पहुंचता है।

WebRTC रियल-टाइम मीडिया के लिए बना है। यह UDP (SRTP के ज़रिए) पर मीडिया चलाता है, इसलिए कोई पैकेट खोने पर स्ट्रीम नहीं रुकती और पाइपलाइन चलती रहती है। इसमें पैकेट पहुंचने के समय में बदलाव सोखने के लिए जिटर बफ़र है, राउटर के पीछे मौजूद पीयर को कनेक्ट करने के लिए यह ICE/STUN/TURN से NAT ट्रैवर्सल नेगोशिएट करता है, और इसमें अपना ऑडियो कैप्चर व एन्कोडिंग तंत्र होता है। 

जो क्लाइंट सीधे कनेक्ट नहीं हो सकते, उनके लिए आम तौर पर TURN सर्वर चाहिए होते हैं, और सर्वर साइड को बाइट स्ट्रीम पढ़ने के बजाय मीडिया स्ट्रीम टर्मिनेट करनी होती है।

संक्षेप में अंतर यह है:

WebSocket
Transport
TCP (reliable, ordered)
Behavior under packet loss
Head-of-line blocking, bursty recovery
Jitter handling
Your responsibility
NAT traversal
Not needed (client-initiated)
Browser support
Universal, trivial
Server complexity
Low
WebRTC
Transport
UDP/SRTP (real-time, loss-tolerant)
Behavior under packet loss
Graceful degradation
Jitter handling
Built-in jitter buffer
NAT traversal
Requires ICE/STUN/TURN
Browser support
Universal, but more API surface
Server complexity
High (media server or SFU)

अधिकांश उपयोग मामलों में WebSocket सही विकल्प है। जब क्लाइंट की कनेक्टिविटी ठीक हो और आप कैप्चर पाथ नियंत्रित करते हों, तब इसका उपयोग करें: सर्वर-टू-सर्वर पाइपलाइन, डेस्कटॉप ऐप, ब्रॉडबैंड पर ब्राउज़र ऐप, और अधिकतर कॉन्टैक्ट-सेंटर बैकएंड जहां ऑडियो पहले ही किसी अन्य तरीके से आपके सर्वर तक पहुंचता है। 

जब आप अविश्वसनीय मोबाइल नेटवर्क पर उपभोक्ता डिवाइसों से सीधे कैप्चर कर रहे हों, पहले से दो-तरफ़ा ऑडियो के लिए WebRTC स्टैक चला रहे हों (जैसे बोलकर जवाब देने वाला वॉइस एजेंट), या इंप्लीमेंटेशन की सरलता से अधिक कम-लॉस रियल-टाइम व्यवहार मायने रखता हो, तब WebRTC चुनें।

इस गाइड के बाकी हिस्से में रिकग्नाइज़र कनेक्शन के लिए WebSocket ट्रांसपोर्ट इस्तेमाल किया गया है, क्योंकि इससे सभी हिस्से स्पष्ट रहते हैं और अधिकांश टीमों के लिए यह सही शुरुआत है। इसमें कुछ भी केवल WebSocket तक सीमित नहीं है, इसलिए बाद में आप आगे WebRTC मीडिया लेग जोड़ सकते हैं, सर्वर पर ऑडियो को PCM में डिकोड कर सकते हैं और वही चंक पाइपलाइन में फ़ॉरवर्ड कर सकते हैं।

पार्शियल और फाइनल ट्रांसक्रिप्ट: अंतरिम नतीजों की व्याख्या

रियल-टाइम रिकग्नाइज़र पूरा वाक्य खत्म होने तक इंतज़ार नहीं करता। इसके बजाय, वह अनुमानों की एक चलती स्ट्रीम देता है जो अधिक ऑडियो आने पर बेहतर होती जाती है और फिर लॉक हो जाती है। इन दोनों अवस्थाओं का अंतर समझना, जीवंत लगने वाले ट्रांसक्रिप्ट और टूटे हुए लगने वाले ट्रांसक्रिप्ट के बीच फर्क पैदा करता है। 

पार्शियल (इंटरिम) हाइपोथिसिस अब तक मिले ऑडियो के आधार पर मॉडल का सबसे अच्छा अनुमान है। पार्शियल डिज़ाइन के अनुसार अस्थिर होते हैं। अधिक ऑडियो आने पर मॉडल पहले के शब्द बदलता है: "मुझे चाहिए" बाद के कॉन्टेक्स्ट से अस्पष्टता खत्म होने पर "मुझे दो टिकट चाहिए" बन सकता है। ये तेज़ी से आते हैं (~150ms लेटेंसी इसी को दर्शाती है) और इन्हें ओवरराइट करने के लिए बनाया गया है।

फाइनल हाइपोथिसिस एक कमिटेड सेगमेंट है जो नहीं बदलेगा। सेगमेंट फाइनल होने पर रिकग्नाइज़र आगे बढ़ता है और बाद की हाइपोथिसिस बाद के ऑडियो का वर्णन करती हैं। फाइनल वही हैं जिन्हें आप सेव करते हैं, LLM को भेजते हैं या ट्रांसक्रिप्ट के रूप में स्टोर करते हैं।

पार्शियल और फाइनल का अंतर तीन चीज़ों को प्रभावित करता है, जिन्हें यह अंतर धुंधला करने पर आप गलत कर देंगे:

  • यूज़र अनुभव: पार्शियल दिखाने से ट्रांसक्रिप्ट लाइव लगता है: यूज़र बोलते समय शब्दों को आते देखता है, जिससे पुष्टि होती है कि माइक्रोफ़ोन काम कर रहा है और सिस्टम सुन रहा है।
  • एंड-पॉइंटिंग: पार्शियल आपको स्पीच एक्टिविटी का लगातार सिग्नल देते हैं। VAD के साथ मिलकर वे तय करने देते हैं कि स्पीकर वास्तव में कब रुका है।
  • डाउनस्ट्रीम टाइमिंग: एक वॉइस एजेंट पाइपलाइन में क्रम है: ऑडियो इन, फिर स्पीच टू टेक्स्ट, फिर LLM, फिर टेक्स्ट टू स्पीच, और फिर ऑडियो आउट। आप पार्शियल पर अनुमानित काम शुरू कर सकते हैं और फाइनल पर पुष्टि कर सकते हैं। कभी-कभी अनुमानित काम हटाना पड़ेगा, लेकिन अनुभव की गई रिस्पॉन्स टाइम कम होगी।

पार्शियल और फाइनल को अलग तरह से रेंडर करें। एक सरल, प्रभावी पैटर्न है कि नवीनतम पार्शियल से जुड़ी एक ही बदलने योग्य "करंट लाइन" रखें और फाइनल आने पर उसे केवल-जुड़ने वाले ट्रांसक्रिप्ट में कमिट करें:

type TranscriptState = {
  committed: string[]; // finalized segments, never rewritten
  current: string;     // latest partial, overwritten on each update
};

const onPartial = (s: TranscriptState, text: string): TranscriptState =>
  ({ ...s, current: text });

const onFinal = (s: TranscriptState, text: string): TranscriptState =>
  ({ committed: [...s.committed, text], current: "" });

दिखने में, कमिटेड टेक्स्ट को स्थिर और करंट टेक्स्ट को हल्के या इटैलिक स्टाइल में रेंडर करें, ताकि यूज़र समझे कि यह अभी बदल सकता है।

एंड-पॉइंटिंग और वॉइस एक्टिविटी डिटेक्शन (VAD)

क्या कहा गया, यह जानना आधी लड़ाई है। रिकग्नाइज़र को यह भी जानना होता है कि बात कब खत्म हुई। इसी निर्णय से तय होता है कि सेगमेंट कब फाइनल करना है और एजेंट में सिस्टम कब जवाब देना शुरू करेगा। 

एंड-पॉइंटिंग यह निर्णय है कि कोई कथन खत्म हो गया है। बहुत जल्दी फाइनलाइज़ करने से यूज़र वाक्य के बीच में कट जाता है। बहुत देर से फाइनलाइज़ करने पर यूज़र के स्पष्ट रूप से खत्म करने के बाद भी एजेंट चुप रहता है।

Scribe v2 Realtime दो पूरक तरीके देता है:

  • वॉइस एक्टिविटी डिटेक्शन, साइलेंस के आधार पर ऑडियो को सेगमेंट करता है: रिकग्नाइज़र पहचानता है कि स्पीच कब लगातार साइलेंस में बदलती है और उस सीमा का उपयोग करके सेगमेंट को अपने आप फाइनल करता है। बातचीत वाले इंटरफ़ेस के लिए VAD सही डिफ़ॉल्ट है, क्योंकि यह आपको हाथ से टाइमिंग ट्रैक किए बिना प्राकृतिक बोलने की लय के अनुसार ढल जाता है।
  • मैन्युअल कमिट कंट्रोल: मैन्युअल कमिट कंट्रोल आपके ऐप्लिकेशन को साइलेंस से स्वतंत्र रूप से मौजूदा सेगमेंट फाइनल करने का निर्णय लेने देता है। आप कमिट सिग्नल भेजते हैं, रिकग्नाइज़र मौजूदा सेगमेंट बंद करता है और फाइनल भेजता है। जब ऐप्लिकेशन को पहले से पता हो कि टर्न खत्म है, तब यह सही टूल है: पुश-टू-टॉक बटन छोड़ना, "send" ऐक्शन या बाहरी टर्न-टेकिंग पॉलिसी।

दोनों साथ में अच्छी तरह काम करते हैं। एक सामान्य वॉइस एजेंट हैंड्स-फ़्री ऑपरेशन के लिए VAD और ओवरराइड के तौर पर मैन्युअल कमिट का उपयोग करता है। इसलिए सोचने के लिए रुकने वाला यूज़र कटता नहीं है, जबकि बटन टैप करने वाले यूज़र को तुरंत सीमा मिलती है।

साइलेंस थ्रेशहोल्ड में वास्तविक समझौता है; इसका कोई एक सार्वभौमिक सही मान नहीं है:

  • स्पीच खत्म होने का छोटा टाइमआउट (उदाहरण के लिए ~200-400ms साइलेंस के बाद फाइनलाइज़ेशन) सिस्टम को तेज़ महसूस कराता है। लेकिन यह वाक्यांशों के बीच स्वाभाविक रूप से रुकने वाले यूज़र्स को भी काट सकता है, एक विचार को कई सेगमेंट में बांट सकता है और एजेंट में समय से पहले जवाब ट्रिगर कर सकता है।
  • लंबा टाइमआउट (उदाहरण के लिए ~800-1200ms) स्वाभाविक ठहराव को सहन करता है और कथनों को बरकरार रखता है, लेकिन सिस्टम के प्रतिक्रिया देने से पहले एक स्पष्ट देरी होती है।

इसके लिए कोई वैश्विक स्थिर मान नहीं है; थ्रेशहोल्ड को इंटरैक्शन के अनुसार ट्यून करें:

  • डिक्टेशन और नोट-टेकिंग में लंबे ठहराव स्वीकार्य हैं, क्योंकि यूज़र वाक्य के बीच सोचते हैं। लंबे टाइमआउट रखें और VAD पर भरोसा करें।
  • कमांड-एंड-कंट्रोल और ट्रांज़ैक्शनल एजेंट को छोटे टाइमआउट और मैन्युअल कमिट से लाभ मिलता है, क्योंकि टर्न छोटे और स्पष्ट होते हैं।
  • बहुभाषी या गैर-नेटिव स्पीकर अधिक रुकते हैं, इसलिए फाइनलाइज़ करने से पहले अधिक साइलेंस का बजट रखें।

इन सुझावों से आप प्रभावी एंड-पॉइंटिंग सिस्टम बना सकते हैं और रियल-टाइम स्पीच टू टेक्स्ट के करीब पहुंच सकते हैं।

इन-स्ट्रीम फीचर्स: भाषा पहचान और स्पीकर डायराइज़ेशन

स्ट्रीमिंग रिकग्निशन सिर्फ शब्द देने से अधिक काम कर सकता है। हालांकि, हर अतिरिक्त सिग्नल लेटेंसी और स्थिरता को प्रभावित करता है। सामान्य नियम यह है कि लाइव अनुभव के लिए जितना चाहिए, केवल उतना ही चालू करें और बाकी को बैच पास के लिए छोड़ दें।

ऑटोमैटिक लैंग्वेज रिकग्निशन, आपको पहले से भाषा बताने की ज़रूरत के बिना Scribe v2 Realtime को अपनी 90+ समर्थित भाषाओं में बोली जा रही भाषा पहचानने देता है। इसकी लागत यह है कि भरोसेमंद निर्णय के लिए मॉडल को थोड़े ऑडियो की ज़रूरत होती है, इसलिए भाषा तय होने तक स्ट्रीम के पहले पार्शियल कम स्थिर हो सकते हैं। अगर भाषा पहले से पता है, तो उसे निर्दिष्ट करने से यह अस्पष्टता हटती है और शुरुआती पार्शियल अधिक स्थिर रहते हैं।

स्पीकर डायराइज़ेशन अलग-अलग स्पीकर्स को स्पीच असाइन करता है और बताता है कि किसने क्या कहा। बैच ट्रांसक्रिप्शन में यह तुलनात्मक रूप से आसान है, क्योंकि मॉडल पूरी फ़ाइल देखता है। स्ट्रीमिंग में यह कठिन है: रिकग्नाइज़र को अब तक के ऑडियो से ही स्पीकर लेबल देना होता है, और शुरुआती ऑडियो को दिया गया लेबल उस स्पीकर की और आवाज़ सुनने पर बदलना पड़ सकता है। स्ट्रीमिंग स्पीकर लेबल को पार्शियल टेक्स्ट की तरह ही मानें: सेगमेंट फाइनल होने तक अस्थायी।

वर्ड-लेवल टाइमिंग और एंटिटी कॉन्टेक्स्ट भी इसी तर्क का पालन करते हैं। आप जितना अधिक प्रति-टोकन मेटाडेटा मांगते हैं, मॉडल और वायर दोनों को उतना अधिक संभालना पड़ता है। ज़्यादातर रियल-टाइम UI में आपको लाइव टेक्स्ट और सेगमेंट सीमाएं ही चाहिए होती हैं; बारीक मेटाडेटा को Scribe v2 के साथ कॉल के बाद बैच पास में लिया जा सकता है।

स्ट्रीमिंग के लिए ऑडियो फ़ॉर्मेट: PCM और mu-law

ट्रांसपोर्ट और रिकग्निशन लॉजिक पर सबसे ज़्यादा ध्यान जाता है, लेकिन वास्तविक दुनिया के बहुत से बग एक लेयर नीचे, ऑडियो को एन्कोड और चंक करने के तरीके से आते हैं। सही फ़ॉर्मेट और चंक साइज़ चुनना स्पीच टू टेक्स्ट लेटेंसी घटाने का सबसे सस्ता तरीका है।

जब आप कैप्चर नियंत्रित करते हैं, तो PCM (लीनियर, 16-बिट साइनड, लिटिल-एंडियन) उपयोग करने वाला फ़ॉर्मेट है। अधिक सैंपल रेट में अधिक ध्वनिक विवरण होता है: 16kHz स्पीच रिकग्निशन का मानक न्यूनतम स्तर है और आमतौर पर पर्याप्त है; 8kHz टेलीफ़ोन-ग्रेड है और हाई-फ़्रीक्वेंसी कंटेंट खो देता है। अपने स्रोत से मेल खाती रेट चुनें। 8kHz टेलीफ़ोनी ऑडियो को 48kHz पर अपसैंपल करने का लाभ नहीं है, क्योंकि खोई जानकारी वापस नहीं मिल सकती।

8kHz पर mu-law टेलीफ़ोनी फ़ॉर्मेट है। अगर आप Twilio जैसे प्रोवाइडर से कॉल ले रहे हैं, तो ऑडियो 8kHz mu-law के रूप में आता है और उसे दो बार ट्रांसकोड करने के बजाय उसी फ़ॉर्मेट में फ़ॉरवर्ड करना चाहिए। स्रोत फ़ॉर्मेट मिलाने से री-सैंपलिंग आर्टिफ़ैक्ट और अनावश्यक कन्वर्ज़न स्टेप से बचते हैं।

चंक साइज़िंग वह लीवर है जो अनुभव की गई लेटेंसी को सबसे सीधे प्रभावित करता है। आप ऑडियो को चंक में भेजते हैं और रिकग्नाइज़र चंक पहुंचने पर पार्शियल बनाता है। छोटे चंक अधिक बार अपडेट और पहले पार्शियल तक कम लेटेंसी देते हैं; बड़े चंक कम मैसेज और हर इन्फ़रेंस के लिए थोड़ा अधिक कॉन्टेक्स्ट देते हैं। व्यावहारिक रेंज प्रति चंक 20-250ms ऑडियो है। उदाहरण के लिए, 16kHz मोनो 16-बिट PCM में एक सेकंड ऑडियो 32,000 बाइट होता है, इसलिए 100ms का चंक लगभग 3,200 बाइट होता है।

ब्राउज़र में माइक्रोफ़ोन इनपुट कैप्चर करना

ब्राउज़र में सही टूल Web ऑडियो API है, AudioWorklet के साथ। वर्कलेट ऑडियो रेंडरिंग थ्रेड पर चलता है, छोटे फ़्रेम में ऑडियो लेता है और पुराने ScriptProcessorNode की तरह मेन-थ्रेड जैंक से प्रभावित नहीं होता। इसका काम ब्राउज़र के नेटिव फ़्लोट सैंपल को 16-बिट PCM में बदलकर मेन थ्रेड को देना है, जो उन्हें WebSocket पर फ़ॉरवर्ड करता है।

वर्कलेट प्रोसेसर का मुख्य भाग फ़्लोट-से-PCM कन्वर्ज़न है:

// pcm-worklet.ts - registered via audioContext.audioWorklet.addModule()
class PCMWorklet extends AudioWorkletProcessor {
  process(inputs: Float32Array[][]) {
    const channel = inputs[0]?.[0]; // mono; Float32, range [-1, 1]
    if (!channel) return true;
    const pcm = new Int16Array(channel.length);
    for (let i = 0; i < channel.length; i++) {
      const s = Math.max(-1, Math.min(1, channel[i]));
      pcm[i] = s < 0 ? s * 0x8000 : s * 0x7fff;
    }
    // Transfer the buffer to the main thread without copying.
    this.port.postMessage(pcm.buffer, [pcm.buffer]);
    return true;
  }
}
registerProcessor("pcm-worklet", PCMWorklet);

कोड में पाइपलाइन

पाइपलाइन के तीन हिस्से हैं: एक ब्राउज़र क्लाइंट जो माइक्रोफ़ोन कैप्चर करके आपके सर्वर पर PCM स्ट्रीम करता है, एक Node सर्वर जो ऑडियो को Scribe v2 Realtime तक और ट्रांसक्रिप्ट को वापस रिले करता है, और एक स्क्रिप्टेबल क्लाइंट जो फ़ाइल या टेलीफ़ोनी ब्रिज से PCM स्ट्रीम करता है।

रिकग्नाइज़र को सीधे ब्राउज़र में एक्सपोज़ करने के बजाय सर्वर रिले करने का एक अहम कारण है: आपकी ElevenLabs API key एक सीक्रेट है और उसे क्लाइंट-साइड कोड में कभी नहीं आना चाहिए। key सर्वर पर रहती है। अगर ब्राउज़र को सीधे रिकग्नाइज़र से बात करनी हो, तो सर्वर-साइड पर कम अवधि वाला, सिंगल-यूज़ टोकन बनाएं और API key की जगह क्लाइंट को वह दें।

ब्राउज़र क्लाइंट

क्लाइंट आपके सर्वर से WebSocket खोलता है, ऊपर दिए वर्कलेट से माइक्रोफ़ोन कैप्चर करता है और बनते ही हर PCM फ़्रेम फ़ॉरवर्ड करता है। आने वाले इवेंट (जिन्हें सर्वर पहले ही { type, text } में नॉर्मलाइज़ कर चुका है) पहले वाली पार्शियल/फाइनल स्थिति को नियंत्रित करते हैं:

// client.ts - runs in the browser. ws is an open WebSocket to your server.
const audioContext = new AudioContext({ sampleRate: 16000 });
await audioContext.audioWorklet.addModule("pcm-worklet.js");

const mediaStream = await navigator.mediaDevices.getUserMedia({
  audio: { channelCount: 1, echoCancellation: true, noiseSuppression: true },
});

const source = audioContext.createMediaStreamSource(mediaStream);
const worklet = new AudioWorkletNode(audioContext, "pcm-worklet");

// Forward each PCM frame to the server the moment it is produced.
worklet.port.onmessage = (e: MessageEvent<ArrayBuffer>) => {
  if (ws.readyState === WebSocket.OPEN) ws.send(e.data);
};
source.connect(worklet);

// Manual commit: tell the server to finalize the current segment.
const commit = () => ws.send(JSON.stringify({ type: "commit" }));

सर्वर रिले

सर्वर हर क्लाइंट के लिए एक रिकग्नाइज़र कनेक्शन खोलता है, API key को सर्वर पर रखता है, बाइनरी PCM को सीधे फ़ॉरवर्ड करता है और रिकग्नाइज़र इवेंट को क्लाइंट द्वारा उपयोग किए जाने वाले स्थिर { type, text } स्वरूप में नॉर्मलाइज़ करता है:

// server.ts - Node, using the `ws` library. ELEVENLABS_API_KEY and the
// recognizer URL come from the environment; see the Speech to Text reference
// for the exact path and query parameters.
import { WebSocketServer, WebSocket } from "ws";

new WebSocketServer({ port: 8080 }).on("connection", (client) => {
  // The API key stays on the server, never on the wire to the browser.
  const recognizer = new WebSocket(process.env.RECOGNIZER_WSS_URL!, {
    headers: { "xi-api-key": process.env.ELEVENLABS_API_KEY! },
  });

  // Browser -> recognizer: forward binary PCM, translate control messages.
  client.on("message", (data, isBinary) => {
    if (recognizer.readyState !== WebSocket.OPEN) return;
    if (isBinary) recognizer.send(data); // raw PCM bytes
    else if (JSON.parse(data.toString()).type === "commit")
      recognizer.send(sendCommit());
  });

  // Recognizer -> browser: normalize events into a stable shape.
  recognizer.on("message", (raw) => {
    const event = parseRecognizerEvent(raw.toString());
    if (event && client.readyState === WebSocket.OPEN)
      client.send(JSON.stringify(event));
  });

  // ... open handshake, queueing pre-open audio, and teardown on close/error
});

एंडपॉइंट-विशिष्ट सब कुछ नीचे दिए दो अडैप्टर फ़ंक्शन तक सीमित है। फ़ील्ड नामों को स्पीच टू टेक्स्ट रेफरेंस में दिए सटीक नामों से बदलें; बाकी पाइपलाइन नहीं बदलेगी:

// The single place that knows the recognizer's wire format.
const sendCommit = (): string => JSON.stringify({ type: "commit" });

type NormalizedEvent =
  | { type: "partial"; text: string }
  | { type: "final"; text: string }
  | { type: "vad"; speaking: boolean };

function parseRecognizerEvent(raw: string): NormalizedEvent | null {
  const msg = JSON.parse(raw);
  if (msg.is_final === true || msg.type === "final")
    return { type: "final", text: msg.text ?? "" };
  if (msg.type === "vad") return { type: "vad", speaking: !!msg.speaking };
  if (typeof msg.text === "string")
    return { type: "partial", text: msg.text };
  return null;
}

स्क्रिप्टेबल बैकएंड क्लाइंट

बैकएंड पाइपलाइनों और नीचे दिए बेंचमार्क के लिए, यही रिकग्नाइज़र कनेक्शन ब्राउज़र के बिना काम करता है: किसी भी स्रोत से PCM पढ़ें, उसे रियल-टाइम चंक कैडेंस पर भेजें और इवेंट वापस पढ़ें। API key और URL सर्वर की तरह एनवायरनमेंट से आते हैं।

// stream-stt.ts - pace ~100ms chunks at real time, then commit the tail.
const SAMPLE_RATE = 16000, CHUNK_MS = 100;
const CHUNK_BYTES = (SAMPLE_RATE * 2 * CHUNK_MS) / 1000; // 3200 bytes
const ws = new WebSocket(process.env.RECOGNIZER_WSS_URL!, {
  headers: { "xi-api-key": process.env.ELEVENLABS_API_KEY! },
});

// Send: walk the PCM buffer in 100ms chunks, sleeping between to mimic a
// live source. For audio that already arrives in real time, drop the sleep.
async function sendAudio(pcm: Buffer) {
  for (let off = 0; off < pcm.length; off += CHUNK_BYTES) {
    ws.send(pcm.subarray(off, off + CHUNK_BYTES));
    await new Promise((r) => setTimeout(r, CHUNK_MS));
  }
  ws.send(JSON.stringify({ type: "commit" })); // finalize the trailing segment
}

// Receive: print partials in place, append finals.
ws.on("message", (raw) => {
  const e = parseRecognizerEvent(raw.toString());
  if (e?.type === "final") console.log(`[final]   ${e.text}`);
  else if (e?.type === "partial") process.stdout.write(`[partial] ${e.text}\r`);
});

स्पीच टू टेक्स्ट लेटेंसी और वर्ड एरर रेट का बेंचमार्किंग

लेटेंसी और वर्ड एरर रेट, स्पीकर, भाषा, ध्वनिक परिस्थितियों, ऑडियो की लंबाई, हर प्रोवाइडर के निकटतम रीजन तक आपके नेटवर्क पाथ और हर सेवा के मौजूदा लोड के अनुसार बदलते हैं। 

एक शहर में लैपटॉप से मापा गया परिणाम, दूसरे शहर में आपके प्रोडक्शन फ़्लीट पर लागू नहीं होता। ऐसे इन्फ़्रास्ट्रक्चर से हार्नेस चलाएं जो प्रोडक्शन जैसा हो, ऐसे ऑडियो पर जो आपके वास्तविक इनपुट जैसा हो, और एकल संख्या के बजाय रेंज व डिस्ट्रीब्यूशन रिपोर्ट करें।

केवल वही लेटेंसी और सटीकता के आंकड़े मायने रखते हैं जिन्हें आप प्रोडक्शन जैसे इन्फ़्रास्ट्रक्चर से अपने ऑडियो पर मापते हैं। यहां स्पीच टू टेक्स्ट लेटेंसी बेंचमार्क करने की गाइड है।

स्पीच टू टेक्स्ट लेटेंसी के लिए क्या मापें

रियल-टाइम स्पीच टू टेक्स्ट लेटेंसी बेंचमार्क करते समय मापने वाले मुख्य मेट्रिक ये हैं:

  • पहले पार्शियल तक समय: पहला ऑडियो चंक भेजने से लेकर पहला गैर-खाली पार्शियल मिलने तक।
  • पार्शियल-से-फाइनल देरी: कथन के आखिरी ऑडियो चंक से फाइनल हाइपोथिसिस तक।
  • वर्ड एरर रेट (WER): फाइनलाइज़्ड ट्रांसक्रिप्ट का मानव रेफरेंस के मुकाबले WER, जिसे सभी सिस्टम में एक ही तरीके से कैलकुलेट किया गया हो।
  • स्टेबिलिटी चर्न: फाइनल होने से पहले कितने पार्शियल दोबारा लिखे जाते हैं। यह इस बात का संकेतक है कि लाइव UI कितना बदलता हुआ दिखेगा।

कंट्रोल

अविश्वसनीय डेटा से बचने के लिए, एकरूपता बनाए रखने हेतु अपने प्रयोग में कई कंट्रोल लागू करें।

यहां स्पीच टू टेक्स्ट लेटेंसी बेंचमार्किंग के मुख्य कंट्रोल हैं:

  • एक जैसा ऑडियो: हर सिस्टम में वही फ़ाइलें, वही सैंपल रेट और वही एन्कोडिंग उपयोग करें।
  • एक जैसी पेसिंग: हर सिस्टम को समान रियल-टाइम चंक कैडेंस पर स्ट्रीम करें (उदाहरण के लिए 100ms चंक)।
  • दोहराएं और डिस्ट्रीब्यूशन रिपोर्ट करें:हर फ़ाइल को पूरे दिन में कई बार चलाएं; मीडियन और टेल (p50/p95) रिपोर्ट करें।
  • एक जैसे रेफरेंस और स्कोरिंग:WER कैलकुलेट करने से पहले टेक्स्ट को एक ही तरह नॉर्मलाइज़ करें (कैसिंग, विराम चिह्न, संख्याएं)।
  • रीजन और नेटवर्क बताएं:हार्नेस कहां चला और हर प्रोवाइडर तक उसका पाथ क्या था, यह बताएं।

इन सभी तत्वों को समान रखने से आपको अधिक सटीक मेट्रिक मिलेंगे।

हार्नेस स्केलेटन

मापन का मुख्य भाग एक प्रोवाइडर अडैप्टर लेता है और पहले पार्शियल तक समय, फाइनलाइज़ेशन देरी और पार्शियल चर्न रिकॉर्ड करता है:

// benchmark.ts - measurement core; one StreamFn adapter per provider.
type StreamFn = (
  audioPath: string,
  onEvent: (kind: "partial" | "final", text: string) => void,
  result: RunResult
) => Promise<void>; // adapter sets result.lastChunkSentAt on the final chunk

interface RunResult {
  firstPartialMs?: number;
  finalLagMs?: number;
  hypothesis: string;
  partialEdits: number;
  lastChunkSentAt: number;
  startedAt: number;
}

async function measure(streamFn: StreamFn, audioPath: string): Promise<RunResult> {
  const result: RunResult = {
    hypothesis: "", partialEdits: 0, lastChunkSentAt: 0,
    startedAt: performance.now(),
  };
  let prevPartial = "";

  await streamFn(audioPath, (kind, text) => {
    const now = performance.now();
    if (kind === "partial") {
      if (text && result.firstPartialMs === undefined)
        result.firstPartialMs = now - result.startedAt;
      if (text !== prevPartial) { result.partialEdits++; prevPartial = text; }
    } else { // final
      result.hypothesis = result.hypothesis ? `${result.hypothesis} ${text}` : text;
      if (result.lastChunkSentAt)
        result.finalLagMs = now - result.lastChunkSentAt;
    }
  }, result);

  return result;
}

वर्ड एरर रेट नॉर्मलाइज़्ड टेक्स्ट पर मानक टोकन-लेवल Levenshtein डिस्टेंस है। इसे कैलकुलेट करने से पहले रेफरेंस और हाइपोथिसिस, दोनों को समान रूप से लोअरकेस करें और विराम चिह्न हटाएं, वरना आप मॉडल के बजाय अपने नॉर्मलाइज़र को मापेंगे। इस माप को ऐसे लूप में रखें जो हर फ़ाइल को प्रति प्रोवाइडर ~10 बार चलाए और मीडियन टाइम-टू-फर्स्ट-पार्शियल व मीडियन WER (p50/p95) रिपोर्ट करे, क्योंकि एकल सैंपल पर नेटवर्क वेरिएंस का प्रभाव अधिक होता है।

इसे चलाने के लिए आपको दो चीज़ें देनी होंगी। पहले, हर सिस्टम के लिए एक StreamFn अडैप्टर लिखें। ऊपर वाला स्क्रिप्टेबल क्लाइंट पहले से एक है; अन्य अडैप्टर उसी (audioPath, onEvent, result) कॉन्ट्रैक्ट का पालन करेंगे और आखिरी ऑडियो चंक भेजते समय result.lastChunkSentAt सेट करेंगे। दूसरा, अपनी ऑडियो फ़ाइलें और रेफरेंस लोड करें और उन पर measure कॉल करें। इसे अपने डिप्लॉयमेंट का प्रतिनिधित्व करने वाली मशीन से, अपने यूज़र्स जैसे ऑडियो पर चलाएं, और आपको दोहराया जा सकने वाला तुलना परिणाम मिलेगा।

रियल-टाइम स्पीच टू टेक्स्ट हासिल करने का सार 

इस लेख में हमने कई आर्किटेक्चरल बदलाव कवर किए हैं, जिनसे आप अपने सिस्टम को चरणबद्ध ढंग से बेहतर बना सकते हैं और रियल-टाइम स्पीच टू टेक्स्ट की ओर बढ़ सकते हैं।

प्रोडक्शन रियल-टाइम STT सिस्टम कुछ अहम फैसलों पर निर्भर करता है:

  • ट्रांसपोर्ट: सरलता और नियंत्रित नेटवर्क के लिए WebSocket चुनें; लॉस टॉलरेंस की ज़रूरत होने और उपभोक्ता डिवाइसों से कैप्चर करने पर WebRTC चुनें।
  • पार्शियल और फाइनल: पार्शियल को अस्थायी और फाइनल को कमिटेड मानें, और उन्हें अलग तरह से रेंडर करें ताकि यूज़र लाइव टेक्स्ट पर भरोसा कर सकें।
  • एंड-पॉइंटिंग: हैंड्स-फ़्री सेगमेंटेशन के लिए VAD, ओवरराइड के लिए मैन्युअल कमिट उपयोग करें और साइलेंस थ्रेशहोल्ड को किसी स्थिर मान के बजाय इंटरैक्शन के अनुसार ट्यून करें।
  • इन-स्ट्रीम फीचर्स: इन-स्ट्रीम फीचर्स सिर्फ वहीं चालू करें जहां लाइव अनुभव में उनकी ज़रूरत हो, और बाकी को Scribe v2 के बैच पास के लिए छोड़ दें।
  • ऑडियो फ़ॉर्मेट: छोटे PCM फ़्रेम में कैप्चर करें, ~100ms चंक भेजें और टेलीफ़ोनी के लिए स्रोत फ़ॉर्मेट से मेल रखें।
  • बेंचमार्किंग: अपने ऑडियो और लक्ष्य मेट्रिक के आधार पर सटीकता-बनाम-लेटेंसी सेटिंग को अनुभवजन्य रूप से तय करें।
  • API सुरक्षा: अपनी API key सर्वर पर रखें, या सीधे क्लाइंट कनेक्शन के लिए सिंगल-यूज़ टोकन बनाएं।

अगर आप जानना चाहते हैं कि वॉइस एजेंट में लेटेंसी कैसे ऑप्टिमाइज़ करें, तो हमने इसके लिए भी एक गाइड लिखी है।

Scribe v2 Realtime के साथ रियल-टाइम स्पीच टू टेक्स्ट सिस्टम बनाएं

Scribe v2 Realtime लगभग 150ms की मॉडल लेटेंसी में पार्शियल देता है। आपके यूज़र को यही संख्या महसूस होगी या इससे अधिक, यह उसके आसपास के आर्किटेक्चर पर निर्भर करता है, जिसे आप नियंत्रित करते हैं। इस लेख में दी गई रणनीतियों से आप ऐसी बेहतर पाइपलाइन आर्किटेक्चर बनाएंगे जो लेटेंसी घटाए और ग्राहक अनुभव सुधारे। 

और गहराई से जानने के लिए स्पीच टू टेक्स्ट क्षमताओं का ओवरव्यू देखें, पूरी फीचर और भाषा सूची के लिए हमारे मॉडल्स रेफरेंस को पढ़ें, और रियल-टाइम प्रोडक्ट पेज देखें: रियल-टाइम स्पीच टू टेक्स्ट API और रियल-टाइम स्पीच टू टेक्स्ट।

जब आप बनाना शुरू करने के लिए तैयार हों, मुफ़्त ElevenLabs अकाउंट बनाएं और आज ही अपना पहला ट्रांसक्रिप्ट स्ट्रीम करें।

रियल-टाइम स्पीच टू टेक्स्ट लेटेंसी FAQ

लेखक

Tadas Petra एक डेवलपर रिलेशंस इंजीनियर हैं, जो डेवलपर्स को ElevenLabs के प्रोडक्ट्स के साथ काम करने में मदद करते हैं। वह Hungrimind के फाउंडर भी हैं, जो एक डेवलपर एजुकेशन प्लेटफॉर्म है। इससे पहले, वह Agora में लीड डेवलपर एडवोकेट और Zero To Mastery में इंस्ट्रक्टर थे।

संबंधित लेख

उच्चतम गुणवत्ता वाले AI ऑडियो के साथ बनाएं