引言

随着HarmonyOS生态的快速发展,音频处理能力成为应用开发的核心需求之一。本文将从音频录制文件存储管理音频播放三个模块出发,结合完整代码示例,详解鸿蒙系统中音频功能的全链路开发实践。

1.1 核心模块

  1. 音频录制:基于Meadia Kit的AVRecorder实现高精度录音,支持实时波形显示。
  2. 文件管理:通过沙箱路径读写音频文件,适配不同设备存储策略。
  3. 音频播放:利用AVPlayer组件实现本地文件播放与状态控制。

官方文档

二、音频录制实现

1、申请权限

在module.json5声明麦克风权限

//src/main/module.json5

"requestPermissions": [
      {
        "name":"ohos.permission.MICROPHONE",
        "usedScene": {},
        "reason":"$string:EntryAbility_desc"
      }
    ],

首先录音,那必然是需要录音权限,我们先询问用户申请录音权限,只有拿到录音权限之后才能够去录音。

使用abilityAccessCtrl.createAtManager() 向用户申请麦克风权限ohos.permission.MICROPHONE

async apply_for_right(){
    // 向用户申请麦克风权限
    const manager = abilityAccessCtrl.createAtManager()
    const result = await manager.requestPermissionsFromUser(getContext(), ['ohos.permission.MICROPHONE'])
    if(result.authResults[0] === -1) {
      //   表示用户点击了禁用,直接打开设置页面,要用户去设置
      // 定义参数
      const want: Want = {
        bundleName: 'com.huawei.hmos.settings',
        abilityName: 'com.huawei.hmos.settings.MainAbility',
        uri: 'application_info_entry',
        parameters: {
          // ✨✨✨修改成你的应用包名,跳转到该应用的设置页面 (包名在APP.json5)
          pushParams: 'com.example.hm_mianjing'
        }
      }
      const uiContext = getContext() as common.UIAbilityContext
      // 调起手机设置中的当前应用设置面板
      uiContext.startAbility(want)
    }
  }

如果用户点击了不允许录音,那我们便要向用户二次申请授权

在“设置”应用中的路径:设置 > 应用和元服务 > 某个应用

跳转的核心代码

// 定义参数
const want: Want = {
  bundleName: 'com.huawei.hmos.settings',
  abilityName: 'com.huawei.hmos.settings.MainAbility',
  uri: 'application_info_entry',
  parameters: {
    // ✨✨✨修改成你的应用包名,跳转到该应用的设置页面
    pushParams: 'com.itcast.interview_bjhm4App'
  }
}

const uiContext = getContext() as common.UIAbilityContext
// 调起手机设置中的当前应用设置面板
uiContext.startAbility(want)
2.录制音频

利用AVRecorder录制基本的.m4a的音频文件

录制的核心代码

// 准备一个文件来接收录音数据写入
const filePath = getContext().filesDir + '/' + Date.now() + '.m4a'
// 以【创建】和【读写】权限打开文件
const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE)
// 获取文件地址
const fd = file.fd 

录音的配置对象

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}`  //第一步中的文件地址
}

完整的录制代码

// 录制
  async transcribe(){
    // 1. 创建了录音对象
    this.avRecorder = await media.createAVRecorder()
    // 2.事件监听
    // 状态上报回调函数
    this.avRecorder.on('stateChange', (state: media.AVRecorderState, reason: media.StateChangeReason) => {
      console.log(`current state is ${state}`);
      // 用户可以在此补充状态发生切换后想要进行的动作
    })

    // 错误上报回调函数
    this.avRecorder.on('error', (err: BusinessError) => {
      console.error(`avRecorder failed, code is ${err.code}, message is ${err.message}`);
    })
    // 3.配置录音参数
    let avProfile: media.AVRecorderProfile = {
      audioBitrate: 100000, // 音频比特率
      audioChannels: 2, // 音频声道数
      audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式,当前支持ACC,MP3,G711MU
      audioSampleRate: 48000, // 音频采样率
      fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // 封装格式,当前支持MP4,M4A,MP3,WAV
    };

    const context: Context = getContext(this); // 参考应用文件访问与管理
    this.filePath = context.filesDir + '/'+ Date.now() + '.mp3';
    let audioFile: fs.File = fs.openSync(this.filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    let fileFd: number = audioFile.fd; // 获取文件fd

    let avConfig: media.AVRecorderConfig = {
      audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // 音频输入源,这里设置为麦克风
      profile: avProfile,
      url: 'fd://' + fileFd.toString(), // 参考应用文件访问与管理中的开发示例获取创建的音频文件fd填入此处
    };
    await this.avRecorder.prepare(avConfig)

    //   4. 开始录音
    this.avRecorder.start()
  }
3.播放音频

使用fd://文件fd 结合 AVPlayer 来完成录音的播放

使用 timeUpdate来监听播放时间的改变

//创建播放器对象
          const player = await media.createAVPlayer();
          player.on('stateChange',state=>{
            if (state === 'initialized') {
              player.prepare()
            }else if(state === 'prepared'){
              // player.loop = true
              player.play()
            }

          })
          const file = fs.openSync(this.filePath,fs.OpenMode.READ_ONLY)
          // 赋值播放源: fd://文件的数字标记
          player.url = 'fd://' + file.fd

完整代码

import { abilityAccessCtrl, common, Want } from '@kit.AbilityKit'
import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
import fs from '@ohos.file.fs';
import { relationalStore } from '@kit.ArkData';
import json from '@ohos.util.json';

interface IArticle {
  id: number
  title: string
  content: string
  create_time: number
}

@Entry
@Component
struct AvrecorderAndRdbDemoPage {
  // 数据库表名
  tableName: string = 'article' //数据库的表名称,由我们自己定义
  store: relationalStore.RdbStore = {} as relationalStore.RdbStore
  filePath: string = ''
  @State buttonColor:boolean = false
  // 创建AVRecorder实例
  avRecorder: media.AVRecorder = {} as media.AVRecorder
  // 申请权限
  async apply_for_right(){
    // 向用户申请麦克风权限
    const manager = abilityAccessCtrl.createAtManager()
    const result = await manager.requestPermissionsFromUser(getContext(), ['ohos.permission.MICROPHONE'])
    if(result.authResults[0] === -1) {
      //   表示用户点击了禁用,直接打开设置页面,要用户去设置
      // 定义参数
      const want: Want = {
        bundleName: 'com.huawei.hmos.settings',
        abilityName: 'com.huawei.hmos.settings.MainAbility',
        uri: 'application_info_entry',
        parameters: {
          // ✨✨✨修改成你的应用包名,跳转到该应用的设置页面 (包名在APP.json5)
          pushParams: 'com.example.hm_mianjing'
        }
      }
      const uiContext = getContext() as common.UIAbilityContext
      // 调起手机设置中的当前应用设置面板
      uiContext.startAbility(want)
    }
  }
  // 录制
  async transcribe(){
    // 1. 创建了录音对象
    this.avRecorder = await media.createAVRecorder()
    // 2.事件监听
    // 状态上报回调函数
    this.avRecorder.on('stateChange', (state: media.AVRecorderState, reason: media.StateChangeReason) => {
      console.log(`current state is ${state}`);
      // 用户可以在此补充状态发生切换后想要进行的动作
    })

    // 错误上报回调函数
    this.avRecorder.on('error', (err: BusinessError) => {
      console.error(`avRecorder failed, code is ${err.code}, message is ${err.message}`);
    })
    // 3.配置录音参数
    let avProfile: media.AVRecorderProfile = {
      audioBitrate: 100000, // 音频比特率
      audioChannels: 2, // 音频声道数
      audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式,当前支持ACC,MP3,G711MU
      audioSampleRate: 48000, // 音频采样率
      fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // 封装格式,当前支持MP4,M4A,MP3,WAV
    };

    const context: Context = getContext(this); // 参考应用文件访问与管理
    this.filePath = context.filesDir + '/'+ Date.now() + '.mp3';
    let audioFile: fs.File = fs.openSync(this.filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    let fileFd: number = audioFile.fd; // 获取文件fd

    let avConfig: media.AVRecorderConfig = {
      audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // 音频输入源,这里设置为麦克风
      profile: avProfile,
      url: 'fd://' + fileFd.toString(), // 参考应用文件访问与管理中的开发示例获取创建的音频文件fd填入此处
    };
    await this.avRecorder.prepare(avConfig)

    //   4. 开始录音
    this.avRecorder.start()
  }

  build() {
    Column({space:10}){
      Button('获取权限')
        .onClick(()=>{
          this.apply_for_right()
        })
        .margin({bottom:20})
      Row({space:10}){
        Button('开始录制')
          .onClick(() => {
            this.buttonColor = true
            this.transcribe()
          })
        Button('停止录制')
          .backgroundColor(this.buttonColor? Color.Red :'common_blue')
          .onClick(() => {
            this.buttonColor = false
            // 停止录制
            this.avRecorder.stop();
            // 重置资源
            this.avRecorder.reset();
            // 销毁实例
            this.avRecorder.release();
          })
      }
      Button('播放音频')
        .margin({top:20})
        .onClick(async ()=>{
          //创建播放器对象
          const player = await media.createAVPlayer();
          player.on('stateChange',state=>{
            if (state === 'initialized') {
              player.prepare()
            }else if(state === 'prepared'){
              // player.loop = true
              player.play()
            }

          })
          const file = fs.openSync(this.filePath,fs.OpenMode.READ_ONLY)
          // 赋值播放源: fd://文件的数字标记
          player.url = 'fd://' + file.fd
        })
    }.width('100%')
    .height('100%')
  }
}

总结:

完整的录制音频的流程:


1.由于录音会调用手机端麦克风,所以需要做如下权限配置,在项目的module.json5中声明麦克风的使用权限使用鸿蒙的底层api来向用户申请麦克风的使用权限使用abilityAccessCtrl这个api来授权
如果用户禁止使用麦克风,则使用UIAbilityContent的startAbility来进行二次引导授权此时用户如果点击了禁止,我们应该引导用户进行二次权限设置。


2.此时可以使用 AVRecorder对象来进行音频的录制在应用程序目录的files下准备一个文件用来接收音频数据的利用AVRecorder的start方法来采集麦克风的音频数据。

3.此时自动将音频数据保存到文件中使用AVPlayer对象播放录制好的音频。

适用HarmonyOS NEXT / API12或以上版本 -----------------

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐