OpenAI WebRTC 与 WARP

2026-11-06 12:39 更新

当前Rust示例针对webrtc-sys 0.3.51:工厂启用SPED,连接配置启用SNAP。绑定库可能只暴露部分开关;在创建对等连接工厂之前初始化field trials,不将该片段误写为一次启用所有三项。 使用 WebRTC 简化往返协议(WARP)缩短 GPT-Live 或 Realtime API 语音会话启动时间。WARP 将 DTLS 1.3、SPED、SNAP 三种传输优化与预协商数据通道结合,以更少网络往返建立连接。

这些优化也可单独启用。即使不能使用完整 WARP,启用客户端支持的功能仍可降低延迟;全部结合才构成完整 WARP 握手。

WARP、SPED 与 SNAP 是 IETF Internet-Draft,规范与客户端支持仍可能变化。

GPT-Live 与 Realtime API 使用相同 WebRTC 优化,无论应用服务器用什么语言,都在浏览器或原生客户端配置。两种 API 的区别在于创建会话时如何发送协商好的数据通道 ID。

在原生客户端启用 WARP

完整 WARP 需要通过 field trials 启用传输优化,并预协商数据通道;两步也可独立使用。

启用 field trials

当前版本的 libwebrtc 包含 WARP 优化。原生客户端使用兼容构建时,启用以下试验开关:

来源示例 1(text):字段名、事件类型和示意值保留原样,含义见相邻正文;片段中的省略号不表示完整可运行程序。

WebRTC-ForceDtls13/Enabled/
WebRTC-Sctp-Snap/Enabled/
WebRTC-IceHandshakeDtls/Enabled/

三项分别启用 DTLS 1.3、SNAP 和 SPED,只包含想启用的优化即可。

某些集成要求合并成一条 field-trial 字符串:

来源示例 2(text):字段名、事件类型和示意值保留原样,含义见相邻正文;片段中的省略号不表示完整可运行程序。

WebRTC-ForceDtls13/Enabled/WebRTC-Sctp-Snap/Enabled/WebRTC-IceHandshakeDtls/Enabled/

应在创建对等连接工厂之前初始化开关。以下Rust绑定示例分别配置SPED与SNAP:

来源示例 3(rust):

// [中文] 按当前绑定库分别配置工厂与连接选项;webrtc-sys 0.3.51示例在工厂启用SPED,并在连接配置中启用SNAP。
use webrtc_sys::peer_connection::ffi::{
    ContinualGatheringPolicy, IceTransportsType, RtcConfiguration,
};
use webrtc_sys::peer_connection_factory::ffi::create_peer_connection_factory_with_options;

// The wrapper enables SPED on the factory; SNAP is an immutable connection option.
let factory = create_peer_connection_factory_with_options(false, true);
let configuration = RtcConfiguration {
    ice_servers: vec![],
    continual_gathering_policy: ContinualGatheringPolicy::GatherOnce,
    ice_transport_type: IceTransportsType::All,
    enable_sctp_snap: true,
};

使用原生 LiveKit SDK 等封装 libwebrtc 的 SDK 时,通过 WebRTC 配置或初始化选项传递同一字符串。如果 SDK 未公开 field trials,应在 SDK 创建连接工厂前初始化底层 libwebrtc,或更新到提供该配置的封装。

预协商数据通道

客户端与信令请求使用相同通道 ID。以下步骤同时适用于原生客户端和浏览器,即使没有启用全部三种传输优化也可使用:

  1. 在 SDP offer 之前创建数据通道,将 negotiated 设为 true,id 设为可用 ID,例如 4;通道标签可为任意字符串。
  2. 把 SDP offer 和通道 ID 发给应用服务器,再按对应 API 执行下述步骤。

连接 GPT-Live

从 GPT-Live WebRTC 快速开始入手。服务器在 POST /v1/live/sessions JSON 请求体的 transport.dcid 包含通道 ID,transport.type 保持 webrtc,transport.sdp 是客户端 SDP offer。

对于 id: 4 的通道,向 POST /v1/live/sessions 发送以下 JSON,将 <SDP offer> 换成实际值:

来源示例 4(json):

程序文字译释114:这里替换为真实SDP offer。

{
  "session": {
    "model": "gpt-live-1"
  },
  "transport": {
    "type": "webrtc",
    "sdp": "<SDP offer>",
    "dcid": 4
  }
}

transport.dcid 启用预协商通道;标准数据通道则省略。不应把 dcid 放到 URL 查询参数:与下述 Realtime 流程不同,GPT-Live 会拒绝查询参数。

将返回的 transport.sdp 应用为远端 answer,等待 session.started 后再发应用命令。HTTP 请求已启动会话,不要在数据通道发送 session.start。

如果 SDK 未公开 transport.dcid,保留快速开始中的标准数据通道设置,仍可启用客户端支持的各项独立传输优化。

连接 Realtime API

使用统一 WebRTC 连接流程。服务器调用 /v1/realtime/calls 时,用 dcid 查询参数提供 ID。对于 id: 4,使用 POST /v1/realtime/calls?dcid=4,multipart 请求体保持相同 SDP 和会话配置。

将返回 SDP 应用为远端 answer。标准通道省略 dcid。下方完整示例展示两种配置。

在浏览器启用 WARP

Chrome、Edge 及其他 Chromium 浏览器内置 libwebrtc,但网页不能配置其 field trials。先启用浏览器可用的传输功能,再用与原生客户端相同的 API 专用信令预协商通道。

启用可用功能与 Origin Trial

Origin Trial让已注册网站启用试验性浏览器功能。逐项检查 WARP 功能可用性:

  • DTLS 1.3:Chrome 无需 Origin Trial 即支持。
  • SNAP:Chrome 151–156 通过 Origin Trial 支持。Edge 应确认当前版本是否提供 SNAP 试验,并注册 Edge 签发的令牌。
  • SPED:尚无浏览器 Origin Trial。需后续关注支持情况。完整 WARP 要求浏览器默认启用 SPED,或由你控制启动参数并启用必要 field trials。

启用 SNAP 试验不等于启用 SPED 或完整 WARP。浏览器未公开 SPED 时,仍可通过 DTLS 1.3 与 SNAP 获得独立优化收益。

检查各浏览器试验:

启用 SNAP 的步骤:

  1. 打开浏览器 Origin Trials 页面,找到 WebRTC Data Channel: SCTP Negotiation Acceleration Protocol (SNAP)。
  2. 选择 Register,输入应用源,例如 https://example.com。
  3. 在页面 <head> 中、创建 RTCPeerConnection 的脚本之前添加签发令牌:

来源示例 5(html):

<!-- [中文] 示例 5:启用可用功能与 Origin Trial;以下保留本语言原始示例。 -->
<!-- [中文] 将占位符替换为当前浏览器为应用源签发的 SNAP Origin Trial 令牌,并把 meta 放在页面 head 中、创建 RTCPeerConnection 的脚本之前。 -->
<!-- [中文] 此标签只启用令牌对应的浏览器试验,不能单独启用 SPED 或完整 WARP;不要保留占位符用于实际测试。 -->
   <meta http-equiv="origin-trial" content="YOUR_ORIGIN_TRIAL_TOKEN" />

  1. 重新加载。Chrome 中打开 DevTools 的 Application,确认 Origin Trials 下出现 WebRtcSctpSnap。

也可通过 HTTP 响应头提供令牌:

来源示例 6(http):字段名、事件类型和示意值保留原样,含义见相邻正文;片段中的省略号不表示完整可运行程序。

Origin-Trial: YOUR_ORIGIN_TRIAL_TOKEN

令牌只对指定功能、签发浏览器及已注册源生效。SNAP 令牌不启用 SPED,Chrome 令牌也不在 Edge 启用试验。Firefox、Safari、iOS 浏览器和旧 Chromium 可支持标准 WebRTC,但不一定支持完整 WARP。

预协商浏览器数据通道

按上述数据通道设置,再依据 GPT-Live 或 Realtime API 的要求发送匹配 ID。预协商通道不需要 Origin Trial,即使没有完整 WARP,也可能缩短等待服务器事件的时间。

通过统一接口连接

使用统一 WebRTC 连接流程加快 Realtime API 连接。浏览器把 SDP offer 和已协商通道 ID 发给应用服务器,服务器调用 /v1/realtime/calls 时,将同一 ID 放在 dcid 查询参数中。

配置应用服务器

此服务器同时支持标准 WebRTC 与 WARP。标记 WARP only 的注释说明 WARP 所需的 dcid 转发:

来源示例 7(javascript):

程序文字译释115:生成令牌时出错。

程序文字译释116:生成令牌失败。

// [中文] 定义会话启动配置,区分Live语音模型与委派后端设置;配置对象本身不会建立连接、执行工具或自动运行后端。
import express from "express";

const app = express();

// Parse raw SDP payloads posted from the browser
app.use(express.text({ type: ["application/sdp", "text/plain"] }));

const sessionConfig = JSON.stringify({
  type: "realtime",
  model: "gpt-realtime-2.1",
  audio: { output: { voice: "marin" } },
});

// An endpoint which creates a Realtime API session.
app.post("/session", async (req, res) => {
  const fd = new FormData();
  fd.set("sdp", req.body);
  fd.set("session", sessionConfig);

  const endpoint = new URL("https://api.openai.com/v1/realtime/calls");

  // WARP only: forward the negotiated data-channel ID to the Realtime API.
  if (typeof req.query.dcid === "string") {
    endpoint.searchParams.set("dcid", req.query.dcid);
  }

  try {
    const r = await fetch(endpoint, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
        "OpenAI-Safety-Identifier": "hashed-user-id",
      },
      body: fd,
    });
    // Send back the SDP we received from the OpenAI REST API
    const sdp = await r.text();
    res.set(
      "Content-Type",
      r.ok ? "application/sdp" : (r.headers.get("Content-Type") ?? "text/plain")
    );
    const location = r.headers.get("Location");
    if (location) res.set("Location", location);
    res.status(r.status).send(sdp);
  } catch (error) {
    console.error("Session creation error:", error);
    res.status(500).json({ error: "Failed to create session" });
  }
});

app.listen(3000);

// [中文] 定义会话启动配置,区分Live语音模型与委派后端设置;配置对象本身不会建立连接、执行工具或自动运行后端。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
func handler(client openai.Client) http.Handler {
    mux := http.NewServeMux()
    mux.HandleFunc("POST /session", func(w http.ResponseWriter, r *http.Request) {
        offer, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 64*1024))
        if err != nil || strings.TrimSpace(string(offer)) == "" {
            http.Error(w, "A valid SDP offer is required", http.StatusBadRequest)
            return
        }
        opts := []option.RequestOption{
            option.WithHeader("OpenAI-Safety-Identifier", "hashed-user-id"),
        }
        // Forward the browser's negotiated data-channel ID when WARP is enabled.
        if dcid := r.URL.Query().Get("dcid"); dcid != "" {
            id, err := strconv.Atoi(dcid)
            if err != nil || id < 0 || id > 65534 {
                http.Error(w, "Invalid data-channel ID", 400)
                return
            }
            opts = append(opts, option.WithQuery("dcid", dcid))
        }
        response, err := client.Realtime.Calls.New(r.Context(), realtime.CallNewParams{
            Sdp: string(offer),
            Session: realtime.RealtimeSessionCreateRequestParam{
                Model: "gpt-realtime-2.1",
                Audio: realtime.RealtimeAudioConfigParam{
                    Output: realtime.RealtimeAudioConfigOutputParam{
                        Voice: realtime.RealtimeAudioConfigOutputVoiceUnionParam{
                            OfString: openai.String("marin"),
                        },
                    },
                },
            },
        }, opts...)
        if err != nil {
            http.Error(w, "Could not create Realtime call", http.StatusBadGateway)
            return
        }
        defer response.Body.Close()
        w.Header().Set("Content-Type", "application/sdp")
        w.Header().Set("Location", response.Header.Get("Location"))
        w.WriteHeader(response.StatusCode)
        if _, err := io.Copy(w, response.Body); err != nil {
            log.Print("Could not write SDP answer")
        }
    })
    return mux
}

从浏览器连接

启用浏览器可用优化后,将 useWarp 设为 true。选择任一可用通道 ID,向服务器传递同一 ID。WARP only 标记的是标准 WebRTC 不需要的配置与信令:

来源示例 8(javascript):

// [中文] 示例 8:从浏览器连接;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:pc.addTrack、pc.createDataChannel、pc.createOffer、pc.setLocalDescription、pc.setRemoteDescription;异步调用及事件接收顺序保留。
// WARP only: set to true after enabling the supported WARP optimizations.
const useWarp = false;

// WARP only: choose any available data-channel ID.
const dataChannelId = 4;

// Create a peer connection
const pc = new RTCPeerConnection();

// Set up to play remote audio from the model
audioElement.current = document.createElement("audio");
audioElement.current.autoplay = true;
pc.ontrack = (e) => (audioElement.current.srcObject = e.streams[0]);

// Add local audio track for microphone input in the browser
const ms = await navigator.mediaDevices.getUserMedia({
  audio: true,
});
pc.addTrack(ms.getTracks()[0]);

// Set up the event channel using any channel label.
// WARP only: pre-negotiate the channel using the selected data-channel ID.
const dc = pc.createDataChannel(
  "events",
  useWarp ? { negotiated: true, id: dataChannelId } : undefined,
);

// Start the session using the Session Description Protocol (SDP)
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

const endpoint = new URL("/session", window.location.origin);

// WARP only: include the matching data-channel ID in the session request.
if (useWarp) {
  endpoint.searchParams.set("dcid", String(dataChannelId));
}

const sdpResponse = await fetch(endpoint, {
  method: "POST",
  body: offer.sdp,
  headers: {
    "Content-Type": "application/sdp",
  },
});

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

使用不支持 WARP 的客户端

若 WebRTC 客户端不使用 libwebrtc,可在所用 WebRTC 栈实现兼容 WARP 的传输优化,或使用该栈支持的其他方式。相关规范包括 WARP、SPED、SNAP、DTLS 1.3 及数据通道建立。

无法使用这些优化时,采用标准 GPT-Live WebRTC 流程或 Realtime API 统一接口。

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

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号