yx_speech_to_text_flutter/SHERPA_ONNX_USAGE.md

6.9 KiB

YX ASR - Sherpa ONNX 使用指南

本文档详细说明如何使用基于 sherpa_onnx 的 YX ASR 语音识别插件。

🎯 核心优势

与原生识别的对比

特性 原生识别 sherpa_onnx
网络依赖 需要网络 完全离线
隐私保护 数据上传云端 本地处理
识别一致性 平台差异 跨平台一致
自定义能力 受限 高度可定制
包体积 无增加 +40-60MB
识别精度 高(云端) 高(本地模型)

📦 安装配置

1. 添加依赖

dependencies:
  yx_asr: ^1.0.0

2. 下载模型文件

sherpa-onnx releases 下载模型:

推荐模型:

  • 中英双语: sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20
  • 纯中文: sherpa-onnx-streaming-zipformer-zh-2023-02-13
  • 纯英文: sherpa-onnx-streaming-zipformer-en-2023-02-21

3. 模型文件结构

assets/models/
├── zh-cn/                    # 中文模型
│   ├── encoder.onnx         # 编码器
│   ├── decoder.onnx         # 解码器
│   ├── joiner.onnx          # 连接器
│   └── tokens.txt           # 词汇表
├── en-us/                   # 英文模型
│   ├── encoder.onnx
│   ├── decoder.onnx
│   ├── joiner.onnx
│   └── tokens.txt
└── bilingual/               # 双语模型
    ├── encoder.onnx
    ├── decoder.onnx
    ├── joiner.onnx
    └── tokens.txt

🚀 使用方法

基本使用

import 'package:yx_asr/yx_asr.dart';

class SpeechRecognitionPage extends StatefulWidget {
  @override
  _SpeechRecognitionPageState createState() => _SpeechRecognitionPageState();
}

class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
  final YxAsr _asr = YxAsr();
  String _result = '';
  bool _isListening = false;

  @override
  void initState() {
    super.initState();
    _initializeASR();
  }

  Future<void> _initializeASR() async {
    // 初始化中文模型
    final success = await _asr.initializeWithModel('assets/models/zh-cn');
    
    if (success) {
      // 监听识别结果
      _asr.onResult.listen((result) {
        setState(() {
          _result = result.recognizedWords;
        });
      });

      // 监听错误
      _asr.onError.listen((error) {
        print('错误: ${error.errorMsg}');
      });

      // 监听状态
      _asr.onListeningStatusChanged.listen((isListening) {
        setState(() {
          _isListening = isListening;
        });
      });
    }
  }

  Future<void> _toggleRecording() async {
    if (_isListening) {
      await _asr.stopListening();
    } else {
      await _asr.startListening(partialResults: true);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('语音识别')),
      body: Column(
        children: [
          Text('识别结果: $_result'),
          ElevatedButton(
            onPressed: _toggleRecording,
            child: Text(_isListening ? '停止录音' : '开始录音'),
          ),
        ],
      ),
    );
  }
}

使用录音按钮组件

RecordingButton(
  onResult: (result) {
    print('识别结果: ${result.recognizedWords}');
    print('是否最终结果: ${result.finalResult}');
    print('置信度: ${result.confidence}');
  },
  onError: (error) {
    print('错误: ${error.errorMsg}');
  },
  localeId: 'zh-CN',  // 会自动选择对应模型
  size: 80.0,
  recordingColor: Colors.red,
  idleColor: Colors.blue,
  tooltip: '点击开始录音',
)

多语言支持

class MultiLanguageASR {
  final YxAsr _asr = YxAsr();
  String _currentLanguage = 'zh-cn';

  Future<void> switchLanguage(String language) async {
    String modelPath;
    switch (language) {
      case 'zh-cn':
        modelPath = 'assets/models/zh-cn';
        break;
      case 'en-us':
        modelPath = 'assets/models/en-us';
        break;
      case 'bilingual':
        modelPath = 'assets/models/bilingual';
        break;
      default:
        modelPath = 'assets/models/zh-cn';
    }

    final success = await _asr.initializeWithModel(modelPath);
    if (success) {
      _currentLanguage = language;
      print('切换到 $language 模型成功');
    }
  }
}

⚙️ 高级配置

自定义采样率

await _asr.initializeWithModel(
  'assets/models/zh-cn',
  sampleRate: 16000, // 默认 16kHz
);

await _asr.startListening(
  partialResults: true,
  sampleRate: 16000,
);

错误处理

_asr.onError.listen((error) {
  switch (error.errorType) {
    case SpeechRecognitionErrorType.permissionDenied:
      // 处理权限被拒绝
      showDialog(context: context, builder: (context) => 
        AlertDialog(title: Text('需要麦克风权限')));
      break;
    case SpeechRecognitionErrorType.service:
      // 处理服务错误
      print('服务错误: ${error.errorMsg}');
      break;
    case SpeechRecognitionErrorType.audio:
      // 处理音频错误
      print('音频错误: ${error.errorMsg}');
      break;
    default:
      print('未知错误: ${error.errorMsg}');
  }
});

🔧 性能优化

1. 模型选择

  • 小型应用: 使用单语言模型 (~40MB)
  • 多语言应用: 使用双语模型 (~60MB)
  • 专业应用: 训练自定义模型

2. 内存管理

@override
void dispose() {
  _asr.dispose(); // 释放资源
  super.dispose();
}

3. 批量处理

// 对于长时间录音,定期获取结果
Timer.periodic(Duration(seconds: 5), (timer) {
  if (_asr.isListening) {
    // 可以在这里保存中间结果
  }
});

📱 平台特定配置

Android

<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />

iOS

<!-- ios/Runner/Info.plist -->
<key>NSMicrophoneUsageDescription</key>
<string>此应用需要麦克风权限进行语音识别</string>

🐛 常见问题

Q: 模型文件太大怎么办?

A: 可以考虑:

  1. 使用模型压缩
  2. 动态下载模型
  3. 只包含必要的语言模型

Q: 识别精度不够怎么办?

A: 可以尝试:

  1. 使用更新的模型
  2. 调整音频参数
  3. 在安静环境中测试
  4. 训练自定义模型

Q: 如何实现实时显示?

A: 启用 partialResults: true 并监听结果流:

_asr.onResult.listen((result) {
  if (result.finalResult) {
    // 最终结果
    finalText = result.recognizedWords;
  } else {
    // 实时结果
    partialText = result.recognizedWords;
  }
});

📚 参考资源

🤝 技术支持

如有问题,请提交 Issue 或参考示例代码。