Skip to content

开发 WHIP Service

WHIP Service 是客户部署和运维的业务服务。最新 Go SDK 将认证、WHIP 接入、业务资源和 Echo 诊断拆成可组合的层。

1. 初始化及启动 SDK

初始化参数来自服务注册信息和部署环境:

参数含义
serviceName服务注册确认的 service_name
accessKey服务注册确认的 Access Key ID,用于选择验签公钥
publicKey与业务服务端签发私钥配对、并已注册的 Ed25519 公钥
candidateIP可选。服务的公网 IP。服务在 NAT 后时需要配置

进程级 TiRTC 引擎只初始化和启动一次。tirtcx.Init 必须在调用 tirtcx 的其他 API 之前完成:

go
if err := tirtcx.Init(); err != nil {
    return err
}
defer tirtcx.Uninit()

verifier, err := tirtcxauth.NewEd25519TokenVerifier(map[string]string{
    accessKey: publicKey,
})
// auth 是一个 http 拦截器,用于验证 Bearer Token,并将 Claims 写入请求 Context。
auth := tirtcxauth.BearerInterceptor(serviceName, verifier)
// acceptor 用于接受 SDP Offer 并创建 WHIP Session。
acceptor := tirtcx.NewWhipAcceptor(candidateIP)

// 其他初始化代码
...

// 启动
if err := tirtcx.Start(); err != nil {
    return err
}
defer tirtcx.Stop()

2. 认证创建请求

只在创建会话的 POST Handler 外应用 BearerInterceptor

go


mux.HandleFunc("POST /whip", auth(postWHIP))
mux.HandleFunc("DELETE /whip/resource/{session_id}", deleteResource)

认证成功后,可从 Context 读取设备身份:

go
claims, ok := tirtcxauth.ClaimsFromContext(r.Context())

3. 接受 WHIP 会话

go
func (s *mediaService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 只接受 SDP Offer。
    mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
    if err != nil || !strings.EqualFold(mediaType, "application/sdp") {
        http.Error(w, "content-type must be application/sdp", http.StatusUnsupportedMediaType)
        return
    }

    // 限制 SDP Offer 大小为 1 MiB。
    offer, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    // 使用 128 位密码学安全随机数生成不可预测的会话 ID。
    sessionID, err := newSessionID()
    if err != nil {
        http.Error(w, "create session ID: "+err.Error(), http.StatusInternalServerError)
        return
    }

    // 在 30 秒内接受 SDP Offer 并生成 SDP Answer。
    acceptCtx, cancelAccept := context.WithTimeout(r.Context(), 30*time.Second)

    // acceptor 在初始化时创建
    whipSession, err := acceptor.WhipAccept(
        acceptCtx,
        offer,
        tirtcx.ConnEventOptions{
            ErrorBuffer:        tirtcx.MinEventBufferSize,
            DisconnectedBuffer: tirtcx.MinEventBufferSize,
        },
    )
    cancelAccept()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // 保存会话,并在独立 Goroutine 中等待建连和处理业务媒体。
    sessionCtx, cancelSession := context.WithCancel(context.Background())
    session := &mediaSession{cancel: cancelSession, whipSession: whipSession}
    s.mu.Lock()
    if s.closed {
        s.mu.Unlock()
        cancelSession()
        _ = whipSession.Close()
        http.Error(w, "service is shutting down", http.StatusServiceUnavailable)
        return
    }
    s.sessions[sessionID] = session
    s.wg.Add(1)
    s.mu.Unlock()
    go s.run(sessionCtx, sessionID, session)

    // 返回 201、SDP Answer 和用于删除会话的 Location。
    w.Header().Set("Content-Type", "application/sdp")
    w.Header().Set("Location", "/whip/resource/"+sessionID)
    w.WriteHeader(http.StatusCreated)
    _, _ = w.Write(whipSession.AnswerSDP())
}

func newSessionID() (string, error) {
    var value [16]byte
    if _, err := rand.Read(value[:]); err != nil {
        return "", err
    }
    return hex.EncodeToString(value[:]), nil
}

4. 管理连接生命周期

在独立 Goroutine 中等待连接:

go
connectCtx, cancelConnect := context.WithTimeout(sessionCtx, 30*time.Second)
defer cancelConnect()

conn, err := session.WaitConn(connectCtx)
if err != nil {
    return
}
defer session.Close()

select {
case <-conn.Done():
    if err := conn.Err(); err != nil {
        // 异常终止
    }
case <-sessionCtx.Done():
}

WaitConn 可并发、重复调用。单个等待者超时不会关闭会话;需要结束待连接或已连接会话时,调用幂等的 WhipSession.Close()

应用负责普通会话的注册表和 DELETEDELETE 不做 Token 鉴权,随机会话 ID 即访问凭据。收到 DELETE <Location> 时,应先从注册表移除资源,再取消业务 Context 并关闭 WhipSession;未知或已删除的 ID 也返回 204 No Content。入口按设备能力支持 HTTP 或 HTTPS,并应限制请求速率、避免记录完整资源路径。

5. 处理事件

只为需要消费的事件配置缓冲区。启用后必须持续读取,否则缓冲区满时事件会被丢弃。

订阅决策不再通过事件通道异步返回,而是由同步 Handler 决定:

go
options := tirtcx.ConnEventOptions{
    AudioBuffer: 16,
    VideoBuffer: 16,
    SubscribeVideoHandler: func(streamID uint8) int {
        return 0
    },
}

订阅 Handler 运行在原生回调中,必须快速返回且不能阻塞。使用 Conn.EventStats() 监控事件丢弃。

6. 可选:嵌入 Echo

go
echo, err := whipecho.NewHTTPAdapter(
    acceptor,
    "/whip/echo/resource",
    whipecho.HTTPOptions{},
)

mux.HandleFunc("POST /whip", auth(echo.Wrap(businessHandler)))
mux.HandleFunc(
    "DELETE /whip/echo/resource/{session_id}",
    echo.DeleteHandler(),
)

whipecho 自动根据 _tg_mode=echo 参数拦截并管理 echo 诊断会话。_tg_* 参数为探鸽保存参数,业务不应使用。 普通请求不会被 whipecho 拦截。

7. 部署要求

部署 WHIP Service 的 Linux 主机必须将本地端口范围设置为 12768 63999

bash
sudo sysctl -w net.ipv4.ip_local_port_range="12768 63999"

需要持久生效时,在 /etc/sysctl.d/ 下增加配置:

text
net.ipv4.ip_local_port_range = 12768 63999

然后执行 sudo sysctl --system。主机防火墙、云安全组及上游网络设备必须开放 12768-63999 范围内的全部 UDP 端口。

8. 优雅退出

退出顺序:

  1. 停止接受新的 HTTP 请求。
  2. 关闭普通业务会话并等待 Goroutine。
  3. 调用 echo.Shutdown(ctx)
  4. 调用 tirtcx.Stop()tirtcx.Uninit()

上线时监控建连成功率、建连耗时、活跃连接、异常断开、发送失败和事件丢弃。Echo 可通过 Stats() 和 Observer 接入现有监控系统。

TiRTC WHIP 开发文档