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

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

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

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

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

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

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

सारांश

  • रियल-टाइम स्पीच टू टेक्स्ट सिस्टम बनाने के लिए आर्किटेक्चर को बारीकी से ट्यून करना होता है, ताकि पूरी पाइपलाइन में लेटेंसी कम रहे।
  • ज़्यादातर पाइपलाइनों के लिए WebSocket सही डिफ़ॉल्ट है, हालांकि WebRTC अधिक जटिल होने के बावजूद कई फायदे देता है।
  • Voice Activity Detection हैंड्स-फ़्री सेगमेंटेशन संभालता है, जबकि मैनुअल कमिट आपके ऐप्लिकेशन को टर्न खत्म होने पर नियंत्रण देता है।
  • पार्शियल अस्थायी होते हैं और फाइनल कमिट किए जाते हैं, इसलिए इन्हें अलग तरीके से रेंडर करना चाहिए।
  • लगभग 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 में डिकोड कर सकते हैं और वही चंक्स पाइपलाइन में भेज सकते हैं।

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

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

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

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

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

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

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

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: "" });

दृश्य रूप से, कमिट किए टेक्स्ट को स्थिर और current को हल्के या इटैलिक स्टाइल में रेंडर करें, ताकि यूज़र समझ सके कि इसमें बदलाव हो सकता है।

एंड-पॉइंटिंग और Voice Activity Detection (VAD)

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

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

Scribe v2 Realtime इसके लिए दो पूरक तरीके देता है:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

वर्कलेट प्रोसेसर का मुख्य हिस्सा float-से-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 स्ट्रीम करने वाला ब्राउज़र क्लाइंट, Scribe v2 Realtime को ऑडियो रिले करने और ट्रांसक्रिप्ट वापस भेजने वाला Node सर्वर, और फ़ाइल या टेलीफ़ोनी ब्रिज से 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
});

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

// 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 अकाउंट बनाएं और आज ही अपना पहला ट्रांसक्रिप्ट स्ट्रीम करें।

रियल-टाइम स्पीच टू टेक्स्ट लेटेंसी: अक्सर पूछे जाने वाले सवाल

संबंधित लेख

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