Нақты уақыттағы транскрипт екі рет келеді: алдымен өзгеріп тұратын жоба ретінде, содан кейін өзгермейтін соңғы жол ретінде. Жобаны ауыстырылатын соңғы бөлік, ал бекітілген жолды тарих ретінде көрсетіңіз: тікелей субтитр көрінісінің қалған бөлігі — тек техникалық байланыс.
Нақты уақыттағы сессия — VocalStack API жасайтын ең қарапайым нәрсе. Сіздің серверіңіз бір HTTP шақыруын жасайды және қолтаңбалы streamUrl алады. Клиентіңіз осы URL-ге WebSocket ашады, аудионы түсірілген сайын шикі күйінде жібереді, ал транскриптті сөйлеушіден сәл кешігіп келетін JSON мәтін кадрлары ретінде оқиды. Аудио аяқталғанда eof жібересіз, сервер соңғы тазартуды орындайды, сессия өлшеген мәліметтерді хабарлайды және байланыс жабылады.
Ештеңе сақталмайды. Транскрипт тек желі арқылы ғана болады, ал бұл субтитр көрінісіне, кездесуге арналған қабатқа немесе өз қолданбаңыздағы дауыстық мүмкіндікке керек. Егер сөздерді кейін сақтап, бөлісіп, экспорттағыңыз келсе, оның орнына тікелей транскрипция мен трансляция керек; соңғы бөлім соған бағыттайды.
Бұл пішін көптеген ағынды сөйлеу API-леріне ортақ: HTTP арқылы тіркелгі мәліметтерін алмасу, аудио кіріп, JSON шығатын ұзақ өмірлі сокет, және уақытша мәтінді соңғы мәтіннен ажыратудың бір тәсілі. Атаулар бір API-ден екіншісіне өзгереді, бірақ жұмыс бөліктері сирек ерекшеленеді. Міне, реті бойынша:
POST /v1/realtime/sessions сұрауын API кілтіңізбен серверіңізде жіберіңіз. Жауап — { sessionId, streamUrl, expiresAt }.streamUrl-ге WebSocket ашыңыз.session.loading жүрек соғуларын session.ready келгенше күтіңіз.transcript.draft (соңғы бөлікті ауыстыру) және transcript.committed (соңғы жолды қосу) оқыңыз.{"type":"eof"} жіберіңіз; session.done оқыңыз.Тек бірінші қадамға кілтіңіз керек, сондықтан ол сіздің серверіңізде орындалады.
Developers, API keys бөлімінде Write рұқсаты бар кілт жасаңыз. Кілт серверлер мен скрипттерге арналған: API оны веб-беттен немесе URL-дан жіберілген болса қабылдамайды, сондықтан сессияны өз маршрутыңыздың ішінде жасайсыз, ал бет тек streamUrl-ді көреді.
URL-дың ішінде өз токені болады, себебі браузердің бір шектеуі бар, оны жалпы білген пайдалы. Браузердің WebSocket конструкторы сұраныс тақырыптарын орната алмайды, сондықтан кілт салатын Authorization тақырыбы жоқ; браузерге қарайтын әрбір сөйлеу API-і сокетті URL ішіндегі (немесе субпротокол атына жасырылған) қысқа мерзімді токенмен растайды. Қысқа мерзімділік — басты ой: URL логтарға және тарихқа түседі, сондықтан ішіндегі мәлімет мерзімі өтуі керек. expiresAt-тен бұрын қосылыңыз.
SDK-лар пакет тізілімінен емес, Developers аймағынан жүктеледі; жылдам бастау нұсқаулығындағы Install бөлімі осы ортаның өз командаларын көрсетеді. TypeScript SDK-мен серверлік маршрут былай көрінеді:
// On your server, behind a route of yours (POST /api/transcribe, say):
import { VocalStack } from '@vocalstack/sdk';
const vs = new VocalStack({ baseUrl: '{{api}}', apiKey: process.env.VOCALSTACK_API_KEY });
export async function createStream(): Promise<{ sessionId: string; streamUrl: string }> {
const { sessionId, streamUrl } = await vs.realtime.createSession({ spokenLanguage: 'en' });
return { sessionId, streamUrl };
}
SDK-сыз бір fetch жеткілікті. Бұл мысал испан тілін сұрайды және сөйлемдерді құрастыруды өшіреді, бұл төмендегі шикі үлгіге сай:
const res = await fetch('{{api}}/v1/realtime/sessions', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.VOCALSTACK_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ spokenLanguage: 'en', translate: ['es'], sentences: { enabled: false } }),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`); // the error envelope: { code, message, … }
const { streamUrl } = (await res.json()) as { sessionId: string; streamUrl: string; expiresAt: string };
Барлық опцияның әдепкі мәні бар. Бастапқыда білгені пайдалы болғандары: spokenLanguage (оны алып тастасаңыз, тіл автоматты түрде анықталады), translate (мақсатты тілдер тізімі), speakerLabels және latencyMode; қалғандары төменде қажеттілікке қарай қарастырылады.
streamUrl-ді аудионы түсіретін құралға беріңіз; осыдан кейін кілт қажет емес.
streamUrl-ге ешбір тақырыпсыз, қалпында қосылыңыз. Сессия іске қосылып жатқанда сервер session.loading жүрек соғуларын жібереді. Содан кейін session.ready келеді. Оған дейін аудио жібермеңіз. Ерте жіберілген аудио кейінге сақталмайды; сервер оны қате деп санап, error кадрын жібереді және сокетті жабады.
Сокет ашылғаннан кейін кез келген қате осы кадр пішінінде болады: {"type":"error","code":"…","message":"…","retryable":true|false}. code — жабық тізім (invalid_audio, invalid_options, invalid_token, capacity_exceeded, session_expired, slow_consumer, engine_unavailable, internal), message логтарыңызға арналған, ал retryable жаңа сессияны байқап көрудің мәнді екенін айтады. Оны бірінші кадрдан бастап өңдеңіз:
const ws = new WebSocket(streamUrl); // the token is in the URL: no header
ws.binaryType = 'arraybuffer';
ws.addEventListener('message', (m: MessageEvent) => {
if (typeof m.data !== 'string') return;
const f = JSON.parse(m.data);
switch (f.type) {
case 'session.loading':
console.error('starting…');
break;
case 'session.ready':
startMicrophone(); // send audio only after session.ready
break;
case 'error':
console.error(`${f.code}: ${f.message}`);
if (f.retryable) scheduleReconnect(); // a new session, with backoff; see "Stop"
break;
}
});
Шикі клиент тағы екі нәрсені өзі өңдеуі керек; SDK-да бұл бұрыннан орындалады. Біріншісі, қабылданбаған жаңарту API-дің әдеттегі HTTP қате конвертін алып жүреді: 401 invalid_token, 410 session_expired, немесе Retry-After тақырыбы бар 503 unavailable — оны сақтау керек.
Екіншісі, жүктеме шегіне жету қатеге емес, тыныштыққа ұқсайды. 30 секунд ішінде бірде-бір кадр келмесе, оны capacity_exceeded деп қабылдап, қайта қосылыңыз; жаңа сессия әдетте өтеді. Егер жүктеу кадрлары келе берсе, бірақ session.ready ешқашан келмесе, оны engine_unavailable деп қабылдаңыз, ол да қайталануға болатын қате. SDK-да RealtimeSession.connect session.ready кезінде аяқталады және readyDeadlineMs-тен кейін дұрыс кодпен сәтсіз болады.
session.ready келгеннен кейін сервер аудионы сөйлеу жылдамдығында күтеді.
Аудио 16 кГц, моно, little-endian 32-биттік float PCM (pcm_f32le, сессияның әдепкі audio форматы) бінарлық кадрлар ретінде жіберіледі. Оны түсірілген сайын, шамамен 100-ден 250 мс-қа дейінгі бөліктермен жіберіңіз; әр бөлік арасында транскриптті күтпеңіз.
Байт есебін бір рет жасап көру пайдалы. Секундына 16,000 үлгі, әр үлгі 4 байт, бұл секундына 64,000 байт, яғни 100 мс — 6,400 байт, ал 250 мс — 16,000 байт. Сөйлеу модельдері әдетте 16 кГц моно дыбыста оқытылады, сондықтан көптеген нақты уақыттағы API-лер осыны сұрайды, ал микрофондар мен браузерлер 44.1 немесе 48 кГц жиілікте, көбіне стерео түрінде түсіреді. Байттар жіберілмес бұрын бірдеңе аудионы қайта дискреттеуі және арналарды бір моноға біріктіруі керек: мұнда бұл ffmpeg, ал браузерде — SDK-ның микрофон көмекшісі. 48 кГц аудионы жіберіп, оны 16 кГц деп жариялау қате бермейді; бірақ сөйлеуді баяу қозғалыстағыдай, нашар транскрипциялайды.
Node-та ffmpeg ең қарапайым аудио құрылғы:
import { spawn } from 'node:child_process';
function startMicrophone(): void {
// Raw PCM: f32le, 16 kHz, mono, as binary frames. macOS: -f avfoundation -i :0, Linux: -f pulse -i default.
const mic = spawn('ffmpeg', ['-loglevel', 'error', '-f', 'avfoundation', '-i', ':0', '-f', 'f32le', '-ac', '1', '-ar', '16000', '-']);
mic.stdout.on('data', (chunk: Buffer) => {
// Backpressure: bytes the socket has accepted but not yet put on the wire.
// Two seconds of audio (128,000 bytes) queued means the link, not the server, is behind.
if (ws.bufferedAmount > 128_000) console.error(`link behind by ${ws.bufferedAmount} bytes`);
ws.send(new Uint8Array(chunk));
});
mic.on('close', () => ws.send(JSON.stringify({ type: 'eof' })));
process.once('SIGINT', () => mic.kill());
}
ws.send ешқашан бөгелмейді; ол кезекке қояды. bufferedAmount — сол кезектің өлшемі, қалыпты байланыста ол нөлге жақын тұрады. Тікелей аудионы кідіртуге болмайды, сондықтан өсіп келе жатқан кезекке ең пайдалы жауап — оны байқау, кадрларды тастау емес. Сервер де кері қысым қолданады: ол аудионы нақты уақыттан бірнеше секунд алдын ала қабылдайды (limits.audioBufferS), содан кейін оқуын тоқтатып, кешігуін жояды. Кері жолда бекітулер жоғалусыз кезекке қойылады, ал жобалар біріктіріледі, сондықтан баяу оқушы азырақ, жаңарақ жобаларды көреді. Бекітулерді де кезек өңдей алмаса, клиент slow_consumer error кадрын алады, retryable: false болады және ажыратылады; мұндай қате көрінсе, хабарлама өңдегішіңіз тым көп жұмыс істеп жатыр деген сөз.
Браузерде @vocalstack/sdk/mic ішіндегі streamMicrophone микрофонға рұқсат сұрайды, 16 кГц-ке қайта дискреттейді және бөліктерді clock кадрларымен бірге жібереді; олардың көмегімен сервер браузер қойындыны баяулатқанда үзілістерді белгілей алады. Бет сессияны сіздің маршрутыңыздан алады және кілтті ешқашан көрмейді:
import { RealtimeSession } from '@vocalstack/sdk';
import { streamMicrophone } from '@vocalstack/sdk/mic';
export async function transcribeMicrophone(
stream: { sessionId: string; streamUrl: string },
render: (committed: string, tail: string) => void,
): Promise<void> {
const session = await RealtimeSession.connect(stream);
const mic = await streamMicrophone(session); // asks for the microphone, sends 16 kHz PCM
document.querySelector('#stop')?.addEventListener('click', () => {
void mic.stop();
session.eof();
});
let committed = '';
for await (const ev of session) {
if (ev.type === 'transcript.draft') render(committed, ev.segments.map((s) => s.text).join(' '));
if (ev.type === 'transcript.committed') {
committed += `${ev.segment.text} `;
render(committed, '');
}
}
}
Бұл render(committed, tail) сигнатурасы — интерфейстің толық үлгісі; келесі бөлім неге екенін түсіндіреді.
Транскриптті құрайтын екі кадр түрі бар, және оларды шатастыру ең жиі кездесетін қате.
Алдымен, неге екеуі бар. Сөйлеу моделі сөздің не екенін ішінара оның артынан келетін сөздерге қарап шешеді: «I scream» және «ice cream» сөйлем жалғасқанша бірдей естіледі. Сондықтан нақты уақыттағы тану жүйесі алдымен болжам береді, содан кейін көбірек аудио келген сайын оны қайта қарайды, және жеткілікті естігенде ғана тұрақтанады. Барлық ағынды сөйлеу API-лері мұны әртүрлі жолмен көрсетеді. Кейбіреулері болжамдарды «аралық» немесе «ішінара» нәтижелер деп атайды, ал тұрақтанғандарын is_final деп белгілейді; атаулар әртүрлі, бірақ тәртіп бірдей. Мұнда болжам — жоба, ал тұрақтанған мәтін — бекіту.
transcript.draft — ағымдағы бөлік: соңғы бекіткеннен бері естілген сөздер, әлі өзгеріп жатыр. Әр жоба алдыңғысын толық ауыстырады. Ағымдағы бөлікті оның segments-і арқылы қайта салыңыз; оған ешқашан қоспаңыз. Жобаларда әдейі seq жоқ: оларды бекітулермен сәйкестендіруге тырыспаңыз.
transcript.committed — түпкілікті: seq (0, 1, 2, үзіліссіз), start, end, text, speaker және сөздердің уақыт белгілері бар words ішіндегі бір segment. Оны қосыңыз, ал оны жауып тұрған жобаны тастаңыз; келесі жоба осы бекітудің аяқталған жерінен басталады.
committed: [seq 0] [seq 1]
draft: "how are" ← frame 1
draft: "how are you do" ← frame 2 REPLACES frame 1
committed: [seq 0] [seq 1] [seq 2 "How are you doing?"]
← the commit consumes the tail; drop the draft
Жобаларды қосатын интерфейс қайталанған, жыпылықтаған мәтін көрсетеді. Субтитр көрінісінде сөйлеушіде болмаған кідіріс пайда болады. Барлық бекітілген сегменттерді көрсетіңіз, содан кейін соңғы жобаны соның артынан көрсетіңіз, ал бекіту келгенде жобаны тазартыңыз. Бекіту жобаларда ешқашан болмаған сөздерді де қамтуы мүмкін.
Міне, осы бөлімнің басындағы fetch мысалынан (translate: ["es"], сөйлем құрастыру өшірілген) бірнеше секундтық сөйлеу сым бойынша:
{"type":"session.loading"}
{"type":"session.ready"}
{"type":"transcript.draft","segments":[{"start":0.0,"text":"Good","speaker":0,"tentative":true}]}
{"type":"transcript.draft","segments":[{"start":0.0,"text":"Good morning every","speaker":0,"tentative":true}]}
{"type":"transcript.committed","segment":{"seq":0,"start":0.0,"end":1.4,"text":"Good morning, everyone.","speaker":0,"words":[{"start":0.0,"end":0.3,"text":"Good"},{"start":0.3,"end":0.8,"text":"morning,"},{"start":0.8,"end":1.4,"text":"everyone."}]}}
{"type":"transcript.translated","seq":0,"translations":{"es":"Buenos días a todos."}}
{"type":"transcript.draft","segments":[{"start":1.6,"text":"Thanks for","speaker":0,"tentative":true}]}
Бұларды айналдыратын тағы екі кадр бар. transcript.amended бекітілген сегментті орнында түзетеді (бірдей seq, жаңа text немесе speaker), сегменттегі сөз түзетілгенде; оны сіз салған жолға қолданыңыз. transcript.words бекітуден тура бұрын ағымдағы бөлік үшін сөз уақыттарын ағызады. Ол events.words-ті қажет етеді, ол әдепкі бойынша қосулы; бекіту оқиға опцияларына қарамастан әрқашан өзінің words-ін енгізеді. Соңында, белгісіз кадр түрлерін елемеңіз: жаңалары ескертусіз қосылады.
Міне, Python SDK-ның оқу циклі, терминал субтитр көрінісі ретінде. Бұл жерде де сөйлем құрастыру өшірулі, сондықтан аудармалар transcript.translated ретінде келеді, ал error кадры циклден VocalStackError ретінде шығады:
import asyncio
import os
from vocalstack import RealtimeSession, VocalStack, VocalStackError
MIC = ["ffmpeg", "-loglevel", "error", "-f", "avfoundation", "-i", ":0",
"-f", "f32le", "-ac", "1", "-ar", "16000", "-"]
async def main() -> None:
async with VocalStack("{{api}}", api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
created = await vs.realtime.create_session(
spoken_language="en", translate=["es"], sentences={"enabled": False}
)
session = await RealtimeSession.connect(created.session_id, created.stream_url)
mic = await asyncio.create_subprocess_exec(*MIC, stdout=asyncio.subprocess.PIPE)
async def feed() -> None:
assert mic.stdout is not None
while chunk := await mic.stdout.read(8000): # 125 ms of audio
await session.send_audio(chunk)
await session.eof() # the server flushes, then sends session.done
feeder = asyncio.create_task(feed())
try:
async with session:
async for event in session:
if event.type == "transcript.draft":
# The live tail: replace it, never append it.
tail = " ".join(s.text for s in event.segments)
print(f"\r\x1b[K\x1b[2m{tail}\x1b[0m", end="", flush=True)
elif event.type == "transcript.committed":
# Final: append it (seq 0, 1, 2...), and the tail it covers is gone.
print(f"\r\x1b[K{event.segment.text}", flush=True)
elif event.type == "transcript.translated":
print(f" es: {event.translations.get('es')}")
await feeder
print(f"done ({session.done_reason})", session.stats)
except VocalStackError as e:
# An `error` frame mid-stream, or a refused connect.
print(f"\nerror {e.code}: {e.message} (retryable: {e.retryable})")
finally:
if mic.returncode is None:
mic.kill()
asyncio.run(main())
Сегментке бөлінген аударма, жоғарыдағыдай, ең қарапайым түрі. Оқырмандар артық көретін түрі келесі бөлімде.
translate: ["es", "fr"] аудармаларды сессия барысында сұрайды. Олардың қалай келетінін сөйлем құрастыру (sentences: { enabled: true }) анықтайды, ал ол әдепкі бойынша қосулы.
Сөйлемдер өшірулі болғанда әр бекітуден кейін сәл соң transcript.translated кадры келеді, ол seq атап көрсетеді және тілдер бойынша кілттелген translations алып жүреді; оларды сол сегментке тіркеңіз. Осы уақытқа дейінгі барлық мысалдар осылай қолданылған. Бұл қарапайым және бекіту журналына ұқсайды: бір сегмент, бір аударма, сөйлем шекарасы сөйлеуші тыныс алған жерде.
Сөйлемдер қосулы болғанда (ол әдепкі бойынша осылай), сервер бекітулерді толық сөйлемдерге жинап, соларды аударады, бұл әлдеқайда жақсы оқылады. Әр transcript.sentence кадры sentenceId бойынша бір сөйлемнің толық ауыстыруы болып табылады. Оның status мәні open-ден closed-ке (немесе dropped, яғни оны тастау керек) өтеді, ал оның translations ашық кезде уақытша, жабылғанда түпкілікті болады. sentenceId бойынша жаңартыңыз, ешқашан қоспаңыз. TypeScript SDK дәл осы үшін applySentence жеткізеді:
import { applySentence, type SentenceSegment } from '@vocalstack/sdk';
let sentences: SentenceSegment[] = [];
for await (const ev of session) {
if (ev.type !== 'transcript.sentence') continue;
sentences = applySentence(sentences, ev.sentence); // upsert by sentenceId
if (ev.sentence.status === 'closed') console.log(`${ev.sentence.text}\n es: ${ev.sentence.translations?.es}`);
}
Аудармалар транскрипттен бір-екі секунд артта жүреді. Оларды дауыстап оқуды қаласаңыз, voice: ["es"] қосыңыз (ол сөйлемдерді қажет етеді). Әр жабылған сөйлемнің испан тіліндегі аудармасы transcript.voice кадры ретінде келеді, клипті base64 Ogg/Opus форматында, 24 кГц моно күйінде тасымалдайды. Ұзын сөйлем бөліктер (parts ішіндегі part) түрінде келеді, оларды бірінен соң бірін ойнатыңыз. Уақытша мәтін ешқашан айтылмайды. Тікелей аударма нұсқаулығы дауыстарды толық қарастырады, ал секундтар қайда кетеді кідіріс тізбегін түсіндіреді.
Мақсатты тілдер сессия құрылған кезде бекітілмейді.
session.update мәтіндік кадры spokenLanguage, translate, speakerLabels, latencyMode, voice немесе voiceByLang параметрлерін сол нүктеден бастап өзгертеді; өткізіп кеткен өрістер өзгеріссіз қалады.
{"type":"session.update","translate":["es","fr"],"latencyMode":"fast"}
SDK-да бұл session.update(…) ретінде көрсетіледі. translate тізімнің бәрін ауыстырады, ал [] аударманы өшіреді. Жеткізілген сегменттер қайта транскрипцияланбайды және қайта аударылмайды; дикторларды белгілеуді қоссаңыз, дикторлар тек осы нүктеден бастап белгіленеді. Айтылғаны айтылған күйінде қалады; параметрлер тек келесіні басқарады.
Аудио аяқталғанда {"type":"eof"} жіберіңіз (SDK-да session.eof()). Сервер өзінде бар мәтінді транскрипциялайды, соңғы бекітулерді жібереді, содан кейін session.done және байланысты жабады. Оның орнына cancel жіберсеңіз, ол бірден тоқтайды және қалғанын тастайды.
{"type":"session.done","reason":"eof","stats":{"audioS":30.0,"processingS":30.6,"realtimeFactor":0.98}}
reason — eof, max_duration (сессия жоспардың ең ұзақ ұзындығына жетті және сол тазартуды орындады) немесе cancelled. stats серверде өлшенеді: audioS — сіз жіберген аудио, processingS — сессияның қабырға сағатындағы уақыты, ал realtimeFactor — олардың қатынасы. Тікелей сессия 1-ден сәл төмен нәтиже береді, себебі қабырға сағаты сіздің eof-тан кейінгі соңғы тазартуды қамтиды. Бұдан едәуір төмен сан сессияның аудио ұзақтығынан ұзағырақ жұмыс істегенін көрсетеді; алдымен аудионың қалай келгенін тексеріңіз. Файл үшін сол сан транскрипттің ойнатудан неше есе жылдам қайтқанын көрсетеді.
Сессия GET /v1/realtime/capabilities ішіндегі limits.maxDurationS-тен аспайды, ал оның аудиосы жоспарыңыздың тікелей тарифі бойынша кредитті жұмсайды, кез келген транскрипция сияқты 5 минуттық минимумен: аудио алған әрбір сессия кем дегенде 5 минутқа төленеді (бағалар). Сол сокет бүкіл файлды да қабылдайды: сессияны input: { kind: "file" } арқылы жасаңыз, файлдың өз байттарын (mp3, wav, flac, ogg, m4a) және eof жіберіңіз, сонда транскрипт аудионың ойнауынан бірнеше есе жылдам қайтады, жолда transcript.progress кадрлары болады.
Жалғастыру мүмкіндігі жоқ. Байланыс үзілсе, сессия аяқталады; қайта қосылу жаңа сессия жасауды білдіреді, оның өз минимумымен төленеді, және тікелей көзіңізден ағынды қайтадан жіберу, seq 0-ден басталады. Қайта қосылудың өзі кез келген сокет API-і үшін стандартты үлгі, оны бір рет дұрыс жасаған пайдалы:
error кадрында retryable: true, 503 және оның Retry-After, немесе кадрсыз 30 секунд, ол capacity_exceeded дегенді білдіреді. invalid_token және session_expired серверіңізден жаңа streamUrl керек дегенді білдіреді, бұрынғысымен қайта әрекет жасау емес.Сессия жасайтын маршрутты шақыруға арзан ұстаңыз және жабылуды жаңа бастау ретінде қабылдаңыз. Жалғастырудың болмауы — ештеңе сақтамаудың бағасы; егер транскрипт сокеттен артық өмір сүруі керек болса, оны сақтаңыз.
Егер сол ағынды жазба ретінде сақтағыңыз, кейін түзеткіңіз, экспорттағыңыз және әр тілдегі тыңдаушылар оқығанын қаласаңыз, оның орнына оны тікелей транскрипцияға жариялаңыз. Тікелей эфирге шығу нұсқаулығы осы PCM сокетін транскрипция мен трансляция сілтемесі арқылы бірге өткізеді, ал тыңдау нұсқаулығы жазылушы жағын көрсетеді: әр тыңдаушы тілін таңдайды, ал кеш қосылған адам lastSeq курсоры арқылы қалғанын жеткізеді. Бұл бөлме үшін қалай көрінетіні бір сілтеме, барлық тіл мақаласының тақырыбы.
Сақтағыңыз келетін файл үшін сақталатын маршрут — API арқылы файлды транскрипциялау. Барлық өрістері бар толық кадр анықтамасы — API анықтамасындағы Микрофонды ағыту нұсқаулығы.