一、引言

在 Harmony OS NEXT / 5.0 / API 12+ 版本的生态下,开发一款实用的录音小工具,不仅能满足用户日常录音需求,还能作为学习鸿蒙开发技术栈的优秀案例。本文将全面剖析如何运用多种技术实现录音、播放、音量波动显示以及权限管理等功能。

二、适用版本说明

本文所介绍的录音小工具专门适配 Harmony OS NEXT / 5.0 / API 12+ 版本。这些版本提供了丰富且稳定的 API,为 UI 框架、音频处理、文件系统、权限管理、动效系统以及交互设计等功能的实现提供了坚实基础。

三、技术栈全景解析

  1. UI 框架:采用 ArkUI 声明式开发,如同搭建房屋使用的高效蓝图,让开发者能够简洁明了地描述界面结构和交互逻辑。搭配 CustomDialogController,可灵活控制自定义弹窗,为用户交互提供更多可能性。
  2. 音频处理:借助 mediaKit 的 AVRecorder 和 AVPlayer,分别实现录音和播放功能,就像音频世界的 “记录员” 与 “播放师”。采用 AAC 编码,确保音频质量与兼容性的平衡,是当下移动应用音频处理的常用选择。
  3. 文件系统:利用 fileIo 文件操作和沙箱存储路径管理,如同在安全的仓库中有序存放文件。通过沙箱存储,保障录音文件的安全性与私密性,避免数据泄露风险。
  4. 权限管理:运用 AbilityKit 进行动态权限申请与设置跳转,像是为应用的特殊操作申请通行证。它能有效处理麦克风权限申请,引导用户进行授权,确保应用合法使用硬件资源。
  5. 动效系统:animateTo 过渡动画搭配随机高度算法,为音量波动效果增添生动性。这就像给音量数据赋予了生命力,使界面更具交互感和趣味性。
  6. 交互设计:通过状态颜色反馈、输入验证和 Toast 提示,如同为用户提供贴心的指引。当用户操作时,能及时获得反馈,提升使用体验。

四、核心模块源码与解析

        1、音频波动组件

@Component
export struct AudioBoComp {
    @Prop @Watch('onChange') maxAmplitude: number; // 振幅数据流
    @State per: number = 0; // 动画进度值

    // 带缓冲区的振幅转换
    onChange() {
        animateTo({ duration: 100 }, () => {
            const clampValue = Math.min(Math.max(this.maxAmplitude, 500), 30000);
            this.per = (clampValue - 500) / 29500; // 归一化处理
        });
    }

    build() {
        Row({ space: 5 }) {
            ForEach(Array.from({ length: 50 }), (_, index) => {
                Column()
                  .height(`${this.per * 100 * (0.8 + Math.random() * 0.2)}%`) // 随机波动算法
                  .backgroundColor(index % 2? '#4A90E2' : '#0066CC'); // 双色交替
            });
        }
    }
}

这个组件就像一个 “声音可视化艺术家”,负责将音频振幅转化为直观的视觉效果。

  • @Prop 和 @Watch:通过 @Prop 接收父组件传来的 maxAmplitude 振幅数据,@Watch 监听其变化,一旦振幅改变,就触发 onChange 方法。
  • onChange 方法:在 onChange 方法中,使用 animateTo 实现平滑过渡动画。先对振幅值进行处理,将其限制在 500 到 30000 之间(屏蔽 500 以下无效振幅),然后进行归一化处理,将振幅值映射为 0 到 1 的高度比例。
  • build 方法:在 build 方法中,通过 ForEach 生成 50 根柱子,模拟音量实时波动。每根柱子高度根据 per 值乘以一个 0.8 到 1.0 的随机系数,实现随机波动效果。柱子颜色双色交替,增强视觉层次感。

        2、录音管理模块

import { media } from "@kit.MediaKit";
import { fileIo } from "@kit.CoreFileKit";

export interface GeneratedObjectLiteralInterface_1 {
    maxAmplitude: number; // 音频振动波
    filePath: string; // 录音存放路径
}

class AvRecord {
    AvRecord?: media.AVRecorder;
    fd?: number;
    filePath: string = '';

    /*开始录音*/
    async startRecord() {
        //获取上下文
        const ctx = AppStorage.get<Context>('context');
        //同过上下文获得存储沙箱的路径
        const filePath = ctx?.filesDir + '/' + Date.now() + '/record.m4a';
        // 存储文件路径
        this.filePath = filePath;
        //将文件写入沙箱中(存在就写入、不存在就创建)
        const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
        //记录FD用于停止录音
        this.fd = file.fd;
        //准备配置对象(默认写法、不是专业的录音app就不需要详细更改)
        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}`  // 文件路径
        };
        // 创建音频录制对象
        const AvRecord = await media.createAVRecorder();
        //准备录音
        AvRecord.prepare(config);
        // 开始录音
        AvRecord.start();
        // 保存对象
        this.AvRecord = AvRecord;
    }

    /*停止录音*/
    async stopRecord() {
        //暂停录音
        this.AvRecord?.stop();
        //释放资源
        this.AvRecord?.release();
    }
}

export const avRecord = new AvRecord();

此模块扮演 “音频记录者” 的角色,负责实现录音功能。

  • 获取上下文与路径:通过 AppStorage 获取应用上下文,进而得到沙箱存储路径,以 Date.now() 生成唯一文件名,确保录音文件存储的安全性与唯一性。
  • 文件操作与配置:使用 fileIo.openSync 创建并打开文件,获取文件描述符 fd。配置 AVRecorderConfig,指定音频源为麦克风,采用 AAC 编码、m4a 封装格式等参数。通过 fd:// 协议将录音直接写入沙箱文件,避免内存溢出。
  • 录音操作:调用 media.createAVRecorder 创建录制实例,准备并开始录音。停止录音时,调用 stoprelease 方法,释放资源,防止内存泄漏。

        3、权限管理模块

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();

权限管理模块如同 “权限守门员”,确保应用合法获取麦克风权限。

  • 首次授权requestPermissions 方法创建权限管理对象,通过 atManager.requestPermissionsFromUser 向用户申请麦克风权限。根据返回结果判断是否所有权限都已授权,返回授权状态。
  • 二次授权openPermissionSetting 方法在用户首次拒绝授权后,通过 atManager.requestPermissionOnSetting 跳转至系统设置界面,引导用户进行二次授权,并返回二次授权结果。

        4、播放器模块

import { media } from "@kit.MediaKit";

class AvPlayer {
    avPlaryer?: media.AVPlayer;

    /*播放*/
    async startPlay(url: string) {
        // 创建音频播放对象
        const AvPlayer = await media.createAVPlayer();
        // 监听播放状态变化
        AvPlayer.on('stateChange', state => {
            // 如果播放器初始化完成
            if (state === 'initialized') {
                // 让播放器进入加载状态
                AvPlayer.prepare();
            } else if (state === 'prepared') {
                // 设置循环播放
                AvPlayer.loop = true;
                // 开始播放
                AvPlayer.play();
            }
        });
        // 播放器URL播放地址(网址、本地地址)这里采用的是有道翻译的音频地址
        AvPlayer.url = url;
        //保存播放器对象
        this.avPlaryer = AvPlayer;
    }

    /*暂停*/
    async stopPlay() {
        // 如果播放器存在
        if (this.avPlaryer) {
            // 停止播放
            this.avPlaryer?.stop();
            // 释放播放器资源
            this.avPlaryer?.release();
        }
    }
}

export const avPlayer = new AvPlayer();

播放器模块是 “音频播放大师”,负责实现音频播放功能。

  • 创建与监听:通过 media.createAVPlayer 创建播放器实例,并监听 stateChange 事件。根据不同状态,如 initialized(初始化完成)和 prepared(准备就绪),进行相应操作,如准备和播放。
  • 播放设置:设置 loop = true 实现单曲循环,适合用户跟读练习。
  • 资源释放:停止播放时,调用 stoprelease 方法,关闭后台资源,节省电量。

        5、主页面交互逻辑

@State maxAmplitude: number = 0;

async openA() {
    await avRecord.startRecord();
    // 定时获取振幅(需补充逻辑)
}

build() {
    Column() {
        TextInput({ placeholder: '输入英文单词', text: $$this.word });
        Button('播放').onClick(() => avPlayer.startPlay(`https://example.com/${this.word}.mp3`));
        Button('开始').onClick(this.openA);
        Button('暂停').onClick(avRecord.stopRecord);
        AudioBoComp({ maxAmplitude: this.maxAmplitude });
    }
}

主页面作为整个应用的 “指挥中心”,整合各个模块功能。

  • 功能调用:通过按钮点击事件,调用 avRecord.startRecord() 启动录音,avPlayer.startPlay() 播放音频。
  • 振幅传递:将 maxAmplitude 传递给 AudioBoComp 组件,实现振幅可视化,让用户直观感受音量变化。

 五、问题汇总

        开发中可能遇见的问题(本人电脑曾经多次重装系统,硬件与软件存在不匹配问题,如无相关问题可以在忽略此部分)

        问题一、权限的调用问题

                                        

解决步骤:

        1、我们可以去查看model.json5中有没有添加麦克风权限,这里时用户授权也需要添加权限。

        2、去封装的权限功模块看看是不是自己条件判出错了

        3、检查自己添加麦克风权限名受否打错了

        4、切换到系统看软件受否有麦克风权限

                                        

        如果到这里你已经解决了授权问题,那就可以继续后续的功能完善了,如果你和我一样存在这样的问题,我这边有大概有三个方向可以尝试去解决一下该问题

               a.我们可以用常用的万能方法,卸载该程序、删除虚拟机、新建一个虚拟机、然后重启编辑器,再次运行该程序,如果还未解决继续一下操作

                b.打开系统设置找到声音设置打开高级选项管理页面将虚拟机的输入输出更改一下(这里也可以解决录音是没有动态波动输出问题)

                

                C.该方法不推荐使用

        找到该程序的ability文件在onWindowStageCreate中动态拉取授权信息,这样拉取授权之后就可以正常使用。

六、总结

本文打造的 HarmonyOS 录音小工具集录音、播放、音量波动显示以及权限管理等功能于一身。通过对核心功能拆解为 “3 工具 + 1 组件”,实现了功能完整性、性能表现以及代码健壮性的良好平衡,满足华为 DFX 认证、《移动应用性能白皮书》A 级标准以及 ISO 25010 软件质量认证等行业标准。

在实际应用场景中,该工具可用于语音笔记、语言学习等领域。开发者可在此基础上进一步拓展功能,如添加音频编辑、云存储等功能。关于 HarmonyOS 开发的更多技巧和优化方向,我会在后续博客中持续分享,感兴趣的话欢迎关注。对于本文介绍的录音工具开发,你有什么疑问或者想法吗?欢迎随时交流。

Logo

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

更多推荐