# Multimodal Dialog SDK for iOS

通义多模态实时对话 iOS SDK 与 Demo。SDK 封装了ASR/LLM/TTS 全链路对话、视频帧上传、唤醒词、VAD、AEC、打断等能力，支持 **tap2talk**、**push2talk**、**duplex** 三种对话模式。仓库内附带一个完整可运行的 `Dialog_Demo`，演示了从配置到对话的完整流程。

---

## 1. 快速开始（最简接入指南）

只需 3 步即可在工程中接入实时多模态对话。

### 1.1 工程依赖

把以下 framework / 静态库 加入到你的 Xcode Target 的 **Frameworks, Libraries, and Embedded Content**（前两个需要 Embed & Sign）：

| 名称 | 类型 | 作用 |
| --- | --- | --- |
| `multimodal_dialog.framework` | Dynamic Framework | 多模态交互入口库，提供多模态交互服务接入 ，包含端侧算法库（含 VAD / AEC / KWS）|
| `AliVCSDK_ARTC.framework` | Dynamic Framework | RTC 链路依赖 |
| `alivcffmpeg.framework` | Dynamic Framework | 音视频编解码 |
| `SnapKit/SnapKit` | Swift Package Manager 导入 | UI 约束（Demo 使用，可选） |
| `MultimodalDialogTongyiMetathings.framework` | Dynamic Framework | License 计费模式签名（License 模式必需） |

并把 `Source/` 目录下的 Swift / OC / 资源文件加入到 Target 中（或将 SDK 编译为独立 framework 引用）。

`Info.plist` 添加麦克风、摄像头、网络权限：

```xml
<key>NSMicrophoneUsageDescription</key>
<string>用于实时语音对话</string>
<key>NSCameraUsageDescription</key>
<string>用于多模态视觉理解</string>
```

### 1.2 三步代码接入

```swift
import UIKit

class MyViewController: UIViewController {

    var dialog: MultiModalDialog?

    func startConversation() {
        // Step 1. 创建 SDK 实例
        dialog = MultiModalDialog(
            url: "wss://dashscope.aliyuncs.com/api-ws/v1/inference/",
            chainMode: .WebSocket,        // .WebSocket 或 .RTC
            workSpaceId: "<your-workspace-id>",
            appId: "<your-app-id>",
            mode: .tap2talk               // .tap2talk / .push2talk / .duplex
        )

        // Step 2. 注册关键回调
        dialog?.onConnected = {
            print("已连接")
        }
        dialog?.onConversationStarted = {
            print("可以开始说话了")
        }
        dialog?.onConversationStatechanged = { state in
            // .idle / .listening / .thinking / .responding
            print("状态: \(state.rawValue)")
        }
        dialog?.onMessageReceived = { payload, type in
            print("收到消息: \(payload ?? [:])")
        }
        dialog?.onErrorReceived = { err in
            print("错误: \(err.key) - \(err.message ?? "")")
        }

        // Step 3. 构造参数并启动
        let params = MultiModalRequestParam { b in
            b.upStream = MultiModalRequestParam.UpStream { up in
                up.mode = "tap2talk"
                up.type = "AudioOnly"   // 或 "AudioAndVideo"
            }
            b.downStream = MultiModalRequestParam.DownStream { down in
                down.sampleRate = 16000
                down.voice = "longanhuan"
            }
            b.clientInfo = MultiModalRequestParam.ClientInfo { info in
                info.userId = "test-user"
                info.device = MultiModalRequestParam.ClientInfo.Device(uuid: "device-uuid")
            }
        }

        dialog?.start(apiKey: "<your-api-key>", params: params) { success, error in
            print("启动: \(success), error: \(String(describing: error))")
        }
    }

    func stopConversation() {
        dialog?.stop()
    }
}
```

至此即可完成最简接入。如需 **License 计费、视频上传、打断、唤醒词、自定义 Prompt** 等高级特性，参见下文。

---

## 2. 项目目录结构

```
multimodal_dialog_sdk/
├── Dialog_Demo/                         # Demo App 源码
│   ├── FirstViewController.swift        # 配置页（host/apiKey/计费方式等）
│   ├── ChatViewController.swift         # 对话主界面
│   ├── AudioController.{h,m}            # 外置音频采集（push2talk 模式）
│   ├── AudioManager.swift
│   ├── VoiceWaveView.swift              # 音量波形
│   ├── SubtitleView.swift               # 字幕
│   └── ...
├── multimodal_dialog.framework               # 对话核心动态库
├── AliVCSDK_ARTC.framework               # RTC SDK
├── alivcffmpeg.framework
├── SnapKit.framework                     # UI 约束（Demo 用）
├── multimodal_dialog/                    # SDK Framework Target 配置
└── multimodal_dialog_sdk.xcodeproj
```

---

## 3. SDK API 详解

### 3.1 初始化

```swift
public init(url: String?,
            chainMode: ChainMode,        // .WebSocket
            workSpaceId: String,
            appId: String,
            mode: DialogMode)            // .tap2talk / .push2talk / .duplex
```

| 参数 | 说明 |
| --- | --- |
| `url` | 服务端地址，WebSocket 链路传 `wss://...`，RTC 链路传鉴权 HTTP 地址 |
| `chainMode` | 数据链路。RTC 适合低延迟语音/视频；WebSocket 适合轻量场景 |
| `workSpaceId` / `appId` | DashScope 控制台获取 |
| `mode` | 对话交互形态，详见 [3.4](#34-对话模式) |

### 3.2 主要方法

| 方法 | 说明 |
| --- | --- |
| `start(apiKey:params:dialogId:taskId:completion:)` | 建立连接并开始对话 |
| `stop()` | 结束对话并释放资源 |
| `interrupt(completion:)` | 主动打断模型说话 |
| `startSpeech()` / `stopSpeech()` | push2talk 模式下手动启停录音 |
| `setModel(model:)` | 切换底层模型（默认通义多模态模型） |
| `enableKWS(enableKws:wakeupOnly:wakeUpWords:)` | 配置唤醒词 |
| `beginInteraction()` | 独立唤醒模式下手动进入对话 |

### 3.3 回调

```swift
dialog.onConnected: () -> Void                        // 服务端建联成功
dialog.onConversationStarted: () -> Void              // 对话协议就绪，可开始说话
dialog.onConversationEvent: (DialogEvent) -> Void     // SpeechStarted/Ended、RespondingStarted/Ended、InterruptAccepted、VoiceTimeout
dialog.onConversationStatechanged: (DialogState) -> Void  // idle/listening/thinking/responding
dialog.onMessageReceived: ([String: Any]?, ResponsetMessageType) -> Void  // ASR / LLM 文本
dialog.onErrorReceived: (TYError) -> Void
dialog.onVolumeChanged: (Float, TYVolumeSourceType) -> Void  // 麦克风 / 播放器音量
dialog.onConnectionStatusChanged: (RTCConnectionStatus) -> Void
dialog.onFirstVideoPacketSent: () -> Void
dialog.onPerformaceDataReceived: (TYChatPerformace) -> Void  // llm/tts/avatar 各阶段耗时
dialog.onRecorderData:    (UnsafeMutablePointer<UInt8>, Int32) -> Void
dialog.onSynthesizedData: (UnsafeMutablePointer<UInt8>, Int32) -> Void
dialog.onKeywordSpotted: (String, KeyWordsType) -> Void  // 唤醒词命中
```

### 3.4 对话模式

| 模式 | 行为 | 典型场景 |
| --- | --- | --- |
| `.tap2talk` | 默认模式，模型说话过程中点击屏幕即可打断 | 通用对话 |
| `.duplex` | 全双工，VAD 自动判断打断 | 拟人化助手 |
| `.push2talk` | 按下录音、抬起结束，外置音频采集 | 翻译、嘈杂环境 |

### 3.5 链路模式

| 模式 | 说明 |
| --- | --- |
| `.WebSocket` | 走 WebSocket 长连接，依赖 `conversation.framework` |

---

## 4. 计费模式

SDK 支持两种鉴权 / 计费模式：

### 4.1 后付费（PayG）

直接使用 DashScope `apiKey` 作为 Bearer Token 调用 `start(apiKey:...)` 即可，**无需额外参数**。

### 4.2 License 计费

License 模式需引入 `MultimodalDialogTongyiMetathings.framework`，按以下流程运行：

```
deviceRegister  →  getToken (获取 dashToken)  →  getLicenseSignature  →  start
```

关键代码（参考 [Dialog_Demo/FirstViewController.swift](Dialog_Demo/FirstViewController.swift) 与 [Dialog_Demo/ChatViewController.swift](Dialog_Demo/ChatViewController.swift)）：

```swift
import MultimodalDialogTongyiMetathings

// 1. 设备注册 / 取 dashToken（首次需用 appSecret）
DeviceAuthClient.companion.instance.initialize(...)
DeviceAuthClient.companion.instance.deviceRegister(...) { ... }
DeviceAuthClient.companion.instance.getToken(...) { tokenJson in
    // 解析 dashToken 作为后续 apiKey
}

// 2. start 之前注入 license 签名
let taskId = UUID().uuidString.replacingOccurrences(of: "-", with: "").lowercased()
let licenseParams = LicenseSignatureParams(appId: appId, deviceName: deviceName, taskId: taskId)
let signData = DeviceAuthClient.companion.instance.getLicenseSignature(params: licenseParams)

let params = MultiModalRequestParam { b in
    b.clientInfo = MultiModalRequestParam.ClientInfo { info in
        info.userId = "test-user"
        info.device = MultiModalRequestParam.ClientInfo.Device(uuid: deviceName)
        if let data = signData.data(using: .utf8),
           let signatureJson = try? JSONSerialization.jsonObject(with: data) {
            info.passThroughParams = ["signature": signatureJson]
        }
    }
    // ...其余字段
}

dialog.start(apiKey: dashToken, params: params, taskId: taskId) { _, _ in }
```

---

## 5. 进阶能力示例

```swift
// 视频帧上传（RTC 模式 / AudioAndVideo）
params.upStream?.type = "AudioAndVideo"

// 自定义 Prompt
params.bizParams = MultiModalRequestParam.BizParams { biz in
    biz.userPromptParams = ["userPrompt": "你是一名英语老师..."]
}

// 唤醒词
dialog.enableKWS(enableKws: true, wakeupOnly: false, wakeUpWords: ["你好通义"])

// 主动打断
dialog.interrupt { ok in print("打断: \(ok)") }
```

---

## 6. 运行 Demo

1. 用 Xcode 16+ 打开 `multimodal_dialog_sdk.xcodeproj`。
2. 选择 `Dialog_Demo` Scheme 与真机测试。
3. 启动后在配置页填写：
   - **Host**：`wss://dashscope.aliyuncs.com/api-ws/v1/inference/`（WebSocket）或控制台获取的 RTC 鉴权地址
   - **WorkspaceId / App ID / API Key**
   - **计费方式**：后付费 / License（License 需额外填写 App Secret）
   - **图片 URL**（可选，多模态视觉输入）
4. 选择对话模式（tap2talk / push2talk / duplex），点击 **开始对话** 进入 `ChatViewController`。

---

## 7. 第三方组件

Demo 与 SDK 引用的开源组件清单与许可证文本见 [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md)。

---

## 8. 常见问题

- **dyld: Library not loaded: @rpath/MultimodalDialogTongyiMetathings.framework/...**：framework 已 link 但未 Embed，需要在 Target → General → Frameworks, Libraries, and Embedded Content 中将其设置为 **Embed & Sign**。
- **真机无声 / 麦克风权限**：检查 `Info.plist` 中的 `NSMicrophoneUsageDescription`，并在系统设置中授权。
