# 解析转写数据 (/zh-CN/realtime-media/speech-to-text/build/process-transcription-data/how-to-use-protobuf)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

Protobuf（全称 [Protocol Buffers](https://protobuf.dev)）是由 Google 开发的一种高效的、跨语言的序列化数据格式，支持多种编程语言，包括 C++、Java、Python、Go 等，可在不同系统间进行数据交换。具有高效、灵活和易于使用的特点。

声网实时转录翻译支持使用 Protobuf 或 JSON 格式通过数据流传输转写和翻译结果。默认使用 Protobuf 格式；如果调用 [`join`](/zh-CN/api-reference/api-ref/speech-to-text/join) 时将 `enableJsonProtocol` 设置为 `true`，则使用 JSON 格式。本文介绍接收端如何解析 Protobuf 和 JSON 数据，并从解析后的数据结构中提取具体的文本字段。

## 前提条件 [#前提条件]

请按照以下要求准备开发环境：

- 可以访问互联网的计算机。如果你的网络环境部署了防火墙，参考[应对防火墙限制](/zh-CN/realtime-media/rtc/build/setup-and-access/firewall)以正常使用声网服务。
- 已安装 Protobuf Compiler，详见 [Protobuf Compiler Installation](https://github.com/protocolbuffers/protobuf#protobuf-compiler-installation)。

  :::warning[注意]
  由于 Protobuf 的格式在不同版本会有差异，声网建议生成代码和客户端反序列化使用的 Protobuf SDK 版本保持一致。
  :::

- 一个有效的声网账号以及声网项目。请确保你的项目已开通实时转录翻译功能，并参考[开通服务](/zh-CN/realtime-media/speech-to-text/build/start-transcribing-and-translating/enable-service)从声网控制台获得以下信息：
  - App ID：声网随机生成的字符串，用于识别你的项目。
  - 临时 Token：Token 也称为动态密钥，在客户端加入频道时对用户鉴权。临时 Token 的有效期为 24 小时。
- 作为主播加入频道发流。你可以参考[实现纯语音互动](/zh-CN/realtime-media/rtc/build/audio/audio-quick-start)来在频道中发流。

## 实现流程 [#实现流程]

本节介绍接收端如何使用 protoc 编译生成不同语言的示例代码来反序列化接收到的 Protobuf 数据。如果你使用 JSON 格式，可直接参考[JSON 协议数据结构](#json-协议数据结构)解析消息。

### 使用 protoc 生成源代码 [#使用-protoc-生成源代码]

参考下列步骤来编写一个脚本调用 protoc 编译器以生成不同语言的代码。

#### 创建脚本并生成源码 [#创建脚本并生成源码]

你可以根据自己实际的业务需求，选择生成不同语言的代码。下文提供生成 Java、Objective-C、C#、JavaScript 语言的代码脚本。

<Tabs defaultValue="java" groupId="script" persist>
<TabsList>
  <TabsTrigger value="java">Java</TabsTrigger>
  <TabsTrigger value="oc">Objective-C</TabsTrigger>
  <TabsTrigger value="csharp">C#</TabsTrigger>
  <TabsTrigger value="js">JavaScript</TabsTrigger>
</TabsList>

<TabsContent value="java">

创建一个 Shell 脚本，将其命名为 `generate_code.sh`，然后在其中添加如下代码：

```shell
#!/bin/sh

# 指定 protoc 编译器的路径，示例代码中使用的 Protobuf 版本为 21.12，你可以根据你的实际需求替换
PROTOC_PATH=./protoc-21.12-osx-aarch_64/bin/protoc

# 指定 .proto 文件的路径，文件中数据结构的详细描述见参考信息
PROTO_FILE=./SttMessage.proto

# 指定输出目录
JAVA_OUT_DIR=$(pwd)/code/java

# 创建输出目录（如果不存在）
mkdir -p $JAVA_OUT_DIR

# 生成 Java 代码
$PROTOC_PATH --java_out=$JAVA_OUT_DIR $PROTO_FILE

# 生成代码完成后输出提示信息
echo "Generate code finished."
```

</TabsContent>

<TabsContent value="oc">

如果你需要生成 Objective-C 代码，请确保在生成代码前已经安装 Protobuf 的相关依赖。你可以参考下列步骤来安装依赖，如果已有依赖，可跳过此步骤。

<Accordions>
<Accordion title="安装 Protobuf 依赖">

1. 打开你的项目的 `Podfile` 文件，并在其中添加如下代码：

   ```ruby
   # 21.12 表示 Protobuf 版本，你可以根据实际需求选择合适的版本
   pod "Protobuf", "3.21.12"
   ```

2. 在 Terminal 中进入到包含 `Podfile` 文件的目录下，运行 `pod install` 命令，CocoaPods 会下载、安装指定的依赖库版本。

成功安装后，项目文件夹下会生成一个后缀为 `.xcworkspace` 的文件，通过 Xcode 打开该文件进行后续操作。

</Accordion>
</Accordions>

创建一个 Shell 脚本，将其命名为 `generate_code.sh`，然后在其中添加如下代码：

```shell
#!/bin/sh

# 指定 protoc 编译器的路径，示例代码中使用的 Protobuf 版本为 21.12，你可以根据你的实际需求替换
PROTOC_PATH=./protoc-21.12-osx-aarch_64/bin/protoc

# 指定 .proto 文件的路径，文件中数据结构的详细描述见参考信息
PROTO_FILE=./SttMessage.proto

# 指定输出目录
OBJC_OUT_DIR=$(pwd)/code/objective-c

# 创建输出目录（如果不存在）
mkdir -p $OBJC_OUT_DIR

# 生成 Objective-C 代码
$PROTOC_PATH --objc_out=$OBJC_OUT_DIR $PROTO_FILE

# 生成代码完成后输出提示信息
echo "Generate code finished."
```

</TabsContent>

<TabsContent value="csharp">

创建一个 Shell 脚本，将其命名为 `generate_code.sh`，然后在其中添加如下代码：

```shell
#!/bin/sh

# 指定 protoc 编译器的路径，示例代码中使用的 Protobuf 版本为 21.12，你可以根据你的实际需求替换
PROTOC_PATH=./protoc-21.12-osx-aarch_64/bin/protoc

# 指定 .proto 文件的路径，文件中数据结构的详细描述见参考信息
PROTO_FILE=./SttMessage.proto

# 指定输出目录
CSHARP_OUT_DIR=$(pwd)/code/csharp

# 创建输出目录（如果不存在）
mkdir -p $CSHARP_OUT_DIR

# 生成 Objective-C 代码
$PROTOC_PATH --csharp_out=$CSHARP_OUT_DIR $PROTO_FILE

# 生成代码完成后输出提示信息
echo "Generate code finished."
```

</TabsContent>

<TabsContent value="js">

如果你需要生成 JavaScript 代码，请确保在生成代码前已经安装 Protobuf 的相关依赖。你可以参考下列步骤来安装依赖，如果已有依赖，可跳过此步骤。

<Accordions>
<Accordion title="安装 Protobuf 依赖">

1. 打开你的项目根目录，编辑 `package.json` 文件，添加以下依赖项：

   ```json
   {
     "dependencies": {
       ...
       "protobufjs": "^7.2.5"
     },
     "devDependencies": {
       ...
       "pbjs": "^0.0.14",
       "protobufjs-cli": "^1.1.2"
     }
   }
   ```

   你可以根据实际需求指定 Protobuf 库和 protobufjs 命令行工具的版本。

2. 在终端运行以下命令以安装依赖：

   ```shell
   npm install
   ```

</Accordion>
</Accordions>

创建一个 Shell 脚本，将其命名为 `generate_code.sh`，然后在其中添加如下代码：

```shell
# 将 protobufjs-cli 的可执行文件路径添加到 PATH 环境变量
# 需要将 {absolute path of protobufjs-cli in your node_modules}/bin 替换为 protobufjs-cli 在 node_modules 中的绝对路径
export "PATH=$PATH:{absolute path of protobufjs-cli in your node_modules}/bin"

# 生成 javascript 的示例代码
pbjs -t json-module -w es6 ./SttMessage.proto > ./SttMessage_es6.js

echo "JavaScript code generation finished."
```

</TabsContent>
</Tabs>

#### 运行脚本 [#运行脚本]

在终端中运行以下命令来运行脚本：

```shell
# 将脚本设置为可执行文件
chmod +x generate_code.sh
# 运行脚本
./generate_code.sh
```

### 反序列化数据 [#反序列化数据]

客户端接收到数据流时，SDK 会触发接收到数据流消息的回调。本节介绍如何对接收到的数据进行反序列化，将其转换回数据结构或对象。以下为不同语言的示例代码：

```java tab="Java" tabGroup="deserialize"
// 加入频道，添加回调事件
rtcManager.joinChannel(roomName, localUid, agora_token, roleType.equals(ROLE_TYPE_BROADCAST), new RtcManager.OnChannelListener() {
    ...
    // 接收到数据流消息的回调
    @Override
    public void onStreamMessage(int uid, int streamId, byte[] data) {
        // 检查远端用户 ID 是否为指定的推流机器人 ID，如果是则解码数据流为文本对象
        if (String.valueOf(uid).equalsIgnoreCase(RTC_UID_STT_STREAM)) {
            AgoraSpeech2TextProtobuffer.Text text = STTManager.getInstance().parseTextByte(roomName, data);
            // 将解析后的文本对象转换为 JSON 格式并打印日志
            LogUtil.d(originLogName, mGson.toJson(text));
        }
    }
    ...
});

public AgoraSpeech2TextProtobuffer.Text parseTextByte(String channel, byte[] data) {
    // 声明一个 AgoraSpeech2TextProtobuffer.Text 类型的变量，用于存储反序列化后的对象
    AgoraSpeech2TextProtobuffer.Text textStream;
    try {
        // 将字节数组 data 反序列化为 AgoraSpeech2TextProtobuffer.Text 对象
        textStream = AgoraSpeech2TextProtobuffer.Text.parseFrom(data);
    } catch (Exception ex) {
        notifyErrorHandler(new ErrorInfo("parseTextByte", "-1", "parseTextByte parseFrom error >> " + ex.toString()));
        return null;
    }
    ...
}
```

```csharp tab="C#" tabGroup="deserialize"
private void InitRtcEngine()
{   // 创建一个 RTC 引擎实例
    RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
    // 创建一个事件处理类的实例
    AgoraEventHandler handler = new AgoraEventHandler(this);
    // 创建 RtcEngineContext 对象，并设置频道场景为直播
    RtcEngineContext context = new RtcEngineContext(_appID, 0,
                                                    CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
                                                    AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
    // 初始化引擎
    RtcEngine.Initialize(context);
    // 添加回调事件
    RtcEngine.InitEventHandler(handler);
}
// 定义一个类用于处理 RTC 相关回调，继承自 IRtcEngineEventHandler
internal class AgoraEventHandler: IRtcEngineEventHandler
{   // 接收到数据流消息的回调
    public override void OnStreamMessage(RtcConnection connection, uint remoteUid, int streamId, byte[] data, uint length, ulong sentTs)
    {
        // Debug.Log(String.Format("remoteUid: {0}", remoteUid));
        // 如果远端用户 ID 等于指定的推流机器人 ID
        if (remoteUid == {pusher bot uid}) {
            // 解析 Protobuf 数据
            AgoraSTTSample.Protobuf.Text t = ProtobufUtility.ParseProtobufData(data);
            ...
        }
    }
}
```

```javascript tab="JavaScript" tabGroup="deserialize"
import AgoraRTC from "agora-rtc-sdk-ng"
import protoRoot from "@/protobuf/SttMessage_es6.js"

// 创建 RTC 客户端实例
this.rtc.client = AgoraRTC.createClient({ mode: "live", codec: "vp8", role: this.role })

// 监听数据流事件并绑定事件处理函数
this.rtc.client.on("stream-message", this.onStreamMessage.bind(this))

// 接收到数据流消息的回调
function onStreamMessage(uid, stream) {
    // 检查远端用户 ID 是否为指定的推流机器人 ID，如果不是则直接返回，不进行后续处理
    if (uid != {pusher bot uid}) {
        return
    }
    // 使用 Protobuf 解码收到的数据流
    let textstream = protoRoot.Agora.SpeechToText.lookup("Text").decode(data)
    ...
}
```

```objc tab="Objective-C" tabGroup="deserialize"
// Temp.h
#import "AgoraRtcKit/AgoraRtcKit.h"
#import "./Protobuff/SttMessage.pbobjc.h"

NS_ASSUME_NONNULL_BEGIN

@interface Temp : NSObject<AgoraRtcEngineDelegate>

@end

NS_ASSUME_NONNULL_END

// Temp.m
@implementation Temp

// 接收到数据流消息的回调
- (void)rtcEngine:(AgoraRtcEngineKit *)engine receiveStreamMessageFromUid:(NSUInteger)uid streamId:(NSInteger)streamId data:(NSData *)data {
    // 检查远端用户 ID 是否为指定的推流机器人 ID，如果不是则直接返回，不进行后续处理
    if (uid != pusherUid) {
        return;
    }

    NSError* error;
    // 解码收到的数据流
    SttText* st =  [SttText parseFromData: data error: &error];
    ...
}

@end
```

```swift tab="Swift" tabGroup="deserialize"
// 接收到数据流消息的回调
func rtcEngine(_ engine: AgoraRtcEngineKit, receiveStreamMessageFromUid uid: UInt, streamId: Int, data: Data) {
    // 检查远端用户 ID 是否为指定的推流机器人 ID，如果不是则直接返回，不进行后续处理
    guard uid == {puher bot uid} else {
        return
    }
    // 解码收到的数据流
    let text = try? SttText.parse(from: data)
    ...
}
```

## 参考信息 [#参考信息]

本节提供使用 Protobuf 反序列化数据的其他相关信息。

### 示例项目 [#示例项目]

声网提供了开源的实时转录翻译示例项目供你参考，你可以前往下载或查看其中的源代码。

<Cards>
  <Card title="Agora-RTT-Demo" href="https://github.com/AgoraIO-Community/Agora-RTT-Demo/tree/main" />
</Cards>

### SttMessage.proto 文件说明 [#sttmessageproto-文件说明]

声网提供的 `SttMessage.proto` 文件中定义了转写后的文本数据结构，各字段说明详见如下：

```protobuf
syntax = "proto3";

package Agora.SpeechToText;
option objc_class_prefix = "Stt";
option csharp_namespace = "AgoraSTTSample.Protobuf";
option java_package = "io.agora.rtc.speech2text";
option java_outer_classname = "AgoraSpeech2TextProtobuffer";

message Text {
  reserved 1 to 3, 5, 7 to 9, 11, 17;
  int64 uid = 4;
  int64 time = 6;
  repeated Word words = 10;
  int32 duration_ms = 12;
  string data_type = 13;
  repeated Translation trans = 14;
  string culture = 15;
  int64 text_ts = 16;
  OriginalTranscript original_transcript = 18;
  int64 sentence_id = 19;
}

message Word {
  reserved 2, 3, 5;
  string text = 1;
  bool is_final = 4;
}

message Translation {
  bool is_final = 1;
  string lang = 2;
  repeated string texts = 3;
}

message OriginalTranscript {
  string culture = 1;
  repeated Word words = 2;
}
```

#### Text 消息类型及其字段说明 [#text-消息类型及其字段说明]

| 字段名称 | 类型 | 含义 |
| --- | --- | --- |
| `uid` | `int64` | 文本所对应的用户 ID。 |
| `time` | `int64` | 该句段转写的起始时间。仅在 `isFinal` 为 `true` 时有值，其他时候为 `0`。 |
| `words` | `repeated` | 转写结果的数组，详见 `Word` 消息类型。 |
| `duration_ms` | `int32` | 转写文本的时长，单位为毫秒。 |
| `data_type` | `string` | [how-to-use-protobuf-markdown-0-5-2 details](#table-slot-how-to-use-protobuf-markdown-0-5-2) |
| `trans` | `repeated` | 翻译结果的数组，详见 `Translation` 消息类型。 |
| `culture` | `string` | 转写的源语言。 |
| `text_ts` | `int64` | 转写结果的时间戳，持续递增，用于实时翻译时原文和译文的对齐。 |
| `original_transcript` | `OriginalTranscript` | 转写后的文本，用于翻译，详见 `OriginalTranscript`。 |
| `sentence_id` | `int64` | 字幕唯一 ID，字幕在数据流里的唯一标识，用于原文与译文字幕的精准对齐。 |

<a id="table-slot-how-to-use-protobuf-markdown-0-5-2"></a>

**how-to-use-protobuf-markdown-0-5-2 details**

数据类型：

- `transcribe`：转写。
- `translate`：文本翻译。

#### Word 消息类型及其字段说明 [#word-消息类型及其字段说明]

| 字段名称 | 类型 | 含义 |
| --- | --- | --- |
| `text` | `string` | 转写的结果。 |
| `is_final` | `bool` | [how-to-use-protobuf-markdown-1-2-2 details](#table-slot-how-to-use-protobuf-markdown-1-2-2) |

<a id="table-slot-how-to-use-protobuf-markdown-1-2-2"></a>

**how-to-use-protobuf-markdown-1-2-2 details**

该句是否为转写的最终结果：

- `true`：已为最终结果。
- `false`：并非最终结果。 该字段为 `true` 时表明转写引擎认为该句的文字转写结果已经确定，无需再进行修改，但并不代表这句话在语义上已经结束。

#### Translation 消息类型及其字段说明 [#translation-消息类型及其字段说明]

| 字段名称 | 类型 | 含义 |
| --- | --- | --- |
| `is_final` | `bool` | [how-to-use-protobuf-markdown-2-1-2 details](#table-slot-how-to-use-protobuf-markdown-2-1-2) |
| `lang` | `string` | 翻译的目标语言。 |
| `texts` | `repeated` | 翻译的结果。 |

<a id="table-slot-how-to-use-protobuf-markdown-2-1-2"></a>

**how-to-use-protobuf-markdown-2-1-2 details**

该句是否为翻译的最终结果：

- `true`：已为最终结果。
- `false`：并非最终结果。该字段为 `true` 时表明翻译引擎认为该句的翻译结果已经确定，无需再进行修改，但并不代表这句话在语义上已经结束。

#### OriginalTranscript 消息类型及其字段说明 [#originaltranscript-消息类型及其字段说明]

| 字段名称   | 类型       | 含义                                                         |
| ---------- | ---------- | ------------------------------------------------------------ |
| `culture`  | `string`   | 转写的源语言。 |
| `words`    | `repeated` | 转写结果的数组，详见 `Word` 消息类型。 |

:::info[信息]
使用 Soniox 供应商时，单次 Protobuf `Text` 消息可能承载多个片段。例如，转写消息中的 `words[]` 或翻译消息中的 `trans[]` 长度可能大于 `1`。客户端不应假设一次数据流消息只包含一个转写或翻译片段。
:::

### JSON 协议数据结构 [#json-协议数据结构]

如果调用 [`join`](/zh-CN/api-reference/api-ref/speech-to-text/join) 时将 `enableJsonProtocol` 设置为 `true`，服务会使用 JSON 格式推送字幕数据。JSON 消息的顶层对象包含 `transcript` 或 `translation` 字段：

- `transcript`：转写结果。
- `translation`：翻译结果。

使用 Soniox 供应商时，JSON 协议改为使用 `results[]` 显式承载一个或多个片段。单片段场景下 `results[]` 长度为 `1`；mixed 场景下，单条消息可能同时包含已稳定的前缀片段和仍会变化的后缀片段，此时 `results[]` 长度大于 `1`。

:::warning[注意]
这是 JSON 客户端协议的不兼容变更。如果你的客户端启用了 JSON 协议，需要与服务端一起升级。升级后不要再假设一次数据流消息只对应一个转写或翻译片段，请遍历 `results[]` 处理每个片段，并兼容 `results[]` 为空数组的消息。
:::

#### transcript 字段 [#transcript-字段]

| 字段 | 类型 | 含义 |
| :--- | :--- | :--- |
| `uid` | Number | 用户的音频流 ID。 |
| `textTs` | Number | 这批转写结果的主时间戳，取首个片段对应的 `text_id`。 |
| `offset` | Number | 这批转写结果的起始时间，单位为毫秒。 |
| `duration` | Number | 这批转写结果的时长，单位为毫秒。 |
| `language` | String | 首个转写片段的源语言。 |
| `text` | String | 转写文本。 |
| `isFinal` | Boolean | 转写文本是否为最终结果。 |
| `sentenceId` | Number | 句级聚合单元 ID，表示 `results[]` 中的转写片段归属于同一句或同一轮句级聚合单元。 |
| `results` | Array | 转写片段数组。详见 [transcript.results 字段](#transcriptresults-字段)。 |

#### transcript.results 字段 [#transcriptresults-字段]

| 字段 | 类型 | 含义 |
| :--- | :--- | :--- |
| `text` | String | 当前转写片段的文本。 |
| `isFinal` | Boolean | 当前转写片段是否为最终结果。 |
| `offset` | Number | 当前转写片段的起始时间，单位为毫秒。 |
| `duration` | Number | 当前转写片段的时长，单位为毫秒。 |

#### translation 字段 [#translation-字段]

| 字段 | 类型 | 含义 |
| :--- | :--- | :--- |
| `uid` | Number | 用户的音频流 ID。 |
| `textTs` | Number | 这批翻译结果的主时间戳，取首个翻译片段对应的 `text_id`。 |
| `offset` | Number | 这批翻译结果的起始时间，单位为毫秒。 |
| `duration` | Number | 这批翻译结果的时长，单位为毫秒。 |
| `isFinal` | Boolean | 翻译结果是否为最终结果。 |
| `sentenceId` | Number | 句级聚合单元 ID，表示 `results[]` 中的翻译片段归属于同一句或同一轮句级聚合单元。 |
| `results0` | Object | 兼容旧版本的翻译结果字段，结构保持不变。 |
| `results` | Array | 翻译片段数组。详见 [translation.results 字段](#translationresults-字段)。 |
| `original_transcript` | Object | 原文转写结果。仅在开启原文返回时出现。 |

#### translation.results 字段 [#translationresults-字段]

| 字段 | 类型 | 含义 |
| :--- | :--- | :--- |
| `language` | String | 翻译的目标语言。 |
| `texts` | Array | 当前翻译片段的译文数组。 |
| `isFinal` | Boolean | 当前翻译片段是否为最终结果。 |

#### original_transcript 字段 [#original_transcript-字段]

| 字段 | 类型 | 含义 |
| :--- | :--- | :--- |
| `language` | String | 原文的源语言。 |
| `text` | String | 合并后的原文文本。 |
| `results` | Array | 原文片段数组，字段包括 `text` 和 `isFinal`。|

### 示例数据 [#示例数据]

本节提供 Protobuf 和 JSON 格式下 `transcribe` 和 `translate` 两种数据类型的示例。

**transcribe**

<Tabs defaultValue="Protobuf" groupId="transcribe-example" persist>
<TabsList>
  <TabsTrigger value="Protobuf">protobuf</TabsTrigger>
  <TabsTrigger value="JSON">json</TabsTrigger>
  <TabsTrigger value="JSON-mixed">json mixed</TabsTrigger>
</TabsList>

<TabsContent value="Protobuf">

```protobuf
time: 1753359518654
words {
  text: "Hello, how are you?"
  is_final: true
}
duration_ms: 770
data_type: "transcribe"
culture: "en-US"
text_ts: 1753359520754
sentence_id: 1753359518654
```

</TabsContent>

<TabsContent value="JSON">

```json
{
    "transcript": {
        "uid": 42,
        "textTs": 1710000012345,
        "offset": 1000,
        "duration": 200,
        "language": "zh-CN",
        "text": "你好。",
        "isFinal": true,
        "sentenceId": 1710000012000,
        "results": [
            {
                "text": "你好。",
                "isFinal": true,
                "offset": 1000,
                "duration": 200
            }
        ]
    }
}
```

</TabsContent>

<TabsContent value="JSON-mixed">

以下示例表示同一条 JSON 消息同时包含已稳定的前缀片段和仍会变化的后缀片段：

```json
{
    "transcript": {
        "uid": 42,
        "textTs": 1710000012345,
        "offset": 1000,
        "duration": 500,
        "language": "zh-CN",
        "text": "你好。",
        "isFinal": true,
        "sentenceId": 1710000012000,
        "results": [
            {
                "text": "你好。",
                "isFinal": true,
                "offset": 1000,
                "duration": 200
            },
            {
                "text": "你",
                "isFinal": false,
                "offset": 1300,
                "duration": 200
            }
        ]
    }
}
```

发送包含 `results[]` 的消息后，服务还会再发送一条 `results` 为空数组的消息：

```json
{
    "transcript": {
        "uid": 42,
        "textTs": 1710000012345,
        "offset": 1000,
        "duration": 500,
        "language": "zh-CN",
        "text": "你好。",
        "isFinal": true,
        "sentenceId": 1710000012000,
        "results": []
    }
}
```

</TabsContent>
</Tabs>

**translate**

```protobuf tab="protobuf" tabGroup="translate-example"
time: 1753359518654
duration_ms: 770
data_type: "translate"
trans {
  is_final: true
  lang: "es-ES"
  texts: "Hola, ¿cómo estás? "
}
text_ts: 1753359520754
sentence_id: 1753359518654
original_transcript {
  culture: "en-US"
  words {
    text: "Hello, how are you?"
    is_final: true
  }
}
```

```json tab="json" tabGroup="translate-example"
{
        "translation": {
            "uid": 42,
            "isFinal": true,
            "offset": 1000,
            "duration": 500,
            "textTs": 1710000012345,
            "sentenceId": 1710000012000,
            "results0": {
                "language": "en-US",
                "texts": [
                    "Hello."
                ]
            },
            "results": [
                {
                    "language": "en-US",
                    "texts": [
                        "Hello."
                    ],
                    "isFinal": true
                },
                {
                    "language": "en-US",
                    "texts": [
                        "World."
                    ],
                    "isFinal": false
                }
            ],
            "original_transcript": {
                "language": "zh-CN",
                "text": "你好。",
                "results": [
                    {
                        "text": "你好。",
                        "isFinal": true
                    },
                    {
                        "text": "世界",
                        "isFinal": false
                    }
                ]
            }
        }
}
```

发送包含 `results[]` 的消息后，服务还会再发送一条 `results` 为空数组的消息：

```json
{
    "translation": {
        "uid": 42,
        "isFinal": true,
        "offset": 1000,
        "duration": 500,
        "textTs": 1710000012345,
        "sentenceId": 1710000012000,
        "results0": {
            "language": "en-US",
            "texts": [
                "Hello."
            ]
        },
        "results": [],
        "original_transcript": {
            "language": "zh-CN",
            "text": "你好。",
            "results": []
        }
    }
}
```
