पेश है Eleven v4पेश है Eleven v4, हमारा अब तक का सबसे भावपूर्ण मॉडल।

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

रीयल-टाइम स्पीच टू टेक्स्ट 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 में डिकोड कर सकते हैं और वही चंक पाइपलाइन में भेज सकते हैं।

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

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

पार्शियल (अंतरिम) हाइपोथेसिस अब तक मिले ऑडियो के आधार पर मॉडल का सबसे अच्छा अनुमान है। पार्शियल जानबूझकर अस्थिर होते हैं। अधिक ऑडियो आने पर मॉडल पहले के शब्दों को बदलता है: "I want to" बाद के संदर्भ से अस्पष्टता खत्म होने पर "I want two tickets" बन सकता है। ये तेज़ी से आते हैं (~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 सही डिफ़ॉल्ट है, क्योंकि यह आपके हाथ से टाइमिंग ट्रैक किए बिना बोलने की प्राकृतिक लय के अनुसार ढल जाता है।
  • मैन्युअल कमिट कंट्रोल: मैन्युअल कमिट कंट्रोल आपके ऐप्लिकेशन को चुप्पी से स्वतंत्र रूप से मौजूदा सेगमेंट फ़ाइनल करने का समय तय करने देता है। आप कमिट सिग्नल भेजते हैं, रिकग्नाइज़र मौजूदा सेगमेंट बंद करता है और फ़ाइनल भेजता है। जब आपके ऐप्लिकेशन को पहले से पता हो कि टर्न खत्म हो गया है, तब यह सही टूल है: पुश-टू-टॉक बटन छोड़ना, "भेजें" कार्रवाई या बाहरी टर्न-टेकिंग नीति।

ये दोनों साथ में अच्छे से काम करते हैं। एक सामान्य वॉइस एजेंट हैंड्स-फ़्री संचालन के लिए 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 सेट करेंगे। दूसरा, अपनी ऑडियो फ़ाइलें और रेफरेंस लोड करें और उन पर measures कॉल करें। इसे ऐसे मशीन से चलाएं जो आपके डिप्लॉयमेंट का प्रतिनिधित्व करती हो, ऐसे ऑडियो पर जो आपके यूज़र का प्रतिनिधित्व करता हो, और आपके पास दोहराया जा सकने वाला तुलना परिणाम होगा।

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

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

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

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

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

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

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

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

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

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

लेखक

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

संबंधित लेख

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