8 changed files with 700 additions and 385 deletions
@ -0,0 +1,195 @@ |
|||||
|
# 火山语音服务配置指南 |
||||
|
|
||||
|
本文档提供了关于火山语音服务的配置和常见问题解决方案。 |
||||
|
|
||||
|
## 环境变量配置 |
||||
|
|
||||
|
火山语音服务需要以下环境变量: |
||||
|
|
||||
|
``` |
||||
|
# 火山语音服务配置 |
||||
|
VOLCANO_APP_ID=your_volcano_app_id_here # 应用ID |
||||
|
VOLCANO_APP_KEY=your_volcano_app_key_here # 应用密钥/Token |
||||
|
VOLCANO_CLUSTER=your_volcano_cluster_here # 集群区域,例如: cn-beijing |
||||
|
|
||||
|
# 语音合成配置 |
||||
|
VOLCANO_VOICE_TYPE=zh_female_wanqudashu_moon_bigtts # 默认语音类型 - 湾区大叔 |
||||
|
``` |
||||
|
|
||||
|
请确保在 `.env` 文件中正确设置这些变量。 |
||||
|
|
||||
|
## 常见问题解决 |
||||
|
|
||||
|
### 1. TTS资源授权错误 |
||||
|
|
||||
|
如果遇到以下错误: |
||||
|
|
||||
|
``` |
||||
|
语音类型授权错误: 您可能没有权限使用当前选择的语音类型 |
||||
|
``` |
||||
|
|
||||
|
**解决方案**: |
||||
|
- 尝试使用基础语音类型,如 `zh_male_qingse_common` 或 `zh_female_qingse_common` |
||||
|
- 确保您的火山引擎账户已开通语音合成服务 |
||||
|
- 检查应用ID和密钥是否正确 |
||||
|
|
||||
|
### 2. 语音识别认证错误 |
||||
|
|
||||
|
如果遇到以下错误: |
||||
|
|
||||
|
``` |
||||
|
authentication signature from request: 'Authorization' header: invalid auth token |
||||
|
``` |
||||
|
|
||||
|
**解决方案**: |
||||
|
|
||||
|
#### 标准语音识别SDK (API v2) |
||||
|
- 确保 `VOLCANO_APP_KEY` 格式正确,这是一个完整的令牌 |
||||
|
- **必须**在Token前添加 `Bearer;` 前缀(注意使用分号而非空格) |
||||
|
- 检查应用ID和密钥是否匹配 |
||||
|
- 确保您的火山引擎账户已开通语音识别服务 |
||||
|
- 使用正确的API路径: `/api/v2/asr` |
||||
|
|
||||
|
#### 大模型流式识别SDK (API v3) |
||||
|
- 使用正确的API路径: `/api/v3/sauc/bigmodel` |
||||
|
- **不要**在Token前添加Bearer前缀 |
||||
|
- 设置正确的资源ID (`RESOURCE_ID_STRING`) |
||||
|
- 设置协议类型为 `PROTOCOL_TYPE_SEED` |
||||
|
- 确保您的火山引擎账户已开通大模型流式语音识别服务 |
||||
|
|
||||
|
### 3. WebSocket连接错误 |
||||
|
|
||||
|
如果遇到以下错误: |
||||
|
|
||||
|
``` |
||||
|
Error during WebSocket handshake: Unexpected response code: 400 |
||||
|
``` |
||||
|
|
||||
|
**解决方案**: |
||||
|
- 确保网络连接正常,可以访问 `openspeech.bytedance.com` |
||||
|
- **集群区域设置非常重要**,必须设置正确的 `VOLCANO_CLUSTER` 环境变量 |
||||
|
- 默认使用 `cn-beijing`,但您的账户可能需要使用其他区域,如 `cn-shanghai` 或 `cn-guangzhou` |
||||
|
- 如果使用默认区域出现错误,请尝试切换到其他区域 |
||||
|
- 确保您的账户已开通相应的语音识别服务 |
||||
|
- 检查请求参数格式是否正确 |
||||
|
- 对于标准语音识别SDK (API v2),确保Token前添加了 `Bearer;` 前缀 |
||||
|
- 如果问题仍然存在,请联系火山引擎技术支持 |
||||
|
|
||||
|
### 4. 检查配置工具 |
||||
|
|
||||
|
我们提供了两个工具来检查火山语音服务的配置: |
||||
|
|
||||
|
1. **检查TTS配置**: |
||||
|
``` |
||||
|
flutter run lib/tools/check_volcano_config.dart |
||||
|
``` |
||||
|
|
||||
|
2. **检查语音识别配置**: |
||||
|
``` |
||||
|
flutter run lib/tools/check_volcano_asr_config.dart |
||||
|
``` |
||||
|
|
||||
|
这些工具将帮助您验证环境变量、网络连接和服务授权是否正确。 |
||||
|
|
||||
|
## 离线TTS支持 |
||||
|
|
||||
|
我们的应用支持离线TTS功能,当在线TTS失败时会自动切换到离线模式。离线模式支持基础语音类型: |
||||
|
- `zh_male_qingse_common`(基础男声) |
||||
|
- `zh_female_qingse_common`(基础女声) |
||||
|
|
||||
|
要使用离线TTS,您可以: |
||||
|
1. 在TTS测试页面选择基础语音类型 |
||||
|
2. 当在线合成失败时,系统会自动尝试使用离线合成 |
||||
|
|
||||
|
## 标准语音识别SDK配置 (API v2) |
||||
|
|
||||
|
标准语音识别SDK是火山语音服务的基础版本,配置相对简单。 |
||||
|
|
||||
|
### 关键配置点 |
||||
|
1. **API路径**:使用 `/api/v2/asr` |
||||
|
2. **认证方式**: |
||||
|
- 必须在Token前添加 `Bearer;` 前缀(注意使用分号而非空格) |
||||
|
- 不需要设置资源ID |
||||
|
3. **集群区域**:确保设置正确的集群区域,如 `cn-beijing` |
||||
|
- 集群区域必须与您的账户配置匹配 |
||||
|
- 如果遇到WebSocket握手错误,尝试切换到其他区域 |
||||
|
|
||||
|
### 配置示例 |
||||
|
```kotlin |
||||
|
// 设置API路径 |
||||
|
engine.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_ASR_URI_STRING, "/api/v2/asr"); |
||||
|
|
||||
|
// 设置认证信息 |
||||
|
engine.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_APP_ID_STRING, "YOUR_APP_ID"); |
||||
|
engine.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_APP_TOKEN_STRING, "Bearer;YOUR_APP_KEY"); // 必须添加Bearer;前缀 |
||||
|
|
||||
|
// 设置集群区域 |
||||
|
engine.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_ASR_CLUSTER_STRING, "cn-beijing"); |
||||
|
``` |
||||
|
|
||||
|
## 大模型流式识别SDK配置 (API v3) |
||||
|
|
||||
|
从2024年2月26日起,火山语音服务提供了新的大模型流式识别SDK。如果您使用的是这个新版本,请注意以下配置差异: |
||||
|
|
||||
|
### 版本信息 |
||||
|
- Android: `com.bytedance.speechengine:speechengine_asr_tob:1.1.7` |
||||
|
- iOS: `pod 'SpeechEngineAsrToB', '1.1.7'` |
||||
|
|
||||
|
### 关键配置差异 |
||||
|
1. **API路径**:使用 `/api/v3/sauc/bigmodel` 而非旧版的 `/api/v2/asr` |
||||
|
2. **认证方式**: |
||||
|
- 不需要在Token前添加 `Bearer` 前缀 |
||||
|
- 需要设置资源ID (`RESOURCE_ID_STRING`) |
||||
|
3. **协议类型**:需要设置为 `PROTOCOL_TYPE_SEED` |
||||
|
4. **集群区域**:确保设置正确的集群区域,如 `cn-beijing` |
||||
|
- 集群区域必须与您的账户配置匹配 |
||||
|
- 如果遇到WebSocket握手错误,尝试切换到其他区域 |
||||
|
|
||||
|
### 配置示例 |
||||
|
```kotlin |
||||
|
// 设置API路径 |
||||
|
mSpeechEngine.setOptionString(SpeechEngineDefines.PARAMS_KEY_ASR_URI_STRING, "/api/v3/sauc/bigmodel"); |
||||
|
|
||||
|
// 设置认证信息 |
||||
|
mSpeechEngine.setOptionString(SpeechEngineDefines.PARAMS_KEY_APP_ID_STRING, "YOUR_APP_ID"); |
||||
|
mSpeechEngine.setOptionString(SpeechEngineDefines.PARAMS_KEY_APP_TOKEN_STRING, "YOUR_APP_KEY"); // 不需要Bearer前缀 |
||||
|
|
||||
|
// 设置资源ID |
||||
|
mSpeechEngine.setOptionString(SpeechEngineDefines.PARAMS_KEY_RESOURCE_ID_STRING, "YOUR_RESOURCE_ID"); |
||||
|
|
||||
|
// 设置协议类型 |
||||
|
mSpeechEngine.setOptionInt(SpeechEngineDefines.PARAMS_KEY_PROTOCOL_TYPE_INT, SpeechEngineDefines.PROTOCOL_TYPE_SEED); |
||||
|
|
||||
|
// 设置集群区域 |
||||
|
mSpeechEngine.setOptionString(SpeechEngineDefines.PARAMS_KEY_ASR_CLUSTER_STRING, "cn-beijing"); |
||||
|
|
||||
|
// 设置ASR请求参数 |
||||
|
mSpeechEngine.setOptionString(SpeechEngineDefines.PARAMS_KEY_ASR_REQ_PARAMS_STRING, |
||||
|
"{"force_to_speech_time":0, "end_window_size":800}"); |
||||
|
``` |
||||
|
|
||||
|
## 支持的语音类型 |
||||
|
|
||||
|
我们支持多种语音类型,包括: |
||||
|
|
||||
|
### 趣味方言 |
||||
|
- 湾区大叔 (`zh_female_wanqudashu_moon_bigtts`) |
||||
|
- 呆萌川妹 (`zh_female_daimengchuanmei_moon_bigtts`) |
||||
|
- 广州德哥 (`zh_male_guozhoudege_moon_bigtts`) |
||||
|
- 北京小爷 (`zh_male_beijingxiaoye_moon_bigtts`) |
||||
|
- 浩宇小哥 (`zh_male_haoyuxiaoge_moon_bigtts`) |
||||
|
|
||||
|
### 通用场景 |
||||
|
- 少年梓辛/Brayan (`zh_male_shaonianzixin_moon_bigtts`) |
||||
|
|
||||
|
### 角色扮演 |
||||
|
- 魅力女友 (`zh_female_meilinvyou_moon_bigtts`) |
||||
|
- 深夜播客 (`zh_male_shenyeboke_moon_bigtts`) |
||||
|
- 柔美女友 (`zh_female_sajiaonvyou_moon_bigtts`) |
||||
|
- 撒娇学妹 (`zh_female_yuanqinvyou_moon_bigtts`) |
||||
|
|
||||
|
### 基础语音类型 |
||||
|
- 基础男声 (`zh_male_qingse_common`) |
||||
|
- 基础女声 (`zh_female_qingse_common`) |
||||
|
- 高级男声 (`zh_male_M392_conversation_wvae_bigtts`) |
||||
|
- 高级女声 (`zh_female_F392_conversation_wvae_bigtts`) |
||||
@ -0,0 +1,259 @@ |
|||||
|
import 'dart:io'; |
||||
|
import 'package:flutter_dotenv/flutter_dotenv.dart'; |
||||
|
import 'package:http/http.dart' as http; |
||||
|
import 'dart:convert'; |
||||
|
|
||||
|
/// 检查火山语音识别服务配置 |
||||
|
/// |
||||
|
/// 该工具用于验证火山语音识别服务的配置是否正确,包括: |
||||
|
/// 1. 检查环境变量是否设置 |
||||
|
/// 2. 检查网络连接 |
||||
|
/// 3. 检查认证是否有效 |
||||
|
void main() async { |
||||
|
// 加载环境变量 |
||||
|
await dotenv.load(); |
||||
|
|
||||
|
print('======== 火山语音识别服务配置检查 ========'); |
||||
|
|
||||
|
// 检查环境变量 |
||||
|
final appId = dotenv.env['VOLCANO_APP_ID']; |
||||
|
final appKey = dotenv.env['VOLCANO_APP_KEY']; |
||||
|
final cluster = dotenv.env['VOLCANO_CLUSTER']; |
||||
|
|
||||
|
print('\n1. 检查环境变量:'); |
||||
|
|
||||
|
if (appId == null || appId.isEmpty) { |
||||
|
print('❌ VOLCANO_APP_ID 未设置'); |
||||
|
} else { |
||||
|
print('✅ VOLCANO_APP_ID: ${appId.substring(0, 3)}***${appId.substring(appId.length - 3)} (长度: ${appId.length})'); |
||||
|
} |
||||
|
|
||||
|
if (appKey == null || appKey.isEmpty) { |
||||
|
print('❌ VOLCANO_APP_KEY 未设置'); |
||||
|
} else { |
||||
|
print('✅ VOLCANO_APP_KEY: ${appKey.substring(0, 3)}***${appKey.substring(appKey.length - 3)} (长度: ${appKey.length})'); |
||||
|
} |
||||
|
|
||||
|
if (cluster == null || cluster.isEmpty) { |
||||
|
print('❌ VOLCANO_CLUSTER 未设置,将使用默认值 cn-beijing'); |
||||
|
} else { |
||||
|
print('✅ VOLCANO_CLUSTER: $cluster'); |
||||
|
} |
||||
|
|
||||
|
// 使用默认值 |
||||
|
final effectiveCluster = cluster ?? 'cn-beijing'; |
||||
|
|
||||
|
if (appId == null || appId.isEmpty || appKey == null || appKey.isEmpty) { |
||||
|
print('\n❌ 环境变量配置不完整,请检查 .env 文件'); |
||||
|
exit(1); |
||||
|
} |
||||
|
|
||||
|
// 检查网络连接 |
||||
|
print('\n2. 检查网络连接:'); |
||||
|
|
||||
|
try { |
||||
|
final result = await InternetAddress.lookup('openspeech.bytedance.com'); |
||||
|
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) { |
||||
|
print('✅ 网络连接正常,可以访问 openspeech.bytedance.com'); |
||||
|
} else { |
||||
|
print('❌ 无法连接到 openspeech.bytedance.com'); |
||||
|
} |
||||
|
} catch (e) { |
||||
|
print('❌ 网络连接异常: $e'); |
||||
|
} |
||||
|
|
||||
|
// 检查认证是否有效 |
||||
|
print('\n3. 检查认证有效性:'); |
||||
|
|
||||
|
http.Response? authResponse; |
||||
|
|
||||
|
try { |
||||
|
// 构建请求URL - 更新为大模型流式识别API路径 |
||||
|
final url = 'https://openspeech.bytedance.com/api/v3/sauc/bigmodel'; |
||||
|
|
||||
|
// 构建请求头 - 不再添加Bearer前缀 |
||||
|
final headers = { |
||||
|
'Content-Type': 'application/json', |
||||
|
'Authorization': appKey, // 不再添加Bearer前缀 |
||||
|
}; |
||||
|
|
||||
|
// 构建请求体 - 添加resourceId参数 |
||||
|
final body = jsonEncode({ |
||||
|
'app_id': appId, |
||||
|
'cluster': effectiveCluster, |
||||
|
'resource_id': appId, // 资源ID暂时使用与APP_ID相同的值 |
||||
|
'ping': true, // 只是ping服务,不进行实际识别 |
||||
|
}); |
||||
|
|
||||
|
print('正在发送测试请求...'); |
||||
|
print('请求URL: $url'); |
||||
|
print('请求头: Authorization=${appKey.substring(0, 3)}***'); |
||||
|
print('集群区域: $effectiveCluster'); |
||||
|
|
||||
|
// 发送请求 |
||||
|
authResponse = await http.post( |
||||
|
Uri.parse(url), |
||||
|
headers: headers, |
||||
|
body: body, |
||||
|
).timeout(const Duration(seconds: 5)); |
||||
|
|
||||
|
// 检查响应 |
||||
|
if (authResponse.statusCode == 200) { |
||||
|
print('✅ 认证有效,服务响应正常'); |
||||
|
print('响应内容: ${authResponse.body}'); |
||||
|
} else { |
||||
|
print('❌ 认证无效或服务异常,状态码: ${authResponse.statusCode}'); |
||||
|
print('错误信息: ${authResponse.body}'); |
||||
|
|
||||
|
// 解析错误信息 |
||||
|
try { |
||||
|
final errorJson = jsonDecode(authResponse.body); |
||||
|
final errorCode = errorJson['code']; |
||||
|
final errorMsg = errorJson['message']; |
||||
|
|
||||
|
if (errorCode == 401) { |
||||
|
print('\n认证错误,可能的原因:'); |
||||
|
print('1. APP_KEY 格式不正确'); |
||||
|
print('2. APP_ID 与 APP_KEY 不匹配'); |
||||
|
print('3. 账户未开通大模型流式语音识别服务或服务已过期'); |
||||
|
print('4. 资源ID不正确或未授权'); |
||||
|
} else if (errorCode == 400) { |
||||
|
print('\nWebSocket握手错误,可能的原因:'); |
||||
|
print('1. 集群区域设置不正确 (当前: $effectiveCluster)'); |
||||
|
print('2. 请求参数格式不正确'); |
||||
|
print('3. 尝试使用不同的集群区域,如 cn-shanghai 或 cn-guangzhou'); |
||||
|
} else { |
||||
|
print('\n未知错误:'); |
||||
|
print('错误码: $errorCode'); |
||||
|
print('错误信息: $errorMsg'); |
||||
|
} |
||||
|
} catch (e) { |
||||
|
print('\n解析错误信息失败: $e'); |
||||
|
|
||||
|
if (authResponse.statusCode == 400) { |
||||
|
print('\nWebSocket握手错误,可能的原因:'); |
||||
|
print('1. 集群区域设置不正确 (当前: $effectiveCluster)'); |
||||
|
print('2. 请求参数格式不正确'); |
||||
|
print('3. 尝试使用不同的集群区域,如 cn-shanghai 或 cn-guangzhou'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} catch (e) { |
||||
|
print('❌ 请求异常: $e'); |
||||
|
} |
||||
|
|
||||
|
print('\n======== 检查完成 ========'); |
||||
|
print('\n提示: 如果您使用的是大模型流式识别SDK,请确保:'); |
||||
|
print('1. 使用了正确的API路径: /api/v3/sauc/bigmodel'); |
||||
|
print('2. 不要在Token前添加Bearer前缀'); |
||||
|
print('3. 设置了正确的资源ID'); |
||||
|
print('4. 设置了协议类型为PROTOCOL_TYPE_SEED'); |
||||
|
print('5. 设置了正确的集群区域 (当前: $effectiveCluster)'); |
||||
|
|
||||
|
// 尝试其他集群区域 |
||||
|
if (authResponse != null && authResponse.statusCode == 400) { |
||||
|
print('\n尝试其他集群区域:'); |
||||
|
final alternativeClusters = [ |
||||
|
'cn-shanghai', |
||||
|
'cn-guangzhou', |
||||
|
'cn-hongkong', |
||||
|
'ap-singapore', |
||||
|
'us-east-1', |
||||
|
'us-west-1' |
||||
|
]; |
||||
|
|
||||
|
bool foundWorkingCluster = false; |
||||
|
|
||||
|
for (final altCluster in alternativeClusters) { |
||||
|
if (altCluster != effectiveCluster) { |
||||
|
print('\n尝试集群区域: $altCluster'); |
||||
|
final success = await _testCluster(appId, appKey, altCluster); |
||||
|
if (success) { |
||||
|
foundWorkingCluster = true; |
||||
|
print('\n✅ 找到可用的集群区域: $altCluster'); |
||||
|
print('建议在 .env 文件中设置 VOLCANO_CLUSTER=$altCluster'); |
||||
|
|
||||
|
// 尝试更新.env文件 |
||||
|
try { |
||||
|
await _updateEnvFile(altCluster); |
||||
|
} catch (e) { |
||||
|
print('无法自动更新.env文件: $e'); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!foundWorkingCluster) { |
||||
|
print('\n❌ 所有集群区域测试均失败'); |
||||
|
print('请联系火山引擎技术支持获取正确的集群区域'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 测试不同的集群区域 |
||||
|
Future<bool> _testCluster(String appId, String appKey, String cluster) async { |
||||
|
try { |
||||
|
final url = 'https://openspeech.bytedance.com/api/v3/sauc/bigmodel'; |
||||
|
|
||||
|
final headers = { |
||||
|
'Content-Type': 'application/json', |
||||
|
'Authorization': appKey, |
||||
|
}; |
||||
|
|
||||
|
final body = jsonEncode({ |
||||
|
'app_id': appId, |
||||
|
'cluster': cluster, |
||||
|
'resource_id': appId, |
||||
|
'ping': true, |
||||
|
}); |
||||
|
|
||||
|
final response = await http.post( |
||||
|
Uri.parse(url), |
||||
|
headers: headers, |
||||
|
body: body, |
||||
|
).timeout(const Duration(seconds: 5)); |
||||
|
|
||||
|
if (response.statusCode == 200) { |
||||
|
print('✅ 集群区域 $cluster 可用,认证有效'); |
||||
|
return true; |
||||
|
} else { |
||||
|
print('❌ 集群区域 $cluster 不可用,状态码: ${response.statusCode}'); |
||||
|
return false; |
||||
|
} |
||||
|
} catch (e) { |
||||
|
print('❌ 测试集群区域 $cluster 时出错: $e'); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 尝试更新.env文件 |
||||
|
Future<void> _updateEnvFile(String newCluster) async { |
||||
|
try { |
||||
|
final file = File('.env'); |
||||
|
if (!await file.exists()) { |
||||
|
print('❌ .env文件不存在,无法自动更新'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
String content = await file.readAsString(); |
||||
|
|
||||
|
// 检查是否已有VOLCANO_CLUSTER |
||||
|
final clusterRegex = RegExp(r'VOLCANO_CLUSTER=.*'); |
||||
|
if (clusterRegex.hasMatch(content)) { |
||||
|
// 替换现有的VOLCANO_CLUSTER |
||||
|
content = content.replaceAll(clusterRegex, 'VOLCANO_CLUSTER=$newCluster'); |
||||
|
} else { |
||||
|
// 添加新的VOLCANO_CLUSTER |
||||
|
content += '\nVOLCANO_CLUSTER=$newCluster'; |
||||
|
} |
||||
|
|
||||
|
// 写入文件 |
||||
|
await file.writeAsString(content); |
||||
|
print('✅ 已自动更新.env文件中的VOLCANO_CLUSTER=$newCluster'); |
||||
|
} catch (e) { |
||||
|
print('❌ 更新.env文件失败: $e'); |
||||
|
throw e; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue