You need to enable JavaScript to run this app.
导航
实时字幕
最近更新时间:2024.12.27 17:25:58首次发布时间:2024.12.27 11:49:57

在实时音视频通话下,你可能需要实时字幕显示房间内指定用户的发言内容。通过配置 startSubtitle 接口你可将获得房间内指定用户发言的内容,作为字幕显示。返回的内容为二进制消息,你可参看本文了解返回内容结构和解析示例代码。

服务端

字幕回调格式

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

参数名类型描述
messageStringBase 64 编码的二进制消息内容。格式参看二进制消息格式
signatureString鉴权签名。可与startSubtitle接口中传入的signature字段值进行对比以进行鉴权验证。

二进制消息格式如下:
alt

参数名类型描述
magic numberbinary消息格式,固定为 subt
lengthbinary字幕消息长度,单位为 bytes。存放方式为大端序。
subtitle_messagebinary字幕消息详细信息。格式参看subtitle_message 格式

subtitle_message 格式:

参数名类型描述
typeString消息格式,固定为 subtitle
datadata字幕详细信息。

data

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

解析字幕信息内容

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

  • 服务端
const (
    subtitleHeader   = "subt"
    exampleSignature = "example_signature"
)

type RtsMessage struct {
    Message   string `json:"message"`
    Signature string `json:"signature"`
}

type subt 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
    }

    subt, err := Unpack(msg.Message)
    if err != nil {
       fmt.Printf("Unpack failed,err:%v\n", err)
       return
    }

    fmt.Println(subt)

    //业务逻辑

    c.String(200, "ok")
}

func Unpack(msg string) (*subt, 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:]
    subt := &subt{}
    err = json.Unmarshal(subData, subt)
    if err != nil {
       return nil, fmt.Errorf("Unmarshal failed,err:%v\n", err)
    }
    return subt, nil
}

func main() {

    r := gin.Default()
    r.POST("/example_domain/vertc/subtitle", HandleSubtitle)
    r.Run()
}

客户端

字幕回调格式

参数名类型描述
uidString消息发送者 ID。
messageStringBase 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 "subt"
    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);
    }
}