在进行对话式 AI 实时交互场景下,你可能需要实时字幕显示你与智能体的对话内容。在 StartVoiceChat
接口中配置 SubtitleConfig
结构体参数后,你可以开启房间内字幕回调功能。开启此功能后,你可在应用服务器和客户端接收字幕结果,以做后续操作。客户端通过 onRoomBinaryMessageReceived 接口实时返回字幕结果,你可以用来显示实时字幕。服务端通过设定的ServerMessageUrl
按照每句话返回字幕结果,你可以用来存储分析对话数据。
返回的字幕回调格式如下:
参数名 | 类型 | 描述 |
---|---|---|
message | String | Base 64 编码的二进制消息内容。格式参看二进制消息格式。 |
signature | String | 鉴权签名。可与StartVoiceChat 接口中传入的ServerMessageSignature 字段值进行对比以进行鉴权验证。 |
二进制消息格式如下:
参数名 | 类型 | 描述 |
---|---|---|
magic number | binary | 消息格式,固定为 subtitle 。 |
length | binary | 字幕消息长度,单位为 bytes。存放方式为大端序。 |
subtitle_message | binary | 字幕消息详细信息。格式参看subtitle_message 格式。 |
subtitle_message 格式:
参数名 | 类型 | 描述 |
---|---|---|
type | String | 消息格式,固定为 subtitle 。 |
data | data | 字幕详细信息。 |
data
参数名 | 类型 | 描述 |
---|---|---|
text | String | 字幕文本。 |
language | String | 字幕语言。 |
userId | String | 说话人 ID。 |
sequence | Int | 字幕序号。 |
definite | Boolean | 字幕是否为完整的一句话。
|
paragraph | Boolean | 字幕是否为一段完整的文本。
|
你可以参考以下示例代码对回调信息中的message
内容进行解析。
const ( subtitleHeader = "subv" exampleSignature = "example_signature" ) type RtsMessage struct { Message string `json:"message"` Signature string `json:"signature"` } type Subv 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 } subv, err := Unpack(msg.Message) if err != nil { fmt.Printf("Unpack failed,err:%v\n", err) return } fmt.Println(subv) //业务逻辑 c.String(200, "ok") } func Unpack(msg string) (*Subv, 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:] subv := &Subv{} err = json.Unmarshal(subData, subv) if err != nil { return nil, fmt.Errorf("Unmarshal failed,err:%v\n", err) } return subv, nil } func main() { r := gin.Default() r.POST("/example_domain/vertc/subtitle", HandleSubtitle) r.Run() }
参数名 | 类型 | 描述 |
---|---|---|
uid | String | 消息发送者 ID。 |
message | String | 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 "subv" 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); } }