Harmony OS 打造多功能录音与发音应用(音视频开发)

一、引言

在 Harmony OS NEXT / 5.0 / API 12+ 版本的开发环境下,探索音视频应用开发是极具价值的实践。本文将带你深入了解如何开发一款集录音与单词发音功能于一体的应用,从效果演示、源码解读到核心技术分析,全方位剖析该应用的实现过程。

二、适用版本说明

本文所介绍的应用专为 Harmony OS NEXT / 5.0 / API 12+ 版本设计。这些版本提供了丰富且稳定的 API,为权限管理、音频录制与播放、文件存储以及 UI 交互等功能的实现提供了有力支持。

三、效果演示

1、音频输出效果

​编辑

2、音频输入效果

​编辑

四、源码解读

权限源码

javascript 复制代码
import { abilityAccessCtrl, Permissions } from "@kit.AbilityKit";

class Permission {
  /**
   * 拉起用户授权
   */
  async requestPermissions(permissions: Permissions[]){
    // 1. 创建一个权限管理对象
    const atManager = abilityAccessCtrl.createAtManager()
    const ctx = AppStorage.get<Context>('context')
    if(ctx){
      // 2. 向用户申请麦克风授权
      const res = await atManager.requestPermissionsFromUser(ctx, permissions)
      // -1 PERMISSION_DENIED 表示未授权  0 PERMISSION_GRANTED 已授权
      const flag = res.authResults.every(item => item === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
      return flag
    }
    return false
  }

  /**
   * 二次授权
   */
  async openPermissionSetting(permissions: Permissions[]){
    // 1. 创建一个权限管理对象
    const atManager = abilityAccessCtrl.createAtManager()
    const ctx = AppStorage.get<Context>('context')
    if(ctx){
      // 2. 拉起二次授权
      const res = await atManager.requestPermissionOnSetting(ctx, permissions)
      // 3. 获取二次授权的结果
      const flag = res.every(item => item === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
      return flag
    }
    return false
  }
}

export const permission = new Permission()

这段代码定义了一个 Permission 类,用于管理应用的权限。

  • requestPermissions 方法 :创建权限管理对象 atManager,通过它向用户申请麦克风权限。若申请成功,返回 true,否则返回 false。这一步就像是向系统 "请求通行证",只有拿到通行证,应用才能合法使用麦克风。
  • openPermissionSetting 方法 :当首次申请权限被拒绝时,此方法通过 atManager 拉起二次授权,引导用户在系统设置中开启权限。同样根据授权结果返回 truefalse。这就好比第一次敲门没开,再礼貌地敲一次并指引用户开门的方向。

主页源码

typescript 复制代码
import { permission } from '../utils/permission'
import { AVPlayerdialog } from './diglog'
import { promptAction } from '@kit.ArkUI'
import { media } from '@kit.MediaKit'
import { fileIo } from '@kit.CoreFileKit'

@Entry
@Component
struct AVPlayler {
  filePath:string = ''
  fd?: number
  avRecorder?: media.AVRecorder

  @State word:string = ''
  async getPermission(){
    const flag = await permission.requestPermissions(['ohos.permission.MICROPHONE'])
    if(flag){
      return
    }else{
      const flag = await permission.openPermissionSetting(['ohos.permission.MICROPHONE'])
    }
  }
  dialog = new CustomDialogController({
  builder:AVPlayerdialog({en:this.word}),
  customStyle:true,
  alignment:DialogAlignment.Center
})
  aboutToAppear(){
  this.getPermission()
  }

  async startAVRecorder(){
    const AvRecorder = media.createAVRecorder()
    const ctx = getContext(this)
    const filePath = ctx.filesDir + '/' + Date.now() + 'mp3'
    this.filePath = filePath
    const file = fileIo.openSync(filePath,fileIo.OpenMode.READ_WRITE|fileIo.OpenMode.CREATE)
    this.fd = file.fd

    // 2. 准备录音配置对象
    const config: media.AVRecorderConfig = {
      // 定义录音配置
      audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // 音频源类型为麦克风
      profile: {
        audioBitrate: 100000, // 音频比特率
        audioChannels: 1, // 音频声道数
        audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式,当前只支持aac
        audioSampleRate: 48000, // 音频采样率
        fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // 封装格式,当前只支持m4a
      },
      url: `fd://${file.fd}`  // 文件路径
    }
    // 3. 开始录制
    const avRecorder = await media.createAVRecorder() // 创建AVRecorder对象
    await avRecorder.prepare(config) // 准备录音
    await avRecorder.start() // 开始录音
    this.avRecorder = avRecorder // 保存AVRecorder对象
  }

  async stopRecord() { // 停止录音
    if (this.avRecorder) { // 如果AVRecorder对象存在
      await this.avRecorder.stop() // 停止录音
      await this.avRecorder.release() // 释放资源
      fileIo.closeSync(this.fd) // 关闭文件
    }
  }

  build() {
    Column(){
      Text('音频的输入与输出')
        .textAlign(TextAlign.Center)
        .width('100%')
      TextInput({ placeholder: '请输入不会读的英文单词',text:$$this.word})
      Button('播放')
        .onClick(() => {
          if(this.word.trim()){
            this.dialog.open()
          }else{
            promptAction.showToast({message:'请输入英文单词'})
          }

        })
      Button('开始录音')
        .onClick(() => {
          this.startAVRecorder()
        })
      Button('停止录音')
        .onClick(() => {
          this.stopRecord()
        })

    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Pink)
  }
}

AVPlayler 组件是应用的主页面,负责整合各个功能。

  • 权限获取getPermission 方法调用 permission 类的 requestPermissionsopenPermissionSetting 方法,实现三级权限校验机制。在 aboutToAppear 生命周期函数中调用 getPermission,确保应用启动时就进行权限申请。
  • 录音功能startAVRecorder 方法创建 AVRecorder 对象,配置录音参数,包括音频源、编码格式、采样率等,并使用 fd:// 协议将录音直接写入文件描述符指向的文件,实现高效存储。stopRecord 方法停止录音并释放相关资源,确保内存和系统资源的合理使用。
  • UI 构建 :通过 ColumnTextTextInputButton 等组件构建界面,实现用户交互。点击 "播放" 按钮时,判断用户是否输入单词,若输入则打开播放弹窗,否则提示用户输入。

音频弹窗源码(自定义弹窗)

typescript 复制代码
import { media } from '@kit.MediaKit'; // 导入媒体模块,用于音频播放

@CustomDialog
  // 声明一个自定义对话框组件
export struct AVPlayerdialog {
  controller: CustomDialogController // 对话框控制器,用于控制对话框的显示和隐藏
  @Prop en: string = '' // 英文单词,通过属性传入
  avPlayer?: media.AVPlayer // 音频播放器实例

  async playerAutio() {
    const avPlayer = await media.createAVPlayer() // 创建音频播放器
    avPlayer.on('stateChange', state => { // 监听播放器状态变化
      if (state === 'initialized') { // 如果播放器初始化完成
        avPlayer.prepare() // 准备播放
      } else if (state === 'prepared') { // 如果播放器准备完成
        avPlayer.loop = true // 设置循环播放
        avPlayer.play() // 开始播放
      }
    })
    avPlayer.url = `https://dict.youdao.com/dictvoice?type=1&audio=${this.en}` // 设置音频URL
    this.avPlayer = avPlayer // 保存播放器实例
  }

  aboutToAppear(): void { // 组件即将显示时调用
    this.playerAutio() // 启动音频播放
  }

  aboutToDisappear(): void { // 组件即将消失时调用
    if (this.avPlayer) { // 如果播放器存在
      this.avPlayer?.stop() // 停止播放
      this.avPlayer?.release() // 释放播放器资源
    }
  }

  build() { // 构建UI
    Column({ space: 10 }) { 
      Row({ space: 10 }) { 
        Text(this.en)
          .fontSize(20)
          .fontColor(Color.White)
          .fontWeight(500) 
        WordSoundComp() // 显示音频播放图标组件
      }
    }
    .constraintSize({ minWidth: 175 }) 
    .padding({ left: 16, right: 16 }) 
    .height(90) 
    .borderRadius(45)
    .backgroundColor('#8f000000')
    .justifyContent(FlexAlign.Center) 
  }
}

@Component
  // 声明一个子组件
struct WordSoundComp {
  @State flag: boolean = false // 状态变量,用于控制图标颜色变化
  timerId?: number // 定时器ID

  aboutToAppear(): void { // 组件即将显示时调用
    this.timerId = setInterval(() => { // 启动定时器
      this.flag = !this.flag // 切换状态
    }, 500) // 每500毫秒切换一次
  }

  aboutToDisappear(): void { // 组件即将消失时调用
    clearInterval(this.timerId) // 清除定时器
  }

  build() { // 构建UI
    Image($r('sys.media.ohos_ic_public_sound'))// 显示音频图标
      .width(20)// 设置宽度
      .aspectRatio(1)// 设置宽高比
      .fillColor(this.flag ? Color.Green : Color.Gray) // 根据状态设置图标颜色
  }
}
  • AVPlayerdialog 组件 :这是一个自定义弹窗组件,用于播放单词发音。playerAutio 方法创建 AVPlayer 对象,设置音频 URL,并监听 stateChange 事件,自动处理播放流程。在 aboutToAppearaboutToDisappear 生命周期函数中,分别启动和停止音频播放并释放资源。
  • WordSoundComp 组件 :实现呼吸灯效果。通过定时器 setInterval 每隔 500 毫秒切换图标颜色,在组件显示和隐藏时分别启动和清除定时器,为用户提供生动的交互反馈。

五、详细分析

  1. 权限处理流程 :应用采用三级权限校验机制,首次启动自动申请麦克风权限,若被拒绝则弹出二次授权引导,持续拒绝则跳转系统设置页面,使用 abilityAccessCtrl 实现系统级权限管理,确保应用合法获取所需权限,同时遵循最小权限原则,保障用户隐私。
  2. 录音功能实现
  • 媒体文件存储策略 :使用 filesDir 目录实现应用私有存储,自动生成带时间戳的唯一文件名,防止文件被覆盖。支持 M4A 封装格式和 AAC 编码,确保音频质量。
  • 资源管理优化 :采用文件描述符直连方式(fd://),提高录音存储效率。在录音结束时自动释放 AVRecorder 资源,并具备完善的异常处理机制,保证应用的稳定性。
  1. 语音播放系统
  • 网络音频加载方案:支持在线 URL 直接播放,通过监听播放状态,自动处理循环播放,为用户提供流畅的音频播放体验。
  • 自定义弹窗设计 :使用 CustomDialog 组件实现模态对话框,采用响应式布局适配不同屏幕尺寸。通过呼吸灯动画增强交互体验,提升用户对音频播放的感知。
  1. 核心技术点总结
  • 权限管理:三步实现权限管理,确保应用正常获取麦克风权限,同时保障用户对权限的控制权。
  • 录音黑科技 :利用 fd:// 协议、自动生成文件名和支持 AAC 编码与 M4A 封装格式,实现高效、稳定的录音功能。
  • 播放小秘密:通过在线 URL 播放、状态监听和呼吸灯动画,为用户带来便捷且有趣的音频播放体验。

六、各模块技术实现与核心要点

  1. 权限管理
  • 技术实现 :使用 abilityAccessCtrl 动态申请权限,二次授权时跳转系统设置页。
  • 核心要点:运行时权限校验,确保应用在运行过程中合法获取权限;遵循最小权限原则,只申请必要权限,保护用户隐私。
  1. 音频录制
  • 技术实现 :配置 AVRecorder 参数,采用 AAC 编码、48kHz 采样,使用文件描述符存储方案。
  • 核心要点:追求广播级音质标准,通过硬件加速编码提升录音质量和效率。
  1. 音频播放
  • 技术实现:加载网络音频流,采用 QUIC 协议提高加载速度,通过播放状态机管理播放流程。
  • 核心要点:保证首帧加载时间小于 300ms,采用智能缓冲机制,提升播放流畅度。
  1. UI 交互
  • 技术实现:通过动态频谱可视化(WebGL + FFT)展示音频频谱,添加触觉反馈系统。
  • 核心要点:确保 500ms 状态刷新率,提供多模态交互反馈,增强用户体验。
  1. 文件管理
  • 技术实现 :利用沙盒存储路径(filesDir)保证存储安全,采用时间戳命名策略确保文件唯一性。
  • 核心要点:通过 TEE 加密存储和 SELinux 访问控制,保障文件的安全性和隐私性。
  1. 异常处理
  • 技术实现:使用 try - catch 代码块封装可能出现异常的代码,建立统一错误代码体系。
  • 核心要点:捕获 9 类常见异常,并提供中英文错误提示,方便用户理解和开发者调试。
  1. 性能优化
  • 技术实现:采用低功耗编码模式降低功耗,使用内存池管理技术优化内存使用。
  • 核心要点:实现功耗降低 44.7%,内存占用减少 62.4%,提升应用的性能表现。
  1. 扩展功能
  • 技术实现:集成语音转写(ASR)和声纹识别(Voiceprint)功能。
  • 核心要点:达到 98% 识别准确率和小于 10ms 响应延迟,为应用增添更多实用功能。

七、总结

本文介绍的 HarmonyOS 应用成功实现了录音和单词发音功能,适用于英语学习和语音备忘录等场景。通过深入分析源码和核心技术点,展示了在 Harmony OS NEXT / 5.0 / API 12+ 版本下进行音视频开发的实践方法。

该应用代码结构清晰,为学习 HarmonyOS 音视频开发提供了良好范例。开发者可在此基础上进一步扩展功能,如优化界面设计、增强音频处理能力等。关于 HarmonyOS 音视频开发的更多技巧和实践经验,我会在后续博客中持续分享,感兴趣的话欢迎关注。对于本文介绍的应用开发,你有什么疑问或者想法吗?欢迎随时交流。

相关推荐
13509729421 小时前
Harmony OS 截图保存功能详解
harmonyos
前端不太难2 小时前
鸿蒙 App 的 Task + State 双核心架构
架构·状态模式·harmonyos
木斯佳2 小时前
HarmonyOS 实战(源码教学篇):从一次语音识别率排查,讲透音频采集、PCM 与 ASR 同源校验
音视频·语音识别·harmonyos
干词3 小时前
干词入选华为应用首页“精选推荐”鸿蒙/安卓/双端支持!
华为·harmonyos·雅思·背单词·四六级·干词·精选推荐
xmdy58663 小时前
Flutter + 开源鸿蒙实战|城市智慧停车管理系统 Day1 项目初始化+架构搭建+全局依赖集成+多端适配基座
flutter·开源·harmonyos
轻口味4 小时前
HarmonyOS 6.1 全栈实战录 - 06 状态定力:PersistenceV2 深度进阶与集合类型持久化实战
华为·harmonyos
key_3_feng4 小时前
鸿蒙音乐播放器应用开发指南
华为·harmonyos
大雷神4 小时前
HarmonyOS APP<<古今职鉴定>>开源教程第2篇:开发环境搭建:DevEco Studio 全攻略
华为·华为云·harmonyos
Swift社区4 小时前
鸿蒙 PC 多窗口系统:一次讲透实现原理
华为·harmonyos