OpenAI 实时翻译

2026-11-06 12:39 更新

Python连接示例还需要安装sounddevice和websocket-client。Go应在发送音频时并发运行listen;收到session.closed后关闭传输。 实时翻译允许把源音频流发送到专用翻译会话,在讲话者仍说话时接收翻译音频和转录增量。适用于实时口译、多语言通话、广播、会议、课程及视频房间。

应用需要翻译人类讲话时,使用 gpt-realtime-translate。如果需要回答问题、调用工具、管理对话的助手,应在标准 Realtime 会话使用 gpt-realtime-2.1。

翻译会话有何不同

实时翻译与语音智能体使用不同架构:

语音智能体会话 翻译会话
连接 /v1/realtime。 连接 /v1/realtime/translations。
模型充当助手。 模型充当口译员。
使用对话与响应生命周期。 持续处理传入音频流。
可以调用工具并生成助手轮次。 生成翻译音频和转录增量。
可调用 response.create。 不调用 response.create。

翻译由音频流本身启动。持续追加音频,包括话语之间的静音,并在输出事件到达时处理。

选择传输方式

浏览器采集或播放音频时使用 WebRTC。源音频通过媒体轨道发送,翻译语音通过远端音轨接收,无需手动重采样或播放 PCM 块。

服务器已接收原始音频时,例如 Twilio Media Streams、SIP 媒体、广播输入或媒体工作进程,使用 WebSockets。此时发送 Base64 编码的 24 kHz PCM16 音频,并自行播放返回的音频增量。

创建浏览器 WebRTC 会话

浏览器应用应在服务器创建短期客户端密钥,不要向浏览器暴露标准 API 密钥。

创建翻译客户端密钥:

来源示例 1(javascript):

// [中文] 示例 1:创建浏览器 WebRTC 会话;以下保留本语言原始示例。
// [中文] 原代码明确设置的模型值:gpt-realtime-translate;各值的作用按其所在配置对象区分。
// [中文] 本段涉及的 HTTP 端点:/v1/realtime/translations/client_secrets;认证和请求体保持原示例形式。
app.post("/session", async (req, res) => {
  const language = req.body.targetLanguage ?? "es";

  const response = await fetch(
    "https://api.openai.com/v1/realtime/translations/client_secrets",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
        "Content-Type": "application/json",
        "OpenAI-Safety-Identifier": "hashed-user-id",
      },
      body: JSON.stringify({
        session: {
          model: "gpt-realtime-translate",
          audio: {
            output: { language },
          },
        },
      }),
    }
  );

  res.status(response.status).json(await response.json());
});

浏览器采集音频、创建对等连接,并把 SDP offer 提交到翻译通话端点:

连接浏览器翻译通话:

来源示例 2(javascript):

// [中文] 示例 2:创建浏览器 WebRTC 会话;以下保留本语言原始示例。
// [中文] 本段涉及的 HTTP 端点:/v1/realtime/translations/calls;认证和请求体保持原示例形式。
// [中文] 沿原示例执行这些方法:pc.addTrack、pc.createDataChannel、pc.createOffer、pc.setLocalDescription、pc.setRemoteDescription;异步调用及事件接收顺序保留。
const { value: clientSecret } = await fetch("/session", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ targetLanguage: "es" }),
}).then((response) => response.json());

const sourceStream = await navigator.mediaDevices.getUserMedia({
  audio: true,
});

const pc = new RTCPeerConnection();
pc.addTrack(sourceStream.getAudioTracks()[0], sourceStream);

const translatedAudio = new Audio();
translatedAudio.autoplay = true;
pc.ontrack = ({ streams }) => {
  translatedAudio.srcObject = streams[0];
};

const events = pc.createDataChannel("oai-events");
events.onmessage = ({ data }) => {
  const event = JSON.parse(data);
  if (event.type === "session.output_transcript.delta") {
    subtitles.textContent += event.delta;
  }
};

const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

const sdpResponse = await fetch(
  "https://api.openai.com/v1/realtime/translations/calls",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${clientSecret}`,
      "Content-Type": "application/sdp",
    },
    body: offer.sdp,
  }
);

if (!sdpResponse.ok) {
  throw new Error(await sdpResponse.text());
}

await pc.setRemoteDescription({
  type: "answer",
  sdp: await sdpResponse.text(),
});

创建 WebSocket 会话

连接专用翻译端点,在 URL 中选择模型。

运行示例前,Node.js 安装 ws,Python 安装 websocket-client,Ruby 安装 async-websocket(gem install async-websocket)。Java 添加 Maven 依赖 com.openai:openai-java:4.75.1。

Java 示例分别展示连接、语言配置与音频输入,不是完整翻译工作流。Java SDK 支持接收事件和结束会话,但本指南未提供这些步骤的 Java 示例;完整流程请跟随 Node.js 或 Python 示例。

连接翻译会话:

来源示例 3(javascript):

// [中文] 示例 3:创建 WebSocket 会话;以下保留本语言原始示例。
// [中文] 在可信服务器连接专用 translations WebSocket 端点,并通过查询参数选择翻译模型。
// [中文] 从环境变量读取标准密钥;安全标识符是示例值,实际应用应使用稳定且保护隐私的用户标识。
// [中文] 本段仅创建连接对象;语言配置、连续音频输入、输出接收和正常关闭见后续片段。
import WebSocket from "ws";

const ws = new WebSocket(
  "wss://api.openai.com/v1/realtime/translations?model=gpt-realtime-translate",
  {
    headers: {
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
      "OpenAI-Safety-Identifier": "hashed-user-id",
    },
  }
);

来源示例 4(python):

## [中文] 示例 4:创建 WebSocket 会话;以下保留本语言原始示例。
## [中文] 沿原示例执行这些方法:ws.connect;异步调用及事件接收顺序保留。
import os
import websocket

ws = websocket.WebSocket()
ws.connect(
    "wss://api.openai.com/v1/realtime/translations?model=gpt-realtime-translate",
    header=[
        f"Authorization: Bearer {os.environ['OPENAI_API_KEY']}",
        "OpenAI-Safety-Identifier: hashed-user-id",
    ],
)

// [中文] 用本语言客户端连接指定WebSocket端点,区分Live与Realtime的凭证、URL和事件;应用继续负责音频收发与会话生命周期。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
client := openai.NewClient()
session, err := client.Realtime.ConnectTranslation(ctx, "gpt-realtime-translate", realtime.TranslationConnectionOptions{}, option.WithHeader("OpenAI-Safety-Identifier", "hashed-user-id"))
if err != nil {
    return err
}
defer session.Abort()
created, err := session.Recv(ctx)
if err != nil {
    return err
}
fmt.Println(created.Type)
if created.Type != "session.created" {
    return fmt.Errorf("expected session.created, got %s", created.Type)
}

来源示例 5(java):

程序文字译释111:已连接翻译会话。

// [中文] 示例 5:创建 WebSocket 会话;以下保留本语言原始示例。
// [中文] 原代码明确设置的模型值:gpt-realtime-translate;各值的作用按其所在配置对象区分。
// [中文] 沿原示例执行这些方法:connection.receive;异步调用及事件接收顺序保留。
import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.helpers.TranslationConnection;
import com.openai.helpers.TranslationWebSocketOptions;
import com.openai.models.realtime.*;

var http = OkHttpClient.builder().build();
var options =
    ClientOptions.builder()
        .fromEnv()
        .httpClient(http)
        .putHeader("OpenAI-Safety-Identifier", "hashed-user-id")
        .build();
try (http;
    var connection =
        TranslationConnection.connect(
            options,
            TranslationWebSocketOptions.builder()
                .model("gpt-realtime-translate")
                .sendTimeout(java.time.Duration.ofSeconds(30))
                .build())) {
  while (true) {
    var event = connection.receive();
    if (event.error().isPresent())
      throw new IllegalStateException(event.error().orElseThrow().toString());
    if (event.sessionCreated().isPresent()) {
      System.out.println("Connected to translation session.");
      System.out.println(event);
      break;
    }
  }
}

来源示例 6(ruby):

程序文字译释112:会话创建前连接已关闭。

程序文字译释113:原本预期session.created,实际收到所示事件。

## [中文] 示例 6:创建 WebSocket 会话;以下保留本语言原始示例。
## [中文] 在异步上下文中连接翻译端点,并用环境变量中的服务端密钥认证。
## [中文] 读取第一条消息,确认类型为 session.created;无消息或类型不符即抛错。
## [中文] 离开连接块会关闭连接;应把后续配置、收发音频及 session.close/等待 session.closed 的流程放在块内,不能创建后立即退出。
require "async"
require "async/http/endpoint"
require "async/websocket/client"
require "json"

endpoint = Async::HTTP::Endpoint.parse("wss://api.openai.com/v1/realtime/translations?model=gpt-realtime-translate", timeout: 10, alpn_protocols: ["http/1.1"])
headers = {
  "Authorization" => "Bearer #{ENV.fetch("OPENAI_API_KEY")}",
  "OpenAI-Safety-Identifier" => "hashed-user-id"
}
Sync do |task|
  task.with_timeout(120) do
    Async::WebSocket::Client.connect(endpoint, headers: headers) do |connection|
      message = connection.read or raise "Connection closed before session creation"
      event = JSON.parse(message.to_str)
      raise "Expected session.created: #{event}" unless event["type"] == "session.created"

      puts(event.fetch("type"))
    end
  end
end

Ruby 的配置和音频追加片段,应放在 Async::WebSocket::Client.connect 块内,位于会话创建检查之后、块结束之前。发送音频和接收翻译事件期间保持连接打开。

Java 连接示例在 try 块结束时关闭套接字。后续配置和音频输入示例要求仍打开的 TranslationConnection;收发音频期间保持连接,离开块前按关闭 WebSocket 会话完成会话并排空事件,直到 session.closed。

套接字打开后配置目标语言:

来源示例 7(javascript):

// [中文] 示例 7:创建 WebSocket 会话;以下保留本语言原始示例。
// [中文] 本段构造的事件类型:session.update;不要将构造载荷等同于收到成功确认。
// [中文] 沿原示例执行这些方法:ws.on、ws.send;异步调用及事件接收顺序保留。
ws.on("open", () => {
  ws.send(
    JSON.stringify({
      type: "session.update",
      session: {
        audio: {
          output: {
            language: "es",
          },
        },
      },
    })
  );
});

来源示例 8(python):

## [中文] 示例 8:创建 WebSocket 会话;以下保留本语言原始示例。
## [中文] 本段构造的事件类型:session.update;不要将构造载荷等同于收到成功确认。
## [中文] 沿原示例执行这些方法:ws.send;异步调用及事件接收顺序保留。
import json

ws.send(
    json.dumps(
        {
            "type": "session.update",
            "session": {
                "audio": {
                    "output": {
                        "language": "es",
                    },
                },
            },
        }
    )
)

// [中文] 用本语言客户端连接指定WebSocket端点,区分Live与Realtime的凭证、URL和事件;应用继续负责音频收发与会话生命周期。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
if err := session.Send(ctx, realtime.RealtimeTranslationClientEventParamOfSessionUpdate(realtime.RealtimeTranslationSessionUpdateRequestParam{
    Audio: realtime.RealtimeTranslationSessionUpdateRequestAudioParam{
        Output: realtime.RealtimeTranslationSessionUpdateRequestAudioOutputParam{
            Language: openai.String("es"),
        },
    },
})); err != nil {
    return err
}

来源示例 9(java):

// [中文] 示例 9:创建 WebSocket 会话;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:connection.send;异步调用及事件接收顺序保留。
import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.helpers.TranslationConnection;
import com.openai.helpers.TranslationWebSocketOptions;
import com.openai.models.realtime.*;

connection.send(
    RealtimeTranslationClientEvent.ofSessionUpdate(
        RealtimeTranslationSessionUpdateEvent.builder()
            .session(
                RealtimeTranslationSessionUpdateRequest.builder()
                    .audio(
                        RealtimeTranslationSessionUpdateRequest.Audio.builder()
                            .output(
                                RealtimeTranslationSessionUpdateRequest.Audio.Output.builder()
                                    .language("es")
                                    .build())
                            .build())
                    .build())
            .build()));

来源示例 10(ruby):

## [中文] 示例 10:创建 WebSocket 会话;以下保留本语言原始示例。
## [中文] 本段构造的事件类型:session.update;不要将构造载荷等同于收到成功确认。
## [中文] 沿原示例执行这些方法:connection.write;异步调用及事件接收顺序保留。
connection.write(JSON.generate(type: "session.update", session: { audio: { output: { language: "es" } } }))
connection.flush

然后连续追加源音频:

来源示例 11(javascript):

// [中文] 示例 11:创建 WebSocket 会话;以下保留本语言原始示例。
// [中文] 本段构造的事件类型:session.input_audio_buffer.append;不要将构造载荷等同于收到成功确认。
// [中文] 沿原示例执行这些方法:ws.send;异步调用及事件接收顺序保留。
ws.send(
  JSON.stringify({
    type: "session.input_audio_buffer.append",
    audio: base64Pcm16,
  })
);

来源示例 12(python):

## [中文] 示例 12:创建 WebSocket 会话;以下保留本语言原始示例。
## [中文] 本段构造的事件类型:session.input_audio_buffer.append;不要将构造载荷等同于收到成功确认。
## [中文] 沿原示例执行这些方法:ws.send;异步调用及事件接收顺序保留。
ws.send(
    json.dumps(
        {
            "type": "session.input_audio_buffer.append",
            "audio": base64_pcm16,
        }
    )
)

// [中文] 用本语言客户端连接指定WebSocket端点,区分Live与Realtime的凭证、URL和事件;应用继续负责音频收发与会话生命周期。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
// audio contains mono PCM16 samples at 24 kHz.
for start := 0; start < len(audio); start += 4800 {
    end := min(start+4800, len(audio))
    if err := session.Send(ctx, realtime.RealtimeTranslationClientEventParamOfSessionInputAudioBufferAppend(base64.StdEncoding.EncodeToString(audio[start:end]))); err != nil {
        return err
    }
}

来源示例 13(java):

// [中文] 示例 13:创建 WebSocket 会话;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:connection.send;异步调用及事件接收顺序保留。
// [中文] 需要发送的原始字节先编码为 Base64;编码不会替代重采样或改变编解码格式。
import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.helpers.TranslationConnection;
import com.openai.helpers.TranslationWebSocketOptions;
import com.openai.models.realtime.*;
import java.util.Base64;

// Call for each PCM16, 24 kHz, mono chunk supplied by your audio source.
static void appendAudio(TranslationConnection connection, byte[] pcmAudio) {
  String base64Pcm16 = Base64.getEncoder().encodeToString(pcmAudio);
  connection.send(
      RealtimeTranslationClientEvent.ofSessionInputAudioBufferAppend(
          RealtimeTranslationInputAudioBufferAppendEvent.builder().audio(base64Pcm16).build()));
}

来源示例 14(ruby):

## [中文] 示例 14:创建 WebSocket 会话;以下保留本语言原始示例。
## [中文] 本段构造的事件类型:session.input_audio_buffer.append;不要将构造载荷等同于收到成功确认。
## [中文] 沿原示例执行这些方法:connection.write;异步调用及事件接收顺序保留。
## [中文] 需要发送的原始字节先编码为 Base64;编码不会替代重采样或改变编解码格式。
require "base64"

File.open("speech.pcm", "rb") do |audio|
  while (chunk = audio.read(4_800))
    connection.write(JSON.generate(type: "session.input_audio_buffer.append", audio: Base64.strict_encode64(chunk)))
    connection.flush
  end
end

监听翻译音频与转录:

来源示例 15(javascript):

// [中文] 示例 15:创建 WebSocket 会话;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:ws.on;异步调用及事件接收顺序保留。
ws.on("message", (data) => {
  const event = JSON.parse(data.toString());

  if (event.type === "session.output_audio.delta") {
    playPcm16(event.delta);
  }

  if (event.type === "session.output_transcript.delta") {
    process.stdout.write(event.delta);
  }

  if (event.type === "session.input_transcript.delta") {
    updateSourceTranscript(event.delta);
  }
});

来源示例 16(python):

## [中文] 请求正常关闭并等待session.closed或对应终结状态;发送关闭事件不等同于已收到最终用量或释放所有资源。
import base64
import json
from concurrent.futures import ThreadPoolExecutor
from contextlib import suppress
from queue import SimpleQueue

import sounddevice as sd

audio_queue = SimpleQueue()


def play_audio():
    try:
        # Closing the stream waits for PortAudio's pending buffers to finish.
        with sd.RawOutputStream(samplerate=24000, channels=1, dtype="int16") as stream:
            while (pcm := audio_queue.get()) is not None:
                stream.write(pcm)
    except Exception:
        # The receiver may have already closed the socket.
        with suppress(OSError):
            ws.abort()  # Wake recv() so the receiver can report the playback failure.
        raise


with ThreadPoolExecutor(max_workers=1) as executor:
    playback = executor.submit(play_audio)
    try:
        while True:
            event = json.loads(ws.recv())
            if playback.done():
                playback.result()  # Report a playback failure on the receiver.

            if event["type"] == "session.output_audio.delta":
                audio_queue.put(base64.b64decode(event["delta"]))

            if event["type"] in (
                "session.output_transcript.delta",
                "session.input_transcript.delta",
            ):
                print(event["delta"], end="", flush=True)

            if event["type"] == "error":
                raise RuntimeError(f"Translation error: {event['error']['code']}")

            if event["type"] == "session.closed":
                break
    finally:
        ws.close()
        audio_queue.put(None)
        playback.result()

关闭 WebSocket 会话

源音频流结束时,在关闭 WebSocket 之前发送 session.close。该事件让服务刷新待处理输入音频、发出剩余翻译音频和转录,再发送 session.closed。这里的 session.close 事件仅由翻译会话支持。

发送 session.close 后停止追加音频,在正常接收循环中继续读取,直到 session.closed。立即关闭套接字可能丢失仍在排出的翻译输出。

关闭翻译会话:

来源示例 17(javascript):

// [中文] 请求正常关闭并等待session.closed或对应终结状态;发送关闭事件不等同于已收到最终用量或释放所有资源。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
// Run this listener while another goroutine sends audio into the session.
func listen(ctx context.Context, session *realtime.TranslationSession) error {
    for {
        event, err := session.Recv(ctx)
        if err != nil {
            return err
        }
        switch event.Type {
        case "error":
            return fmt.Errorf("translation error: %s", event.OfRealtimeErrorEvent.Error.Message)
        case "session.output_transcript.delta":
            fmt.Print(event.OfRealtimeTranslationOutputTranscriptDeltaEvent.Delta)
        case "session.output_audio.delta":
            pcm, err := base64.StdEncoding.DecodeString(event.OfRealtimeTranslationOutputAudioDeltaEvent.Delta)
            if err != nil {
                return err
            }
            // Feed pcm into your application's 24 kHz audio player.
            fmt.Fprintln(log.Writer(), "Received audio bytes:", len(pcm))
        case "session.closed":
            return session.Close()
        }
    }
}

来源示例 18(python):

// [中文] 请求正常关闭并等待session.closed或对应终结状态;发送关闭事件不等同于已收到最终用量或释放所有资源。
let translationSessionClosing = false;
let gotTranslationClosed = false;
let drainDeadline;

ws.on("close", () => {
  clearTimeout(drainDeadline);
  if (!gotTranslationClosed) {
    console.error("Translation disconnected before final session.closed");
    process.exitCode = 1;
  }
});

function closeTranslationSession() {
  if (translationSessionClosing) {
    return;
  }

  translationSessionClosing = true;
  // Adjust this deadline for your application's finalization requirements.
  drainDeadline = setTimeout(() => {
    console.error("Translation finalization timed out");
    process.exitCode = 1;
    ws.close();
  }, 15_000);
  ws.send(JSON.stringify({ type: "session.close" }));
}

// Use this in place of the earlier output handler, so audio plays only once.
ws.on("message", (data) => {
  const event = JSON.parse(data.toString());

  if (event.type === "session.output_audio.delta") {
    playPcm16(event.delta);
  }

  if (event.type === "session.output_transcript.delta") {
    process.stdout.write(event.delta);
  }

  if (event.type === "session.input_transcript.delta") {
    updateSourceTranscript(event.delta);
  }

  if (event.type === "error") {
    clearTimeout(drainDeadline);
    console.error("Translation error", event.error?.code);
    process.exitCode = 1;
    ws.close();
  }

  if (event.type === "session.closed") {
    clearTimeout(drainDeadline);
    gotTranslationClosed = true;
    ws.close();
  }
});

// Call this when the source stream ends.
closeTranslationSession();

## [中文] 请求正常关闭并等待session.closed或对应终结状态;发送关闭事件不等同于已收到最终用量或释放所有资源。
import base64
import json
import time
from concurrent.futures import ThreadPoolExecutor
from contextlib import suppress
from queue import SimpleQueue

import sounddevice as sd

audio_queue = SimpleQueue()


def play_audio():
    try:
        # Closing the stream waits for PortAudio's pending buffers to finish.
        with sd.RawOutputStream(samplerate=24000, channels=1, dtype="int16") as stream:
            while (pcm := audio_queue.get()) is not None:
                stream.write(pcm)
    except Exception:
        # The receiver may have already closed the socket.
        with suppress(OSError):
            ws.abort()  # Wake recv() so the receiver can report the playback failure.
        raise


translation_session_closing = False


def close_translation_session():
    global translation_session_closing
    if translation_session_closing:
        return

    translation_session_closing = True
    ws.send(json.dumps({"type": "session.close"}))


with ThreadPoolExecutor(max_workers=1) as executor:
    playback = executor.submit(play_audio)
    try:
        # Call this when the source stream ends.
        close_translation_session()
        # Adjust this deadline for your application's finalization requirements.
        drain_deadline = time.monotonic() + 15.0

        while True:
            remaining = drain_deadline - time.monotonic()
            if remaining <= 0:
                raise TimeoutError("Translation finalization timed out")
            ws.settimeout(remaining)
            data = ws.recv()
            if not data:
                raise RuntimeError("Translation disconnected before session.closed")
            event = json.loads(data)
            if playback.done():
                playback.result()  # Report a playback failure on the receiver.

            if event["type"] == "error":
                raise RuntimeError(f"Translation error: {event['error']['code']}")

            if event["type"] == "session.output_audio.delta":
                audio_queue.put(base64.b64decode(event["delta"]))

            if event["type"] in (
                "session.output_transcript.delta",
                "session.input_transcript.delta",
            ):
                print(event["delta"], end="", flush=True)

            if event["type"] == "session.closed":
                break
    finally:
        # The server deadline covers receiving events, not local playback.
        ws.close()
        audio_queue.put(None)
        playback.result()

// [中文] 请求正常关闭并等待session.closed或对应终结状态;发送关闭事件不等同于已收到最终用量或释放所有资源。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
// Stop sending audio first. The listener closes the transport after session.closed.
if err := session.Send(ctx, realtime.RealtimeTranslationClientEventUnionParam{
    OfSessionClose: &realtime.RealtimeTranslationSessionCloseEventParam{},
}); err != nil {
    return err
}

构建伴随收听翻译

一个源讲话者或音频流需要为听众提供翻译音频时,使用伴随收听翻译,例如直播、会议演讲、网络研讨会、财报电话会、讲座和视频。

典型架构如下:

来源示例 19(text):

本例中文译文与说明:

源音频 → 翻译会话 → 翻译音频与字幕。

source audio -> translation session -> translated audio + subtitles

每种目标语言创建一个翻译会话。同一个英语源需要西班牙语和法语输出时,分别创建英译西和英译法会话。

浏览器伴随收听应用通过 getDisplayMedia() 采集标签页音频,经 WebRTC 发送并播放远端翻译音轨。生产广播可在服务端媒体工作进程中翻译,再把翻译音轨或字幕发布给听众。

构建对话翻译

两位或更多参与者跨语言交流时,使用对话翻译,如客服、销售、辅导和视频房间。

保持参与者音轨分离。混入同一流会使讲话者身份、字幕和重叠讲话更难处理。

两人通话时,每个方向各创建一个翻译会话:

来源示例 20(text):

本例中文译文与说明:

来电者 A 的音频 → 翻译成 B 的语言 → 播放给 B。
来电者 B 的音频 → 翻译成 A 的语言 → 播放给 A。

Caller A audio -> translate into Caller B language -> play to Caller B
Caller B audio -> translate into Caller A language -> play to Caller A

群组房间的会话数量取决于活动讲话者与目标语言:

来源示例 21(text):

本例中文译文与说明:

翻译会话数约等于:活动源讲话者音轨数 × 不同目标语言数。

translation sessions ~= active source speaker tracks x distinct target languages

小房间中,各听众可为需要翻译的远端讲话者创建浏览器侧翻译辅助会话。较大房间应使用服务端参与者或媒体工作进程,每个源讲话者只订阅一次,为各目标语言建立翻译会话,再重新发布翻译轨道。

测试质量与延迟

使用真实音频和双语审阅测试翻译。自动指标有帮助,但无法发现用户会注意到的所有错误。

测试应包括:

  • 各语言对的质量。
  • 姓名、数字、日期、货币与电话号码。
  • 领域专用术语。
  • 语码切换与混合语言对话。
  • 口音、快速讲话与重叠讲话。
  • 首段翻译音频延迟。
  • 话语结束时的延迟。
  • 字幕时序。
  • 声音一致性。
  • 重连行为。

场景依赖精确姓名或领域术语时,上线前建立人工校验的标准集,并人工审查失败案例。

生产检查清单

  • 浏览器媒体选 WebRTC,服务端媒体选 WebSockets。
  • 使用专用 /v1/realtime/translations 端点。
  • 连续传输音频,包括语句间静音。
  • WebSocket 用 session.close 并等到 session.closed 后再关闭。
  • 对话翻译保持各讲话者音轨分离。
  • 每种输出语言使用一个会话。
  • 有帮助时同时显示原文与译文转录。
  • 提供原始音频、翻译音频、字幕、静音与音量控制。
  • 显示重连中、延迟和不可用状态。
  • 延迟与翻译质量分别追踪。

相关指南

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号