HarmonyOS 6实战(源码教学篇)--- Speech Kit 新特性【仿某云音乐实现并集成AI字幕】
- [HarmonyOS 音乐播放器集成系统级AI字幕完整教程](#HarmonyOS 音乐播放器集成系统级AI字幕完整教程)
-
- 前言
- 一、案例结构
- 二、核心概念
-
- [1. AICaptionComponent(AI字幕组件)](#1. AICaptionComponent(AI字幕组件))
- [2. AICaptionController(字幕控制器)](#2. AICaptionController(字幕控制器))
- [3. AudioData(音频数据)](#3. AudioData(音频数据))
- [4. AICaptionOptions(配置选项)](#4. AICaptionOptions(配置选项))
- [5. 音频格式要求](#5. 音频格式要求)
- 三、实现AI字幕组件
-
- [1. 导入必要的模块](#1. 导入必要的模块)
- [2. 日志工具类](#2. 日志工具类)
- [3. 主组件结构](#3. 主组件结构)
- [4. 组件初始化](#4. 组件初始化)
- [5. 读取并处理音频数据(核心逻辑)](#5. 读取并处理音频数据(核心逻辑))
-
- [a. 资源读取](#a. 资源读取)
- [b. 分块处理](#b. 分块处理)
- [c. 音频数据格式](#c. 音频数据格式)
- [6. UI布局](#6. UI布局)
- [7. 运行调试](#7. 运行调试)
- 四:集成到播放器页面
-
- [1 实现步骤](#1 实现步骤)
- [2 完整流程](#2 完整流程)
- 五:项目配置与权限
-
- [1 完整项目结构](#1 完整项目结构)
- [2 权限配置](#2 权限配置)
- 六:扩展功能
-
- [1 字幕样式自定义](#1 字幕样式自定义)
- [2 字幕与歌词同步显示](#2 字幕与歌词同步显示)
- 七、常见问题
-
- [Q1: 字幕不显示?](#Q1: 字幕不显示?)
- [Q2: 识别不准确?](#Q2: 识别不准确?)
- [Q3: 如何支持实时麦克风输入?](#Q3: 如何支持实时麦克风输入?)
- [Q4: 如何自定义字幕样式?](#Q4: 如何自定义字幕样式?)
- 参考资源
- 结语
HarmonyOS 音乐播放器集成系统级AI字幕完整教程
前言
大家好!我是你们的老朋友木斯佳,是华为云 HDE 认证专家和 OpenTiny 开源社区的布道师。熟悉我们的小伙伴们已经跟随着之前的分享,一步步实现了 HarmonyOS 音频播放的小案例,并体验了 AVSession Kit 的强大管控能力。
今天,我们将继续深入 HarmonyOS 6 的精彩世界,聚焦另一个重磅基础能力------ Speech Kit 的新特性。你是否曾想象过,在聆听音乐时,系统能实时"听懂"歌曲内容并显示为动态字幕?本篇文章将手把手教你如何利用 HarmonyOS 6 提供的 AI 字幕(AICaption) 能力,在你的音乐播放器中实现并集成这一酷炫功能,仿照某云音乐,打造一个集成系统级 AI 字幕的智能播放器。
让我们即刻启程,解锁 HarmonyOS 语音识别的奥秘,为你的应用增添智能"耳朵"与"字幕"!

一、案例结构
应用架构
音乐播放器 + AI字幕集成架构
├── entry (UI层)
│ ├── 播放器界面
│ ├── AI字幕显示区
│ └── 控制交互
├── MediaService (服务层)
│ ├── AudioRendererController (音频播放控制)
│ ├── AVSessionController (媒体会话管理)
│ └── BackgroundUtil (后台任务)
└── AI字幕集成模块
├── AICaptionController (字幕控制器)
技术栈
- 开发语言: ArkTS (TypeScript)
- 系统要求: HarmonyOS 5.0.0 Release及以上
- 开发工具: DevEco Studio 6.0.0 Release及以上
- 推荐版本: HarmonyOS 5.1.0(18)及以上
- 核心API :
- AudioRenderer (音频播放)
- AVSession (媒体会话)
- AICaptionComponent (AI字幕)
- BackgroundTask (后台任务)
二、核心概念

1. AICaptionComponent(AI字幕组件)
这是HarmonyOS提供的系统级字幕组件,能够:
- 实时显示语音识别结果
- 自动处理字幕样式和布局
- 支持透明度调节
- 提供生命周期回调
2. AICaptionController(字幕控制器)
用于控制字幕组件的行为:
writeAudio(): 向组件写入音频数据- 管理音频流的输入
3. AudioData(音频数据)
封装音频数据的结构:
typescript
interface AudioData {
data: Uint8Array; // PCM格式的音频数据
}
4. AICaptionOptions(配置选项)
typescript
interface AICaptionOptions {
initialOpacity?: number; // 初始透明度 (0-1)
onPrepared?: () => void; // 准备完成回调
onError?: (error: BusinessError) => void; // 错误回调
}
5. 音频格式要求
AI字幕组件对音频格式有严格要求 ,通过 getAudioInfo() 可以获取:
typescript
const audioInfo = controller.getAudioInfo();
// 返回:
// {
// audioType: "pcm", // 仅支持PCM格式
// sampleRate: 16000, // 仅支持16kHz采样率
// soundChannel: 1, // 仅支持单声道
// sampleBit: 16 // 仅支持16位采样
// }
| 参数 | 要求值 | 说明 |
|---|---|---|
| audioType | "pcm" | 仅支持PCM格式,其他格式会失败 |
| sampleRate | 16000 | 仅支持16kHz采样率 |
| soundChannel | 1 | 仅支持单声道 |
| sampleBit | 16 | 仅支持16位采样 |
| 数据包大小 | 640 或 1280 字节 | 其他大小会导致识别失败 |
| 调用间隔 | 20ms 或 40ms | 640字节→20ms, 1280字节→40ms |
三、实现AI字幕组件

音频文件 → 分块读取 → writeAudio() → AI识别 → 字幕显示
1. 导入必要的模块
typescript
import {
AICaptionComponent, // AI字幕组件
AICaptionOptions, // 配置选项接口
AICaptionController, // 字幕控制器
AudioData // 音频数据接口
} from '@kit.SpeechKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
2. 日志工具类
typescript
const TAG = 'AI_CAPTION_DEMO'
class Logger {
static info(...msg: string[]) {
hilog.info(0x0000, TAG, msg.join())
}
static error(...msg: string[]) {
hilog.error(0x0000, TAG, msg.join())
}
}
说明:
- 使用hilog进行日志记录,便于调试
- 0x0000是日志域ID,TAG用于过滤日志
3. 主组件结构
typescript
@Entry
@Component
struct Index {
// 字幕配置选项
private captionOption?: AICaptionOptions;
// 字幕控制器实例
private controller: AICaptionController = new AICaptionController();
// 字幕显示状态(响应式)
@State isShown: boolean = false;
// 音频读取状态
isReading: boolean = false;
// ... 方法实现
}
关键点:
@Entry: 标记为页面入口组件@Component: 声明为自定义组件@State: 状态变量,变化时会触发UI更新AICaptionController: 必须在组件中创建实例
4. 组件初始化
typescript
aboutToAppear(): void {
// 初始化字幕配置
this.captionOption = {
initialOpacity: 1, // 设置初始透明度为完全不透明
// 字幕组件准备完成的回调
onPrepared: () => {
Logger.info('onPrepared')
},
// 错误处理回调
onError: (error: BusinessError) => {
Logger.error(`AICaption component error. Error code: ${error.code}, message: ${error.message}`)
}
}
}
说明:
aboutToAppear(): 组件即将出现时的生命周期回调initialOpacity: 范围0-1,1表示完全不透明- 错误处理很重要,可以捕获权限、资源等问题
5. 读取并处理音频数据(核心逻辑)
typescript
async readPcmAudio() {
this.isReading = true;
// 1. 从资源管理器读取PCM音频文件
const fileData: Uint8Array | undefined =
await this.getUIContext()?.getHostContext()?.resourceManager.getMediaContent($r('app.media.chineseAudio').id);
if (fileData === undefined){
return;
}
// 2. 设置音频数据分块参数
const bufferSize = 640; // 每次读取640字节
const byteLength = fileData.byteLength;
let offset = 0;
Logger.info(`Pcm data total bytes: ${byteLength.toString()}`)
let startTime = new Date().getTime();
// 3. 循环分块发送音频数据
while (offset < byteLength) {
// 计算下一个读取位置
let nextOffset = offset + bufferSize
if (offset > byteLength) {
this.isReading = false;
return
}
// 切片获取音频数据
const arrayBuffer = fileData.buffer.slice(offset, nextOffset);
let data = new Uint8Array(arrayBuffer);
// 封装为AudioData格式
const audioData: AudioData = {
data: data
}
// 4. 写入音频数据到字幕组件
if (this.controller) {
this.controller.writeAudio(audioData)
}
offset = offset + bufferSize;
// 5. 模拟实时音频流,等待一段时间
const waitTime = bufferSize / 32 // 根据采样率计算等待时间
await this.sleep(waitTime)
}
let endTime = new Date().getTime()
this.isReading = false;
Logger.info(`Audio play time: ${JSON.stringify(endTime - startTime)}`)
}
// 辅助睡眠函数
sleep(time: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, time))
}
重点解析:
a. 资源读取
typescript
this.getUIContext()?.getHostContext()?.resourceManager.getMediaContent($r('app.media.chineseAudio').id)
getUIContext(): 获取UI上下文getHostContext(): 获取宿主上下文resourceManager: 资源管理器$r('app.media.chineseAudio'): 资源引用语法
b. 分块处理
- 为什么要分块? 模拟实时音频流,如果一次性发送所有数据,识别效果会不准确
- bufferSize = 640: 根据PCM音频采样率计算(16kHz采样率,16bit,单声道)
- waitTime计算 :
bufferSize / 32确保音频按实际播放速度发送
c. 音频数据格式
- 必须是PCM格式(未压缩的原始音频)
- 推荐参数:16kHz采样率,16bit位深,单声道
6. UI布局
typescript
build() {
Column({ space: 20 }) {
// 按钮1:切换字幕显示状态
Button('Toggle subtitle display status:' + (this.isShown ? 'Show' : 'Hide'))
.backgroundColor('#B8BDA0')
.width(200)
.onClick(() => {
this.isShown = !this.isShown; // 切换显示状态
})
// 按钮2:读取音频
Button('Read PCM Audio')
.backgroundColor('#B8BDA0')
.width(200)
.onClick(() => {
if (!this.isReading) {
this.readPcmAudio() // 防止重复点击
}
})
Divider()
// AI字幕组件
AICaptionComponent({
isShown: this.isShown, // 控制显示/隐藏
controller: this.controller, // 传入控制器
options: this.captionOption // 传入配置选项
})
.width('100%')
.height(100)
Divider()
// 提示文本
if (this.isShown) {
Text('Above is the subtitle area.')
.fontColor(Color.White)
}
}
.width('100%')
.height('100%')
.padding(10)
.backgroundColor('#7A7D6A')
}
UI组件说明:
Column: 垂直布局容器space: 20: 子组件间距isShown: 控制字幕组件的显示状态controller: 必须传入,用于后续控制options: 可选配置参数
7. 运行调试
模拟器部分功能不支持,真机验证可以正常运行。

四:集成到播放器页面
用户设置 isShown = true
↓
系统自动捕获 AudioRenderer 输出
↓
系统自动转换格式(48kHz→16kHz, 双声道→单声道)
↓
系统自动进行语音识别
↓
实时显示字幕
1 实现步骤
步骤1:整合AI字幕组件
创建文件:entry/src/main/ets/components/AICaptionArea.ets
步骤2:集成到播放器页面(需真机验证)
修改 entry/src/main/ets/pages/PlayerPage.ets:
typescript
import { AICaptionArea } from '../components/AICaptionArea';
import { PlayerInfoComponent } from '../components/PlayerInfoComponent';
import { ControlAreaComponent } from '../components/ControlAreaComponent';
import { AudioRendererController } from '../../MediaService/src/main/ets/utils/AudioRendererController';
@Component
export struct PlayerPage {
@StorageLink('isShowPlay') isShowPlay: boolean = false;
@State isCaptionShown: boolean = false;
aboutToAppear(): void {
// 初始化歌曲列表
AppStorage.setOrCreate('songList', songList);
// 创建音频控制器
AudioRendererController.getInstance();
}
aboutToDisappear(): void {
// 释放资源
AudioRendererController.getInstance().release();
}
build() {
NavDestination() {
Column() {
// 播放器信息区域
PlayerInfoComponent({ isShowPlay: this.isShowPlay })
.layoutWeight(1)
// 🌟 AI字幕区域 - 就这么简单!
AICaptionArea({ isCaptionShown: $isCaptionShown })
// 控制区域
ControlAreaComponent()
.height(200)
}
.width('100%')
.height('100%')
.backgroundColor('#1A1A1A')
}
.hideTitleBar(true)
}
}

2 完整流程
1. 用户点击"显示AI字幕"开关
↓
2. isCaptionShown 设置为 true
↓
3. AICaptionComponent 的 isShown 属性变为 true
↓
4. 系统自动开始捕获 AudioRenderer 的音频输出
↓
5. 系统自动进行格式转换(48kHz→16kHz, 双声道→单声道)
↓
6. 系统自动进行语音识别
↓
7. 实时显示字幕
五:项目配置与权限
1 完整项目结构
MusicPlayerWithAICaption/
├── AppScope/
│ └── app.json5 # 应用配置
├── entry/
│ ├── src/main/
│ │ ├── ets/
│ │ │ ├── entryability/
│ │ │ │ └── EntryAbility.ets # 应用入口
│ │ │ ├── pages/
│ │ │ │ ├── Root.ets # 主页面
│ │ │ │ └── PlayerPage.ets # 播放器页面
│ │ │ ├── components/
│ │ │ │ ├── PlayerInfoComponent.ets # 播放器信息
│ │ │ │ ├── ControlAreaComponent.ets # 控制区
│ │ │ │ ├── AICaptionArea.ets # AI字幕
│ │ │ └── common/
│ │ │ └── utils/
│ │ ├── resources/
│ │ │ └── rawfile/
│ │ │ └── music/ # 音频文件
│ │ └── module.json5 # 模块配置
│ └── build-profile.json5
├── MediaService/ # 音频服务模块
│ └── src/main/ets/
│ ├── utils/
│ │ ├── AudioRendererController.ets # 音频播放控制
│ │ ├── AVSessionController.ets # 媒体会话
│ │ └── BackgroundUtil.ets # 后台任务
│ └── songdatacontroller/
│ └── SongData.ets # 歌曲数据
└── build-profile.json5
2 权限配置
在 entry/src/main/module.json5 中配置必要权限:
json5
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
// 权限申请
"requestPermissions": [
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
"reason": "$string:reason_background",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "always"
}
}
],
// Ability配置
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:ic_music_icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
// 后台模式配置
"backgroundModes": ["audioPlayback"],
"skills": [
{
"entities": ["entity.system.home"],
"actions": ["action.system.home"]
}
]
}
]
}
}
权限说明:
KEEP_BACKGROUND_RUNNING: 后台播放权限(必需)backgroundModes: ["audioPlayback"]: 声明音频播放后台模式(必需)
六:扩展功能
1 字幕样式自定义
虽然 AICaptionComponent 是系统组件,样式由系统控制,但可以通过 initialOpacity 调整透明度:
typescript
@Component
export struct AICaptionArea {
@State captionOpacity: number = 0.9;
@State showSettings: boolean = false;
build() {
Column() {
// 设置按钮
Button('设置')
.onClick(() => {
this.showSettings = !this.showSettings;
})
// 设置面板
if (this.showSettings) {
Column() {
Row() {
Text('透明度')
.fontSize(12)
.fontColor('#AAAAAA')
Slider({
value: this.captionOpacity * 100,
min: 0,
max: 100,
step: 10
})
.width('60%')
.onChange((value: number) => {
this.captionOpacity = value / 100;
// 重新初始化配置
this.initCaptionOptions();
})
Text(`${Math.round(this.captionOpacity * 100)}%`)
.fontSize(12)
.fontColor(Color.White)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(10)
}
.width('90%')
.backgroundColor('rgba(255, 255, 255, 0.1)')
.borderRadius(8)
}
// AI字幕组件
AICaptionComponent({
isShown: this.isCaptionShown,
controller: this.controller,
options: {
initialOpacity: this.captionOpacity,
onPrepared: () => { /* ... */ },
onError: (error) => { /* ... */ }
}
})
}
}
}
注意:
- Phone/Tablet: 透明度范围 [0, 1],连续值
- 2in1: 只有3个档位:0(全透明)、0.5(半透明)、1(不透明)
2 字幕与歌词同步显示
如果你想同时显示传统歌词和AI识别字幕:
typescript
@Component
export struct LyricWithCaptionArea {
@StorageLink('showAICaption') showCaption: boolean = false;
@StorageLink('showLyric') showLyric: boolean = true;
build() {
Column() {
// 传统歌词显示
if (this.showLyric) {
LyricsComponent()
.height(200)
}
Divider()
.color('#333333')
.margin({ top: 10, bottom: 10 })
// AI字幕显示
if (this.showCaption) {
AICaptionArea({ isCaptionShown: $showCaption })
}
// 控制按钮
Row({ space: 20 }) {
Button(this.showLyric ? '隐藏歌词' : '显示歌词')
.onClick(() => {
AppStorage.setOrCreate('showLyric', !this.showLyric);
})
Button(this.showCaption ? '隐藏AI字幕' : '显示AI字幕')
.onClick(() => {
AppStorage.setOrCreate('showAICaption', !this.showCaption);
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
.padding(10)
}
}
}
七、常见问题
Q1: 字幕不显示?
检查项:
isShown是否为truecontroller是否正确初始化- 音频数据是否正确写入
Q2: 识别不准确?
优化方案:
- 确保音频格式正确(PCM, 16kHz)
- 调整分块大小和发送频率
- 检查音频质量(噪音、音量)
Q3: 如何支持实时麦克风输入?
实现思路:
typescript
// 1. 使用AudioCapturer录音
// 2. 在onReadData回调中获取音频数据
// 3. 直接调用controller.writeAudio()
Q4: 如何自定义字幕样式?
说明:
AICaptionComponent是系统组件,样式由系统控制- 可通过
initialOpacity调整透明度 - 如需完全自定义,需要使用语音识别API自行实现
参考资源
官方文档
开发工具
社区资源
附录:错误码速查表
| 错误码 | 说明 | 解决方案 |
|---|---|---|
| 401 | 参数错误 | 检查数据包大小(640或1280字节) |
| 1012900010 | AI字幕服务繁忙 | 稍后重试 |
| 1012900011 | 控制器初始化失败 | 检查系统版本和权限 |
| 1012900012 | 音频识别失败 | 检查音频格式(PCM, 16kHz, 单声道, 16位) |
结语
通过本教程的实践,我们成功解锁了HarmonyOS 6中Speech Kit的AI字幕核心能力,打造出了一个集音频播放与智能识别于一体的现代化音乐应用。这一实现不仅展示了HarmonyOS在系统级AI能力开放上的强大实力,更为开发者提供了将前沿技术融入日常应用的新范式。
核心要点:
- 使用
AICaptionComponent系统组件 - 通过
AICaptionController控制音频输入 - PCM格式音频分块发送
- 响应式状态管理
代码改变世界,而创意定义未来。 让我们在HarmonyOS的生态中,继续探索、创造、连接,用技术为每一段旋律赋予文字,为每一次聆听增添理解。
祝你开发顺利,期待在HarmonyOS的星空中,看到属于你的那颗璀璨应用! 🎵🚀✨
有任何问题或分享,欢迎在评论区交流讨论! 👇