本文将通过一个完整的音频录制与播放示例,介绍如何在HarmonyOS应用中实现音频采集和播放功能。该应用包含开始录音、结束录音、播放录音和停止播放四个核心功能按钮,展示了HarmonyOS音频相关API的使用方法

1 App概述

1.1 功能介绍

本应用主要实现以下功能:

音频录制:通过设备麦克风采集音频数据并保存为PCM文件。

音频播放:读取PCM文件并通过音频设备进行播放。

状态显示:实时显示音频数据处理状态和字节数统计。

资源管理:完善的音频资源申请、使用和释放机制。

1.2 技术组件

应用使用了以下HarmonyOS ArkUI框架组件和API:

按钮组件(Button)

文本组件(Text)

弹性布局组件(Flex)

水平布局组件(Row)

垂直布局组件(Column)

音频采集器(audio.AudioCapturer)

音频渲染器(audio.AudioRenderer)

文件操作API(fileIo)

2 实战:实现音频录制播放应用

2.1 应用首页

Index.ets文件作为应用的入口页面,包含完整的UI布局和业务逻辑,首先初始化音频配置参数、音频渲染器实例、音频采集器实例。代码如下:

import { audio } from '@kit.AudioKit';
import { fileIo } from '@kit.CoreFileKit';

// 录音文件缓存位置
const filePath: string = getContext().cacheDir + '/result_48000_1.pcm';

@Entry
@Component
struct Index {
  @State message: string = '待开始';

  // ...音频配置参数

private audioRendererInfo: audio.AudioRendererInfo = {
    usage: audio.StreamUsage.STREAM_USAGE_MOVIE,
    rendererFlags: 0
  };

  // 音频渲染器实例
  private audioRenderer: audio.AudioRenderer | null = null
  private audioStreamInfo: audio.AudioStreamInfo = {
    samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率
    channels: audio.AudioChannel.CHANNEL_2, // 通道
    sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
    encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
  };

private audioCapturerInfo: audio.AudioCapturerInfo = {
source: audio.SourceType.SOURCE_TYPE_MIC,
capturerFlags: 0

};

// 音频采集器实例

private audioCapturer: audio.AudioCapturer | null = null

}

2.2 核心组件实现

2.2.1 UI布局组件

应用界面采用垂直布局,包含状态显示文本和四个功能按钮。

build() {
  Row() {
    Column() {
      Text(this.message)
        .fontSize(50)
        .fontWeight(FontWeight.Bold)

      Button(('开始录音'), { type: ButtonType.Capsule })
        .width(240)
        .fontSize(40)
        .fontWeight(FontWeight.Medium)
        .margin({ top: 20, bottom: 20 })
        .onClick(() => {
          this.startCapturer(filePath)
        })

      Button(('结束录音'), { type: ButtonType.Capsule })
        .width(240)
        .fontSize(40)
        .fontWeight(FontWeight.Medium)
        .margin({ top: 20, bottom: 20 })
        .onClick(() => {
          // 获取结果
          this.stopCapturer();
        })

      Button(('播放录音'), { type: ButtonType.Capsule })
        .width(240)
        .fontSize(40)
        .fontWeight(FontWeight.Medium)
        .margin({ top: 20, bottom: 20 })
        .onClick(() => {

          this.startRenderer(filePath)
        })

      Button(('停止播放'), { type: ButtonType.Capsule })
        .width(240)
        .fontSize(40)
        .fontWeight(FontWeight.Medium)
        .margin({ top: 20, bottom: 20 })
        .onClick(() => {
          this.stopRenderer();
        })
    }
    .width('100%')
  }
  .height('100%')
}

2.2.2音频采集器管理

实现音频采集器的创建、启动和停止功能。

// 获取音频采集器实例
async getAudioCapturer() {
  // 如果已经存在,直接返回
  if (this.audioCapturer) {
    return this.audioCapturer
  }
  // 创建音频采集器
  const audioCapturer = await audio.createAudioCapturer({
    streamInfo: this.audioStreamInfo,
    capturerInfo: this.audioCapturerInfo
  })
  // 保存方便下次直接获取
  this.audioCapturer = audioCapturer
  // 返回音频采集器
  return audioCapturer
}

// 开始录音
async startCapturer(filePath: string) {
  // 根据 filePath 打开文件,可读可写模式,如果文件不存在自动创建
  const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE)
  // 1. 获取音频采集器
  const audioCapturer = await this.getAudioCapturer()
  // 偏移值
  let bufferSize: number = 0
  // 2. 调用on('readData')方法,订阅监听音频数据读入回调
  audioCapturer.on('readData', (buffer) => {
    // 把采集的音频信息写入到打开的文件中
    fileIo.writeSync(file.fd, buffer, { offset: bufferSize, length: buffer.byteLength })
    // 累加偏移值
    bufferSize += buffer.byteLength
    // 测试用的
    this.message = bufferSize.toString();
  })
  // 3. 开始录音采集
  audioCapturer.start()
}

async stopCapturer(){
  // 获取音频采集器
  const audioCapturer = await this.getAudioCapturer()
  await audioCapturer.stop() // 停止采集
  audioCapturer.release() // 释放资源
  this.audioCapturer = null // 重置采集器变量

  // 测试用的
  this.message = '停止录音';
}

2.2.3音频渲染器管理

实现音频播放器的创建、播放和停止功能

// 获取音频渲染器(播放器)
async getAudioRenderer() {
  if (this.audioRenderer) {
    return this.audioRenderer
  }
  this.audioRenderer = await audio.createAudioRenderer({
    streamInfo: this.audioStreamInfo,
    rendererInfo: this.audioRendererInfo
  })
  return this.audioRenderer
}

// 播放录音
async startRenderer(filePath: string) {
  // 根据路径打开文件
  const file = fileIo.openSync(filePath)
  // 获取文件信息,如果读取时已经超出文件大小,自动停止
  const stat = fileIo.statSync(file.fd)
  // 1. 获取音频渲染器(播放器)
  const audioRenderer = await this.getAudioRenderer()
  // 偏移值
  let bufferSize: number = 0
  // 2. 调用on('writeData')方法,订阅监听音频数据写入回调
  let writeDataCallback = (buffer: ArrayBuffer) => {
    fileIo.readSync(file.fd, buffer, { offset: bufferSize, length: buffer.byteLength })
    bufferSize += buffer.byteLength

    this.message = bufferSize.toString()
    if (bufferSize >= stat.size) {
      // 停止渲染器(播放器)
      audioRenderer.stop() // 停止
      audioRenderer.release() // 释放资源
      this.audioRenderer = null // 清理变量
    }
  }
  audioRenderer.on('writeData', writeDataCallback)
  // 3. 启动音频渲染器(播放器)
  audioRenderer.start()
}

// 停止播放录音
async stopRenderer() {
  // 获取音频渲染器(播放器)
  const audioRenderer = await this.getAudioRenderer()
  if (
    audioRenderer.state === audio.AudioState.STATE_RUNNING
      ||
      audioRenderer.state === audio.AudioState.STATE_PAUSED
  ) {
    await audioRenderer.stop() // 停止
    audioRenderer.release() // 释放资源
    this.audioRenderer = null // 清理变量
  }
  this.message = '停止播放'
}

2.2.4 音频参数配置

应用使用标准的音频参数配置,确保录制和播放的质量:

private audioStreamInfo: audio.AudioStreamInfo = {
  samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率
  channels: audio.AudioChannel.CHANNEL_2, // 通道
  sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
  encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
};

3 注意事项

3.1 权限配置

在module.json5中配置必要的音频权限:

"requestPermissions":[
  {
    "name" : "ohos.permission.INTERNET"
  },
  {
    "name" : "ohos.permission.MICROPHONE",
    "reason": "$string:reason",
    "usedScene": {
      "abilities": [
        "FormAbility"
      ],
      "when":"always"
    }
  }
]

3.2 权限校验

在entry\src\main\ets\entryability\EntryAbility.ets文件中,进行权限校验,用户同意授权后可以访问Index.ets文件。

onWindowStageCreate(windowStage: window.WindowStage): void {
  // Main window is created, set main page for this ability
  hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

  // 权限校验
  let context = this.context;
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  let permissions: Array<Permissions> = ["ohos.permission.MICROPHONE"];

  // requestPermissionsFromUser会判断权限的授权状态
  atManager.requestPermissionsFromUser(context, permissions).then((data) => {
    let grantStatus: Array<number> = data.authResults;
    let length: number = grantStatus.length;
    for (let i = 0; i < length; i++) {
      if (grantStatus[i] === 0) {
        // 用户同意授权
        windowStage.loadContent('pages/Index', (err, data) => {
          if (err.code) {
            hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
            return;
          }
          hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
        });
      } else {
        // 用户拒绝授权
        return;
      }
    }
    // 授权成功
  }).catch((err: BusinessError) => {
    console.error(`requestPermissionsFromUser failed, code is ${err.code}, message is ${err.message}`);
  })
}

4 应用效果展示

应用运行效果如图所示,界面简洁明了,包含状态显示区和功能操作区。用户可以通过按钮完成完整的录音播放流程。

点击“开始录音”,录音时使用本地麦克风进行录制,如图所示:

点击“结束录音”,如图所示:

点击“播放录音”,如图所示:

点击“停止播放”,如图所示:

5 小结

本文详细介绍了HarmonyOS音频录制播放应用的完整开发过程,涵盖了音频采集、播放、文件操作等核心功能。通过本实例的学习,开发者可以掌握:

(1)HarmonyOS音频API的基本使用方法。

(2)音频采集器和渲染器的生命周期管理。

(3)文件读写操作与音频数据的处理。

(4)事件监听机制在音频开发中的应用。

这个实例为后续开发更复杂的音频处理应用奠定了坚实的基础,开发者可以在此基础上扩展音频编辑、特效处理等高级功能。

Logo

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

更多推荐