






















@@ -15,6 +15,8 @@ const MAX_AUDIO_BASE64_BYTES = 512 * 1024;
1515const MAX_TRANSCRIPTION_SESSIONS_PER_CONN = 2;
1616const MAX_TRANSCRIPTION_SESSIONS_GLOBAL = 64;
1717const TRANSCRIPTION_EVENT = "talk.event";
18+const RELAY_INPUT_ENCODING = "g711_ulaw";
19+const RELAY_INPUT_SAMPLE_RATE_HZ = 8000;
18201921type TalkTranscriptionRelayEventPayload =
2022| { transcriptionSessionId: string; type: "ready" }
@@ -54,14 +56,92 @@ type TalkTranscriptionRelaySessionResult = {
5456transport: "gateway-relay";
5557transcriptionSessionId: string;
5658audio: {
57-inputEncoding: "pcm16";
58-inputSampleRateHz: 24000;
59+inputEncoding: "g711_ulaw";
60+inputSampleRateHz: 8000;
5961};
6062expiresAt: number;
6163};
62646365const transcriptionSessions = new Map<string, TranscriptionRelaySession>();
646667+function normalizeRelayInputEncoding(
68+value: unknown,
69+): "g711_ulaw" | "g711_alaw" | "pcm16" | undefined {
70+if (typeof value !== "string") {
71+return undefined;
72+}
73+const normalized = value.trim().toLowerCase();
74+if (!normalized) {
75+return undefined;
76+}
77+if (
78+normalized === "mulaw" ||
79+normalized === "ulaw" ||
80+normalized === "g711_ulaw" ||
81+normalized === "g711-mulaw" ||
82+normalized === "pcm_mulaw" ||
83+normalized === "audio/pcmu" ||
84+normalized === "ulaw_8000"
85+) {
86+return "g711_ulaw";
87+}
88+if (
89+normalized === "alaw" ||
90+normalized === "g711_alaw" ||
91+normalized === "g711-alaw" ||
92+normalized === "pcm_alaw"
93+) {
94+return "g711_alaw";
95+}
96+if (
97+normalized === "pcm" ||
98+normalized === "pcm16" ||
99+normalized === "linear16" ||
100+normalized === "pcm_s16le"
101+) {
102+return "pcm16";
103+}
104+return undefined;
105+}
106+107+function readFiniteNumber(value: unknown): number | undefined {
108+const next =
109+typeof value === "number"
110+ ? value
111+ : typeof value === "string"
112+ ? Number.parseFloat(value)
113+ : undefined;
114+return Number.isFinite(next) ? next : undefined;
115+}
116+117+function inferSampleRateFromAudioFormat(value: unknown): number | undefined {
118+if (typeof value !== "string") {
119+return undefined;
120+}
121+const match = value.match(/_(\d+)$/);
122+return match ? readFiniteNumber(match[1]) : undefined;
123+}
124+125+function assertRelayInputAudioConfig(providerConfig: RealtimeTranscriptionProviderConfig): void {
126+const encodingValue =
127+providerConfig.encoding ?? providerConfig.audioFormat ?? providerConfig.audio_format;
128+const encoding = normalizeRelayInputEncoding(encodingValue);
129+if (encoding && encoding !== RELAY_INPUT_ENCODING) {
130+throw new Error(
131+`Gateway transcription relay requires ${RELAY_INPUT_ENCODING}/${RELAY_INPUT_SAMPLE_RATE_HZ} audio`,
132+);
133+}
134+135+const sampleRate =
136+readFiniteNumber(providerConfig.sampleRate ?? providerConfig.sample_rate) ??
137+inferSampleRateFromAudioFormat(encodingValue);
138+if (sampleRate && sampleRate !== RELAY_INPUT_SAMPLE_RATE_HZ) {
139+throw new Error(
140+`Gateway transcription relay requires ${RELAY_INPUT_ENCODING}/${RELAY_INPUT_SAMPLE_RATE_HZ} audio`,
141+);
142+}
143+}
144+65145function broadcastToOwner(
66146context: GatewayRequestContext,
67147connId: string,
@@ -137,6 +217,7 @@ export function createTalkTranscriptionRelaySession(
137217params: CreateTalkTranscriptionRelaySessionParams,
138218): TalkTranscriptionRelaySessionResult {
139219enforceTranscriptionSessionLimits(params.connId);
220+assertRelayInputAudioConfig(params.providerConfig);
140221const transcriptionSessionId = randomUUID();
141222const expiresAtMs = Date.now() + TRANSCRIPTION_SESSION_TTL_MS;
142223const talk = createTalkSessionController(
@@ -262,8 +343,8 @@ export function createTalkTranscriptionRelaySession(
262343transport: "gateway-relay",
263344 transcriptionSessionId,
264345audio: {
265-inputEncoding: "pcm16",
266-inputSampleRateHz: 24000,
346+inputEncoding: RELAY_INPUT_ENCODING,
347+inputSampleRateHz: RELAY_INPUT_SAMPLE_RATE_HZ,
267348},
268349expiresAt: Math.floor(expiresAtMs / 1000),
269350};
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。