Node.js में स्ट्रीमिंग TTS: ट्रांसपोर्ट, कोडेक और बफरिंग गाइड

Node.js में स्ट्रीमिंग टेक्स्ट-टू-स्पीच जोड़ना सीखें: लेटेंसी, हकलाहट और प्लेबैक गैप को खत्म करने के लिए ट्रांसपोर्ट, कोडेक और बफरिंग रणनीतियों का चयन करें।

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

असली इंजीनियरिंग तीन फैसलों में है: आप किस ट्रांसपोर्ट प्रोटोकॉल का उपयोग करते हैं, आप किस ऑडियो कोडेक का अनुरोध करते हैं, और आप प्रदाता (प्रोवाइडर) और क्लाइंट के बीच चंक्स (chunks) को कैसे बफ़र करते हैं। इन तीनों को सही तरीके से सेट कर लें और स्ट्रीमिंग टीटीएस (TTS) वाकई रीयल-टाइम महसूस होने लगेगा। इनमें से किसी एक को भी गलत करें और आप रात के 2 बजे सन्नाटे या अटकने की समस्या को डीबग कर रहे होंगे।

Node.js में "स्ट्रीमिंग टीटीएस" का असल मतलब क्या है

दो अलग-अलग ट्रांसपोर्ट पैटर्न मौजूद हैं, और वे एक-दूसरे की जगह नहीं ले सकते। पहला है HTTP बॉडी स्ट्रीमिंग (चंक्ड ट्रांसफर या SSE)। आप एक POST अनुरोध करते हैं, और एक पूरी ऑडियो फ़ाइल का इंतज़ार करने के बजाय, आप MDN वेब डॉक्स में बताए गए अनुसार for await...of एसिंक इटरेशन का उपयोग करके response.body को एक ReadableStream के रूप में पढ़ते हैं। प्रत्येक चंक बाइनरी ऑडियो पेलोड का एक हिस्सा होता है। आपका Node.js सर्वर उन बाइट्स को प्राप्त करता है और रिस्पॉन्स पूरा होने से पहले ही उन्हें आगे भेज देता है।

दूसरा है वेबसॉकेट (WebSocket) स्ट्रीमिंग। प्रदाता अलग-अलग इवेंट मैसेज भेजता है, जिसमें से प्रत्येक में एक ऑडियो डेल्टा होता है। ये डेल्टा अक्सर base64-एन्कोडेड PCM फ्रेम्स होते हैं। आप base64 ऑडियो डेल्टा को वापस रॉ Buffer ऑब्जेक्ट्स में डिकोड करते हैं और उन्हें अपने पाइपलाइन में धकेलते हैं।

कोडेक का चुनाव दोनों पैटर्नों के साथ इस तरह जुड़ता है कि लोग अक्सर हैरान रह जाते हैं। MP3 फ्रेम-लेवल हेडर के साथ एक कंप्रेस्ड फॉर्मेट है; सीधे <audio> को दिया गया एक आंशिक MP3 स्ट्रीम आमतौर पर तब तक रुका रहेगा जब तक कि ब्राउज़र का डिकोडर बिटरेट और कंटेनर संरचना निर्धारित करने के लिए पर्याप्त फ्रेम एकत्र नहीं कर लेता। इसके विपरीत, रॉ PCM या WAV बिना हेडर वाला सैंपल डेटा होता है (या एक हेडर जिसके बाद रॉ सैंपल्स होते हैं), जिसे एक AudioWorklet बिना किसी डिकोडिंग अस्पष्टता के धीरे-धीरे ले सकता है। OpenAI के TTS दस्तावेज़ स्पष्ट रूप से बताते हैं: "सबसे तेज़ रिस्पॉन्स समय के लिए, हम रिस्पॉन्स फॉर्मेट के रूप में wav या pcm का उपयोग करने की सलाह देते हैं।" यह सलाह फॉर्मेट के विकल्प देने वाले किसी भी प्रदाता पर लागू होती है।

प्रॉक्सी आर्किटेक्चर: बीच में Node.js

न्यूनतम पाइपलाइन कुछ इस तरह दिखती है:


आपका Node.js रूट तीन चीजें करता है: यह प्रदाता के पास ऑथेंटिकेट करता है (API की को सर्वर-साइड सुरक्षित रखते हुए), यह प्रदाता के स्ट्रीम का उपयोग करता है, और प्रदाता स्ट्रीम समाप्त होने से पहले ही ब्राउज़र रिस्पॉन्स में बाइनरी चंक्स को आगे भेज देता है।

प्रोडक्शन में कैंसिलेशन बहुत मायने रखता है। आउटबाउंड फ़ेच को एक AbortController में लपेटें और इसके abort() कॉल को क्लाइंट के close इवेंट या उपयोगकर्ता द्वारा शुरू की गई स्टॉप एक्टिविटी से जोड़ें:


const controller = new AbortController();
req.on('close', () => controller.abort());

const upstream = await fetch(TTS_ENDPOINT, {
  method: 'POST',
  headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
  signal: controller.signal,
});
const controller = new AbortController();
req.on('close', () => controller.abort());

const upstream = await fetch(TTS_ENDPOINT, {
  method: 'POST',
  headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
  signal: controller.signal,
});
const controller = new AbortController();
req.on('close', () => controller.abort());

const upstream = await fetch(TTS_ENDPOINT, {
  method: 'POST',
  headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
  signal: controller.signal,
});

प्रत्येक माइक्रो-चंक के आते ही उसे तुरंत प्ले करने की कोशिश न करें। ऑडियो पाइपलाइन में भेजने से पहले एक छोटी कतार (queue) में 80-200 मिलीसेकंड का ऑडियो डेटा एकत्र करें। यह प्री-बफ़र जिटर (jitter) को सोख लेता है और उस अटकने की समस्या को रोकता है जो तब दिखाई देती है जब कोई चंक थोड़ी देर से पहुंचता है।

विकल्प A: ब्राउज़र पर HTTP SSE स्ट्रीमिंग

कुछ भी आगे भेजने से पहले अपने रिस्पॉन्स हेडर सेट करें:


res.setHeader('Content-Type', 'audio/wav');
res.setHeader('Transfer-Encoding', 'chunked');
res.setHeader('X-Accel-Buffering', 'no'); // disables Nginx proxy buffering
res.setHeader('Content-Type', 'audio/wav');
res.setHeader('Transfer-Encoding', 'chunked');
res.setHeader('X-Accel-Buffering', 'no'); // disables Nginx proxy buffering
res.setHeader('Content-Type', 'audio/wav');
res.setHeader('Transfer-Encoding', 'chunked');
res.setHeader('X-Accel-Buffering', 'no'); // disables Nginx proxy buffering

फिर प्रदाता के रिस्पॉन्स बॉडी को इटरेट करें और प्रत्येक चंक को सीधे लिखें:


for await (const chunk of upstream.body) {
  res.write(chunk);
}
res.end();
for await (const chunk of upstream.body) {
  res.write(chunk);
}
res.end();
for await (const chunk of upstream.body) {
  res.write(chunk);
}
res.end();

ब्राउज़र की तरफ, रिंग बफ़र वाला एक MediaSource एक्सटेंशन या AudioWorklet इस स्ट्रीम को धीरे-धीरे ले सकता है। PCM/WAV के लिए, AudioWorklet स्ट्रीम किए गए PCM डेटा के बिना किसी रुकावट के प्लेबैक का एक बेहतर रास्ता है।

विकल्प B: PCM इवेंट्स के साथ वेबसॉकेट स्ट्रीमिंग

कुछ प्रदाता ऑडियो को बेस64-एन्कोडेड ऑडियो डेल्टा वाले अलग JSON संदेशों के रूप में भेजते हैं। Node.js साइड इसे डिकोड करता है और बाइनरी फ्रेम को आगे भेजता है:


ws.on('message', (data) => {
  const event = JSON.parse(data);
  if (event.type === 'audio_delta') {
    const pcmBuffer = Buffer.from(event.audio, 'base64');
    browserWs.send(pcmBuffer); // forward as binary frame
  }
});
ws.on('message', (data) => {
  const event = JSON.parse(data);
  if (event.type === 'audio_delta') {
    const pcmBuffer = Buffer.from(event.audio, 'base64');
    browserWs.send(pcmBuffer); // forward as binary frame
  }
});
ws.on('message', (data) => {
  const event = JSON.parse(data);
  if (event.type === 'audio_delta') {
    const pcmBuffer = Buffer.from(event.audio, 'base64');
    browserWs.send(pcmBuffer); // forward as binary frame
  }
});

एक चीज़ जो बिना किसी एरर के प्लेबैक को खराब कर देती है: बेमेल सैंपल रेट (mismatched sample rates)। यदि प्रदाता 44100 हर्ट्ज़ PCM भेजता है और आपका AudioContext 48000 हर्ट्ज़ पर इनिशियलाइज़ किया गया है, तो आपको पिच शिफ्ट और टेम्पो ड्रिफ्ट की समस्या मिलेगी। स्ट्रीम से मिलान करने के लिए हमेशा new AudioContext({ sampleRate: <provider_sample_rate> }) को इनिशियलाइज़ करें।

कोडेक और प्लेबैक के फैसले जो प्रोडक्शन में फेल हो जाते हैं

MP3 तब स्वीकार्य है जब उपयोगकर्ता प्लेबैक शुरू होने से पहले 1-2 सेकंड का इंतज़ार कर सकता है, या जब आप स्ट्रीम को डिस्क पर लिख रहे हों और बाद में उसे सर्व कर रहे हों। वास्तविक कम-विलंबता (low-latency) वाले स्पीच सिंथेसिस के लिए जहां पहले वाक्य के भीतर ही ऑडियो प्ले होना शुरू हो जाना चाहिए, यदि उपलब्ध हो तो प्रदाता से PCM या WAV का अनुरोध करें।

ब्राउज़र में स्ट्रीम किए गए PCM के बिना किसी रुकावट के प्लेबैक के लिए, रिंग बफ़र वाला AudioWorklet एक मानक तरीका है। वर्कलेट का process() कॉलबैक प्रत्येक ऑडियो फ्रेम (एक समय में 128 सैंपल्स) पर रिंग बफ़र से डेटा लेता है, इसलिए प्लेबैक जारी रहता है भले ही नेटवर्क चंक्स असमान रूप से पहुंचें।

TTS कन्टिन्यूएशन (जारी रखना) एक और अंतर-पैदा करने वाला कारक है जिसका शायद ही कभी उल्लेख किया जाता है। यदि आपका एप्लिकेशन LLM-जनरेटेड टेक्स्ट को एक-एक टोकन करके स्ट्रीम करता है और आप प्रति वाक्य एक नया TTS अनुरोध भेजते हैं, तो आपको वाक्यों के बीच एक झटकेदार रीसेट सुनाई देगा क्योंकि TTS मॉडल प्रत्येक टुकड़े के लिए फिर से शुरू होता है। एक कन्टिन्यूएशन मैकेनिज्म उन टोकन टुकड़ों को एक एकल निरंतर सिंथेसिस कार्य में बदल देता है। यह उद्यम संपर्क केंद्र (enterprise contact center) वॉयस एआई परिनियोजन के लिए महत्वपूर्ण है जहां बहु-वाक्य प्रतिक्रियाओं में स्वाभाविक लय सीधे कॉल की गुणवत्ता को प्रभावित करती है।

Smallest AI Lightning v3.1: ठोस एंडपॉइंट संदर्भ

Smallest AI का Lightning v3.1 मॉडल एकीकृत एंडपॉइंट्स पर तीन ट्रांसपोर्ट प्रदान करता है (Smallest AI दस्तावेज़, सितंबर 2026 से सत्यापित):

  • नॉन-स्ट्रीमिंग: POST /waves/v1/tts

  • SSE स्ट्रीमिंग ऑडियो: POST /waves/v1/tts/live

  • वेबसॉकेट स्ट्रीमिंग TTS: WSS /waves/v1/tts/live

Lightning v3.1 मूल 44.1 kHz सैंपल रेट पर काम करता है, इसलिए तदनुसार अपने AudioContext को sampleRate: 44100 पर इनिशियलाइज़ करें।

वॉइस क्लोनिंग के लिए, Lightning v3.1 API या Smallest AI कंसोल के माध्यम से 5-15 सेकंड का साफ़ रेफरेंस ऑडियो स्वीकार करता है। ध्यान दें कि वॉइस क्लोनिंग Lightning v3.1 पर उपलब्ध है लेकिन Lightning v3.1 Pro पर नहीं। रेफरेंस ऑडियो सबमिट करने से पहले सहमति रिकॉर्ड एकत्र करें और स्टोर करें; अधिकांश उद्यम परिनियोजन (enterprise deployments) के लिए स्पष्ट सहमति दस्तावेज़ों की आवश्यकता होती है।

जब आपका एप्लिकेशन धीरे-धीरे टेक्स्ट जेनरेट करता है (उदाहरण के लिए, LLM टोकन स्ट्रीम से), तो टेक्स्ट के टुकड़ों को एक ही चल रहे सिंथेसिस सेशन में पास करने के लिए Lightning की कन्टिन्यूएशन सुविधा का उपयोग करें। यह अलग-अलग TTS कॉल्स को जोड़ने से मिलने वाले झटकेदार रीसेट के बिना टुकड़ों में आवाज़ को निरंतर बनाए रखता है।

उत्पादन समस्या निवारण: लक्षण और कारण


लक्षण

संभावित कारण

पूरी तरह सन्नाटा

प्रॉक्सी बफ़रिंग सक्षम है (X-Accel-Buffering: no जोड़ें), या उपयोगकर्ता जेस्चर के बाद AudioContext फिर से शुरू नहीं हुआ

कटकटाहट/अटकना

प्री-बफ़र बहुत छोटा है, या बिना कतार में रखे चंक्स सीधे भेजे जा रहे हैं

पिच/टेम्पो में गड़बड़ी

AudioContext सैंपल रेट प्रदाता के सैंपल रेट से मेल नहीं खाता (44100 बनाम 48000)

ऑडियो समय से पहले कट जाता है

AbortController समय से पहले ट्रिगर हो गया, या स्ट्रीम खाली होने से पहले ही रिस्पॉन्स का end इवेंट सक्रिय हो गया

मेमोरी बढ़ना

बिना किसी बैकप्रेशर के असीमित चंक कतार; कतार की गहराई को सीमित करें और अपस्ट्रीम फ़ेच पर बैकप्रेशर लागू करें

CORS एरर्स

TTS प्रदाता को सीधे ब्राउज़र से कॉल किया गया; API की को सर्वर-साइड रखने के लिए हमेशा Node.js के माध्यम से प्रॉक्सी करें

चंक आकार, पहले-बाइट-का-समय (time-to-first-byte), और कतार की गहराई को मेट्रिक्स के रूप में लॉग करें। यदि पहले-बाइट-का-समय लगातार 300 ms से अधिक हो जाता है, तो प्रदाता रूटिंग की जांच करें या देखें कि क्या आपका Node.js इंस्टेंस प्रदाता के इंफरेंस एंडपॉइंट के साथ सह-स्थित (co-located) है। स्व-होस्टेड परिनियोजन के लिए, प्रोडक्शनConcurrency स्तरों पर रीयल-टाइम थ्रूपुट बनाए रखने के लिए NVIDIA L40S 48 GB GPU पर Lightning v3.1 की सिफारिश की जाती है।

तीन निर्णय जो यह तय करते हैं कि TTS रीयल-टाइम लगता है या नहीं

पहला: ट्रांसपोर्ट। HTTP पर SSE को प्रॉक्सी और डीबग करना आसान है; वेबसॉकेट तब बेहतर होता है जब आपको द्विदिश (bidirectional) समन्वय की आवश्यकता होती है (मान लीजिए, STT इनपुट के आधार पर वाक्य के बीच में TTS को रोकना)। इसे अपने इंटरैक्शन मॉडल के आधार पर चुनें, न कि आदत के आधार पर।

दूसरा: ऑडियो फॉर्मेट। तत्काल प्लेबैक की आवश्यकता वाले किसी भी काम के लिए PCM या WAV। MP3 केवल तभी जब विलंबता (latency) सहनशीलता एक सेकंड से अधिक हो।

तीसरा: बफ़रिंग रणनीति। 80-200 ms का एक छोटा प्री-बफ़र और बैकप्रेशर के साथ एक सीमित कतार कटकटाहट और अटकने की अधिकांश शिकायतों को खत्म कर देती है। इसे क्लाइंट डिस्कनेक्ट इवेंट से जुड़े AbortController कैंसिलेशन के साथ जोड़ें।

पहले किसी भी प्रदाता के खिलाफ Node.js प्रॉक्सी पैटर्न बनाएं। एक बार पाइपलाइन काम करने लगे, तो अपस्ट्रीम एंडपॉइंट को बदलना सिर्फ एक लाइन का काम है। आर्किटेक्चर स्थिर रहता है; मॉडल इसके आसपास बेहतर होता जाता है।

अक्सर पूछे जाने वाले प्रश्न

स्ट्रीमिंग टीटीएस (TTS) के लिए मुझे किस ट्रांसपोर्ट का उपयोग करना चाहिए?

स्ट्रीमिंग के लिए MP3 की तुलना में PCM या WAV को क्यों चुनें?

प्लेबैक के लिए भेजने से पहले मुझे कितना ऑडियो बफ़र करना चाहिए?

मुझे रद्दीकरण (cancellations) और बीच में रोके गए स्ट्रीम (aborted streams) को कैसे संभालना चाहिए?

क्या बेमेल सैंपल दरें समस्याएं पैदा करेंगी?

लेख सुनें
2:00
लेख सुनें
2:00

एआई (AI) के साथ सारांशित करें

Node.js TTS की आवाज़ को वास्तव में रीयल-टाइम बनाएं

स्ट्रीमिंग ट्रांसपोर्ट और PCM बफरिंग के साथ लेटेंसी और स्टटरिंग (अटकने की समस्या) को कम करें।

एआई (AI) के साथ सारांशित करें

Node.js TTS की आवाज़ को वास्तव में रीयल-टाइम बनाएं

स्ट्रीमिंग ट्रांसपोर्ट और PCM बफरिंग के साथ लेटेंसी और स्टटरिंग (अटकने की समस्या) को कम करें।

वॉयस एजेंट ऑर्केस्ट्रेशन के भविष्य का निर्माण करें

311 कैलिफ़ोर्निया स्ट्रीट, सुइट 320
सैन फ्रांसिस्को, सीए 94104

वॉयस एजेंट ऑर्केस्ट्रेशन के भविष्य का निर्माण करें

311 कैलिफ़ोर्निया स्ट्रीट, सुइट 320
सैन फ्रांसिस्को, सीए 94104

वॉयस एजेंट ऑर्केस्ट्रेशन के भविष्य का निर्माण करें

311 कैलिफ़ोर्निया स्ट्रीट, सुइट 320
सैन फ्रांसिस्को, सीए 94104