# 使用说明 (/zh-CN/realtime-media/marketplace/build/integrate-extensions/iflytek-asr)

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

<_PlatformTabsGroup>
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
## 技术原理 [#技术原理]

本文介绍如何在你的项目中集成和使用讯飞语音实时转写及翻译（多语种）插件（以下简称“讯飞转写及翻译插件”）。讯飞转写及翻译插件的工作流程如下：

1. 设置源语种和目标语种。
2. 声网 SDK 通过麦克风来流式录音。
3. 音频传递给讯飞转写及翻译插件。
4. 插件把音频上传给语音转写服务，并下载语音转写结果。
5. 解析语音转写结果，得到语音转写文本。
6. 把语音转写文本上传给文本翻译服务，并下载文本翻译结果。
7. 解析文本翻译结果，得到文本翻译文本。

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

Android 开发环境需满足以下要求：

- Android Studio 4.1 以上版本。
- 运行 Android 4.1 或以上版本的真机（非模拟器）。

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

讯飞转写及翻译插件提供了示例项目，你可以前往克隆或下载并进行体验。

| 平台    | 语言        | 示例代码                                                     |
| ------- | ----------- | ------------------------------------------------------------ |
| Android | Java        | [点击下载](https://download.agora.io/marketplace/release/AgoraWithHyAndroid.zip) |
| iOS     | Objective-C |[点击下载](https://download.agora.io/marketplace/release/AgoraWithHyIos.zip) |
| Windows | C++         | [点击下载](https://download.agora.io/marketplace/release/AgoraWithHyWin.zip) |

### 运行步骤 [#运行步骤]

参考以下步骤快速跑通示例项目：

1. 在 Android Studio 中打开示例项目。
2. 将项目与 Gradle 文件同步。
3. 打开 `AgoraWithHyAndroid/app/src/main/java/agoramarketplace/hy/demo/MainActivity.java` 文件，将 `#YOUR APP ID#` 替换为你的声网 App ID。获取 App ID 请参考[开通云市场服务](/zh-CN/realtime-media/marketplace/get-started/enable-service)。

    ```java
    private static final String appId = "#YOUR APP ID#";
    ```

4. 打开 `AgoraWithHyAndroid/app/src/main/java/agoramarketplace/hy/demo/HyUtil.java` 文件，填入以下参数：

    ```java
    /** 在声网控制台购买插件后、由讯飞提供的 APPID */
    public static final String APP_ID = "";
    /** 在声网控制台购买插件后、由讯飞提供的 APIKey */
    public static final String API_KEY = "";
    /** 在声网控制台购买插件后、由讯飞提供的 APISecret */
    public static final String API_SECRET = "";
    ```

5. 连接一台 Android 真机（非模拟器），运行项目。

### 预期效果 [#预期效果]
运行成功后，示例项目会安装到你的设备上。

1. 启动 App，你可以在界面上看到**中译英**下拉控件，**start**、**flush**、**stop** 按钮，以及 **tip**、**ist_text**、**its_text** 文本视图。
2. 可以单击**中译英**下拉控件选择参数。
3. 单击 **start** 按钮启动倾听。
4. 对着麦克风说话，语音转写文本会显示在 **ist_text** 文本视图中，文本翻译文本会显示在 **its_text** 文本视图中。
5. 单击 **flush** 按钮结束音频获取结果，或单击 **stop** 按钮停止倾听。
6. **tip** 文本视图会显示 **onEnd**。

## 集成和调用流程 [#集成和调用流程]

### 1. 集成 SDK 和插件 [#1-集成-sdk-和插件]

开始前，你需要在项目中分别集成声网音频 SDK 和讯飞转写及翻译插件。

#### 1.1 集成声网音频 SDK [#11-集成声网音频-sdk]

讯飞转写及翻译插件需要与[声网音频 SDK v4.x](/zh-CN/realtime-media/rtc) 搭配使用。参考以下文档集成音频 SDK v4.x 并实现基础的语音通话：

<Cards>
  <Card title="实现语音通话" href="/zh-CN/realtime-media/rtc/build/audio/audio-quick-start" />
</Cards>

<a id="buy"></a>
#### 1.2 购买和激活插件 [#12-购买和激活插件]

你需要进入[声网控制台 > 云市场](https://console.shengwang.cn/marketplace/list/all)页面，按照提示购买讯飞转写及翻译插件。购买成功后，你会收到由讯飞提供的 APPID、APIKey 和 APISecret，后续启动倾听时需要用到。

<a id="integration"></a>
#### 1.3 集成插件 [#13-集成插件]

参考以下步骤集成插件：

1. 进入[声网控制台 > 云市场](https://console.shengwang.cn/marketplace/list/all)页面下载**讯飞语音实时转写及翻译（多语种）**的 Android 插件包。
2. 解压文件夹，将所有 `.aar` 文件保存到项目文件夹的 `/app/libs` 路径。
3. 打开 `app/build.gradle` 文件，在 `dependencies` 中添加如下行：

    ```java
    implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
    ```

4. 打开编写逻辑的 Java 文件，添加如下行，导入插件信息类：

    ```java
    import io.agora.hy.extension.ExtensionManager;
    ```

#### 1.4 升级插件 [#14-升级插件]

声网云市场推荐你在更新插件版本前使用数据库工具（如 SQLite）实现插件版本管理，从而确保插件版本与对应的资源包版本一致。因为当插件版本和资源包版本未对齐时，会出现 App 闪退、崩溃等现象。

以下是用 SQLite 来进行插件版本管理的示例：

  1. 使用 SQLite 数据库自行维护插件包和资源包的版本信息。
  2. 每次项目初始化时，自动检测当前项目使用的资源包版本：
      - 如果无记录文件或记录的资源包版本为旧，则更新资源。成功更新后，回写最新资源包版本信息到 SQLite。
      - 如果记录的资源包版本与当前匹配，则正常实现业务。

如果你的项目已经集成过声网云市场提供的第三方插件，并需要更新插件，可以参考以下步骤来保证更新后的可用性。

以下升级流程以**相芯美颜特效**插件为例：

  1. 参考[集成插件](#13-集成插件)章节下载所需平台最新版本的插件包和资源包。

  2. 删除项目内旧版本的插件包及其资源包后，再将新版插件和资源包放入对应位置。或者直接在对应目录下用新版插件和资源包替换旧版。

  3. 删除设备上用老版本插件和资源包编译的 App，重新编译你的项目并运行。

:::warning[注意]
编译 App 时，系统会优先使用连接设备上存在的资源包。请务必在删除旧版本应用程序后再进行编译和运行，否则可能会出现插件版本与资源包不匹配的情况，从而导致各种意外问题。
:::

### 2. 初始化 [#2-初始化]

集成插件后，你需要先初始化插件，步骤如下：

1. `RtcEngineConfig` 实例调用 `addExtension`，将插件添加到 SDK 中。

    ```java
    config.addExtension(ExtensionManager.EXTENSION_NAME);
    ```

2. `RtcEngineConfig` 实例设置 `mExtensionObserver` 监听器。

    ```java
    private final IMediaExtensionObserver mMediaExtensionObserver = new IMediaExtensionObserver() {
        @Override
        public void onEvent(String provider, String extension, String key, String value) {
            Log.i(TAG, "onEvent | provider: " + provider + ", extension: " + extension
                    + ", key: " + key + ", value: " + value);
            if (!ExtensionManager.EXTENSION_VENDOR_NAME.equals(provider)
                    || !ExtensionManager.EXTENSION_AUDIO_FILTER_NAME.equals(extension)) {
                return;
            }

            mHyUtil.onEvent(key, value);
        }
    }
    ```

    ```java
    config.mExtensionObserver = mMediaExtensionObserver;
    ```

3. 在加入频道前，调用 `enableExtension` 开启插件。

    ```java
    mRtcEngine.enableExtension(ExtensionManager.EXTENSION_VENDOR_NAME, ExtensionManager.EXTENSION_AUDIO_FILTER_NAME, true);
    ```

4. 插件设置 logcat 日志等级。

    ```java
    mRtcEngine.setExtensionProviderProperty(ExtensionManager.EXTENSION_VENDOR_NAME, "log_lvl", "" + 8);
    ```

### 3. 启动倾听 [#3-启动倾听]

空闲时可调用，调用后会进入会话。会话最长 5 小时。

```java
public void start(ParamWrap paramWrap) {
    mParser.clear();

    String val = null;
    try {
        JSONObject rootJo = new JSONObject();
        // 公共对象。必选。
        JSONObject commonJo = new JSONObject();
        {
            // 应用标识。必选。对应在声网控制台购买插件后、由讯飞提供的 APPID。
            commonJo.put("app_id", APP_ID);
            // API 密钥。必选。对应在声网控制台购买插件后、由讯飞提供的 APIKey。
            commonJo.put("api_key", API_KEY);
            // API 秘密。必选。对应在声网控制台购买插件后、由讯飞提供的 APISecret。
            commonJo.put("api_secret", API_SECRET);
        }
        rootJo.put("common", commonJo);
        // 语音转写对象。必选。
        JSONObject istJo = new JSONObject();
        {
            // URI。必选。
            istJo.put("uri", "wss://ist-api.xfyun.cn/v2/ist");
            // 请求对象。必选。
            JSONObject reqJo = new JSONObject();
            {
                // 业务对象。必选。
                JSONObject businessJo = new JSONObject();
                {
                    // 语种。必选。
                    businessJo.put("language", paramWrap.mIstLanguage);
                    // 口音。必选。
                    businessJo.put("accent", paramWrap.mIstAccent);
                    // 领域。必选。
                    businessJo.put("domain", paramWrap.mIstDomain);
                    // 语言类型
                    // 值类型：int
                    // 值范围：
                    // 1：中英文模式，中文英文均可识别；
                    // 3：英文模式，只识别出英文
                    // 值默认：1
                    businessJo.put("language_type", paramWrap.mIstLanguageType);
                    // 动态修正
                    businessJo.put("dwa", paramWrap.mIstDwa);
                }
                reqJo.put("business", businessJo);
            }
            istJo.put("req", reqJo);
        }
        rootJo.put("ist", istJo);
        // 文本翻译对象。
        JSONObject itsJo = new JSONObject();
        {
            // URI。必选。
            itsJo.put("uri", "https://itrans.xfyun.cn/v2/its");
            // 请求对象。必选。
            JSONObject reqJo = new JSONObject();
            {
                // 业务对象。必选。
                JSONObject businessJo = new JSONObject();
                {
                    // 源语种。必选。
                    businessJo.put("from", paramWrap.mItsFrom);
                    // 目标语种。必选。
                    businessJo.put("to", paramWrap.mItsTo);
                }
                reqJo.put("business", businessJo);
            }
            itsJo.put("req", reqJo);
        }
        rootJo.put("its", itsJo);
        val = rootJo.toString();
    } catch (JSONException e) {
        mListener.onLogE(TAG + ".start | json fail", e);
        return;
    }
    int errCode = mRtcEngine.setExtensionProperty(ExtensionManager.EXTENSION_VENDOR_NAME,
            ExtensionManager.EXTENSION_AUDIO_FILTER_NAME, "start_listening", val);
    mListener.onLogI(TAG + ".start | mRtcEngine.setExtensionProperty errCode: " + errCode);
}
```

### 4. 结束音频获取结果 [#4-结束音频获取结果]

会话时可调用，调用后会先处理完音频和结果，再进入空闲，回调 `"end"`。

```java
public void flush() {
    // 值不能为空，否则收不到。
    int errCode = mRtcEngine.setExtensionProperty(ExtensionManager.EXTENSION_VENDOR_NAME, ExtensionManager.EXTENSION_AUDIO_FILTER_NAME, "flush_listening", "{}");
    mListener.onLogI(TAG + ".flush | mRtcEngine.setExtensionProperty errCode: " + errCode);
}
```

讯飞转写及翻译插件会通过 `onEvent` 回调返回语音转写和文本翻译结果。识别结果的含义详见 [onEvent 回调的 key](/zh-CN/realtime-media/marketplace/reference/iflytek-asr-api#回调-key)。

### 5. 停止倾听 [#5-停止倾听]

会话时可调用，调用后会先丢弃音频和结果，再进入空闲，回调 `"end"`。

```java
public void stop() {
    int errCode = mRtcEngine.setExtensionProperty(ExtensionManager.EXTENSION_VENDOR_NAME, ExtensionManager.EXTENSION_AUDIO_FILTER_NAME, "stop_listening", "{}");
    mListener.onLogI(TAG + ".stop | mRtcEngine.setExtensionProperty errCode: " + errCode);
}
```

<a id="ref"></a>
## 参考信息 [#参考信息]
### API 参考 [#api-参考]

- [声网 SDK 的插件相关方法和回调](/zh-CN/api-reference/rtc/android/extension#toc_extension)
- [插件的 key-value 说明](/zh-CN/realtime-media/marketplace/reference/iflytek-asr-api)

### 常见问题 [#常见问题]

**1. 在 MacBook 上运行 Android 示例项目为什么会编译失败？**

由于示例项目中 Android NDK 的路径默认为 Windows 设备上的路径，因此在 MacBook 上会编译失败并提示 `Location specified by ndk.dir did not contain a valid NDK`。参考以下步骤解决：

1. 在 Android Studio 的顶部菜单栏中依次点击 **File > Project Structure... > SDK Location**，将 **Android NDK location** 的路径替换为你的本地路径，并且复制该路径。
 ![](https://web-cdn.agora.io/docs-files/1678351419119)

2. 打开 `local.properties` 文件，将 `ndk.dir` 的路径替换为你刚才复制的路径，并且保存修改。
 ![](https://web-cdn.agora.io/docs-files/1678351521401)

3. 连接 Android 设备，再次运行。

**2. 为什么在编译 App 时会出现 `models: no match` 的报错？**

该错误通常是由于资源包匹配问题引起的。很可能是因为在编译新插件时，没有删除旧版本的 App。建议你删除旧版 App 后重新编译运行。
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
本文介绍如何在你的项目中集成和使用讯飞语音实时转写及翻译（多语种）插件（以下简称“讯飞转写及翻译插件”）。
## 技术原理 [#技术原理-1]

讯飞转写及翻译插件的工作流程如下：

1. 设置源语种和目标语种。
2. 声网 SDK 通过麦克风来流式录音。
3. 音频传递给讯飞转写及翻译插件。
4. 插件把音频上传给语音转写服务，并下载语音转写结果。
5. 解析语音转写结果，得到语音转写文本。
6. 把语音转写文本上传给文本翻译服务，并下载文本翻译结果。
7. 解析文本翻译结果，得到文本翻译文本。

## 前提条件 [#前提条件-1]

iOS 开发环境需满足以下要求：

- Xcode 9.0 或以上版本。
- 运行 iOS 9.0 或以上版本的真机（非模拟器）。

## 示例项目 [#示例项目-1]

讯飞转写及翻译插件提供了示例项目，你可以前往克隆或下载并进行体验。

| 平台    | 语言        | 示例代码                                                     |
| ------- | ----------- | ------------------------------------------------------------ |
| Android | Java        | [点击下载](https://download.agora.io/marketplace/release/AgoraWithHyAndroid.zip) |
| iOS     | Objective-C |[点击下载](https://download.agora.io/marketplace/release/AgoraWithHyIos.zip) |
| Windows | C++         | [点击下载](https://download.agora.io/marketplace/release/AgoraWithHyWin.zip) |

### 运行步骤 [#运行步骤-1]

参考以下步骤快速跑通示例项目：

1. 在 Xcode 中打开 `AgoraWithHyIos/AgoraWithHyIos.xcodeproj`。
2. 打开 `AppID.m` 文件，将 `#YOUR APP ID#` 替换为你的声网 App ID。获取 App ID 请参考[开通云市场服务](/zh-CN/realtime-media/marketplace/get-started/enable-service)。

    ```objc
    NSString *const appID = @"#YOUR APP ID#";
    ```

3. 打开 `AgoraWithHyIos/VideoChatViewController.m` 文件，填入以下参数：

    ```objc
    // "app_id"、"api_key"、"api_secret" 分别对应在声网控制台购买插件后、由讯飞提供的 APPID、APIKey 和 APISecret
    NSDictionary *commonDict =  @{@"app_id":@"",@"api_key":@"",@"api_secret":@""};
    ```

4. 连接一台 iOS 真机（非模拟器），运行项目。

### 预期效果 [#预期效果-1]
运行成功后，示例项目会安装到你的设备上。

1. 启动 App，你可以在界面上看到**中译英**下拉控件，**start**、**flush**、**stop** 按钮，以及 **tip**、**ist_text**、**its_text** 文本视图。
2. 可以单击**中译英**下拉控件选择参数。
3. 单击 **start** 按钮启动倾听。
4. 对着麦克风说话，语音转写文本会显示在 **ist_text** 文本视图中，文本翻译文本会显示在 **its_text** 文本视图中。
5. 单击 **flush** 按钮结束音频获取结果，或单击 **stop** 按钮停止倾听。
6. **tip** 文本视图会显示 **onEnd**。

## 集成和调用流程 [#集成和调用流程-1]

### 1. 集成 SDK 和插件 [#1-集成-sdk-和插件-1]

开始前，你需要在项目中分别集成声网音频 SDK 和讯飞转写及翻译插件。

#### 1.1 集成声网音频 SDK [#11-集成声网音频-sdk-1]

讯飞转写及翻译插件需要与[声网音频 SDK v4.x](/zh-CN/realtime-media/rtc) 搭配使用。参考以下文档集成音频 SDK v4.x 并实现基础的语音通话：

<Cards>
  <Card title="实现语音通话" href="/zh-CN/realtime-media/rtc/build/audio/audio-quick-start" />
</Cards>

<a id="buy"></a>
#### 1.2 购买和激活插件 [#12-购买和激活插件-1]

你需要进入[声网控制台 > 云市场](https://console.shengwang.cn/marketplace/list/all)页面，按照提示购买讯飞转写及翻译插件。购买成功后，你会收到由讯飞提供的 APPID、APIKey 和 APISecret，后续启动倾听时需要用到。

<a id="integration"></a>
#### 1.3 集成插件 [#13-集成插件-1]

参考以下步骤集成插件：

1. 进入[声网控制台 > 云市场](https://console.shengwang.cn/marketplace/list/all)页面下载**讯飞语音实时转写及翻译（多语种）**的 iOS 插件包。
2. 解压文件夹，将所有 `.framework` 文件保存到你的项目文件夹下。以如下项目结构为例，你可以把插件保存到 `frameworks` 路径下。

    ```shell
    .
    ├── <ProjectName>
    │   ├── frameworks
    ├── <ProjectName>.xcodeproj
    ```

#### 1.4 升级插件 [#14-升级插件-1]

声网云市场推荐你在更新插件版本前使用数据库工具（如 SQLite）实现插件版本管理，从而确保插件版本与对应的资源包版本一致。因为当插件版本和资源包版本未对齐时，会出现 App 闪退、崩溃等现象。

以下是用 SQLite 来进行插件版本管理的示例：

  1. 使用 SQLite 数据库自行维护插件包和资源包的版本信息。
  2. 每次项目初始化时，自动检测当前项目使用的资源包版本：
      - 如果无记录文件或记录的资源包版本为旧，则更新资源。成功更新后，回写最新资源包版本信息到 SQLite。
      - 如果记录的资源包版本与当前匹配，则正常实现业务。

如果你的项目已经集成过声网云市场提供的第三方插件，并需要更新插件，可以参考以下步骤来保证更新后的可用性。

以下升级流程以**相芯美颜特效**插件为例：

  1. 参考[集成插件](#13-集成插件-1)章节下载所需平台最新版本的插件包和资源包。

  2. 删除项目内旧版本的插件包及其资源包后，再将新版插件和资源包放入对应位置。或者直接在对应目录下用新版插件和资源包替换旧版。

  3. 删除设备上用老版本插件和资源包编译的 App，重新编译你的项目并运行。

:::warning[注意]
编译 App 时，系统会优先使用连接设备上存在的资源包。请务必在删除旧版本应用程序后再进行编译和运行，否则可能会出现插件版本与资源包不匹配的情况，从而导致各种意外问题。
:::

### 2. 初始化 [#2-初始化-1]

集成插件后，你需要先初始化插件，步骤如下：

1. 开启插件。

    ```objc
    AgoraRtcEngineConfig *cfg = [AgoraRtcEngineConfig new];
    cfg.appId = appID;
    cfg.eventDelegate = self;
    self.agoraKit = [AgoraRtcEngineKit sharedEngineWithConfig:cfg delegate:self];
    [self.agoraKit enableExtensionWithVendor:@"Hy" extension:@"IstIts" enabled:YES];
    ```

2. 实现代理方法。

    ```objc
    #pragma mark - AgoraMediaFilterEventDelegate (self需要实现代理方法)
    - (void)onEvent:(NSString * _Nullable)vendor extension:(NSString *)extension key:(NSString * _Nullable)key value:(NSString * _Nullable)value {}
    ```

3. 设置插件日志等级。

    ```objc
    [self.agoraKit setExtensionProviderPropertyWithVendor:@"Hy" key:@"log_lvl" value:@"4"];
    ```

### 3. 启动倾听 [#3-启动倾听-1]

空闲时可调用，调用后会进入会话。会话最长 5 小时。

```objc
- (void)hyStart:(id)obj{
    [itsDataDict removeAllObjects];
    [istDataDict removeAllObjects];
    NSString *type =@"中译英";
    NSMutableDictionary *rootDict =  [NSMutableDictionary dictionary];
	// "app_id"、"api_key"、"api_secret" 分别对应在声网控制台购买插件后、由讯飞提供的 APPID、APIKey 和 APISecret
    NSDictionary *commonDict =  @{@"app_id":@"",@"api_key":@"",@"api_secret":@""};
    [rootDict setObject:commonDict forKey:@"common"];
    NSMutableDictionary *istDict =  [NSMutableDictionary dictionary];
    [istDict setObject:@"wss://ist-api.xfyun.cn/v2/ist" forKey:@"uri"];
    NSMutableDictionary *itsDict =  [NSMutableDictionary dictionary];
    [itsDict setObject:@"https://itrans.xfyun.cn/v2/its" forKey:@"uri"];
    if([mSelectedType isEqualToString:type]){
        NSDictionary *istBusinessDict = @{@"language":@"zh_cn",@"accent":@"mandarin",@"domain":@"ist_ed_open",@"language_type":@1,@"dwa":@"wpgs"};
        NSDictionary *istReqDict = @{@"business":istBusinessDict};
        [istDict setObject:istReqDict forKey:@"req"];
        [rootDict setObject:istDict forKey:@"ist"];

        NSDictionary *itsBusinessDict = @{@"from":@"cn",@"to":@"en"};
        NSDictionary *itsReqDict = @{@"business":itsBusinessDict};
        [itsDict setObject:itsReqDict forKey:@"req"];
        [rootDict setObject:itsDict forKey:@"its"];

    }else{
        NSDictionary *businessDict = @{@"language":@"zh_cn",@"accent":@"mandarin",@"domain":@"ist_ed_open",@"language_type":@3,@"dwa":@"wpgs"};
        NSDictionary *reqDict = @{@"business":businessDict};
        [istDict setObject:reqDict forKey:@"req"];
        [rootDict setObject:istDict forKey:@"ist"];

        NSDictionary *itsBusinessDict = @{@"from":@"en",@"to":@"cn"};
        NSDictionary *itsReqDict = @{@"business":itsBusinessDict};
        [itsDict setObject:itsReqDict forKey:@"req"];
        [rootDict setObject:itsDict forKey:@"its"];
    }

    NSData *data=[NSJSONSerialization dataWithJSONObject:rootDict options:NSJSONWritingPrettyPrinted error:nil];
    NSString *str=[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
    [self.agoraKit setExtensionPropertyWithVendor:@"Hy" extension:@"IstIts" key:@"start_listening" value:str];
}
```

### 4. 结束音频获取结果 [#4-结束音频获取结果-1]

会话时可调用，调用后会先处理完音频和结果，再进入空闲，回调 `"end"`。

```objc
- (void)hyFlush:(id)obj{
    [self.agoraKit setExtensionPropertyWithVendor:@"Hy" extension:@"IstIts" key:@"flush_listening" value:@"{}"];
}
```

讯飞转写及翻译插件会通过 `onEvent` 回调返回语音转写和文本翻译结果。识别结果的含义详见 [onEvent 回调的 key](/zh-CN/realtime-media/marketplace/reference/iflytek-asr-api#回调-key)。

### 5. 停止倾听 [#5-停止倾听-1]

会话时可调用，调用后会先丢弃音频和结果，再进入空闲，回调 `"end"`。

```objc
- (void)hyStop:(id)obj{
    [self.agoraKit setExtensionPropertyWithVendor:@"Hy" extension:@"IstIts" key:@"stop_listening" value:@"{}"];
}
```

<a id="ref"></a>
## 参考信息 [#参考信息-1]
### API 参考 [#api-参考-1]

- [声网 SDK 的插件相关方法和回调](/zh-CN/api-reference/rtc/ios/extension#toc_extension)
- [插件的 key-value 说明](/zh-CN/realtime-media/marketplace/reference/iflytek-asr-api)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
本文介绍如何在你的 Web 项目中集成和使用讯飞语音实时转写及翻译（多语种）插件（以下简称“讯飞转写及翻译插件”）。
## 前提条件 [#前提条件-2]

- Windows 或 macOS 计算机，需满足以下要求：
  - 下载桌面端 Chrome 90 或以上版本。
  - 具备物理音视频采集设备。
  - 可连接到互联网。如果你的网络环境部署了防火墙，请参考[应对防火墙限制](/zh-CN/realtime-media/rtc/build/setup-and-access/firewall)以正常使用声网服务。
  - 搭载 2.2 GHz Intel 第二代 i3/i5/i7 处理器或同等性能的其他处理器。
- 安装 [Node.js 及 npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)。

## 集成和调用流程 [#集成和调用流程-2]

### 1. 集成 SDK 和插件 [#1-集成-sdk-和插件-2]

开始前，你需要在项目中分别集成声网 Web SDK 和讯飞转写及翻译插件。

#### 1.1 集成声网 Web SDK [#11-集成声网-web-sdk]

讯飞转写及翻译插件需要与声网 Web SDK 4.x（v4.10.0 或以上）搭配使用。参考以下文档集成 Web SDK 并实现基础的语音通话：

<Cards>
  <Card title="实现语音通话" href="/zh-CN/realtime-media/rtc/get-started/quick-start" />
</Cards>

#### 1.2 购买和激活插件 [#12-购买和激活插件-2]

在声网控制台[购买和激活](/zh-CN/realtime-media/marketplace/get-started/enable-service#购买和激活插件)讯飞转写及翻译插件。购买成功后，你会收到由讯飞提供的 `appKey` 和 `appSecret`，后续使用插件时需要用到。

#### 1.3 集成插件 [#13-集成插件-2]

通过 npm 将讯飞转写及翻译插件集成到你的项目中。

1. 运行以下命令安装插件：

    ```shell
    npm install hyrt
    ```

2. 在你的 `.js` 文件中加入以下代码导入插件模块：

    ```javascript
    import { HySimpleAudioExtension } from 'hyrt'
    ```

### 2. 配置反向代理 [#2-配置反向代理]

如果需要使用插件的翻译功能，你需要配置反向代理以解决翻译跨域问题。

以 Nginx 为例：

```nginx
server {
    listen       80;
    server_name  localhost;

    location / {
        root   D:\\proj\\test-plugin;
        index  index.html index.htm;
    }

    # 示例如下
    location /v2/its {
        proxy_http_version 1.1;
        proxy_pass https://itrans.xfyun.cn/v2/its;
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host 'itrans.xfyun.cn'; # 固定请求头中的Host
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 300;
        proxy_send_timeout 300;
    }
}
```

### 3. 启用插件 [#3-启用插件]

启用插件的示例代码如下：

```javascript
import AgoraRTC from "agora-rtc-sdk-ng"
import { HySimpleAudioExtension } from 'hyrt'

// 创建 extension 实例
const audioExtension = new HySimpleAudioExtension();
// 注册插件
AgoraRTC.registerExtensions([audioExtension]);
// 创建 processor 实例
const processor = audioExtension.createProcessor();
// 通过麦克风采集的音频创建音频轨道
const audioTrack = await AgoraRTC.createMicrophoneAudioTrack();
// 将讯飞插件注入声网的音频处理管道，并从扬声器输出
audioTrack.pipe(processor).pipe(audioTrack.processorDestination);
// 设置识别参数
processor.setOptions(common, ist, its, callback)
// 启用插件
processor.enable()
// 停止插件
setTimeout(() => { processor.disable() }, 60000)
```

`setOptions` 方法中的参数设置详见[插件独有 API](/zh-CN/realtime-media/marketplace/reference/iflytek-asr-api)。你可以使用 `setOptions` 方法中的 `callback` 参数来处理识别结果，示例如下：

```javascript
function callback(r) {
    if (r.key === 'ist_result') {
        let sn = r.value.data.result.sn
        let ws = r.value.data.result.ws
        let words = ''
        for (let index = 0; index < ws.length; index++) {
            const element = ws[index];
            words += element.cw[0].w
        }
        console.log(`sn: ${sn}, words: ${words}`)
    } else if (r.key === 'its_result') {
        let sn = r.value.ist_sn
        let trans_result = r.value.data.result.trans_result
        console.log(`sn: ${sn}, [${trans_result.src}] => [${trans_result.dst}]`)
    } else if (r.key === 'error') {
        console.log(r.key, r.value)
    } else if (r.key === 'end') {
        console.log('识别结束')
    }
}
```

<a id="api"></a>
## API 参考 [#api-参考-2]

- 声网 Web SDK 的插件相关方法：
  - [`AgoraRTC.registerExtensions`](/zh-CN/api-reference/rtc/web/interfaces/iagorartc#registerextensions)
  - [`IMicrophoneAudioTrack.pipe`](/zh-CN/api-reference/rtc/web/interfaces/imicrophoneaudiotrack#pipe)
- [插件独有 API](/zh-CN/realtime-media/marketplace/reference/iflytek-asr-api)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />
本文介绍如何在你的项目中集成和使用讯飞语音实时转写及翻译（多语种）插件（以下简称“讯飞转写及翻译插件”）。
## 技术原理 [#技术原理-2]

讯飞转写及翻译插件的工作流程如下：

1. 设置源语种和目标语种。
2. 声网 SDK 通过麦克风来流式录音。
3. 音频传递给讯飞转写及翻译插件。
4. 插件把音频上传给语音转写服务，并下载语音转写结果。
5. 解析语音转写结果，得到语音转写文本。
6. 把语音转写文本上传给文本翻译服务，并下载文本翻译结果。
7. 解析文本翻译结果，得到文本翻译文本。

## 前提条件 [#前提条件-3]

Windows 开发环境需满足以下要求：

- Microsoft Visual Studio 2017 或以上版本。
- Windows 7 或以上版本的设备。

## 示例项目 [#示例项目-2]

讯飞转写及翻译插件提供了示例项目，你可以前往克隆或下载并进行体验。

| 平台    | 语言        | 示例代码                                                     |
| ------- | ----------- | ------------------------------------------------------------ |
| Android | Java        | [点击下载](https://download.agora.io/marketplace/release/AgoraWithHyAndroid.zip) |
| iOS     | Objective-C |[点击下载](https://download.agora.io/marketplace/release/AgoraWithHyIos.zip) |
| Windows | C++         | [点击下载](https://download.agora.io/marketplace/release/AgoraWithHyWin.zip) |

### 运行步骤 [#运行步骤-2]

参考以下步骤快速跑通示例项目：

1. 在 Microsoft Visual Studio 中打开 `AgoraWithHyWin/AgoraWithHyWin.sln`，并且安装 MFC 组件。
2. 打开头文件筛选器中的 `AgoraWithHyWinDlg.h` 文件，将 `#YOUR APP ID#` 替换为你的声网 App ID。获取 App ID 请参考[开通云市场服务](/zh-CN/realtime-media/marketplace/get-started/enable-service)。

    ```cpp
    #define AGORA_APP_ID "#YOUR APP ID#"
    ```

3. 打开头文件筛选器中的 `HyUtil.h` 文件，填入以下参数：

    ```cpp
    /** 在声网控制台购买插件后、由讯飞提供的 APPID */
    #define EXTENSION_APP_ID ""
    /** 在声网控制台购买插件后、由讯飞提供的 APIKey */
    #define EXTENSION_API_KEY ""
    /** 在声网控制台购买插件后、由讯飞提供的 APISecret */
    #define EXTENSION_API_SECRET ""
    ```

4. 运行项目。

### 预期效果 [#预期效果-2]
运行成功后，示例项目会安装到你的设备上。

1. 启动 App，你可以在界面上看到**中译英**下拉控件，**start**、**flush**、**stop** 按钮，以及 **tip**、**ist_text**、**its_text** 文本视图。
2. 可以单击**中译英**下拉控件选择参数。
3. 单击 **start** 按钮启动倾听。
4. 对着麦克风说话，语音转写文本会显示在 **ist_text** 文本视图中，文本翻译文本会显示在 **its_text** 文本视图中。
5. 单击 **flush** 按钮结束音频获取结果，或单击 **stop** 按钮停止倾听。
6. **tip** 文本视图会显示 **onEnd**。

## 集成和调用流程 [#集成和调用流程-3]

### 1. 集成 SDK 和插件 [#1-集成-sdk-和插件-3]

开始前，你需要在项目中分别集成声网音频 SDK 和讯飞转写及翻译插件。

#### 1.1 集成声网音频 SDK [#11-集成声网音频-sdk-2]

讯飞转写及翻译插件需要与[声网音频 SDK v4.x](/zh-CN/realtime-media/rtc) 搭配使用。参考以下文档集成音频 SDK v4.x 并实现基础的语音通话：

<Cards>
  <Card title="实现语音通话" href="/zh-CN/realtime-media/rtc/build/audio/audio-quick-start" />
</Cards>

<a id="buy"></a>
#### 1.2 购买和激活插件 [#12-购买和激活插件-3]

你需要进入[声网控制台 > 云市场](https://console.shengwang.cn/marketplace/list/all)页面，按照提示购买讯飞转写及翻译插件。购买成功后，你会收到由讯飞提供的 APPID、APIKey 和 APISecret，后续启动倾听时需要用到。

<a id="integration"></a>
#### 1.3 集成插件 [#13-集成插件-3]

参考以下步骤集成插件：

1. [点击下载](https://download.agora.io/marketplace/release/Agora_Marketplace_Hy_v1002_Extension_for_Win_v4.1.0.zip)**讯飞语音实时转写及翻译（多语种）**的 Windows 插件包。

2. 解压文件夹，将所有 `.dll` 文件保存到项目生成的 `.exe` 文件所在的目录下。若要保存到其他目录，需要在调用 `loadExtensionProvider` 时传入对应的路径。

#### 1.4 升级插件 [#14-升级插件-2]

声网云市场推荐你在更新插件版本前使用数据库工具（如 SQLite）实现插件版本管理，从而确保插件版本与对应的资源包版本一致。因为当插件版本和资源包版本未对齐时，会出现 App 闪退、崩溃等现象。

以下是用 SQLite 来进行插件版本管理的示例：

  1. 使用 SQLite 数据库自行维护插件包和资源包的版本信息。
  2. 每次项目初始化时，自动检测当前项目使用的资源包版本：
      - 如果无记录文件或记录的资源包版本为旧，则更新资源。成功更新后，回写最新资源包版本信息到 SQLite。
      - 如果记录的资源包版本与当前匹配，则正常实现业务。

如果你的项目已经集成过声网云市场提供的第三方插件，并需要更新插件，可以参考以下步骤来保证更新后的可用性。

以下升级流程以**相芯美颜特效**插件为例：

  1. 参考[集成插件](#13-集成插件-3)章节下载所需平台最新版本的插件包和资源包。

  2. 删除项目内旧版本的插件包及其资源包后，再将新版插件和资源包放入对应位置。或者直接在对应目录下用新版插件和资源包替换旧版。

  3. 删除设备上用老版本插件和资源包编译的 App，重新编译你的项目并运行。

:::warning[注意]
编译 App 时，系统会优先使用连接设备上存在的资源包。请务必在删除旧版本应用程序后再进行编译和运行，否则可能会出现插件版本与资源包不匹配的情况，从而导致各种意外问题。
:::

### 2. 初始化 [#2-初始化-2]

集成插件后，你需要先初始化插件，步骤如下：

1. 初始化 `IRtcEngine` 前，在 `RtcEngineContext` 实例中设置插件事件回调 `IRtcEngineEventHandler`。

    ```cpp
    class CAgoraWithHyWinEventHandler: public agora::rtc::IRtcEngineEventHandler{
        public:
            // 插件事件回调。
            virtual void onExtensionEvent(const char * provider, const char * extension, const char * ky, const char * value) override {
            }

        private: HWND m_hMsgHanlder = NULL;
        HyUtil * mHyUtilPtr = NULL;
    };

    m_rtcEngine = createAgoraRtcEngine();
    CAgoraWithHyWinEventHandler m_eventHandler;
    agora::rtc::RtcEngineContext context;
    ······
    context.eventHandler = &m_eventHandler;
    ······
    ret = m_rtcEngine->initialize(context);
    ```

2. 调用 `loadExtensionProvider`，将插件添加到 SDK 中。

    ```cpp
    ret = m_rtcEngine->loadExtensionProvider("libagora-hy.dll");
    ```

3. 在加入频道前，调用 `enableExtension` 开启插件。

    ```cpp
    ret = m_rtcEngine->enableExtension(EXTENSION_PROVIDER_NAME, EXTENSION_AUDIO_FILTER_NAME, true);
    ```

4. 设置插件日志等级，需要在 `enableVideo` 和 `enableAudio` 之前调用。

    ```cpp
    m_rtcEngine->setExtensionProviderProperty(EXTENSION_PROVIDER_NAME, "log_lvl", "8");
    ```

### 3. 启动倾听 [#3-启动倾听-2]

空闲时可调用，调用后会进入会话。会话最长 5 小时。

```cpp
int HyUtil::start(ParamWrap param) {
    mParser.clear();
    Json::Value root;
    // 公共对象。必选。
    Json::Value common; {
        // 应用标识。必选。对应在声网控制台购买插件后、由讯飞提供的 APPID。
        common["app_id"] = EXTENSION_APP_ID;
        // API key。必选。对应在声网控制台购买插件后、由讯飞提供的 APIKey。
        common["api_key"] = EXTENSION_API_KEY;
        // API secret。必选。对应在声网控制台购买插件后、由讯飞提供的 APISecret。
        common["api_secret"] = EXTENSION_API_SECRET;
    }
    root["common"] = common;
    // 语音转写对象。必选。
    Json::Value ist; {
        // URI。必选。
        ist["uri"] = EXTENSION_API_IST_URI;
        // 请求对象。必选。
        Json::Value req; {
            Json::Value business; {
                // 语种。必选。
                business["language"] = param.mIstLanguage;
                // 口音。必选。
                business["accent"] = param.mIstAccent;
                // 领域。必选。
                business["domain"] = param.mIstDomain;
                // 语言类型
                // 值类型：int
                // 值范围：
                // 1：中英文模式，中文英文均可识别；
                // 3：英文模式，只识别出英文
                // 值默认：1
                business["language_type"] = param.mIstLanguageType;
                // 动态修正
                business["dwa"] = param.mIstDwa;
            }
            req["business"] = business;
        }
        ist["req"] = req;
    }
    root["ist"] = ist;
    // 文本翻译对象。
    Json::Value its; {
        // URI。必选。
        its["uri"] = EXTENSION_API_ITS_URI;
        // 请求对象。必选。
        Json::Value req; {
            // 业务对象。必选。
            Json::Value business; {
                // 源语种。必选。
                business["from"] = param.mItsFrom;
                // 目标语种。必选。
                business["to"] = param.mItsTo;
            }
            req["business"] = business;
        }
        its["req"] = req;
    }
    root["its"] = its;
    string val = root.toStyledString();
    int errCode = m_rtcEngine->setExtensionProperty(EXTENSION_PROVIDER_NAME, EXTENSION_AUDIO_FILTER_NAME, "start_listening", val.c_str());
    return errCode;
}
```

### 4. 结束音频获取结果 [#4-结束音频获取结果-2]

会话时可调用，调用后会先处理完音频和结果，再进入空闲，回调 `"end"`。

```cpp
int HyUtil::flush() {
    return m_rtcEngine->setExtensionProperty(EXTENSION_PROVIDER_NAME, EXTENSION_AUDIO_FILTER_NAME, "flush_listening", "{}");
}
```

讯飞转写及翻译插件会通过 `onEvent` 回调返回语音转写和文本翻译结果。识别结果的含义详见 [onEvent 回调的 key](/zh-CN/realtime-media/marketplace/reference/iflytek-asr-api#回调-key)。

### 5. 停止倾听 [#5-停止倾听-2]

会话时可调用，调用后会先丢弃音频和结果，再进入空闲，回调 `"end"`。

```cpp
int HyUtil::stop() {
    return m_rtcEngine->setExtensionProperty(EXTENSION_PROVIDER_NAME, EXTENSION_AUDIO_FILTER_NAME, "stop_listening", "{}");
}
```

<a id="ref"></a>
## 参考信息 [#参考信息-2]
### API 参考 [#api-参考-3]

- [声网 SDK 的插件相关方法和回调](/zh-CN/api-reference/rtc/cpp-all-platforms/extension#toc_extension)
- [插件的 key-value 说明](/zh-CN/realtime-media/marketplace/reference/iflytek-asr-api)
<_PlatformProcessedMarker close="true" />
</_PlatformTabsGroup>
