You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
166 lines
4.3 KiB
166 lines
4.3 KiB
import 'dart:async';
|
|
import 'dart:typed_data';
|
|
|
|
/// 语音识别服务接口
|
|
abstract class AstService {
|
|
/// 支持的语言
|
|
List<String> get supportedLanguages;
|
|
|
|
/// 初始化语音识别服务
|
|
/// [supportedLanguages] 支持的语言列表
|
|
/// [provider] 提供商:azure / volcano / alibaba / iflytek
|
|
Future<bool> initialize(
|
|
{required List<String> supportedLanguages, String provider = 'azure'});
|
|
|
|
/// 设置低音量阈值
|
|
void setLowVolumeThreshold(int threshold);
|
|
|
|
/// 开始录音
|
|
Future<bool> enableRecord(String filePath);
|
|
|
|
/// 开始连续翻译
|
|
Future<bool> startContinuousTranslation();
|
|
|
|
/// 停止连续翻译
|
|
Future<bool> stopContinuousTranslation();
|
|
|
|
/// 返回一个包含识别事件的流
|
|
Future<Stream<ASTEvent>> recognizeCallback();
|
|
|
|
/// 开始录音
|
|
Future<bool> path(String filePath);
|
|
|
|
/// 通话翻译:是否本地播报对方(B)翻译后的音频。
|
|
/// true → 原生层把 B 路 E2E PCM 直接播出,同时启用播放期 gate 防回环
|
|
/// false → 原生层丢弃 B 路播放,gate 永不触发
|
|
/// 未实现的原生端应 no-op 并返回 true
|
|
Future<void> setBroadcastPeerTranslate(bool enabled);
|
|
|
|
/// 把外部捕获的 PCM(杰理 runtime 解码后的 SCO 上下行)推给 AST 流水线。
|
|
/// [leg] 'A'/'uplink' = 本机麦克风;'B'/'downlink' = 对端通话音
|
|
/// [pcm] 16k mono 16bit PCM little-endian
|
|
Future<void> pushExternalAudio({required String leg, required Uint8List pcm});
|
|
|
|
/// AST 流水线 / E2E 服务合成的 TTS PCM 帧流。每帧包含 leg('A'|'B') + pcm。
|
|
/// 调用方负责把它转发给设备(如 [Jielihome.feedTranslatedAudio])。
|
|
Stream<AstTtsFrame> get astTtsFrames;
|
|
|
|
/// 释放资源
|
|
Future<void> dispose();
|
|
}
|
|
|
|
/// AST 服务输出的 TTS PCM 帧(用于通话翻译双向回灌)。
|
|
class AstTtsFrame {
|
|
/// 'A' = 己方→对端 译文(让对端听);'B' = 对端→己方 译文(让本机听)
|
|
final String leg;
|
|
final Uint8List pcm;
|
|
|
|
/// 是否为当前 utterance 的最后一帧。
|
|
///
|
|
/// 仅当原生 AST 服务(azure / iflytek / volcano / alibaba 任一)确实发出段尾事件时为 true。
|
|
/// 段尾帧通常 pcm 为空字节,仅作信号传递,由下游(杰理 RCSP runtime)触发立即整段下发。
|
|
final bool isFinal;
|
|
|
|
const AstTtsFrame({
|
|
required this.leg,
|
|
required this.pcm,
|
|
this.isFinal = false,
|
|
});
|
|
}
|
|
|
|
// 识别事件类型
|
|
enum ASTEventType {
|
|
/// 最终识别结果
|
|
finalResult,
|
|
|
|
/// 中间识别结果(实时反馈)
|
|
intermediateResult,
|
|
|
|
/// 翻译结果
|
|
translationResult,
|
|
|
|
/// 翻译中(实时反馈)
|
|
translationInterim,
|
|
|
|
/// 会话开始
|
|
sessionStarted,
|
|
|
|
/// 会话结束
|
|
sessionStopped,
|
|
|
|
/// 识别取消
|
|
canceled,
|
|
|
|
/// 识别错误
|
|
error,
|
|
}
|
|
|
|
// 识别事件
|
|
class ASTEvent {
|
|
/// 事件类型
|
|
final ASTEventType type;
|
|
|
|
/// 识别ID
|
|
final String utteranceId;
|
|
|
|
/// 服务ID(A/B)
|
|
final String serviceId;
|
|
|
|
/// 识别文本(仅在 finalResult 和 intermediateResult 类型中有效)
|
|
final String text;
|
|
|
|
/// 检测到的语言
|
|
final String detectedLanguage;
|
|
|
|
/// 原始音频
|
|
final Uint8List? audio;
|
|
|
|
/// 错误信息(仅在 error 和 canceled 类型中有效)
|
|
final String error;
|
|
|
|
ASTEvent({
|
|
required this.type,
|
|
this.utteranceId = '',
|
|
this.text = '',
|
|
this.detectedLanguage = '',
|
|
this.audio,
|
|
this.error = '',
|
|
this.serviceId = '',
|
|
});
|
|
|
|
/// 创建最终结果事件的快捷构造函数
|
|
factory ASTEvent.finalResult({
|
|
required String text,
|
|
String utteranceId = '',
|
|
String detectedLanguage = '',
|
|
String serviceId = '',
|
|
}) {
|
|
return ASTEvent(
|
|
type: ASTEventType.finalResult,
|
|
utteranceId: utteranceId,
|
|
text: text,
|
|
detectedLanguage: detectedLanguage,
|
|
serviceId: serviceId,
|
|
);
|
|
}
|
|
|
|
/// 创建错误事件的快捷构造函数
|
|
factory ASTEvent.error(String errorMessage) {
|
|
return ASTEvent(
|
|
type: ASTEventType.error,
|
|
error: errorMessage,
|
|
);
|
|
}
|
|
|
|
/// 检查是否为最终结果
|
|
bool get isFinalResult => type == ASTEventType.finalResult;
|
|
|
|
/// 检查是否为错误
|
|
bool get isError =>
|
|
type == ASTEventType.error || type == ASTEventType.canceled;
|
|
|
|
@override
|
|
String toString() {
|
|
return 'ASTEvent{type: $type, text: $text, detectedLanguage: $detectedLanguage, error: $error}';
|
|
}
|
|
}
|
|
|