DEV Community

HarmonyOS
HarmonyOS

Posted on

Decibel Meter with ArkTS

Read the original article:Decibel Meter with ArkTS

Decibel Meter with ArkTS

Introduction

Let’s write some magnificent code. Here we will use the AVRecorder to implement a decibel meter.
We need the microphone permission. Let’s get it.

  • Modify module.json5; define required permission.
"requestPermissions": [
  {
    "name": "ohos.permission.MICROPHONE",
    "reason": "$string:mic_reason",
    "usedScene": {
      "when": "inuse"
    }
  }
]
Enter fullscreen mode Exit fullscreen mode
  • Modify Index.ets to ask for permission when the app is opened.
onDidBuild(): void {
  this.requestMicPermission().then(() => {
    this.dBMeter.measure(this.getUIContext().getHostContext()!) // we will come to this in a minute
  })
}

async requestMicPermission() {
  const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  const context = this.getUIContext().getHostContext()
  const permissions: Permissions[] = ['ohos.permission.MICROPHONE'];

  try {
    // request permission from user
    let granted =
      (await (atManager.requestPermissionsFromUser(context,
        permissions) as Promise<PermissionRequestResult>)).authResults[0] === 0
    if (!granted) {
      // request permission on settings
      granted = (await atManager.requestPermissionOnSetting(context, permissions))[0] ===
      abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED

      if (!granted) {
        // this will close the app if user does not give permission
        await (this.getUIContext().getHostContext() as common.UIAbilityContext).terminateSelf()
      }
    }
  } catch (error) {
    const err: BusinessError = error as BusinessError;
    console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
  }
}
Enter fullscreen mode Exit fullscreen mode
  • You may see that the app stays in the recently used apps after we close it programmatically. Let’s fix it. Modify module.json5; set removeMissionAfterTerminate to true.
{
  "module": {
    ...
    "abilities": [
      {
        ...
        "removeMissionAfterTerminate": true,
      }
    ],
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode
  • The app should ask for permission.

Microphone Permission

dBMeter

We will use the Observed class decorator to create a view model. It will measure the decibel level and store UI state.

import { media } from '@kit.MediaKit';
import { fileIo as fs } from '@kit.CoreFileKit';

@Observed
export default class dBMeter {
  private recorder: media.AVRecorder | undefined = undefined;
  decibel: number = 0

  async measure(context: Context) {
    this.recorder = await media.createAVRecorder()

    let avProfile: media.AVRecorderProfile = {
      audioBitrate: 100000, // Audio bit rate.
      audioChannels: 2, // Number of audio channels.
      audioCodec: media.CodecMimeType.AUDIO_AAC, // Audio encoding format.
      audioSampleRate: 48000, // Audio sampling rate.
      fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // Container format.
    };

    let filePath: string = context.cacheDir + `/file.mp3`;
    let audioFile: fs.File = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
    let fileFd = audioFile.fd; // Obtain the file FD.

    let avConfig: media.AVRecorderConfig = {
      audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // Audio input source.
      profile: avProfile,
      url: 'fd://' + fileFd.toString(), // Obtain the file descriptor of the created audio file
    };

    await this.recorder.prepare(avConfig)
    await this.recorder.start()

    setInterval(async () => {
      this.decibel = await this.getDecibel()
      console.log('decibel', this.decibel)
    }, 200)
  }

  private async getDecibel(): Promise<number> {
    const amp = await this.recorder?.getAudioCapturerMaxAmplitude()!
    if (amp === 0) {
      return 0
    }
    const db = Math.round(20 * Math.log10(amp))
    return db;
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Let’s modify the Index.ets to see things in action.
import { abilityAccessCtrl, common, PermissionRequestResult, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import dBMeter from '../viewmodel/dBMeter';

@Entry
@Component
struct Index {
  @State dBMeter: dBMeter = new dBMeter()

  onDidBuild(): void {
    this.requestMicPermission().then(() => {
      this.dBMeter.measure(this.getUIContext().getHostContext()!)
    })
  }

  build() {
    Stack() {
      Progress({
        type: ProgressType.ScaleRing,
        value: this.dBMeter.decibel,
        total: 120
      })
        .size({ width: '100%', height: '100%' })
        .color(this.getColor(this.dBMeter.decibel))
        .style({
          enableSmoothEffect: true,
          strokeWidth: 50,
          scaleCount: 60,
          scaleWidth: 4
        })

      Text(`${this.dBMeter.decibel} dB`).fontSize(16)
    }.size({ width: '100%', height: '100%' })
  }

  getColor(val: number) {
    if (val < 80) {
      return Color.Green
    }
    if (val < 90) {
      return Color.Yellow
    }
    if (val < 100) {
      return Color.Orange
    }
    return Color.Red
  }

  async requestMicPermission() {
    const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    const context = this.getUIContext().getHostContext()
    const permissions: Permissions[] = ['ohos.permission.MICROPHONE'];

    try {
      // request permission from user
      let granted =
        (await (atManager.requestPermissionsFromUser(context,
          permissions) as Promise<PermissionRequestResult>)).authResults[0] === 0
      if (!granted) {
        // request permission on settings
        granted = (await atManager.requestPermissionOnSetting(context, permissions))[0] ===
        abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED

        if (!granted) {
          await (this.getUIContext().getHostContext() as common.UIAbilityContext).terminateSelf()
        }
      }
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The result should look like this.

Decibel Meter

Conclusion

Congratulations for not giving up! Keep calm and protect your ears. See you all in new adventures. :)
~Fortuna Favet Fortibus

References

(https://forums.developer.huawei.com/forumPortal/en/topic/0203189328610671010?fid=0102647487706140266)

Written by Mr. Karaaslan

Top comments (0)