Ուղիղ տառադարձումը գալիս է երկու անգամ. նախ՝ որպես փոփոխվող նախնական տարբերակ, ապա՝ որպես վերջնական տող, որը չի փոխվում։ Նախնականը ներկայացրեք որպես փոխարինվող պոչ, իսկ վերջնականը՝ որպես պատմություն, և ուղիղ ենթագրերի դիտման մնացած մասը պարզապես կապակցում է։
Ուղիղ սեսիան VocalStack API-ի ամենափոքր գործողությունն է։ Ձեր սերվերը կատարում է մեկ HTTP կանչ և ստանում ստորագրված streamUrl։ Ձեր կլիենտը բացում է WebSocket այդ URL-ով, ուղարկում է չմշակված ձայնը հենց որ ձայնագրվում է, և խոսողից մի քանի վայրկյան հետո ստանում է տառադարձված տեքստը՝ որպես JSON տեքստային շրջանակներ։ Երբ ձայնն ավարտվում է, դուք ուղարկում եք eof, սերվերն ավարտում է մշակումը, հաղորդում, թե ինչ է չափել սեսիան, և փակում կապը։
Ոչինչ չի պահվում։ Տառադարձված տեքստը գոյություն ունի միայն կապի վրա, և հենց դա է պետք ենթագրերի դիտմանը, հանդիպման վերածածկույթին կամ ձեր հավելվածի ձայնային գործառույթին։ Եթե ցանկանում եք, որ խոսքը պահպանվի, կիսվի և հետո արտահանվի, ձեզ պետք է ուղիղ տառադարձում և հեռարձակում. վերջին բաժինը հենց այդ ուղղությամբ է ցույց տալիս։
Կառուցվածքը նույնն է, ինչ շատ հոսքային խոսքի API-ների մեծ մասում․ նույնականացման տվյալների փոխանակում HTTP-ով, երկար կյանքով սոկետ, որի մեջ ձայնն է մտնում, իսկ JSON-ը՝ դուրս գալիս, և մի եղանակ՝ նախնական տեքստը վերջնականից տարբերելու համար։ Անունները տարբեր են մի API-ից մյուսը, իսկ շարժվող մասերը գրեթե երբեք։ Ահա դրանք հերթականությամբ՝
POST /v1/realtime/sessions ձեր API բանալիով, ձեր սերվերից։ Պատասխանը { sessionId, streamUrl, expiresAt } է։streamUrl-ին։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;
}
});
Չմշակված (raw) կլիենտը պետք է ինքը մշակի ևս երկու բան. SDK-ներն արդեն դա անում են։ Առաջին, մերժված կապի թարմացումը (upgrade) կրում է API-ի սովորական HTTP սխալի պատասխանը՝ 401 invalid_token, 410 session_expired կամ 503 unavailable՝ Retry-After վերնագրով, որը պետք է հաշվի առնել։
Երկրորդ, գերբեռնվածությունը նման է լռության, ոչ թե սխալի։ Եթե 30 վայրկյանի ընթացքում ոչ մի շրջանակ չի գալիս, մեկնաբանեք այն որպես capacity_exceeded և նորից միացեք. նոր սեսիան սովորաբար անցնում է։ Եթե բեռնման շրջանակները շարունակում են գալ, բայց session.ready երբեք չի գալիս, մեկնաբանեք այն որպես engine_unavailable, որը նույնպես կրկնվող է։ SDK-ներում RealtimeSession.connect-ը լուծվում է session.ready-ի դեպքում և readyDeadlineMs-ից հետո ձախողվում է համապատասխան կոդով։
Երբ session.ready-ը ստացվում է, սերվերը ձայնը ակնկալում է այն արագությամբ, որով այն արտասանվում է։
Ձայնն ուղարկվում է որպես երկուական շրջանակներ՝ չմշակված PCM-ով. 32-bit float, little-endian, 16 kHz, մոնո (pcm_f32le, սեսիայի լռելյայն audio ձևաչափը)։ Ուղարկեք այն այնպես, ինչպես ձայնագրվում է, մոտ 100-ից 250 ms մասերով, առանց սպասելու տառադարձմանը մասերի միջև։
Բայթերի հաշվարկը արժե մեկ անգամ անել։ Վայրկյանում 16,000 նմուշ՝ յուրաքանչյուրը 4 բայթ, կազմում է 64,000 բայթ վայրկյանում, ուստի 100 ms-ը 6,400 բայթ է, իսկ 250 ms-ը՝ 16,000։ Խոսքի մոդելները սովորաբար վարժեցվում են 16 kHz մոնո ձայնով, ինչն է պատճառը, որ այդքան շատ ուղիղ API-ներ այն են պահանջում, մինչդեռ խոսափողներն ու բրաուզերները ձայնագրում են 44.1 կամ 48 kHz-ով և հաճախ ստերեոյով։ Բայթերն ուղարկելուց առաջ ինչ-որ բան պետք է վերանմուշակի ձայնը և միավորի ալիքները մեկի մեջ. այստեղ դա ffmpeg-ն է, իսկ բրաուզերում՝ SDK-ի խոսափողի օգնականը։ 48 kHz ձայնը ուղարկելը և 16 kHz հայտարարելը չի ձախողվում. այն տառադարձում է դանդաղեցված խոսքը, վատ։
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-ը այդ հերթի չափն է, և առողջ կապի դեպքում այն մոտ է զրոյի։ Ուղիղ ձայնը չի կարող դադարեցվել, ուստի հերթի աճին օգտակար արձագանքը նրա նկատումն է, ոչ թե շրջանակների թողնելը։ Սերվերն էլ է կիրառում հետադարձ ճնշում (backpressure). այն ընդունում է ձայնը իրական ժամանակից մի քանի վայրկյան առաջ (limits.audioBufferS), ապա կանգ է առնում կարդալուց, մինչև հասնի։ Հետ ուղղությամբ վերջնական տողերը հերթ են մտնում առանց կորստի, մինչդեռ նախնական տողերը միավորվում են, ուստի դանդաղ ընթերցողը ավելի քիչ, բայց թարմ նախնական տողեր է տեսնում։ Կլիենտը, որը չի կարողանում հետևել նույնիսկ վերջնական տողերին, ստանում է error շրջանակ slow_consumer-ով և retryable: false, և անջատվում է. եթե այն տեսնում եք, ձեր հաղորդագրությունների մշակիչը չափից շատ աշխատանք է կատարում։
Բրաուզերում @vocalstack/sdk/mic-ից streamMicrophone-ը խնդրում է խոսափողը, վերանմուշակում է 16 kHz-ի և ուղարկում մասերը 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) սիգնատուրան ամբողջ UI-ի օրինաչափությունն է. հաջորդ բաժինը բացատրում է, թե ինչու։
Տառադարձումը կառուցվում է երկու տեսակի շրջանակներով, և դրանք խառնելը ամենատարածված սխալն է։
Նախ՝ ինչու են երկուսը ընդհանրապես։ Խոսքի մոդելը բառի որոշումը մասամբ կայացնում է դրան հաջորդող բառերով. "I scream" և "ice cream" նույնն են հնչում, մինչև նախադասությունը շարունակվի։ Ուստի ուղիղ ճանաչիչը վաղ արտանետում է ենթադրություն, հետո այն վերանայում է, երբ ավելի շատ ձայն է գալիս, և ամրանում է միայն այն ժամանակ, երբ բավականաչափ է լսել։ Խոսքի յուրաքանչյուր հոսքային API սա որոշ ձևով բացահայտում է. ոմանք ենթադրությունները կոչում են "interim" կամ "partial" արդյունքներ և ամրացվածները նշում is_final-ով. բառապաշարը տարբեր է, սկզբունքը՝ ոչ։ Այստեղ ենթադրությունը նախնական տարբերակն է, իսկ ամրացված տեքստը՝ վերջնականը։
transcript.draft-ը ուղիղ պոչն է. վերջին վերջնական տողից ի վեր լսված բառերը, որոնք դեռ փոխվում են։ Յուրաքանչյուր նախնական տարբերակ նախորդի լրիվ փոխարինումն է։ Նկարեք պոչը նորից նրա segments-ով. երբեք մի ավելացրեք այն։ Նախնական տարբերակները նախագծով seq չեն կրում, ուստի մի փորձեք դրանք համապատասխանեցնել այն վերջնական տողերին, որոնց դառնում են։
transcript.committed-ը վերջնական է. մեկ segment՝ seq-ով (0, 1, 2, առանց բացերի), start, end, text, speaker և words՝ յուրաքանչյուր բառի ժամանակներով։ Ավելացրեք այն և հեռացրեք այն նախնական տարբերակը, որը ծածկում է. հաջորդ նախնականը սկսվում է այնտեղից, որտեղ վերջացել է այս վերջնականը։
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
UI, որը նախնական տարբերակներն ավելացնում է, ցուցադրում է կրկնվող, թարթող տեքստ. ձեր ենթագրերի դիտումը սկսում է կակազել, ինչ մարդը երբեք չէր կակազում։ Նկարեք բոլոր վերջնական հատվածները, ապա դրանցից հետո վերջին նախնականը, և երբ վերջնական տարբերակ է ստացվում, մաքրեք նախնականը։ Վերջնական տարբերակը կարող է պարունակել բառեր, որոնք երբևէ չեն հայտնվել ոչ մի նախնական տարբերակում։
Ահա մի քանի վայրկյան խոսք կապի վրա՝ վերևի 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-ը նախնական է, մինչ այն բաց է, և վերջնական՝ երբ փակվում է։ Թարմացրեք (upsert) 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 kHz մոնո։ Երկար նախադասությունը գալիս է մասերով (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 ձեր սերվերից, ոչ թե մեկ այլ փորձ հին URL-ով։Պահեք սեսիա ստեղծող երթուղին էժան կանչելու համար և դիտեք փակումը որպես նոր սկիզբ։ Վերսկսման բացակայությունը այն գինն է, որ վճարվում է ոչինչ չպահելու համար. եթե տառադարձությունը պետք է սոկետից դուրս գոյատևի, պահեք այն։
Երբ ցանկանում եք, որ նույն հոսքը պահվի որպես ձայնագրություն, հետո ուղղվի, արտահանվի և կարդացվի իրենց լեզուներով ունկնդիրների կողմից, այն փոխարենը հրապարակեք որպես ուղիղ տառադարձում։ Ուղիղ եթերի ուղեցույցը նույն PCM սոկետը ներկայացնում է տառադարձման և հեռարձակման հղման միջոցով, իսկ լսելու ուղեցույցը ցույց է տալիս բաժանորդի կողմը, որտեղ յուրաքանչյուր ունկնդիր ընտրում է լեզու, իսկ ուշ միացողը lastSeq կուրսորից հետ է բռնում։ Թե ինչ է դա սենյակի համար, քննարկվում է մեկ հղում, բոլոր լեզուները հոդվածում։
Պահպանվող ֆայլի համար պահված ուղին ֆայլի տառադարձումն API-ով է։ Շրջանակների ամբողջական տեղեկատուն, բոլոր դաշտերով, Խոսափողի հոսքը ուղեցույցն է API հղումում։