鸿蒙 AI 字幕控件开发全攻略:从入门到实战(ArkTS 版)
本文系统介绍了鸿蒙应用开发中AI字幕控件的实现方法。AI字幕功能可将音频实时转换为文字显示,适用于多语言场景和静音环境。文章从使用场景、核心接口、开发步骤到实战案例,详细讲解了如何通过AICaptionComponent、AICaptionOptions和AICaptionController三大核心组件实现字幕功能,并提供了完整的ArkTS代码示例。开发过程包括:导入必要包、搭建界面布局、读取本
在鸿蒙应用开发中,AI 字幕控件是提升用户体验的重要功能,尤其适合多语言场景和静音环境。本文将用口语化的表达,结合完整 ArkTS 代码,从适用场景、核心接口、开发步骤到实战案例,手把手教你掌握 AI 字幕控件开发,每个重点都搭配可直接复用的代码,确保你能跟着做、学得会。
一、先搞懂:AI 字幕控件到底能干嘛?
AI 字幕控件的核心作用,就是把音频内容实时转成文字显示在界面上,解决两类常见问题:一是用户听不懂音频语言,比如看外语视频时;二是环境不允许开声音,比如在图书馆看视频、地铁里听播客。
举几个实际应用场景:
- 视频类 App:用户开启静音模式后,自动显示视频台词字幕;
- 会议软件:参会者听不懂发言语言时,实时生成多语言字幕;
- 音频播放器:听播客、有声书时,同步显示文字内容,方便用户抓重点。
简单说,只要你的 App 涉及音频播放,加个 AI 字幕功能,用户体验直接上一个台阶。
二、核心接口解析:3 个类搞定 AI 字幕
要开发 AI 字幕,关键是掌握 3 个核心接口,它们各司其职,共同实现字幕功能。这里我用表格把每个接口的作用讲清楚,后面开发时直接对应着用就行。
| 接口名称 | 核心作用 | 实际用途 |
|---|---|---|
| AICaptionComponent | 字幕显示组件 | 在界面上划出一块区域,专门用来显示生成的字幕 |
| AICaptionOptions | 初始化参数类 | 配置字幕的基础属性,比如透明度、错误回调函数 |
| AICaptionController | 功能控制器 | 字幕功能的 “大脑”,负责传递音频数据、控制字幕逻辑 |
下面逐个拆解每个接口的重点,每个重点都配代码片段,方便你理解用法。
2.1 AICaptionComponent:字幕的 “显示窗口”
这个组件是直接呈现在界面上的,就像一个 “文字显示器”,必须在布局里声明才能用。它需要 3 个关键参数:
- isShown:控制字幕区域显示 / 隐藏,布尔值;
- controller:关联的控制器,用来接收字幕数据;
- options:初始化配置,比如透明度。
代码示例:基础布局中添加字幕组件
import { AICaptionComponent, AICaptionController, AICaptionOptions } from '@kit.SpeechKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
// 先定义日志工具,方便调试
const TAG = 'AI_CAPTION_DEMO';
class Logger {
static info(...msg: string[]) {
hilog.info(0x0000, TAG, msg.join());
}
}
@Entry
@Component
struct AICaptionDemo {
// 声明控制器和配置参数
private captionOption?: AICaptionOptions;
private controller = new AICaptionController();
// 控制字幕显示/隐藏的状态变量
@State isShown: boolean = false;
build() {
Column({ space: 20 }) {
// 1. 这就是AI字幕的显示区域
AICaptionComponent({
isShown: this.isShown, // 控制是否显示
controller: this.controller, // 关联控制器
options: this.captionOption // 初始化配置
})
.width('80%') // 设置宽度,占屏幕80%
.height(100) // 固定高度100,避免字幕太多时占满屏幕
.backgroundColor('#F5F5F5') // 加个背景色,方便看到区域
// 加个提示文字,告诉用户这里是字幕区
if (this.isShown) {
Text('字幕将显示在上方区域')
.fontSize(14)
.fontColor('#666666');
}
}
.width('100%')
.height('100%')
.padding(20);
}
}
这段代码的重点是:AICaptionComponent 必须放在布局容器(比如 Column、Row)里,并且通过 width 和 height 设置大小,不然可能看不到字幕。
2.2 AICaptionOptions:字幕的 “初始配置”
这个类用来设置字幕的基础属性,最常用的是两个:
- initialOpacity:字幕区域的透明度,0-1 之间的数值,1 是完全不透明;
- 回调函数:onPrepared(字幕准备好时触发)、onError(出现错误时触发),用来做调试和异常处理。
代码示例:配置字幕初始化参数
import { AICaptionOptions, BusinessError } from '@kit.SpeechKit';
// 在之前的组件类中,补充aboutToAppear生命周期函数
aboutToAppear(): void {
// 初始化字幕配置
this.captionOption = {
// 1. 设置透明度为1,完全不透明
initialOpacity: 1,
// 2. 字幕准备完成的回调
onPrepared: () => {
Logger.info('AI字幕准备好了,可以开始传音频了');
},
// 3. 错误回调,关键!出问题时能定位原因
onError: (error: BusinessError) => {
Logger.error(`字幕出错了!错误码:${error.code},错误信息:${error.message}`);
}
};
}
这里要特别注意 onError 回调,开发时一定要加上。比如音频格式不对、权限不够时,都会触发这个回调,通过错误码能快速找到问题 —— 比如错误码 “1001” 可能是音频数据为空,“2002” 可能是控制器未初始化。
2.3 AICaptionController:字幕的 “功能大脑”
这个控制器是核心中的核心,所有和 “数据传递”“功能控制” 相关的操作,都要通过它来做。最常用的方法是writeAudio(),用来把音频数据传给字幕组件,让组件生成文字。
代码示例:控制器的基础使用
import { AICaptionController, AudioData } from '@kit.SpeechKit';
// 在组件类中,声明控制器(前面代码已经声明过,这里强调用法)
private controller = new AICaptionController();
// 模拟传递音频数据的函数
async sendAudioData() {
// 假设这里拿到了音频数据(Uint8Array格式,后面会讲怎么读本地音频)
const audioBuffer = new Uint8Array([10, 20, 30, 40]); // 示例数据,实际要传真实音频
// 1. 把音频数据包装成AudioData格式
const audioData: AudioData = {
data: audioBuffer // 核心:音频的二进制数据
};
// 2. 通过控制器把数据传给字幕组件
if (this.controller) {
this.controller.writeAudio(audioData);
Logger.info('成功传递一帧音频数据');
} else {
Logger.error('控制器还没初始化,传不了音频');
}
}
这里有个关键知识点:传给writeAudio()的必须是AudioData格式,而不是直接传 Uint8Array。AudioData是鸿蒙定义的标准格式,只有这样字幕组件才能识别。
三、开发步骤:4 步实现基础 AI 字幕功能
前面搞懂了核心接口,现在就来走完整开发流程。从导入包、写布局,到加控制按钮、传音频数据,一步一步来,每个步骤都给完整代码,你可以直接复制到项目里运行。
3.1 第一步:导入必要的包
开发 AI 字幕需要两个核心包:
@kit.SpeechKit:包含 AI 字幕的所有接口(AICaptionComponent、控制器等);@kit.PerformanceAnalysisKit:包含 hilog 日志工具,用来调试。
代码示例:导入包
// 1. 导入AI字幕相关的所有类
import {
AICaptionComponent,
AICaptionController,
AICaptionOptions,
AudioData,
BusinessError
} from '@kit.SpeechKit';
// 2. 导入日志工具,用于调试
import { hilog } from '@kit.PerformanceAnalysisKit';
// 3. (可选)导入资源管理相关的包,后面读本地音频会用到
import { ResourceManager } from '@kit.AbilityKit';
注意:导入包时要确保拼写正确,比如AICaptionComponent不能少写字母,不然会报 “找不到模块” 的错误。
3.2 第二步:搭建基础界面布局
界面需要包含 3 个部分:
- 控制按钮:一个 “切换字幕显示” 按钮,控制字幕区是否显示;
- 音频按钮:一个 “播放音频并生成字幕” 按钮,触发音频读取和字幕生成;
- 字幕区域:AICaptionComponent 组件,用来显示字幕。
代码示例:基础布局
// 先定义日志工具类,方便后续调试
const TAG = 'AI_CAPTION_DEMO';
class Logger {
static info(...msg: string[]) {
hilog.info(0x0000, TAG, `[INFO] ${msg.join()}`);
}
static error(...msg: string[]) {
hilog.error(0x0000, TAG, `[ERROR] ${msg.join()}`);
}
}
@Entry
@Component
struct AICaptionDemo {
// 1. 声明AI字幕相关的变量
private captionOption?: AICaptionOptions;
private controller = new AICaptionController();
@State isShown: boolean = false; // 控制字幕显示/隐藏
private isReading: boolean = false; // 控制音频是否正在读取,避免重复触发
// 2. 初始化字幕配置(在组件准备显示前执行)
aboutToAppear(): void {
this.captionOption = {
initialOpacity: 1,
onPrepared: () => {
Logger.info('字幕组件准备完成,等待音频数据');
},
onError: (error: BusinessError) => {
Logger.error(`字幕错误:code=${error.code}, msg=${error.message}`);
}
};
}
// 3. 搭建界面
build() {
Column({ space: 30 }) {
// 标题
Text('鸿蒙AI字幕演示')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#333333');
// 第一组按钮:控制字幕显示/隐藏
Button(`切换字幕显示:${this.isShown ? '隐藏' : '显示'}`)
.width(250)
.height(45)
.fontSize(16)
.backgroundColor('#007AFF')
.fontColor(Color.White)
.onClick(() => {
this.isShown = !this.isShown;
Logger.info(`字幕显示状态切换为:${this.isShown ? '显示' : '隐藏'}`);
});
// 第二组按钮:读取音频并生成字幕
Button('播放音频并生成字幕')
.width(250)
.height(45)
.fontSize(16)
.backgroundColor('#34C759')
.fontColor(Color.White)
.onClick(() => {
// 避免重复点击:如果正在读取音频,就不执行
if (!this.isReading) {
this.readLocalPcmAudio(); // 这个函数后面第三步实现,用来读本地音频
} else {
Logger.info('正在读取音频中,请勿重复点击');
}
});
// 分割线:区分按钮和字幕区
Divider()
.width('90%')
.color('#E5E5E5');
// 字幕显示区域
Text('字幕区域:')
.fontSize(14)
.fontColor('#666666')
.alignSelf(ItemAlign.Start)
.marginLeft('5%');
AICaptionComponent({
isShown: this.isShown,
controller: this.controller,
options: this.captionOption
})
.width('90%')
.height(120)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.padding(10);
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#FAFAFA');
}
}
这段代码运行后,界面会有两个按钮和一个字幕区域。点击 “切换字幕显示” 按钮,能看到字幕区的背景色显示 / 隐藏;点击 “播放音频并生成字幕” 按钮,目前还没效果,因为还没实现读取音频的函数。
3.3 第三步:实现本地音频读取功能
AI 字幕需要接收音频数据才能生成文字,这里我们用 “读取本地 PCM 音频文件” 的方式来模拟(PCM 是鸿蒙 AI 字幕支持的常用音频格式)。步骤如下:
- 把 PCM 音频文件放到项目的
resources/base/media目录下(比如文件名叫testAudio.pcm); - 通过
resourceManager读取文件,转成 Uint8Array 格式; - 把音频数据按固定大小分割(比如每次 640 字节),模拟实时音频流;
- 调用控制器的
writeAudio()方法,把分割后的音频数据传给字幕组件。
代码示例:读取本地 PCM 音频并传递
// 在AICaptionDemo组件类中,添加readLocalPcmAudio函数
async readLocalPcmAudio() {
this.isReading = true;
Logger.info('开始读取本地PCM音频文件');
try {
// 1. 获取资源管理器,用来读本地文件
const resourceManager: ResourceManager | undefined = this.getUIContext()?.getHostContext()?.resourceManager;
if (!resourceManager) {
Logger.error('获取资源管理器失败,无法读取音频文件');
this.isReading = false;
return;
}
// 2. 读取本地PCM文件(注意:文件名要和resources目录下的一致,不带后缀)
// 比如文件是testAudio.pcm,这里就写$r('app.media.testAudio')
const fileData: Uint8Array | undefined = await resourceManager.getMediaContent($r('app.media.testAudio'));
if (!fileData) {
Logger.error('读取音频文件失败,可能文件不存在或路径错误');
this.isReading = false;
return;
}
// 3. 配置音频分割参数:每次传640字节,模拟实时流
const bufferSize = 640; // 每次传递的音频数据大小,根据实际音频格式调整
const totalLength = fileData.byteLength; // 音频文件总字节数
let currentOffset = 0; // 当前读取到的位置
const startTime = new Date().getTime(); // 记录开始时间,计算播放时长
Logger.info(`音频文件总大小:${totalLength}字节,每次传递${bufferSize}字节`);
// 4. 循环分割音频数据,逐帧传递给字幕组件
while (currentOffset < totalLength) {
// 计算当前帧的结束位置
const endOffset = Math.min(currentOffset + bufferSize, totalLength);
// 分割音频数据:从currentOffset到endOffset
const audioFrame = fileData.slice(currentOffset, endOffset);
// 5. 把分割后的音频数据包装成AudioData格式
const audioData: AudioData = {
data: audioFrame
};
// 6. 通过控制器传递音频数据给字幕组件
if (this.controller) {
this.controller.writeAudio(audioData);
Logger.info(`传递音频帧:起始位置${currentOffset},大小${audioFrame.byteLength}字节`);
}
// 更新当前读取位置
currentOffset = endOffset;
// 7. 模拟实时播放:让每次传递间隔一段时间(关键!不然读太快,字幕跟不上)
// 计算等待时间:根据音频采样率,这里按32字节/毫秒估算(可根据实际音频调整)
const waitTime = bufferSize / 32;
await this.sleep(waitTime);
}
// 8. 音频传递完成,计算总时长
const totalTime = new Date().getTime() - startTime;
Logger.info(`音频传递完成!总时长:${totalTime}毫秒`);
} catch (error) {
// 捕获异常,避免程序崩溃
Logger.error(`读取音频时出错:${JSON.stringify(error)}`);
} finally {
// 无论成功还是失败,都要把isReading设为false
this.isReading = false;
}
}
// 辅助函数:实现延迟等待(模拟实时音频流)
sleep(time: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, time));
}
这里有几个关键注意点:
- 音频文件路径:必须放在
resources/base/media目录下,且读取时用$r('app.media.文件名')(不带后缀),比如testAudio.pcm就写$r('app.media.testAudio'); - bufferSize 大小:不是固定的,要根据音频的采样率调整,比如 8kHz 采样率的 PCM 音频,每次传 640 字节,对应播放时间是(640 字节 / 16 位 / 样本 / 1 通道) / 8000Hz = 0.02 秒,也就是 20 毫秒,符合实时流的要求;
- sleep 等待:必须加等待时间,不然代码会一瞬间把所有音频数据传完,字幕组件来不及处理,导致显示异常。
3.4 第四步:测试和调试
代码写好后,需要测试是否能正常生成字幕。测试步骤如下:
- 确保 PCM 音频文件正确放在
resources目录下,且文件名和代码中的一致; - 运行项目,点击 “切换字幕显示” 按钮,让字幕区域显示;
- 点击 “播放音频并生成字幕” 按钮,观察日志和界面;
- 如果字幕正常显示,说明成功;如果没显示,查看日志中的错误信息(比如文件路径错、音频格式不对)。
常见问题及解决方法:
- 问题 1:日志显示 “读取音频文件失败”—— 检查文件是否在
resources/base/media目录下,文件名是否和代码中的一致; - 问题 2:日志显示 “字幕错误:code=1002”—— 可能是控制器未初始化,检查
controller是否在组件中正确声明; - 问题 3:音频传完了但没字幕 —— 检查 PCM 文件格式是否正确(AI 字幕通常支持 8kHz 采样率、16 位深度、单通道的 PCM)。
四、完整实战案例:可直接运行的 ArkTS 代码
把前面的步骤整合起来,这里给一个完整的可运行代码。你只需要做两件事:
- 把 PCM 音频文件(比如
testAudio.pcm)放到resources/base/media目录; - 把代码中的
$r('app.media.testAudio')改成你的文件名; - 复制代码到项目的
src/main/ets/pages目录下(比如文件名叫AICaptionPage.ets)。
完整代码:AICaptionPage.ets
// 1. 导入所有必要的包
import {
AICaptionComponent,
AICaptionController,
AICaptionOptions,
AudioData,
BusinessError
} from '@kit.SpeechKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { ResourceManager } from '@kit.AbilityKit';
import { FontWeight, ItemAlign, Color } from '@ohos.ui.components';
// 2. 日志工具类:方便调试,打印关键信息
const TAG = 'AI_CAPTION_FULL_DEMO';
class Logger {
static info(...msg: string[]) {
hilog.info(0x0000, TAG, `[AICaptionDemo-INFO] ${msg.join()}`);
}
static error(...msg: string[]) {
hilog.error(0x0000, TAG, `[AICaptionDemo-ERROR] ${msg.join()}`);
}
}
// 3. 主组件:包含界面和所有逻辑
@Entry
@Component
struct AICaptionFullDemo {
// AI字幕核心变量
private captionOption?: AICaptionOptions; // 字幕初始化配置
private controller = new AICaptionController(); // 字幕控制器
@State isCaptionShown: boolean = false; // 控制字幕区域显示/隐藏
private isAudioReading: boolean = false; // 控制音频是否正在读取
@State audioStatusText: string = '未播放'; // 显示音频播放状态
// 4. 组件准备显示时,初始化字幕配置
aboutToAppear(): void {
Logger.info('组件准备显示,初始化AI字幕配置');
this.initCaptionOptions();
}
// 5. 初始化字幕配置的函数(抽成单独函数,代码更清晰)
private initCaptionOptions() {
this.captionOption = {
// 字幕区域透明度:1表示完全不透明,0表示完全透明
initialOpacity: 1,
// 字幕组件准备完成的回调
onPrepared: () => {
Logger.info('AI字幕组件已准备就绪,可以接收音频数据');
},
// 字幕组件出错时的回调(关键!用来定位问题)
onError: (error: BusinessError) => {
Logger.error(`AI字幕出错:错误码=${error.code},错误信息=${error.message}`);
// 显示错误信息给用户
this.audioStatusText = `字幕出错:${error.message}`;
}
};
}
// 6. 读取本地PCM音频文件并传递给字幕组件
private async readAndSendLocalAudio() {
// 防止重复点击
if (this.isAudioReading) {
Logger.info('音频正在读取中,请勿重复触发');
return;
}
this.isAudioReading = true;
this.audioStatusText = '正在读取音频...';
Logger.info('开始读取本地PCM音频文件');
try {
// 6.1 获取资源管理器:用来读取resources目录下的文件
const resManager: ResourceManager | undefined = this.getUIContext()?.getHostContext()?.resourceManager;
if (!resManager) {
throw new Error('获取资源管理器失败,无法读取音频文件');
}
// 6.2 读取本地PCM音频文件(注意:这里的文件名要和resources目录下的一致)
// 例如:文件是testAudio.pcm,就写$r('app.media.testAudio')
const audioFileData: Uint8Array | undefined = await resManager.getMediaContent($r('app.media.testAudio'));
if (!audioFileData) {
throw new Error('读取音频文件失败,文件可能不存在或路径错误');
}
// 6.3 配置音频传递参数
const bufferSize = 640; // 每次传递的音频数据大小(根据音频格式调整)
const totalAudioLength = audioFileData.byteLength; // 音频文件总字节数
let currentReadOffset = 0; // 当前读取到的音频位置
const audioStartTime = new Date().getTime(); // 记录开始时间
Logger.info(`音频文件信息:总大小=${totalAudioLength}字节,每次传递=${bufferSize}字节`);
this.audioStatusText = `正在播放音频(总大小:${(totalAudioLength/1024).toFixed(2)}KB)`;
// 6.4 循环分割音频数据,逐帧传递给字幕组件
while (currentReadOffset < totalAudioLength) {
// 计算当前帧的结束位置(避免最后一帧超出总长度)
const frameEndOffset = Math.min(currentReadOffset + bufferSize, totalAudioLength);
// 分割当前帧的音频数据
const currentAudioFrame = audioFileData.slice(currentReadOffset, frameEndOffset);
// 6.5 包装音频数据为AI字幕能识别的格式(AudioData)
const audioData: AudioData = {
data: currentAudioFrame // 核心:音频的二进制数据
};
// 6.6 通过控制器传递音频数据给字幕组件
if (this.controller) {
this.controller.writeAudio(audioData);
Logger.info(`传递音频帧:起始位置=${currentReadOffset},帧大小=${currentAudioFrame.byteLength}字节`);
} else {
throw new Error('字幕控制器未初始化,无法传递音频数据');
}
// 更新当前读取位置
currentReadOffset = frameEndOffset;
// 6.7 模拟实时音频流:每次传递后等待一段时间(关键!)
// 等待时间计算:根据音频采样率,这里按32字节/毫秒估算(可根据实际音频调整)
const waitTime = bufferSize / 32;
await this.sleep(waitTime);
}
// 6.8 音频传递完成
const totalPlayTime = (new Date().getTime() - audioStartTime) / 1000;
Logger.info(`音频传递完成!总播放时间:${totalPlayTime.toFixed(2)}秒`);
this.audioStatusText = `播放完成!总时长:${totalPlayTime.toFixed(2)}秒`;
} catch (error) {
// 6.9 捕获异常,处理错误
const errorMsg = error instanceof Error ? error.message : '未知错误';
Logger.error(`读取音频时出错:${errorMsg}`);
this.audioStatusText = `播放失败:${errorMsg}`;
} finally {
// 6.10 无论成功还是失败,都要重置状态
this.isAudioReading = false;
}
}
// 7. 辅助函数:实现延迟等待(Promise版)
private sleep(time: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, time));
}
// 8. 搭建完整界面
build() {
Column({ space: 35 }) {
// 页面标题
Text('鸿蒙AI字幕实战演示')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor('#222222')
.marginTop(10);
// 第一行:字幕显示控制按钮
Column({ space: 10 }) {
Button(`切换字幕显示:${this.isCaptionShown ? '隐藏' : '显示'}`)
.width(280)
.height(50)
.fontSize(17)
.backgroundColor('#007AFF')
.fontColor(Color.White)
.borderRadius(8)
.onClick(() => {
this.isCaptionShown = !this.isCaptionShown;
Logger.info(`字幕显示状态切换为:${this.isCaptionShown ? '显示' : '隐藏'}`);
});
Text(`当前字幕状态:${this.isCaptionShown ? '已显示' : '已隐藏'}`)
.fontSize(14)
.fontColor('#666666');
}
// 第二行:音频播放控制按钮和状态显示
Column({ space: 10 }) {
Button('播放本地音频并生成字幕')
.width(280)
.height(50)
.fontSize(17)
.backgroundColor('#34C759')
.fontColor(Color.White)
.borderRadius(8)
.onClick(() => {
// 点击时先检查字幕是否显示
if (!this.isCaptionShown) {
this.audioStatusText = '请先显示字幕区域再播放音频';
Logger.info('用户未显示字幕区域,提示先显示');
return;
}
// 触发音频读取和传递
this.readAndSendLocalAudio();
});
Text(`音频状态:${this.audioStatusText}`)
.fontSize(14)
.fontColor('#666666');
}
// 分割线:区分控制区和字幕区
Divider()
.width('92%')
.color('#E5E5E5')
.marginTop(10);
// 第三部分:字幕显示区域
Column({ space: 12 }) {
Text('AI字幕显示区:')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
.alignSelf(ItemAlign.Start)
.marginLeft('4%');
// AI字幕组件(核心显示区域)
AICaptionComponent({
isShown: this.isCaptionShown,
controller: this.controller,
options: this.captionOption
})
.width('92%')
.height(150)
.backgroundColor('#F5F5F5')
.borderRadius(10)
.padding(15)
.shadow({ radius: 3, color: '#00000010', offsetX: 0, offsetY: 2 });
Text('提示:字幕会实时显示音频对应的文字内容')
.fontSize(12)
.fontColor('#999999')
.alignSelf(ItemAlign.Start)
.marginLeft('4%');
}
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#FAFAFA');
}
}
五、进阶技巧:让 AI 字幕功能更完善
基础功能实现后,还可以加一些进阶特性,让用户体验更好。这里介绍 3 个常用技巧,每个都给代码片段。
5.1 支持多语言字幕
AI 字幕可以生成不同语言的字幕,只需要在AICaptionOptions中配置sourceLanguage(源音频语言)和targetLanguage(目标字幕语言)。
代码示例:配置多语言
// 在initCaptionOptions函数中,补充语言配置
private initCaptionOptions() {
this.captionOption = {
initialOpacity: 1,
// 源音频语言:比如中文(zh-CN)、英文(en-US)
sourceLanguage: 'zh-CN',
// 目标字幕语言:比如要生成英文字幕,就写'en-US'
targetLanguage: 'en-US',
onPrepared: () => {
Logger.info('多语言字幕组件准备完成');
},
onError: (error: BusinessError) => {
Logger.error(`多语言字幕出错:${error.message}`);
}
};
}
支持的语言列表可以参考鸿蒙官方文档,常见的有:
- 中文:zh-CN;
- 英文:en-US;
- 日语:ja-JP;
- 韩语:ko-KR。
5.2 字幕样式自定义
虽然 AICaptionComponent 本身不直接支持修改文字大小、颜色,但可以通过 “外层容器 + 样式覆盖” 的方式实现。
代码示例:自定义字幕文字样式
// 在build函数的字幕区域,给AICaptionComponent加外层容器,并设置样式
Column() {
AICaptionComponent({
isShown: this.isCaptionShown,
controller: this.controller,
options: this.captionOption
})
.width('100%')
.height('100%')
// 覆盖字幕文字样式:通过style属性
.style({
fontSize: 16, // 文字大小
color: '#333333', // 文字颜色
fontWeight: 'medium',// 文字粗细
lineHeight: 24 // 行高
});
}
.width('92%')
.height(150)
.backgroundColor('#F5F5F5')
.borderRadius(10)
.padding(15);
注意:不同鸿蒙版本对 style 属性的支持可能不同,如果样式不生效,可以查看对应版本的官方文档。
5.3 实时音频流支持(比如麦克风输入)
除了读取本地音频,还可以支持实时麦克风输入,让用户说话时实时生成字幕。核心是通过AudioRecorder获取麦克风音频流,再传给字幕控制器。
代码示例:麦克风实时音频输入
import { AudioRecorder, AudioRecorderConfig } from '@kit.MultimediaKit';
// 在组件类中,声明音频 recorder
private audioRecorder?: AudioRecorder;
// 初始化麦克风录音
private initMicrophoneRecorder() {
// 配置录音参数:PCM格式,8kHz采样率
const recorderConfig: AudioRecorderConfig = {
audioSourceType: 1, // 1表示麦克风
outputFormat: 3, // 3表示PCM格式
audioEncoder: 1, // 1表示PCM编码
sampleRate: 8000, // 采样率8kHz(AI字幕常用)
channelCount: 1, // 单通道
bitRate: 128000 // 比特率
};
// 创建音频 recorder
this.audioRecorder = new AudioRecorder();
this.audioRecorder.on('dataAvailable', (buffer: ArrayBuffer) => {
// 录音数据实时回调,转成Uint8Array
const audioData = new Uint8Array(buffer);
// 传递给字幕控制器
if (this.controller) {
this.controller.writeAudio({ data: audioData });
}
});
// 开始录音
this.audioRecorder.start(recorderConfig)
.then(() => {
Logger.info('麦克风录音开始,实时生成字幕');
})
.catch(error => {
Logger.error(`录音开始失败:${error.message}`);
});
}
// 停止录音
private stopMicrophoneRecorder() {
if (this.audioRecorder) {
this.audioRecorder.stop()
.then(() => {
Logger.info('麦克风录音停止');
})
.catch(error => {
Logger.error(`录音停止失败:${error.message}`);
});
}
}
使用麦克风功能时,需要在module.json5中添加录音权限:
{
"module": {
"abilities": [
// ...其他配置
],
"requestPermissions": [
{
"name": "ohos.permission.MICROPHONE",
"reason": "需要麦克风权限以实现实时字幕",
"usedScene": {
"ability": [".AICaptionFullDemo"],
"when": "always"
}
}
]
}
}
六、常见问题汇总:避坑指南
开发过程中难免会遇到问题,这里整理了 8 个常见问题,帮你快速解决。
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 点击播放按钮,日志显示 “读取音频文件失败” | 1. 文件没放在 resources/base/media 目录;2. 文件名写错;3. 文件格式不是 PCM | 1. 检查文件路径;2. 确认代码中的 $r ('app.media. 文件名 ') 和实际一致;3. 用工具将音频转成 PCM 格式(比如用 Audacity 软件) |
| 音频传递完成,但字幕没显示 | 1. 音频格式不对(比如采样率不是 8kHz);2. 字幕区域没显示(isCaptionShown 为 false);3. 控制器没关联 | 1. 确认 PCM 文件是 8kHz 采样率、16 位深度、单通道;2. 点击 “切换字幕显示” 按钮,让字幕区显示;3. 检查 AICaptionComponent 的 controller 参数是否正确 |
| 日志显示 “字幕错误:code=2003” | 权限不够,比如读取资源的权限 | 1. 检查 module.json5 中的权限配置;2. 重启项目或模拟器 |
| 麦克风录音没反应 | 1. 没加 MICROPHONE 权限;2. 用户拒绝了权限;3. 录音参数配置错误 | 1. 在 module.json5 中添加权限;2. 代码中请求权限(用 requestPermissions 接口);3. 确认录音参数中的采样率、格式正确 |
| 字幕显示乱码 | 1. 源语言或目标语言配置错误;2. 音频数据损坏 | 1. 检查 sourceLanguage 和 targetLanguage 是否正确;2. 更换一个正常的 PCM 音频文件 |
| 音频播放时,字幕卡顿 | 1. sleep 等待时间不对;2. bufferSize 太大 | 1. 调整 waitTime,比如按 bufferSize/32 计算;2. 减小 bufferSize(比如从 640 改成 320) |
| 组件初始化时,controller 为 undefined | 1. controller 没在组件中声明;2. 声明时没 new | 1. 确保代码中有 private controller = new AICaptionController (); 2. 不要只声明不初始化(比如 private controller?: AICaptionController; 这样会是 undefined) |
| 运行项目时,报 “找不到模块 @kit.SpeechKit” | 1. 鸿蒙 SDK 版本太低,不支持 SpeechKit;2. 项目配置中没引入 SpeechKit | 1. 升级鸿蒙 SDK 到 5.0 及以上版本;2. 在项目的 oh-package.json5 中添加 SpeechKit 依赖 |
七、总结
通过本文的学习,你已经掌握了鸿蒙 AI 字幕控件的完整开发流程:从理解适用场景、核心接口,到一步步写代码实现基础功能,再到进阶特性和问题解决。
关键要点回顾:
- 3 个核心接口:AICaptionComponent(显示)、AICaptionOptions(配置)、AICaptionController(控制);
- 开发四步走:导入包→搭布局→读音频→传数据;
- 调试关键:加 onError 回调和日志,快速定位问题;
- 进阶方向:多语言、自定义样式、实时麦克风输入。
更多推荐
所有评论(0)