在鸿蒙应用开发中,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 字幕支持的常用音频格式)。步骤如下:

  1. 把 PCM 音频文件放到项目的resources/base/media目录下(比如文件名叫testAudio.pcm);
  2. 通过resourceManager读取文件,转成 Uint8Array 格式;
  3. 把音频数据按固定大小分割(比如每次 640 字节),模拟实时音频流;
  4. 调用控制器的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 第四步:测试和调试

代码写好后,需要测试是否能正常生成字幕。测试步骤如下:

  1. 确保 PCM 音频文件正确放在resources目录下,且文件名和代码中的一致;
  2. 运行项目,点击 “切换字幕显示” 按钮,让字幕区域显示;
  3. 点击 “播放音频并生成字幕” 按钮,观察日志和界面;
  4. 如果字幕正常显示,说明成功;如果没显示,查看日志中的错误信息(比如文件路径错、音频格式不对)。

常见问题及解决方法:

  • 问题 1:日志显示 “读取音频文件失败”—— 检查文件是否在resources/base/media目录下,文件名是否和代码中的一致;
  • 问题 2:日志显示 “字幕错误:code=1002”—— 可能是控制器未初始化,检查controller是否在组件中正确声明;
  • 问题 3:音频传完了但没字幕 —— 检查 PCM 文件格式是否正确(AI 字幕通常支持 8kHz 采样率、16 位深度、单通道的 PCM)。

四、完整实战案例:可直接运行的 ArkTS 代码

把前面的步骤整合起来,这里给一个完整的可运行代码。你只需要做两件事:

  1. 把 PCM 音频文件(比如testAudio.pcm)放到resources/base/media目录;
  2. 把代码中的$r('app.media.testAudio')改成你的文件名;
  3. 复制代码到项目的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 字幕控件的完整开发流程:从理解适用场景、核心接口,到一步步写代码实现基础功能,再到进阶特性和问题解决。

关键要点回顾:

  1. 3 个核心接口:AICaptionComponent(显示)、AICaptionOptions(配置)、AICaptionController(控制);
  2. 开发四步走:导入包→搭布局→读音频→传数据;
  3. 调试关键:加 onError 回调和日志,快速定位问题;
  4. 进阶方向:多语言、自定义样式、实时麦克风输入。
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐