欢迎光临
我们一直在努力

HarmonyOS 6实战(源码教学篇)— Speech Kit 新特性【仿某云音乐实现并集成AI字幕】

HarmonyOS 6实战(源码教学篇)— Speech Kit 新特性【仿某云音乐实现并集成AI字幕】

  • HarmonyOS 音乐播放器集成系统级AI字幕完整教程
    • 前言
    • 一、案例结构
      • 应用架构
      • 技术栈
    • 二、核心概念
      • 1. AICaptionComponent(AI字幕组件)
      • 2. AICaptionController(字幕控制器)
      • 3. AudioData(音频数据)
      • 4. AICaptionOptions(配置选项)
      • 5. 音频格式要求
    • 三、实现AI字幕组件
      • 1. 导入必要的模块
      • 2. 日志工具类
      • 3. 主组件结构
      • 4. 组件初始化
      • 5. 读取并处理音频数据(核心逻辑)
        • a. 资源读取
        • b. 分块处理
        • c. 音频数据格式
      • 6. UI布局
      • 7. 运行调试
    • 四:集成到播放器页面
      • 1 实现步骤
        • 步骤1:整合AI字幕组件
        • 步骤2:集成到播放器页面(需真机验证)
      • 2 完整流程
    • 五:项目配置与权限
      • 1 完整项目结构
      • 2 权限配置
    • 六:扩展功能
      • 1 字幕样式自定义
      • 2 字幕与歌词同步显示
    • 七、常见问题
      • Q1: 字幕不显示?
      • Q2: 识别不准确?
      • Q3: 如何支持实时麦克风输入?
      • 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(音频数据)

封装音频数据的结构:

interface AudioData {
data: Uint8Array; // PCM格式的音频数据
}

4. AICaptionOptions(配置选项)

interface AICaptionOptions {
initialOpacity?: number; // 初始透明度 (0-1)
onPrepared?: () => void; // 准备完成回调
onError?: (error: BusinessError) => void; // 错误回调
}


5. 音频格式要求

AI字幕组件对音频格式有严格要求,通过 getAudioInfo() 可以获取:

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. 导入必要的模块

import {
AICaptionComponent, // AI字幕组件
AICaptionOptions, // 配置选项接口
AICaptionController, // 字幕控制器
AudioData // 音频数据接口
} from '@kit.SpeechKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

2. 日志工具类

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. 主组件结构

@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. 组件初始化

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. 读取并处理音频数据(核心逻辑)

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. 资源读取

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布局

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:

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 中配置必要权限:

{
"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 调整透明度:

@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识别字幕:

@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是否为true
  • controller是否正确初始化
  • 音频数据是否正确写入

Q2: 识别不准确?

优化方案:

  • 确保音频格式正确(PCM, 16kHz)
  • 调整分块大小和发送频率
  • 检查音频质量(噪音、音量)

Q3: 如何支持实时麦克风输入?

实现思路:

// 1. 使用AudioCapturer录音
// 2. 在onReadData回调中获取音频数据
// 3. 直接调用controller.writeAudio()

Q4: 如何自定义字幕样式?

说明:

  • AICaptionComponent是系统组件,样式由系统控制
  • 可通过initialOpacity调整透明度
  • 如需完全自定义,需要使用语音识别API自行实现

参考资源

官方文档

  • AICaptionComponent API参考
  • AudioRenderer API参考

开发工具

  • DevEco Studio下载
  • HarmonyOS SDK

社区资源

  • HarmonyOS开发者社区
  • HarmonyOS技术博客

附录:错误码速查表

错误码说明解决方案
401 参数错误 检查数据包大小(640或1280字节)
1012900010 AI字幕服务繁忙 稍后重试
1012900011 控制器初始化失败 检查系统版本和权限
1012900012 音频识别失败 检查音频格式(PCM, 16kHz, 单声道, 16位)

结语

通过本教程的实践,我们成功解锁了HarmonyOS 6中Speech Kit的AI字幕核心能力,打造出了一个集音频播放与智能识别于一体的现代化音乐应用。这一实现不仅展示了HarmonyOS在系统级AI能力开放上的强大实力,更为开发者提供了将前沿技术融入日常应用的新范式。

核心要点:

  • 使用AICaptionComponent系统组件
  • 通过AICaptionController控制音频输入
  • PCM格式音频分块发送
  • 响应式状态管理
  • 代码改变世界,而创意定义未来。 让我们在HarmonyOS的生态中,继续探索、创造、连接,用技术为每一段旋律赋予文字,为每一次聆听增添理解。

    祝你开发顺利,期待在HarmonyOS的星空中,看到属于你的那颗璀璨应用! 🎵🚀✨

    有任何问题或分享,欢迎在评论区交流讨论! 👇

    赞(0)
    未经允许不得转载:171主机测评 » HarmonyOS 6实战(源码教学篇)— Speech Kit 新特性【仿某云音乐实现并集成AI字幕】
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址