鸿蒙跨端外语发音评分系统开发指南 原创

进修的泡芙
发布于 2025-6-22 17:58
浏览
0收藏

鸿蒙跨端外语发音评分系统开发指南

一、项目概述

本文基于HarmonyOS的语音识别和音频分析能力,结合分布式技术,开发一款外语发音评分系统。该系统能够实时评估用户外语发音准确度,并将评分结果同步到多设备,借鉴了《鸿蒙跨端U同步》中多设备数据同步的技术原理。

二、系统架构

±--------------------+ ±--------------------+ ±--------------------+
主设备 <-----> 分布式数据总线 <-----> 从设备
(手机/平板) (Distributed Bus) (智能手表/电脑)
±---------±---------+ ±---------±---------+ ±---------±---------+

±---------v----------+ ±---------v----------+ ±---------v----------+
语音分析模块 发音评估模块 数据同步模块
(Speech Analysis) (Pronunciation) (Data Sync)

±--------------------+ ±--------------------+ ±--------------------+

三、核心代码实现
发音评估服务

// src/main/ets/service/PronunciationService.ts
import { distributedData } from ‘@ohos.data.distributedData’;
import { BusinessError } from ‘@ohos.base’;
import { audio } from ‘@ohos.multimedia.audio’;
import { speechRecognizer } from ‘@ohos.multimedia.speech’;
import { ai } from ‘@ohos.ai’;

interface PronunciationScore {
accuracy: number; // 发音准确度(0-100)
fluency: number; // 流畅度(0-100)
completeness: number; // 完整度(0-100)
overall: number; // 综合评分(0-100)
timestamp: number;
interface EvaluationResult {

text: string; // 原文
pronunciation: string; // 用户发音
scores: PronunciationScore;
feedback: string[]; // 改进建议
export class PronunciationService {

private static instance: PronunciationService;
private kvStore: distributedData.KVStore | null = null;
private readonly STORE_ID = ‘pronunciation_store’;
private audioCapturer: audio.AudioCapturer | null = null;
private speechRecognizer: speechRecognizer.SpeechRecognizer | null = null;
private currentResult: EvaluationResult | null = null;
private referenceTexts: string[] = [
“Hello world”,
“How are you”,
“I love programming”,
“HarmonyOS is great”
];

private constructor() {
this.initKVStore();
this.initAudioCapturer();
this.initSpeechRecognizer();
public static getInstance(): PronunciationService {

if (!PronunciationService.instance) {
  PronunciationService.instance = new PronunciationService();

return PronunciationService.instance;

private async initKVStore(): Promise<void> {

try {
  const options: distributedData.KVManagerConfig = {
    bundleName: 'com.example.pronunciation',
    userInfo: {
      userId: '0',
      userType: distributedData.UserType.SAME_USER_ID

};

  const kvManager = distributedData.createKVManager(options);
  this.kvStore = await kvManager.getKVStore({
    storeId: this.STORE_ID,
    options: {
      createIfMissing: true,
      encrypt: false,
      backup: false,
      autoSync: true,
      kvStoreType: distributedData.KVStoreType.SINGLE_VERSION

});

  this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_REMOTE, (data) => {
    data.insertEntries.forEach((entry: distributedData.Entry) => {
      if (entry.key === 'evaluation_result') {
        this.notifyResultChange(entry.value.value as EvaluationResult);

});

  });

catch (e) {

  console.error(Failed to initialize KVStore. Code: {e.code}, message: {e.message});

}

private async initAudioCapturer(): Promise<void> {
try {
const audioStreamInfo: audio.AudioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_16000,
channels: audio.AudioChannel.CHANNEL_1,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
};

  const audioCapturerInfo: audio.AudioCapturerInfo = {
    source: audio.SourceType.SOURCE_TYPE_MIC,
    capturerFlags: 0
  };
  
  this.audioCapturer = await audio.createAudioCapturer({
    streamInfo: audioStreamInfo,
    capturerInfo: audioCapturerInfo
  });

catch (e) {

  console.error(Failed to initialize audio capturer. Code: {e.code}, message: {e.message});

}

private async initSpeechRecognizer(): Promise<void> {
try {
this.speechRecognizer = await speechRecognizer.createSpeechRecognizer();

  const config: speechRecognizer.SpeechRecognizerConfig = {
    language: 'en-US', // 英语(美国)
    grammar: 'default'
  };
  
  await this.speechRecognizer.setConfig(config);
  
  this.speechRecognizer.on('result', (result) => {
    this.handleRecognitionResult(result);
  });

catch (e) {

  console.error(Failed to initialize speech recognizer. Code: {e.code}, message: {e.message});

}

private async initAITextGenerator(): Promise<void> {
try {
this.aiTextGenerator = await ai.createTextGenerator();
catch (e) {

  console.error(Failed to initialize AI text generator. Code: {e.code}, message: {e.message});

}

public async startEvaluation(referenceText?: string): Promise<void> {
if (!this.audioCapturer || !this.speechRecognizer) return;

const text = referenceText || this.getRandomReferenceText();
this.currentResult = {
  text,
  pronunciation: '',
  scores: {
    accuracy: 0,
    fluency: 0,
    completeness: 0,
    overall: 0,
    timestamp: Date.now()
  },
  feedback: []
};

try {
  await this.audioCapturer.start();
  await this.speechRecognizer.start();

catch (e) {

  console.error(Failed to start evaluation. Code: {e.code}, message: {e.message});

}

private getRandomReferenceText(): string {
const index = Math.floor(Math.random() * this.referenceTexts.length);
return this.referenceTexts[index];
private handleRecognitionResult(result: speechRecognizer.SpeechRecognizerResult): void {

if (!this.currentResult) return;

this.currentResult.pronunciation = result.text;
this.evaluatePronunciation();

private evaluatePronunciation(): void {

if (!this.currentResult) return;

const { text, pronunciation } = this.currentResult;

// 简化的评估逻辑 (实际应用中应使用更复杂的算法)
const accuracy = this.calculateAccuracy(text, pronunciation);
const fluency = this.calculateFluency(pronunciation);
const completeness = this.calculateCompleteness(text, pronunciation);

this.currentResult.scores = {
  accuracy,
  fluency,
  completeness,
  overall: Math.floor((accuracy + fluency + completeness) / 3),
  timestamp: Date.now()
};

this.generateFeedback();
this.syncResult();

private calculateAccuracy(reference: string, pronunciation: string): number {

// 基于编辑距离的准确度计算
const distance = this.levenshteinDistance(reference.toLowerCase(), pronunciation.toLowerCase());
const maxLength = Math.max(reference.length, pronunciation.length);
return Math.max(0, 100 - (distance / maxLength * 100));

private levenshteinDistance(a: string, b: string): number {

const matrix = Array(a.length + 1).fill(null).map(() => Array(b.length + 1).fill(null));

for (let i = 0; i <= a.length; i++) matrix[i][0] = i;
for (let j = 0; j <= b.length; j++) matrix[0][j] = j;

for (let i = 1; i <= a.length; i++) {
  for (let j = 1; j <= b.length; j++) {
    const cost = a[i - 1] === b[j - 1] ? 0 : 1;
    matrix[i][j] = Math.min(
      matrix[i - 1][j] + 1,
      matrix[i][j - 1] + 1,
      matrix[i - 1][j - 1] + cost
    );

}

return matrix[a.length][b.length];

private calculateFluency(pronunciation: string): number {

// 基于单词数量和停顿的流畅度评估
const words = pronunciation.split(' ');
if (words.length === 0) return 0;

const wordLengths = words.map(word => word.length);
const avgLength = wordLengths.reduce((sum, len) => sum + len, 0) / wordLengths.length;
const lengthScore = Math.min(100, 100 - Math.abs(avgLength - 4.5) * 10);
const countScore = Math.min(100, words.length * 20);

return Math.floor((lengthScore + countScore) / 2);

private calculateCompleteness(reference: string, pronunciation: string): number {

// 基于覆盖率的完整度评估
const refWords = new Set(reference.toLowerCase().split(' '));
const pronWords = new Set(pronunciation.toLowerCase().split(' '));

let matched = 0;
refWords.forEach(word => {
  if (pronWords.has(word)) matched++;
});

return Math.floor(matched / refWords.size * 100);

private generateFeedback(): void {

if (!this.currentResult) return;

const { scores, text, pronunciation } = this.currentResult;
this.currentResult.feedback = [];

if (scores.accuracy < 70) {
  this.currentResult.feedback.push('注意单词发音准确性');

if (scores.fluency < 60) {

  this.currentResult.feedback.push('尝试说得更流畅一些');

if (scores.completeness < 80) {

  const missingWords = this.findMissingWords(text, pronunciation);
  if (missingWords.length > 0) {
    this.currentResult.feedback.push(遗漏了单词: ${missingWords.join(', ')});

}

if (scores.overall > 85) {
  this.currentResult.feedback.push('发音很棒!继续保持');

}

private findMissingWords(reference: string, pronunciation: string): string[] {
const refWords = reference.toLowerCase().split(’ ‘);
const pronWords = new Set(pronunciation.toLowerCase().split(’ '));

return refWords.filter(word => !pronWords.has(word));

private async syncResult(): Promise<void> {

if (this.kvStore && this.currentResult) {
  try {
    await this.kvStore.put('evaluation_result', { value: this.currentResult });

catch (e) {

    console.error(Failed to sync result. Code: {e.code}, message: {e.message});

}

public async stopEvaluation(): Promise<void> {

try {
  if (this.audioCapturer) {
    await this.audioCapturer.stop();

if (this.speechRecognizer) {

    await this.speechRecognizer.stop();

} catch (e) {

  console.error(Failed to stop evaluation. Code: {e.code}, message: {e.message});

}

public async getCurrentResult(): Promise<EvaluationResult | null> {
if (!this.kvStore) return this.currentResult;

try {
  const entry = await this.kvStore.get('evaluation_result');
  return entry?.value || this.currentResult;

catch (e) {

  console.error(Failed to get evaluation result. Code: {e.code}, message: {e.message});
  return this.currentResult;

}

public async destroy(): Promise<void> {
if (this.kvStore) {
this.kvStore.off(‘dataChange’);
if (this.audioCapturer) {

  await this.audioCapturer.release();

if (this.speechRecognizer) {

  await this.speechRecognizer.destroy();

}

发音评分组件

// src/main/ets/components/PronunciationCard.ets
@Component
export struct PronunciationCard {
private pronService = PronunciationService.getInstance();
@State evaluationResult: EvaluationResult | null = null;
@State isEvaluating: boolean = false;
@State referenceText: string = ‘’;

aboutToAppear(): void {
this.loadReferenceText();
private loadReferenceText(): void {

this.referenceText = this.pronService.getRandomReferenceText();

build() {

Column() {
  // 参考文本
  Text('请朗读以下句子:')
    .fontSize(16)
    .margin({ bottom: 10 });
  
  Text(this.referenceText)
    .fontSize(20)
    .fontWeight(FontWeight.Bold)
    .textAlign(TextAlign.Center)
    .margin({ bottom: 30 });
  
  // 评分结果
  if (this.evaluationResult) {
    this.buildScoreDisplay();

// 控制按钮

  Row() {
    Button(this.isEvaluating ? '停止评估' : '开始评估')
      .type(ButtonType.Capsule)
      .width('40%')
      .backgroundColor(this.isEvaluating ? '#F44336' : '#4CAF50')
      .fontColor('#FFFFFF')
      .onClick(() => {
        this.toggleEvaluation();
      });
    
    if (!this.isEvaluating && !this.evaluationResult) {
      Button('换一句')
        .type(ButtonType.Capsule)
        .width('40%')
        .backgroundColor('#2196F3')
        .fontColor('#FFFFFF')
        .margin({ left: 20 })
        .onClick(() => {
          this.loadReferenceText();
        });

}

  .width('100%')
  .justifyContent(FlexAlign.Center)
  .margin({ top: 30 });

.width(‘100%’)

.height('100%')
.padding(20);

@Builder

private buildScoreDisplay() {
Column() {
// 综合评分
Row() {
Text(‘综合评分:’)
.fontSize(18)
.margin({ right: 10 });

    Text(${this.evaluationResult?.scores.overall || 0})
      .fontSize(36)
      .fontColor(this.getScoreColor(this.evaluationResult?.scores.overall || 0));

.width(‘100%’)

  .justifyContent(FlexAlign.Start)
  .margin({ bottom: 30 });
  
  // 详细评分
  Grid() {
    GridItem() {
      this.buildMetricCard('准确度', this.evaluationResult?.scores.accuracy || 0);

GridItem() {

      this.buildMetricCard('流畅度', this.evaluationResult?.scores.fluency || 0);

GridItem() {

      this.buildMetricCard('完整度', this.evaluationResult?.scores.completeness || 0);

}

  .columnsTemplate('1fr 1fr')
  .rowsTemplate('1fr')
  .columnsGap(10)
  .rowsGap(10)
  .width('100%')
  .height(100)
  .margin({ bottom: 20 });
  
  // 用户发音
  Text(你的发音: ${this.evaluationResult?.pronunciation || ''})
    .fontSize(16)
    .margin({ bottom: 20 });
  
  // 反馈建议
  if (this.evaluationResult?.feedback && this.evaluationResult.feedback.length > 0) {
    Column() {
      Text('改进建议:')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 10 });
      
      ForEach(this.evaluationResult.feedback, (item) => {
        Text(• ${item})
          .fontSize(14)
          .margin({ bottom: 5 });
      })

.width(‘100%’)

    .padding(15)
    .backgroundColor('#FFF3E0')
    .borderRadius(10);

}

@Builder

private buildMetricCard(title: string, score: number) {
Column() {
Text(title)
.fontSize(14)
.fontColor(‘#666666’);

  Row() {
    Text(${score})
      .fontSize(18)
      .fontWeight(FontWeight.Bold)
      .margin({ right: 5 });
    
    Text('/100')
      .fontSize(12)
      .fontColor('#999999');

.margin({ top: 5 });

  Progress({ value: score, total: 100, type: ProgressType.Linear })
    .width('100%')
    .height(6)
    .margin({ top: 5 })
    .color('#4CAF50');

.width(‘100%’)

.height('100%')
.padding(10)
.backgroundColor('#FFFFFF')
.borderRadius(10)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center);

private async toggleEvaluation(): Promise<void> {

if (this.isEvaluating) {
  await this.pronService.stopEvaluation();
  this.evaluationResult = await this.pronService.getCurrentResult();

else {

  await this.pronService.startEvaluation(this.referenceText);

this.isEvaluating = !this.isEvaluating;

private getScoreColor(score: number): string {

if (score >= 85) return '#4CAF50';
if (score >= 70) return '#FFC107';
return '#F44336';

}

主界面实现

// src/main/ets/pages/PronunciationPage.ets
import { PronunciationService } from ‘…/service/PronunciationService’;
import { PronunciationCard } from ‘…/components/PronunciationCard’;

@Entry
@Component
struct PronunciationPage {
@State activeTab: number = 0;
@State deviceList: string[] = [];
private pronService = PronunciationService.getInstance();

build() {
Column() {
// 标题
Text(‘外语发音评分’)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 });

  // 标签页
  Tabs({ barPosition: BarPosition.Start }) {
    TabContent() {
      // 发音评估标签页
      PronunciationCard()

.tabBar(‘发音评估’);

    TabContent() {
      // 历史记录标签页
      this.buildHistoryTab()

.tabBar(‘历史记录’);

    TabContent() {
      // 设备管理标签页
      this.buildDevicesTab()

.tabBar(‘设备管理’);

.barWidth(‘100%’)

  .barHeight(50)
  .width('100%')
  .height('80%')

.width(‘100%’)

.height('100%')
.padding(20)
.onAppear(() => {
  // 模拟获取设备列表
  setTimeout(() => {
    this.deviceList = ['我的手机', '智能手表', '平板电脑'];
  }, 1000);
});

@Builder

private buildHistoryTab() {
Column() {
Text(‘历史评分记录’)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 });

  // 模拟历史数据
  List({ space: 15 }) {
    ListItem() {
      this.buildHistoryItem('Hello world', 85, '10分钟前');

ListItem() {

      this.buildHistoryItem('How are you', 76, '30分钟前');

ListItem() {

      this.buildHistoryItem('I love programming', 92, '1小时前');

}

  .width('100%')
  .layoutWeight(1);

.width(‘100%’)

.height('100%')
.padding(10);

@Builder

private buildHistoryItem(text: string, score: number, time: string) {
Row() {
Column() {
Text(text)
.fontSize(16)
.fontWeight(FontWeight.Bold);

    Text(time)
      .fontSize(12)
      .fontColor('#666666')
      .margin({ top: 5 });

.layoutWeight(1);

  Text(${score})
    .fontSize(24)
    .fontColor(this.getScoreColor(score));

.width(‘100%’)

.padding(15)
.backgroundColor('#FFFFFF')
.borderRadius(10);

@Builder

private buildDevicesTab() {
Column() {
Text(‘已连接设备’)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 });

  if (this.deviceList.length > 0) {
    List({ space: 15 }) {
      ForEach(this.deviceList, (device) => {
        ListItem() {
          Row() {
            Image($r('app.media.ic_device'))
              .width(40)
              .height(40)
              .margin({ right: 15 });
            
            Text(device)
              .fontSize(16)
              .layoutWeight(1);
            
            if (device === '我的手机') {
              Text('主设备')
                .fontSize(14)
                .fontColor('#4CAF50');

}

          .width('100%')
          .padding(15)
          .backgroundColor('#FFFFFF')
          .borderRadius(10)

})

.width(‘100%’)

    .layoutWeight(1);

else {

    Text('没有连接的设备')
      .fontSize(16)
      .fontColor('#666666')
      .margin({ top: 50 });

Button(‘添加设备’)

    .type(ButtonType.Capsule)
    .width('80%')
    .margin({ top: 30 })
    .backgroundColor('#2196F3')
    .fontColor('#FFFFFF');

.width(‘100%’)

.height('100%')
.padding(10);

private getScoreColor(score: number): string {

if (score >= 85) return '#4CAF50';
if (score >= 70) return '#FFC107';
return '#F44336';

}

四、与游戏同步技术的结合点
分布式状态同步:借鉴游戏中多玩家状态同步机制,实现发音评分结果的跨设备同步

实时音频流处理:类似游戏中的实时数据流,处理音频数据

设备角色分配:类似游戏中的主机/客户端角色,确定主评分设备和从属设备

时间同步机制:确保多设备间的时间戳一致,类似游戏中的时间同步

数据压缩传输:优化音频和评分数据的传输效率,类似游戏中的网络优化

五、关键特性实现
语音识别配置:

  const config: speechRecognizer.SpeechRecognizerConfig = {
 language: 'en-US', // 英语(美国)
 grammar: 'default'

};

发音准确度计算:

  private calculateAccuracy(reference: string, pronunciation: string): number {
 const distance = this.levenshteinDistance(reference.toLowerCase(), pronunciation.toLowerCase());
 const maxLength = Math.max(reference.length, pronunciation.length);
 return Math.max(0, 100 - (distance / maxLength * 100));

流畅度评估:

  private calculateFluency(pronunciation: string): number {
 const words = pronunciation.split(' ');
 const wordLengths = words.map(word => word.length);
 const avgLength = wordLengths.reduce((sum, len) => sum + len, 0) / wordLengths.length;
 const lengthScore = Math.min(100, 100 - Math.abs(avgLength - 4.5) * 10);
 const countScore = Math.min(100, words.length * 20);
 return Math.floor((lengthScore + countScore) / 2);

数据同步:

  private async syncResult(): Promise<void> {
 if (this.kvStore && this.currentResult) {
   await this.kvStore.put('evaluation_result', { value: this.currentResult });

}

六、性能优化策略
音频采样率优化:

  const audioStreamInfo: audio.AudioStreamInfo = {
 samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_16000, // 16kHz采样率
 channels: audio.AudioChannel.CHANNEL_1,
 sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE

};

批量数据处理:

  private scheduleSync(): void {
 if (this.syncTimer) clearTimeout(this.syncTimer);
 this.syncTimer = setTimeout(() => {
   this.syncResult();
   this.syncTimer = null;
 }, 2000); // 2秒内多次更新只同步一次

本地缓存优先:

  public async getCurrentResult(): Promise<EvaluationResult | null> {
 // 先返回本地缓存
 const cachedResult = this.currentResult;
 
 // 异步从分布式存储获取最新结果
 if (this.kvStore) {
   this.kvStore.get('evaluation_result').then((entry) => {
     if (entry?.value) {
       this.currentResult = entry.value;

});

return cachedResult;

资源释放管理:

  public async destroy(): Promise<void> {
 if (this.audioCapturer) {
   await this.audioCapturer.release();

if (this.speechRecognizer) {

   await this.speechRecognizer.destroy();

}

七、项目扩展方向
多语言支持:扩展支持更多语言的发音评估

发音示范:提供标准发音示范音频

长期进步追踪:记录用户发音进步曲线

社交功能:分享评分结果和进步

发音训练课程:基于薄弱环节推荐训练内容

八、总结

本外语发音评分系统实现了以下核心功能:
基于HarmonyOS语音识别能力的发音评估

多维度评分体系(准确度、流畅度、完整度)

个性化改进建议生成

多设备间的评分结果同步

通过借鉴游戏中的多设备同步技术,我们构建了一个专业的外语发音训练工具。该项目展示了HarmonyOS在语音处理和分布式技术方面的强大能力,为开发者提供了教育类应用开发的参考方案。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐