
ईयरबड्स वाला बग: जब दूसरा इंसान हमारे ट्रांसक्रिप्ट से गायब हो गया
डुअल-चैनल ट्रांसक्रिप्शन लैपटॉप स्पीकर और ओवर-ईयर हेडफ़ोन पर बिल्कुल सही चलता था। फिर किसी ने ईयरबड्स लगाए, और जिससे वे बात कर रहे थे वह इंसान बस ट्रांसक्रिप्ट से गायब हो गया — क्योंकि जिस पल वे बोले, Chromium ने चुपचाप हमारे ऑडियो को म्यूट कर दिया। ग्यारह फ़िक्स नाकाम रहे। बारहवें ने पूरी आर्किटेक्चर बदल दी।
GeekBye एक बातचीत के दो पहलुओं को ट्रांसक्राइब करता है: एक चैनल पर आपका माइक्रोफ़ोन, दूसरे पर दूसरे प्रतिभागी का ऑडियो (जो आपका कंप्यूटर बजा रहा है)। यह लैपटॉप स्पीकर पर बेदाग़ चलता था। यह ओवर-ईयर हेडफ़ोन पर चलता था। फिर एक उपयोगकर्ता ने ईयरबड्स लगाए, और जिससे वे बात कर रहे थे वह इंसान ट्रांसक्रिप्ट से गायब हो गया। न गड़बड़, न ग़लत नाम के साथ। गायब।
यह GeekBye v1.6.12 की कहानी है, वह बग जिसने ग्यारह नाकाम फ़िक्स ले लिए, और वह पल जब हमें एहसास हुआ कि हम ब्राउज़र के साथ काम करने के बजाय उससे लड़ रहे थे।
वह ट्रैक जो ज़िंदा था और खाली था
लक्षण डरावना था। आप एक मीटिंग शुरू करते, सब ठीक दिखता, आप एक शब्द बोलते — और उसी पल से, दूसरे इंसान का ऑडियो चैनल शुद्ध डिजिटल साइलेंस में चला जाता। हमने इसे मापा: सिस्टम-ऑडियो सिग्नल एक सामान्य ऊर्जा स्तर पर बैठा था (RMS करीब 0.03–0.06), और जिस पल माइक ने आपकी आवाज़ पकड़ी, वह 0.0000 पर गिर जाता और बाकी पूरे सेशन के लिए वहीं ठहरा रहता।
यहाँ वह हिस्सा है जिसने हमारे कई दिन खा लिए। हर ब्राउज़र API अड़ा रहता कि ऑडियो ट्रैक बिल्कुल स्वस्थ है। readyState: live. enabled: true. muted: false. प्लेटफ़ॉर्म ने जो भी स्टेटस फ़्लैग दिखाए, उन सबके हिसाब से स्ट्रीम ठीक थी। मरे हुए तो सैंपल थे — एक ज़िंदा पाइप जो शून्य के सिवा कुछ नहीं ढो रहा था। हमने बहुत देर तक अपने ही ऊर्जा-मीटर पर भरोसा नहीं किया, क्योंकि सरकारी कहानी कह रही थी कि ट्रैक काम कर रहा है।
ट्रिगर ख़ास ईयरबड्स के लिए था, और वजह भौतिक है: एक ईयरबड एक ही डिवाइस है जो एक साथ आपका माइक्रोफ़ोन इनपुट और आपका ऑडियो आउटपुट है। लैपटॉप स्पीकर और ओवर-ईयर हेडफ़ोन इन्हें अलग करते हैं — आउटपुट एक जगह जाता है, माइक कहीं और है। पर जब इनपुट और आउटपुट एक ही डिवाइस हों, तो परिभाषा के हिसाब से आपके पास एक फ़ीडबैक रास्ता है। हमारा पुख़्ता अनुमान (यह ब्राउज़र की अँतड़ियों में बसा है, इसलिए हम अध्याय-श्लोक का हवाला नहीं दे सकते) यह है कि Chromium एक-डिवाइस-पर-माइक-और-स्पीकर वाले उस आकार को भाँप लेता है, तय करता है कि यह एक चीख़ने को तैयार फ़ीडबैक लूप है, और बचाव में कैप्चर को म्यूट कर देता है — चुपचाप, बिना ऐप को उन किसी भी फ़्लैग के ज़रिए बताए जिन्हें एक ऐप पढ़ सकता है।
ग्यारह फ़िक्स, जिनमें से कई और बुरे
हमने पहले उन ज़ाहिर तरीक़ों से हमला किया, और हर कोशिश लिख ली। दोनों स्ट्रीम पर इको कैंसिलेशन, नॉइज़ सप्रेशन और ऑटो गेन बंद करो। दोनों स्ट्रीम को एक साथ शुरू करने के बजाय एक देरी के साथ बारी-बारी शुरू करो। हर स्ट्रीम को उसका अपना सैंपल रेट दो। एक साइलेंट गेन नोड जोड़ो। सब कुछ एक साझा ऑडियो कॉन्टेक्स्ट से गुज़ारो। AudioWorklet पर जाओ। सारे तरीक़े से नीचे उतरकर कच्चे WebCodecs ट्रैक प्रोसेसर तक पहुँचो।
ग्यारह तरीक़े। वे सिर्फ़ नाकाम ही नहीं रहे — कई ने हालात और बिगाड़े। एक ने 30-सेकंड का लैग ला दिया। दूसरे ने आपके माइक्रोफ़ोन का ट्रांसक्रिप्ट दूसरे इंसान के नाम से दिखा दिया। हम गहरे झाड़-झंखाड़ में थे, हर लक्षण का इलाज कर रहे थे, और बग सरकता रहा।
सफलता एक नए नज़रिए में थी: अब तक की हर फ़िक्स दो अलग स्ट्रीम को स्वस्थ रखने की कोशिश करती थी। पर दो अलग स्ट्रीम ठीक वही चीज़ थीं जिन्हें Chromium का फ़ीडबैक ह्यूरिस्टिक देख और म्यूट कर सकता था। क्या हो अगर तुलना करने को कुछ हो ही नहीं?
फ़िक्स: ब्राउज़र को एक स्ट्रीम दो, दो नहीं
v1.6.12 ने लड़ना बंद किया और आर्किटेक्चर बदल दी। माइक और सिस्टम ऑडियो को दो स्वतंत्र स्ट्रीम के रूप में भेजने के बजाय, यह ऐप से कुछ भी निकलने से पहले ही उन्हें क्लाइंट पर एक mono स्ट्रीम में मिक्स कर देता है। जब दोनों स्रोत एक ही पाइपलाइन से बहते हैं, तो ट्रैक अलग-अलग मर नहीं सकते — अब ब्राउज़र के भाँपने और म्यूट करने के लिए कोई दो-डिवाइस वाला फ़ीडबैक आकार बचा ही नहीं। ट्रिगर की शर्त बस मौजूद रहना बंद कर देती है।
एक प्यारा बोनस भी था। दो स्ट्रीम का मतलब था दो क्लाइंट-से-बैकएंड WebSocket और दो बैकएंड-से-प्रोवाइडर कनेक्शन; एक मिक्स्ड स्ट्रीम ने उसे एक और एक तक समेट दिया। ईयरबड्स बग को ठीक करने ने बतौर साइड इफ़ेक्ट हमारे ट्रांसक्रिप्शन खर्च को आधा कर दिया।
पर mono मिक्सिंग में एक ज़ाहिर दिक्कत है: अगर आप दोनों लोगों को एक चैनल में घोल दें, तो आपको फिर भी कैसे पता चलेगा कि किसने क्या कहा? आपने वही अलगाव फेंक दी है जो आपको बताती थी।
"किसने बोला" को ऊर्जा से फिर से बनाना
जवाब था हर स्रोत की तेज़ी को मिक्स करने से पहले मापना, और ऑडियो के हर चंक के साथ एक संकेत जोड़ना कि किस स्रोत का बोलबाला था — माइक, सिस्टम, या साइलेंस। जो स्रोत ज़्यादा तेज़, वह जीता। स्पीकर पर काफ़ी सीधा।
ईयरबड्स को एक और परत चाहिए थी, और यहीं इको फिर कहानी में लौटता है — पर उस दिशा से उल्टा जिसका आप अनुमान लगाते। ईयरबड्स पर, दूसरे इंसान की आवाज़ बड से रिसकर वापस माइक में घुस जाती है। तो बिना सावधानी के, उनका इको, आपके माइक चैनल पर पहुँचकर, आपके नाम चढ़ जाता है। फ़िक्स इको-जागरूक थ्रेशोल्ड का एक सेट है: जब सिस्टम ऑडियो एक छोटे फ़र्श से ऊपर बज रहा हो, तो आपके माइक्रोफ़ोन को "आप बोल रहे हैं" गिने जाने के लिए जो पैमाना पार करना है वह ख़ासा ऊपर उछल जाता है — एक सामान्य 0.015 से 0.1 तक। दूसरे शब्दों में: जब तक दूसरा इंसान बोल रहा है, आपके माइक को उनके इको से साफ़, बेशक तेज़ होना होगा तभी हम शब्दों को आपके नाम करेंगे। अस्पष्ट फ़्रेम इस मान्यता पर "सिस्टम" पर चले जाते हैं कि वे इको हैं। यह एक जानबूझकर रखा झुकाव है जो एट्रिब्यूशन को ठीक उसी हालात में सही कर देता है जो पहले ग़लत होता था।
वह प्रूफ़-ऑफ़-कॉन्सेप्ट — mono मिक्सिंग साथ में ऊर्जा-आधारित, इको-जागरूक वॉइस डिटेक्शन — उस एट्रिब्यूशन सिस्टम का पुरखा है जो GeekBye आज चलाता है। खुद को साबित कर देने के बाद उसी रिलीज़ में वह ढाँचा उखाड़ दिया गया, और ऊर्जा का गणित बाद में एक ठीक से परखे गए मॉड्यूल में कड़ा हुआ। पर v1.6.12 ही वह जगह है जहाँ यह विचार जन्मा, दबाव में, एक जोड़ी ईयरबड्स वाले उपयोगकर्ता के हाथों।
तीन चीज़ें जो ईयरबड्स बग ने सिखाईं
- एक स्वस्थ हैंडल मरा हुआ डेटा ढो सकता है। पूरी जाँच इसी पर टिकी कि हमने प्लेटफ़ॉर्म के स्टेटस फ़्लैग के बजाय अपने ऊर्जा-मीटर पर भरोसा किया —
live,enabled,unmuted, सब झूठ बोल रहे थे जबकि सैंपल शून्य थे। API की अपनी रिपोर्ट नहीं, पेलोड की निगरानी करो। अगर कोई स्ट्रीम ठीक होने का दावा करती है, तो नापो कि वह सचमुच है या नहीं। - जब प्लेटफ़ॉर्म आपकी आर्किटेक्चर से लड़े, तो आर्किटेक्चर बदलो — प्लेटफ़ॉर्म नहीं। हम Chromium के फ़ीडबैक बचाव को बहस में नहीं हरा सके, और किनारों से उसे बंद करने की हर कोशिश नाकाम रही या उल्टी पड़ी। दिखाई देने वाली शर्त को हटाना — दो स्ट्रीम का एक बन जाना — ने पूरी समस्या ही गायब कर दी। ब्राउज़र से लड़ाई जीतने के बजाय ट्रिगर हटाना लगभग हमेशा सस्ता होता है।
- जब आप मज़बूती के लिए चैनल समेटें, तो जो नष्ट किया उसे फिर से बनाना ही होगा। mono में मिक्स करने ने वह चैनल अलगाव मार दी जो "किसने बोला" ढोती थी। वह जानकारी मिक्स-से-पहले की ऊर्जा और इको-जागरूक थ्रेशोल्ड से फिर से निकालनी पड़ी। भरपाई करने वाला यह लॉजिक फ़िक्स के बाद का कोई ख़याल नहीं है — यह ही आधा फ़िक्स है।
यह उस विश्वसनीयता कहानी का चौथा अध्याय है जो GeekBye v2 बनती है। पिछले अध्याय के लिए, देखें एक कनेक्शन टूटने से पूरा ऐप क्रैश नहीं होना चाहिए (v1.6.8); जिस पाइपलाइन री-राइट पर यह ऑडियो काम टिका, उसके लिए हमने 5,000 लाइन ऑडियो कोड हटाया (v1.6.0); और पूरे चाप के लिए, सॉफ़्टवेयर को पूर्णता तक पहुँचाने का शरीरशास्त्र।