基于HarmonyOS的AI能力和分布式技术,我们设计了一个语音翻译便签应用,能够实时将语音输入转换为文字并进行翻译,最终生成多语言便签,支持跨设备同步。
https://example.com/ai-voice-translator-arch.png
系统包含三个核心模块:
@ohos.multimedia.audio
和AI语音识别服务@ohos.distributedData
实现多设备同步// VoiceRecognitionService.ets
import audio from '@ohos.multimedia.audio';
import plugin from '@ohos.ai.nlp';
const TRANSLATION_PLUGIN_NAME = 'com.example.translation';
class VoiceRecognitionService {
private static instance: VoiceRecognitionService = null;
private audioCapturer: audio.AudioCapturer;
private translationPlugin: plugin.NlpPlugin;
private listeners: VoiceRecognitionListener[] = [];
private isRecognizing: boolean = false;
private constructor() {
this.initAudioCapturer();
this.initTranslationPlugin();
}
public static getInstance(): VoiceRecognitionService {
if (!VoiceRecognitionService.instance) {
VoiceRecognitionService.instance = new VoiceRecognitionService();
}
return VoiceRecognitionService.instance;
}
private initAudioCapturer(): void {
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 = audio.createAudioCapturer({
audioStreamInfo: audioStreamInfo,
audioCapturerInfo: audioCapturerInfo
});
}
private async initTranslationPlugin(): Promise {
try {
this.translationPlugin = await plugin.getNlpPlugin(TRANSLATION_PLUGIN_NAME);
} catch (err) {
console.error('初始化翻译插件失败:', JSON.stringify(err));
}
}
public async startRecognition(): Promise {
if (this.isRecognizing) return false;
try {
await this.audioCapturer.start();
this.isRecognizing = true;
// 开始读取音频数据并识别
this.processAudioData();
return true;
} catch (err) {
console.error('开始语音识别失败:', JSON.stringify(err));
return false;
}
}
public async stopRecognition(): Promise {
if (!this.isRecognizing) return;
try {
await this.audioCapturer.stop();
this.isRecognizing = false;
} catch (err) {
console.error('停止语音识别失败:', JSON.stringify(err));
}
}
private async processAudioData(): Promise {
while (this.isRecognizing) {
const buffer = await this.audioCapturer.read();
if (buffer && buffer.byteLength > 0) {
// 实际应用中这里应该调用语音识别API
const text = await this.mockSpeechToText(buffer);
this.notifyRecognitionResult(text);
}
}
}
private async mockSpeechToText(audioData: ArrayBuffer): Promise {
// 模拟语音识别,实际应用中应调用真实API
return new Promise((resolve) => {
setTimeout(() => {
resolve("这是模拟的语音识别结果");
}, 500);
});
}
public async translate(text: string, targetLang: string): Promise {
if (!this.translationPlugin) {
console.error('翻译插件未初始化');
return text;
}
try {
const result = await this.translationPlugin.translate({
text: text,
targetLang: targetLang,
sourceLang: 'auto'
});
return result.translatedText;
} catch (err) {
console.error('翻译失败:', JSON.stringify(err));
return text;
}
}
public addListener(listener: VoiceRecognitionListener): void {
if (!this.listeners.includes(listener)) {
this.listeners.push(listener);
}
}
public removeListener(listener: VoiceRecognitionListener): void {
this.listeners = this.listeners.filter(l => l !== listener);
}
private notifyRecognitionResult(text: string): void {
this.listeners.forEach(listener => {
listener.onRecognitionResult(text);
});
}
private notifyRecognitionError(error: string): void {
this.listeners.forEach(listener => {
listener.onRecognitionError(error);
});
}
}
interface VoiceRecognitionListener {
onRecognitionResult(text: string): void;
onRecognitionError(error: string): void;
}
export const voiceService = VoiceRecognitionService.getInstance();
// NoteService.ets
import distributedData from '@ohos.distributedData';
const NOTE_SYNC_CHANNEL = 'translation_notes';
class NoteService {
private static instance: NoteService = null;
private dataManager: distributedData.DataManager;
private listeners: NoteListener[] = [];
private notes: TranslationNote[] = [];
private constructor() {
this.initDataManager();
}
public static getInstance(): NoteService {
if (!NoteService.instance) {
NoteService.instance = new NoteService();
}
return NoteService.instance;
}
private initDataManager(): void {
this.dataManager = distributedData.createDataManager({
bundleName: 'com.example.voicenotes',
area: distributedData.Area.GLOBAL
});
this.dataManager.registerDataListener(NOTE_SYNC_CHANNEL, (data) => {
this.handleSyncData(data);
});
}
public async createNote(originalText: string, translations: { [lang: string]: string }): Promise {
const note: TranslationNote = {
id: Date.now().toString(),
originalText: originalText,
translations: translations,
createdAt: Date.now(),
updatedAt: Date.now(),
deviceId: this.getDeviceId()
};
this.notes.unshift(note);
this.syncNote(note);
return note;
}
public async updateNote(noteId: string, updates: Partial): Promise {
const index = this.notes.findIndex(n => n.id === noteId);
if (index === -1) return false;
this.notes[index] = {
...this.notes[index],
...updates,
updatedAt: Date.now()
};
this.syncNote(this.notes[index]);
return true;
}
public async deleteNote(noteId: string): Promise {
const index = this.notes.findIndex(n => n.id === noteId);
if (index === -1) return false;
this.notes.splice(index, 1);
this.syncDelete(noteId);
return true;
}
public getNotes(): TranslationNote[] {
return [...this.notes];
}
private syncNote(note: TranslationNote): void {
this.dataManager.syncData(NOTE_SYNC_CHANNEL, {
type: 'updateNote',
note: note
});
}
private syncDelete(noteId: string): void {
this.dataManager.syncData(NOTE_SYNC_CHANNEL, {
type: 'deleteNote',
noteId: noteId
});
}
private handleSyncData(data: any): void {
if (!data) return;
switch (data.type) {
case 'updateNote':
this.handleNoteUpdate(data.note);
break;
case 'deleteNote':
this.handleNoteDelete(data.noteId);
break;
}
}
private handleNoteUpdate(note: TranslationNote): void {
if (note.deviceId === this.getDeviceId()) return;
const index = this.notes.findIndex(n => n.id === note.id);
if (index === -1) {
this.notes.unshift(note);
} else {
this.notes[index] = note;
}
this.notifyNotesUpdated();
}
private handleNoteDelete(noteId: string): void {
if (this.notes.some(n => n.id === noteId && n.deviceId !== this.getDeviceId())) {
this.notes = this.notes.filter(n => n.id !== noteId);
this.notifyNotesUpdated();
}
}
private notifyNotesUpdated(): void {
this.listeners.forEach(listener => {
listener.onNotesChanged(this.getNotes());
});
}
public addListener(listener: NoteListener): void {
if (!this.listeners.includes(listener)) {
this.listeners.push(listener);
}
}
public removeListener(listener: NoteListener): void {
this.listeners = this.listeners.filter(l => l !== listener);
}
private getDeviceId(): string {
// 实际实现需要获取设备ID
return 'local_device';
}
}
interface TranslationNote {
id: string;
originalText: string;
translations: { [lang: string]: string };
createdAt: number;
updatedAt: number;
deviceId: string;
}
interface NoteListener {
onNotesChanged(notes: TranslationNote[]): void;
}
export const noteService = NoteService.getInstance();
// VoiceTranslationApp.ets
import { voiceService, noteService } from './VoiceRecognitionService';
@Entry
@Component
struct VoiceTranslationApp {
@State isRecognizing: boolean = false;
@State recognizedText: string = '';
@State translatedTexts: { [lang: string]: string } = {};
@State targetLanguages: string[] = ['en', 'ja', 'ko', 'fr'];
@State notes: TranslationNote[] = [];
@State activeTab: 'record' | 'notes' = 'record';
aboutToAppear() {
noteService.addListener({
onNotesChanged: (notes) => {
this.notes = notes;
}
});
this.notes = noteService.getNotes();
}
build() {
Column() {
// 顶部选项卡
Tabs({ barPosition: BarPosition.Start }) {
TabContent() {
this.buildRecorderUI()
}
.tabBar('语音翻译')
TabContent() {
this.buildNotesUI()
}
.tabBar('便签列表')
}
.onChange((index: number) => {
this.activeTab = index === 0 ? 'record' : 'notes';
})
.height('100%')
}
.width('100%')
.height('100%')
}
@Builder
buildRecorderUI() {
Column() {
// 语音识别状态指示器
Row() {
Text(this.isRecognizing ? '正在聆听...' : '点击开始录音')
.fontSize(18)
Circle()
.width(20)
.height(20)
.fill(this.isRecognizing ? Color.Red : Color.Gray)
.margin({ left: 10 })
}
.margin({ bottom: 30 })
// 录音控制按钮
Button(this.isRecognizing ? '停止录音' : '开始录音')
.width(200)
.height(60)
.fontSize(20)
.onClick(async () => {
if (this.isRecognizing) {
await voiceService.stopRecognition();
this.isRecognizing = false;
} else {
this.isRecognizing = await voiceService.startRecognition();
}
})
.margin({ bottom: 30 })
// 识别结果
if (this.recognizedText) {
Text('识别结果:')
.fontSize(16)
.margin({ bottom: 10 })
Text(this.recognizedText)
.fontSize(18)
.margin({ bottom: 30 })
.padding(10)
.backgroundColor('#F5F5F5')
.borderRadius(8)
}
// 翻译结果
if (Object.keys(this.translatedTexts).length > 0) {
Text('翻译结果:')
.fontSize(16)
.margin({ bottom: 10 })
ForEach(this.targetLanguages, (lang) => {
Column() {
Text(`${this.getLanguageName(lang)}:`)
.fontSize(14)
.fontColor('#666666')
Text(this.translatedTexts[lang] || '翻译中...')
.fontSize(16)
.margin({ top: 4 })
.padding(10)
.backgroundColor('#F0F8FF')
.borderRadius(8)
}
.margin({ bottom: 15 })
})
}
// 保存按钮
if (this.recognizedText) {
Button('保存为便签')
.width(200)
.height(50)
.margin({ top: 30 })
.onClick(async () => {
const note = await noteService.createNote(
this.recognizedText,
this.translatedTexts
);
promptAction.showToast({ message: '便签已保存', duration: 2000 });
this.resetRecorder();
})
}
}
.padding(20)
.width('100%')
.height('100%')
}
@Builder
buildNotesUI() {
Column() {
if (this.notes.length === 0) {
Text('没有便签记录')
.fontSize(16)
.margin({ top: 50 })
} else {
List({ space: 10 }) {
ForEach(this.notes, (note) => {
ListItem() {
this.buildNoteItem(note)
}
})
}
.layoutWeight(1)
}
}
.padding(20)
.width('100%')
.height('100%')
}
@Builder
buildNoteItem(note: TranslationNote) {
Column() {
// 原始文本
Text(note.originalText)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 8 })
// 翻译文本
ForEach(Object.entries(note.translations), ([lang, text]) => {
Row() {
Text(`${this.getLanguageName(lang)}:`)
.fontSize(12)
.fontColor('#666666')
.width(60)
Text(text)
.fontSize(14)
.layoutWeight(1)
}
.margin({ bottom: 4 })
})
// 时间和操作按钮
Row() {
Text(this.formatTime(note.createdAt))
.fontSize(12)
.fontColor('#999999')
.layoutWeight(1)
Button('删除')
.width(80)
.height(30)
.fontSize(12)
.onClick(async () => {
await noteService.deleteNote(note.id);
promptAction.showToast({ message: '便签已删除', duration: 1000 });
})
}
.margin({ top: 8 })
}
.padding(15)
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(8)
.shadow({ radius: 2, color: '#000000', offsetX: 1, offsetY: 1 })
}
private async setupVoiceRecognition(): Promise {
voiceService.addListener({
onRecognitionResult: async (text) => {
this.recognizedText = text;
// 开始翻译
for (const lang of this.targetLanguages) {
this.translatedTexts[lang] = await voiceService.translate(text, lang);
}
},
onRecognitionError: (error) => {
promptAction.showToast({ message: `识别错误: ${error}`, duration: 2000 });
this.isRecognizing = false;
}
});
}
private resetRecorder(): void {
this.recognizedText = '';
this.translatedTexts = {};
}
private getLanguageName(code: string): string {
const languages: { [code: string]: string } = {
'en': '英语',
'ja': '日语',
'ko': '韩语',
'fr': '法语',
'zh': '中文'
};
return languages[code] || code;
}
private formatTime(timestamp: number): string {
const date = new Date(timestamp);
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${date.getMinutes()}`;
}
}
// module.json5
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.MICROPHONE",
"reason": "录制语音输入"
},
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC",
"reason": "跨设备同步便签"
},
{
"name": "ohos.permission.INTERNET",
"reason": "访问翻译API"
},
{
"name": "ohos.permission.ACCESS_NETWORK_STATE",
"reason": "检查网络连接状态"
}
],
"abilities": [
{
"name": "MainAbility",
"type": "page",
"visible": true
}
],
"distributedNotification": {
"scenarios": [
{
"name": "translation_notes",
"value": "note_sync"
}
]
}
}
}
{
"string": [
{
"name": "app_name",
"value": "语音翻译便签"
},
{
"name": "recording_tab",
"value": "语音翻译"
},
{
"name": "notes_tab",
"value": "便签列表"
},
{
"name": "listening",
"value": "正在聆听..."
},
{
"name": "tap_to_record",
"value": "点击开始录音"
},
{
"name": "start_recording",
"value": "开始录音"
},
{
"name": "stop_recording",
"value": "停止录音"
},
{
"name": "recognition_result",
"value": "识别结果:"
},
{
"name": "translation_result",
"value": "翻译结果:"
},
{
"name": "translating",
"value": "翻译中..."
},
{
"name": "save_note",
"value": "保存为便签"
},
{
"name": "note_saved",
"value": "便签已保存"
},
{
"name": "no_notes",
"value": "没有便签记录"
},
{
"name": "delete",
"value": "删除"
},
{
"name": "note_deleted",
"value": "便签已删除"
},
{
"name": "recognition_error",
"value": "识别错误"
}
]
}
// 在VoiceRecognitionService中添加离线翻译
class VoiceRecognitionService {
private async getOfflineTranslation(text: string, targetLang: string): Promise {
try {
// 加载离线翻译模型
const model = await plugin.loadNlpModel({
modelName: `translation_${targetLang}`,
modelPath: 'resources/rawfile/models/'
});
// 执行离线翻译
const result = await model.translate({ text });
return result.translatedText;
} catch (err) {
console.error('离线翻译失败:', JSON.stringify(err));
return text;
}
}
public async translate(text: string, targetLang: string): Promise {
if (navigator.onLine) {
// 优先使用在线翻译
try {
return await this.getOnlineTranslation(text, targetLang);
} catch (err) {
console.error('在线翻译失败,尝试离线翻译:', JSON.stringify(err));
}
}
// 回退到离线翻译
return await this.getOfflineTranslation(text, targetLang);
}
private async getOnlineTranslation(text: string, targetLang: string): Promise {
// 实现在线翻译逻辑
// ...
}
}
// 添加语音合成服务
import audio from '@ohos.multimedia.audio';
import tts from '@ohos.multimedia.tts';
class TextToSpeechService {
private audioRenderer: audio.AudioRenderer;
private ttsEngine: tts.TtsEngine;
constructor() {
this.initAudioRenderer();
this.initTtsEngine();
}
private initAudioRenderer(): void {
const audioStreamInfo: audio.AudioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
channels: audio.AudioChannel.CHANNEL_2,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
};
this.audioRenderer = audio.createAudioRenderer({
audioStreamInfo: audioStreamInfo
});
}
private async initTtsEngine(): Promise {
this.ttsEngine = await tts.createTtsEngine();
await this.ttsEngine.init({
volume: 0.8,
speed: 1.0,
pitch: 1.0
});
}
public async speak(text: string, lang: string): Promise {
try {
await this.ttsEngine.setLanguage(lang);
await this.ttsEngine.speak(text);
} catch (err) {
console.error('语音合成失败:', JSON.stringify(err));
}
}
public async stop(): Promise {
await this.ttsEngine.stop();
}
}
export const ttsService = new TextToSpeechService();
// 在UI中添加播放按钮
@Builder
buildNoteItem(note: TranslationNote) {
// ...其他代码
Button('朗读')
.width(80)
.height(30)
.fontSize(12)
.margin({ left: 10 })
.onClick(async () => {
await ttsService.speak(note.originalText, 'zh');
})
}
// 在NoteService中添加分类支持
class NoteService {
private categories: string[] = ['工作', '个人', '学习', '其他'];
public getCategories(): string[] {
return [...this.categories];
}
public async addCategory(name: string): Promise {
if (!this.categories.includes(name)) {
this.categories.push(name);
}
}
public async createNote(
originalText: string,
translations: { [lang: string]: string },
category?: string
): Promise {
const note: TranslationNote = {
// ...其他字段
category: category || '未分类'
};
// ...
}
public getNotesByCategory(category: string): TranslationNote[] {
return this.notes.filter(note => note.category === category);
}
}
// 在UI中添加分类筛选
@Builder
buildNotesUI() {
Column() {
// 分类筛选
Picker({
selected: this.selectedCategory,
range: noteService.getCategories()
})
.onChange((value: string) => {
this.selectedCategory = value;
})
.margin({ bottom: 15 })
// 便签列表
// ...
}
}
通过这个AI语音翻译便签应用的实现,我们学习了:
系统特点:
这个应用可以进一步扩展为功能更完善的语音助手,如: