目录

使用AudioCapturer录制音频时,如何获取音量分贝大小

问题现象

在使用AudioCapturer录制PCM音频数据时,需要监听音频音量大小,并通过绘制波形图等来反映出音频变化的趋势。该如何获取音量大小?

背景知识

解决方案

完整代码参考如下:

import { audio } from '@kit.AudioKit';
import { abilityAccessCtrl, common, PermissionRequestResult, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

let audioStreamInfo: audio.AudioStreamInfo = {
  channels: audio.AudioChannel.CHANNEL_1,
  samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000,
  sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
  encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW,
};
let audioCapturerInfo: audio.AudioCapturerInfo = {
  capturerFlags: 0,
  source: audio.SourceType.SOURCE_TYPE_MIC,
};
let audioCapturerOptions: audio.AudioCapturerOptions = {
  streamInfo: audioStreamInfo,
  capturerInfo: audioCapturerInfo,
};

@Entry
@Component
struct Index {
  private capturer?: audio.AudioCapturer;
  context = this.getUIContext().getHostContext() as common.UIAbilityContext;

  aboutToAppear(): void {
    requestPermissions(this.context, ['ohos.permission.MICROPHONE']);
  }

  aboutToDisappear(): void {
    this.stopCapturer();
    this.releaseCapturer();
  }

  build() {
    Column({ space: 20 }) {
      Row() {
        Text('音频分贝大小:');

        Button('监听')
          .onClick(async () => {
            await this.initCapturerGetDb();
            await this.startCapturer();
          });

        Button('取消监听')
          .onClick(async () => {
            await this.stopCapturer();
            await this.releaseCapturer();
          });
      }
      .width('85%')
      .justifyContent(FlexAlign.SpaceEvenly);

      Row() {
        Text('音频振幅大小:');

        Button('监听')
          .onClick(async () => {
            await this.initCapturerGetMaxAmplitude();
            await this.startCapturer();
          });
        Button('取消监听')
          .onClick(async () => {
            await this.stopCapturer();
            await this.releaseCapturer();
          });
      }
      .width('85%')
      .justifyContent(FlexAlign.SpaceEvenly);
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center);
  }

  // buffer:AudioCapturer录制的PCM音频数据
  private pcm2DB(buffer: ArrayBuffer): number {
    const refAmplitude = 32767; // 采样深度为有符号16bit时,参考振幅值为最大正值32767。
    const amplitudeArr = new Int16Array(buffer); // 16bit为一个采样点
    let sum: number = 0;
    for (let i = 0; i < amplitudeArr.length; i++) {
      sum += amplitudeArr[i] * amplitudeArr[i]; // 计算平方和
    }
    let rms = Math.sqrt(sum / amplitudeArr.length); // 计算均方根能量值
    let db = 20 * Math.log10(rms / refAmplitude); // 计算音量分贝大小
    return db;
  }

  async initCapturerGetDb(): Promise<void> {
    this.capturer = await audio.createAudioCapturer(audioCapturerOptions);

    try {
      this.capturer.on('readData', (buffer: ArrayBuffer) => {
        let db = this.pcm2DB(buffer);
        console.info(`DB Value: ${db}`);
      });
    } catch (err) {
      console.error(`failed to register readData event: ${JSON.stringify(err)}`);
    }
  }

  // 监听最大电平值
  async getMaxAmplitude() {
    let audioManager = audio.getAudioManager();
    let audioVolumeManager: audio.AudioVolumeManager = audioManager.getVolumeManager();
    let groupId: number = audio.DEFAULT_VOLUME_GROUP_ID;
    let audioVolumeGroupManager: audio.AudioVolumeGroupManager =
      await audioVolumeManager.getVolumeGroupManager(groupId);
    let deviceDescriptors: audio.AudioDeviceDescriptors | undefined =
      this.capturer?.getCurrentInputDevices();

    if (deviceDescriptors === undefined) {
      return;
    }
    audioVolumeGroupManager.getMaxAmplitudeForInputDevice(deviceDescriptors[0]).then((value) => {
      console.info(`max amplitude is: ${value}`);
    }).catch((err: BusinessError) => {
      console.error(`getMaxAmplitudeForInputDevice error. Code: ${err.code}, message: ${err.message}`);
    });
  }

  async initCapturerGetMaxAmplitude(): Promise<void> {
    this.capturer = await audio.createAudioCapturer(audioCapturerOptions);

    try {
      this.capturer.on('readData', () => {
        this.getMaxAmplitude();
      });
    } catch (err) {
      console.error(`failed to register readData event: ${JSON.stringify(err)}`);
    }
  }

  async startCapturer(): Promise<void> {
    if (this.capturer === undefined) {
      throw new Error(`AudioCapturer is undefined`);
    }
    let state = this.capturer.state;
    if (state !== audio.AudioState.STATE_PREPARED && state !== audio.AudioState.STATE_STOPPED) {
      throw new Error(`AudioCapturer is at wrong state, ${state}`);
    }
    await this.capturer.start();
  }

  async stopCapturer(): Promise<void> {
    if (this.capturer === undefined) {
      throw new Error(`AudioCapturer is undefined`);
    }
    let state = this.capturer.state;
    if (state !== audio.AudioState.STATE_RUNNING) {
      throw new Error(`AudioCapturer is at wrong state, ${state}`);
    }

    await this.capturer.stop();
  }

  async releaseCapturer(): Promise<void> {
    if (this.capturer === undefined) {
      throw new Error(`AudioCapturer is undefined`);
    }
    let state = this.capturer.state;
    if (state !== audio.AudioState.STATE_PREPARED && state !== audio.AudioState.STATE_STOPPED) {
      throw new Error(`AudioCapturer is at wrong state, ${state}`);
    }

    try {
      this.capturer.off('readData');
    } catch (err) {
      console.error(`failed to unregister readData event: ${JSON.stringify(err)}`);
    }

    await this.capturer.release();
    this.capturer = undefined;
  }
}

function requestPermissions(context: common.UIAbilityContext, permissions: Permissions[]): void {
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  atManager.requestPermissionsFromUser(context, permissions)
    .then((result: PermissionRequestResult) => {
      let grantStatus: number[] = result.authResults;
      let length = grantStatus.length;
      for (let i = 0; i < length; i++) {
        if (grantStatus[i] !== 0) {
          console.info(`User reject to grant permission: ${permissions[i]}`);
        }
      }
    })
    .catch((err: BusinessError) => {
      console.error(`Request permissions from user failed, ${JSON.stringify(err)}`);
    });
}

应用可以调用麦克风录制音频,但该行为属于隐私敏感行为,在调用麦克风前,需要先向用户申请权限:ohos.permission.MICROPHONE。