You need to enable JavaScript to run this app.
导航
实时通话字幕和翻译
最近更新时间:2025.04.17 14:09:39首次发布时间:2024.12.27 11:49:57
我的收藏
有用
有用
无用
无用

注意

本文档不适用于实时对话式 AI 场景,该场景是通过接口 StartVoiceChat 实现字幕生成和接收,具体请参见实时对话式 AI 字幕

功能介绍

通过实时字幕功能,可以将用户的语音实时转换为文字,并支持将文字翻译成其他语言,可用于直播字幕、会议记录生成、跨语言沟通实时翻译等场景。

  • 支持通过客户端或服务端实现该功能。

  • 根据使用场景的不同,可生成以下 2 种类型的字幕:

    特性

    按源语言生成字幕(无需翻译)

    生成指定语言的字幕(将语音转为文字后并翻译)

    作用

    识别用户语音,直接按照源语言转为文字,适用于实时字幕显示场景。

    识别用户语音,先按照源语言转为文字,再被翻译为指定目标语言,适用于跨语言实时翻译场景。

    所用服务

    火山的流式语音识别

    火山的机器翻译

    可识别的语种

    不同场景,支持的语言不同,具体请参见语种支持

    仅支持 zh、ja、en

    翻译字幕

    不支持

    支持。支持翻译为的目标语言,请参见语言支持

    返回的字幕数据

    仅返回字幕原文

    同时返回字幕原文和字幕译文

    费用

    音频订阅费、流式语音识别费

    音频订阅费、实时语音翻译费

计费说明

使用实时字幕功能除基础音视频通话费以外,还会产生以下费用:

费用

说明

音视订阅费

启动实时字幕任务后,系统会自动分配一个有机器人加入房间,订阅需要生成字幕的音频流,产生音频订阅费。

流式语音识别费

若使用了流式语音识别服务(即识别模式),会产生流式语音识别费。

实时语音翻译费

若是使用了机器翻译(即翻译模式),会产生实时语音翻译费。

生成字幕

以下分别介绍识别模式和翻译模式下的字幕生成方法。

按源语言生成字幕(识别模式)

  1. 开通服务。

    1. 开通流式语音识别服务:前往语音技术控制台_流式语音识别,创建流式语音识别应用,服务类型选择 流式语音识别,并记录 APP ID,Access Token,和语言的 Cluster ID,以备使用。
    2. 开启实时字幕:前往 RTC 控制台_实时字幕进行以下操作:
      • 实时字幕:开启开关。
      • 流式语音识别:开启开关,并按照提示填入 APP ID、Access Token 和语言的 Cluster ID。

        注意

        • 语种的 Cluster ID:即设置待识别的源语种代号,系统已经预设了 4 种常用语言。若不满足需求,可自定义语种代号。
        • 支持识别的语言,请参见语种支持
  2. 生成字幕并接收字幕。

    • 通过客户端实现:字幕结果仅可返回至客户端,但是无需解析。
    • 通过服务端实现:字幕结果仅可返回至客户端或服务端,字幕为二进制数据,使用前需先解析。

    实现步骤

    相关接口说明,请参见相关API

    1. (可选)调用 joinRoom 接口进房时,设置源语言。
      通过 extraInfo 参数传入 "source_language": "语种代号"。如未指定源语言,SDK 会将系统语种设定为源语言。如果你的系统语种不是中文、英文和日文,此时 SDK 会自动将中文设为源语言。
    2. 进房后,调用 startSubtitle 启动字幕服务,模式(mode)设置为识别模式,无需设置目标翻译语言。
    3. 通过 onSubtitleStateChanged 回调获取字幕内容,包括用户 ID、字幕文本、语言、是否为完整句子等。
    4. 当不再需要字幕功能时,调用 stopSubtitle 关闭字幕。

生成指定语言的字幕(翻译模式)

  1. 开通服务。

    1. 开通机器翻译服务:具体操作,请参见开通服务
    2. 开通实时字幕服务:前往 RTC 控制台_实时字幕,开启实时字幕实时语音翻译的开关。
  2. 生成字幕并接收字幕结果。

    • 客户端生成:字幕结果仅可返回至客户端,但是无需解析。
    • 服务端生成:字幕结果仅可返回至客户端或服务端,字幕为二进制数据,使用前需先解析。

    实现步骤

    相关接口说明,请参见相关API

    1. (可选)调用 joinRoom 接口进房时,设置源语言。
    2. 进房后,调用 startSubtitle 启动字幕服务。模式(mode)选择翻译模式,并指定字幕要翻译的语言。
    3. 通过 onSubtitleStateChanged 回调接收字幕生成结果。
    4. 当不再需要字幕功能时,调用 stopSubtitle 关闭字幕。

停止字幕生成

  • 单个房间停止字幕生成:调用 stopSubtitle 接口停止。
  • 整个应用停止字幕生成:前往 RTC 控制台_实时字幕,直接关闭实时字幕开关。

解析字幕

解析回调到服务端的字幕

  • 字幕回调格式
    返回的字幕回调格式如下:

    参数名类型描述
    messageString生成的字幕数据,格式为Base 64 编码的二进制,具体说明参看二进制消息格式
    signatureString鉴权签名。可与 startSubtitle 接口中传入的 signature 字段值进行对比,以进行鉴权验证。
    • 二进制消息格式:
      alt

      参数名类型描述
      magic numberbinary消息格式标识,固定为 subc
      lengthbinary字幕消息(subtitle_message)的长度,单位为 bytes,存放方式为大端序(Big-Endian)。
      subtitle_messagebinary字幕详细信息,格式参看 subtitle_message
    • subtitle_message:

      参数名类型描述
      typeString消息类型,固定为 subtitle(表示消息类型为字幕)。
      datadata字幕详细信息,包含字幕的文本、语言、说话人 ID 等具体信息。
    • data:

      参数名类型描述
      textString字幕文本。
      languageString字幕语言。
      userIdString说话人 ID。
      sequenceInt字幕序号。
      definiteBoolean字幕是否为完整的一句话:
      • true:是
      • false:否
      paragraphBoolean字幕是否为一段完整的文本:
      • true:是
      • false:否
  • 字幕解析示例

    你可以参考以下示例代码对回调信息中的message内容进行解析。

    const (
            subtitleHeader   = "subc"
            exampleSignature = "example_signature"
        )
    
        type RtsMessage struct {
            Message   string `json:"message"`
            Signature string `json:"signature"`
        }
    
        type subc struct {
            Type string `json:"type"`
            Data []Data `json:"data"`
        }
    
        type Data struct {
            Definite  bool  `json:"definite"`
            Paragraph bool   `json:"paragraph"`
            Language  string `json:"language"`
            Sequence  int    `json:"sequence"`
            Text      string `json:"text"`
            UserID    string `json:"userId"`
        }
    
        func HandleSubtitle(c *gin.Context) {
            msg := &RtsMessage{}
            if err := c.BindJSON(&msg); err != nil {
            fmt.Printf("BindJson failed,err:%v\n", err)
            return
            }
            if msg.Signature != exampleSignature {
            fmt.Printf("Signature not match\n")
            return
            }
    
            subc, err := Unpack(msg.Message)
            if err != nil {
            fmt.Printf("Unpack failed,err:%v\n", err)
            return
            }
    
            fmt.Println(subc)
    
            //业务逻辑
    
            c.String(200, "ok")
        }
    
        func Unpack(msg string) (*subc, error) {
            data, err := base64.StdEncoding.DecodeString(msg)
            if err != nil {
            return nil, fmt.Errorf("DecodeString failed,err:%v", err)
            }
            if len(data) < 8 {
            return nil, fmt.Errorf("Data invalid")
            }
            dataHeader := string(data[:4])
            if dataHeader != subtitleHeader {
            return nil, fmt.Errorf("Header not match")
            }
            dataSize := binary.BigEndian.Uint32(data[4:8])
            if dataSize+8 != uint32(len(data)) {
            return nil, fmt.Errorf("Size not match")
            }
    
            subData := data[8:]
            subc := &subc{}
            err = json.Unmarshal(subData, subc)
            if err != nil {
            return nil, fmt.Errorf("Unmarshal failed,err:%v\n", err)
            }
            return subc, nil
        }
    
        func main() {
    
            r := gin.Default()
            r.POST("/example_domain/vertc/subtitle", HandleSubtitle)
            r.Run()
        }

解析回调到客户端的字幕

  • 字幕回调格式

    参数名类型描述
    uidString消息发送者 ID。
    messageString生成的字幕数据,格式为Base 64 编码的二进制。与服务端返回二进制消息格式相同,详细参看二进制消息格式
  • 字幕解析示例
    你可以参考以下示例代码对回调信息中的 message 内容进行解析。

    //定义结构体
        struct SubtitleMsgData {
            bool definite;
            std::string language;
            bool paragraph;
            int sequence;
            std::string text;
            std::string userId;
        };
    
        //回调事件
        void onRoomBinaryMessageReceived(const char* uid, int size, const uint8_t* message) {
            std::string subtitles;
            bool ret = Unpack(message, size, subtitles);
            if(ret) {
                ParseData(subtitles);
            }
        }
    
        //拆包校验
        bool Unpack(const uint8_t *message, int size, std::string& subtitles) {
            int kSubtitleHeaderSize = 8;
            if(size < kSubtitleHeaderSize) { 
                return false;
            }
            // magic number "subc"
            if(static_cast<uint32_t>((static_cast<uint32_t>(message[0]) << 24) 
                | (static_cast<uint32_t>(message[1]) << 16) 
                | (static_cast<uint32_t>(message[2]) << 8) 
                | static_cast<uint32_t>(message[3])) != 0x73756276U) {
                return false;
            }
        
            uint32_t length = static_cast<uint32_t>((static_cast<uint32_t>(message[4]) << 24) 
                | (static_cast<uint32_t>(message[5]) << 16) 
                | (static_cast<uint32_t>(message[6]) << 8) 
                | static_cast<uint32_t>(message[7]));
               
            if(size - kSubtitleHeaderSize != length) {
                return false;
            }
    
            if(length) {
                subtitles.assign((char*)message + kSubtitleHeaderSize, length);
            } else {
                subtitles = "";
            }
            return true;
        }
    
        //解析
        void ParseData(const std::string& msg) {
            // 解析 JSON 字符串
            nlohmann::json json_data = nlohmann::json::parse(subtitles);
            // 存储解析后的数据
            std::vector<SubtitleMsgData> subtitles;
            // 遍历 JSON 数据并填充结构体
            for (const auto& item : json_data["data"]) {
                SubtitleMsgData subData;
                subData.definite = item["definite"];
                subData.language = item["language"];
                subData.paragraph = item["paragraph"];
                subData.sequence = item["sequence"];
                subData.text = item["text"];
                subData.userId = item["userId"];
                subtitles.push_back(subData);
            }
        }
    
        

字幕使用建议

  • 渲染字幕

    在前端显示字幕时,可以根据 definitesequence 字段来决定如何更新字幕。

    • 如果 definite=false,用序号大的字幕覆盖序号小的。
    • 如果 definite=true,重新开始新的一句话,覆盖前一句话。
  • 存储字幕
    如果仅为了存储字幕,可只保存 definite=true 的字幕,减少存储的数据量,并确保保存的字幕是完整的。

相关 API

功能AndroidiOSmacOSWindowsLinux服务端
生成字幕startSubtitlestartSubtitle:startSubtitle:startSubtitlestartSubtitlestartSubtitle
停止字幕生成stopSubtitlestopSubtitlestopSubtitlestopSubtitlestopSubtitleStopSubtitle
接收字幕结果onSubtitleMessageReceivedrtcRoom:onSubtitleMessageReceived:rtcRoom:onSubtitleMessageReceived:onSubtitleMessageReceivedonSubtitleMessageReceived通过 startSubtitleDistributionMode 字段指定