鸿蒙NEXT开发案例:分贝仪

zhongcx
发布于 2024-12-1 09:50
浏览
1收藏

鸿蒙NEXT开发案例:分贝仪-鸿蒙开发者社区

【1】引言
分贝仪是一个简单的应用,用于测量周围环境的噪音水平。通过麦克风采集音频数据,计算当前的分贝值,并在界面上实时显示。该应用不仅展示了鸿蒙系统的基础功能,还涉及到了权限管理、音频处理和UI设计等多个方面。
【2】环境准备
电脑系统:windows 10
开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
工程版本:API 12
真机:mate60 pro
语言:ArkTS、ArkUI
权限:ohos.permission.MICROPHONE(麦克风权限)
系统库:
• @kit.AudioKit:用于音频处理的库。
• @kit.AbilityKit:用于权限管理和应用能力的库。
• @kit.BasicServicesKit:提供基本的服务支持,如错误处理等。
【3】功能模块
3.1 权限管理
在使用麦克风之前,需要请求用户的权限。如果用户拒绝,会显示一个对话框引导用户手动开启权限。

// 请求用户权限
requestPermissionsFromUser() {
  const context = getContext(this) as common.UIAbilityContext;
  const atManager = abilityAccessCtrl.createAtManager();
  atManager.requestPermissionsFromUser(context, this.requiredPermissions, (err, data) => {
    const grantStatus: Array<number> = data.authResults;
    if (grantStatus.toString() == "-1") {
      this.showAlertDialog();
    } else if (grantStatus.toString() == "0") {
      this.initialize();
    }
  });
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

3.2 分贝计算
通过读取麦克风采集的音频数据,计算当前环境的分贝值。计算过程中会对音频样本进行归一化处理,并计算其均方根(RMS)值,最终转换成分贝值。

// 分贝计算
calculateDecibel(pcm: ArrayBuffer): number {
  let sum = 0;
  const pcmView = new DataView(pcm);
  const numSamples = pcm.byteLength / 2;

  for (let i = 0; i < pcm.byteLength; i += 2) {
    const sample = pcmView.getInt16(i, true) / 32767.0;
    sum += sample * sample;
  }

  const meanSquare = sum / numSamples;
  const rmsAmplitude = Math.sqrt(meanSquare);
  const referencePressure = 20e-6;
  const decibels = 20 * Math.log10(rmsAmplitude / referencePressure);

  if (isNaN(decibels)) {
    return -100;
  }

  const minDb = 20;
  const maxDb = 100;
  const mappedValue = ((decibels - minDb) / (maxDb - minDb)) * 100;
  return Math.max(0, Math.min(100, mappedValue));
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

3.3 UI设计
界面上包含一个仪表盘显示当前分贝值,以及一段文字描述当前的噪音水平。分贝值被映射到0到100的范围内,以适应仪表盘的显示需求。界面上还有两个按钮,分别用于开始和停止分贝测量。

// 构建UI
build() {
  Column() {
    Text("分贝仪")
      .width('100%')
      .height(44)
      .backgroundColor("#fe9900")
      .textAlign(TextAlign.Center)
      .fontColor(Color.White);

    Row() {
      Gauge({ value: this.currentDecibel, min: 1, max: 100 }) {
        Column() {
          Text(`${this.displayedDecibel}分贝`)
            .fontSize(25)
            .fontWeight(FontWeight.Medium)
            .fontColor("#323232")
            .width('40%')
            .height('30%')
            .textAlign(TextAlign.Center)
            .margin({ top: '22.2%' })
            .textOverflow({ overflow: TextOverflow.Ellipsis })
            .maxLines(1);

          Text(`${this.displayType}`)
            .fontSize(16)
            .fontColor("#848484")
            .fontWeight(FontWeight.Regular)
            .width('47.4%')
            .height('15%')
            .textAlign(TextAlign.Center)
            .backgroundColor("#e4e4e4")
            .borderRadius(5);
        }.width('100%');
      }
      .startAngle(225)
      .endAngle(135)
      .colors(this.gaugeColors)
      .height(250)
      .strokeWidth(18)
      .description(null)
      .trackShadow({ radius: 7, offsetX: 7, offsetY: 7 })
      .padding({ top: 30 });
    }.width('100%').justifyContent(FlexAlign.Center);

    Column() {
      ForEach(this.typeArray, (item: ValueBean, index: number) => {
        Row() {
          Text(item.description)
            .textAlign(TextAlign.Start)
            .fontColor("#3d3d3d");
        }.width(250)
          .padding({ bottom: 10, top: 10 })
          .borderWidth({ bottom: 1 })
          .borderColor("#737977");
      });
    }.width('100%');

    Row() {
      Button('开始检测').clickEffect({ level: ClickEffectLevel.LIGHT }).onClick(() => {
        if (this.audioRecorder) {
          this.startRecording();
        } else {
          this.requestPermissionsFromUser();
        }
      });

      Button('停止检测').clickEffect({ level: ClickEffectLevel.LIGHT }).onClick(() => {
        if (this.audioRecorder) {
          this.stopRecording();
        }
      });
    }.width('100%')
      .justifyContent(FlexAlign.SpaceEvenly)
      .padding({
        left: 20,
        right: 20,
        top: 40,
        bottom: 40
      });
  }.height('100%').width('100%');
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.

【4】关键代码解析
4.1 权限检查与请求
在应用启动时,首先检查是否已经获得了麦克风权限。如果没有获得权限,则请求用户授权。

// 检查权限
checkPermissions() {
  const atManager = abilityAccessCtrl.createAtManager();
  const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
  const tokenId = bundleInfo.appInfo.accessTokenId;

  const authResults = this.requiredPermissions.map((permission) => atManager.checkAccessTokenSync(tokenId, permission));
  return authResults.every(v => v === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED);
}

// 请求用户权限
requestPermissionsFromUser() {
  const context = getContext(this) as common.UIAbilityContext;
  const atManager = abilityAccessCtrl.createAtManager();
  atManager.requestPermissionsFromUser(context, this.requiredPermissions, (err, data) => {
    const grantStatus: Array<number> = data.authResults;
    if (grantStatus.toString() == "-1") {
      this.showAlertDialog();
    } else if (grantStatus.toString() == "0") {
      this.initialize();
    }
  });
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

4.2 音频记录器初始化
在获得权限后,初始化音频记录器,设置采样率、通道数、采样格式等参数,并开始监听音频数据。

// 初始化音频记录器
initialize() {
  const streamInfo: audio.AudioStreamInfo = {
    samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
    channels: audio.AudioChannel.CHANNEL_1,
    sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
    encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
  };

  const recorderInfo: audio.AudioCapturerInfo = {
    source: audio.SourceType.SOURCE_TYPE_MIC,
    capturerFlags: 0
  };

  const recorderOptions: audio.AudioCapturerOptions = {
    streamInfo: streamInfo,
    capturerInfo: recorderInfo
  };

  audio.createAudioCapturer(recorderOptions, (err, recorder) => {
    if (err) {
      console.error(`创建音频记录器失败, 错误码: ${err.code}, 错误信息: ${err.message}`);
      return;
    }
    console.info(`${this.TAG}: 音频记录器创建成功`);
    this.audioRecorder = recorder;

    if (this.audioRecorder !== undefined) {
      this.audioRecorder.on('readData', (buffer: ArrayBuffer) => {
        this.currentDecibel = this.calculateDecibel(buffer);
        this.updateDisplay();
      });
    }
  });
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.

4.3 更新显示
每秒钟更新一次显示的分贝值,并根据当前分贝值确定其所属的噪音级别。

// 更新显示
updateDisplay() {
  if (Date.now() - this.lastUpdateTimestamp > 1000) {
    this.lastUpdateTimestamp = Date.now();
    this.displayedDecibel = Math.floor(this.currentDecibel);

    for (const item of this.typeArray) {
      if (this.currentDecibel >= item.minDb && this.currentDecibel < item.maxDb) {
        this.displayType = item.label;
        break;
      }
    }
  }
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

【5】完整代码
5.1 配置麦克风权限
路径:src/main/module.json5

{

  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.MICROPHONE",
        "reason": "$string:microphone_reason",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when":"inuse"
        }
      }
    ],
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

5.2 配置权限弹窗时的描述文字
路径:src/main/resources/base/element/string.json

{
  "string": [
    {
      "name": "module_desc",
      "value": "module description"
    },
    {
      "name": "EntryAbility_desc",
      "value": "description"
    },
    {
      "name": "EntryAbility_label",
      "value": "label"
    },
    {
      "name": "microphone_reason",
      "value": "需要麦克风权限说明"
    }
  ]
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

5.3 完整代码
路径:src/main/ets/pages/Index.ets

import { audio } from '@kit.AudioKit'; // 导入音频相关的库
import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit'; // 导入权限管理相关的库
import { BusinessError } from '@kit.BasicServicesKit'; // 导入业务错误处理

// 定义一个类,用于存储分贝范围及其描述
class ValueBean {
  label: string; // 标签
  description: string; // 描述
  minDb: number; // 最小分贝值
  maxDb: number; // 最大分贝值
  colorStart: string; // 起始颜色
  colorEnd: string; // 结束颜色

  // 构造函数,初始化属性
  constructor(label: string, description: string, minDb: number, maxDb: number, colorStart: string, colorEnd: string) {
    this.label = label;
    this.description = description;
    this.minDb = minDb;
    this.maxDb = maxDb;
    this.colorStart = colorStart;
    this.colorEnd = colorEnd;
  }
}

// 定义分贝仪组件
@Entry
@Component
struct DecibelMeter {
  TAG: string = 'DecibelMeter'; // 日志标签
  audioRecorder: audio.AudioCapturer | undefined = undefined; // 音频记录器
  requiredPermissions: Array<Permissions> = ['ohos.permission.MICROPHONE']; // 需要的权限
  @State currentDecibel: number = 0; // 当前分贝值
  @State displayedDecibel: number = 0; // 显示的分贝值
  lastUpdateTimestamp: number = 0; // 上次更新时间戳
  @State displayType: string = ''; // 当前显示类型
  // 定义分贝范围及其描述
  typeArray: ValueBean[] = [
    new ValueBean("寂静", "0~20dB : 寂静,几乎感觉不到", 0, 20, "#02b003", "#016502"),
    new ValueBean("安静", '20~40dB :安静,轻声交谈', 20, 40, "#7ed709", "#4f8800"),
    new ValueBean("正常", '40~60dB :正常,普通室内谈话', 40, 60, "#ffef01", "#ad9e04"),
    new ValueBean("吵闹", '60~80dB :吵闹,大声说话', 60, 80, "#f88200", "#965001"),
    new ValueBean("很吵", '80~100dB: 很吵,可使听力受损', 80, 100, "#f80000", "#9d0001"),
  ];
  gaugeColors: [LinearGradient, number][] = [] // 存储仪表颜色的数组

  // 组件即将出现时调用
  aboutToAppear(): void {
    // 初始化仪表颜色
    for (let i = 0; i < this.typeArray.length; i++) {
      this.gaugeColors.push([new LinearGradient([{ color: this.typeArray[i].colorStart, offset: 0 },
        { color: this.typeArray[i].colorEnd, offset: 1 }]), 1])
    }
  }

  // 请求用户权限
  requestPermissionsFromUser() {
    const context = getContext(this) as common.UIAbilityContext; // 获取上下文
    const atManager = abilityAccessCtrl.createAtManager(); // 创建权限管理器
    // 请求权限
    atManager.requestPermissionsFromUser(context, this.requiredPermissions, (err, data) => {
      const grantStatus: Array<number> = data.authResults; // 获取授权结果
      if (grantStatus.toString() == "-1") { // 用户拒绝权限
        this.showAlertDialog(); // 显示提示对话框
      } else if (grantStatus.toString() == "0") { // 用户同意权限
        this.initialize(); // 初始化音频记录器
      }
    });
  }

  // 显示对话框提示用户开启权限
  showAlertDialog() {
    this.getUIContext().showAlertDialog({
      autoCancel: true, // 自动取消
      title: '权限申请', // 对话框标题
      message: '如需使用此功能,请前往设置页面开启麦克风权限。', // 对话框消息
      cancel: () => {
      },
      confirm: {
        defaultFocus: true, // 默认聚焦确认按钮
        value: '好的', // 确认按钮文本
        action: () => {
          this.openPermissionSettingsPage(); // 打开权限设置页面
        }
      },
      onWillDismiss: () => {
      },
      alignment: DialogAlignment.Center, // 对话框对齐方式
    });
  }

  // 打开权限设置页面
  openPermissionSettingsPage() {
    const context = getContext() as common.UIAbilityContext; // 获取上下文
    const bundleInfo =
      bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); // 获取包信息
    context.startAbility({
      bundleName: 'com.huawei.hmos.settings', // 设置页面的包名
      abilityName: 'com.huawei.hmos.settings.MainAbility', // 设置页面的能力名
      uri: 'application_info_entry', // 打开设置->应用和元服务
      parameters: {
        pushParams: bundleInfo.name // 按照包名打开对应设置页
      }
    });
  }

  // 分贝计算
  calculateDecibel(pcm: ArrayBuffer): number {
    let sum = 0; // 初始化平方和
    const pcmView = new DataView(pcm); // 创建数据视图
    const numSamples = pcm.byteLength / 2; // 计算样本数量

    // 归一化样本值并计算平方和
    for (let i = 0; i < pcm.byteLength; i += 2) {
      const sample = pcmView.getInt16(i, true) / 32767.0; // 归一化样本值
      sum += sample * sample; // 计算平方和
    }

    // 计算平均平方值
    const meanSquare = sum / numSamples; // 计算均方

    // 计算RMS(均方根)振幅
    const rmsAmplitude = Math.sqrt(meanSquare); // 计算RMS值

    // 使用标准参考压力值
    const referencePressure = 20e-6; // 20 μPa

    // 计算分贝值
    const decibels = 20 * Math.log10(rmsAmplitude / referencePressure); // 计算分贝

    // 处理NaN值
    if (isNaN(decibels)) {
      return -100; // 返回一个极小值表示静音
    }

    // 调整动态范围
    const minDb = 20; // 调整最小分贝值
    const maxDb = 100; // 调整最大分贝值

    // 将分贝值映射到0到100之间的范围
    const mappedValue = ((decibels - minDb) / (maxDb - minDb)) * 100; // 映射分贝值

    // 确保值在0到100之间
    return Math.max(0, Math.min(100, mappedValue)); // 返回映射后的值
  }

  // 初始化音频记录器
  initialize() {
    const streamInfo: audio.AudioStreamInfo = {
      samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, // 采样率
      channels: audio.AudioChannel.CHANNEL_1, // 单声道
      sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
      encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码类型
    };
    const recorderInfo: audio.AudioCapturerInfo = {
      source: audio.SourceType.SOURCE_TYPE_MIC, // 音频源为麦克风
      capturerFlags: 0 // 捕获标志
    };
    const recorderOptions: audio.AudioCapturerOptions = {
      streamInfo: streamInfo, // 音频流信息
      capturerInfo: recorderInfo // 记录器信息
    };
    // 创建音频记录器
    audio.createAudioCapturer(recorderOptions, (err, recorder) => {
      if (err) {
        console.error(`创建音频记录器失败, 错误码: ${err.code}, 错误信息: ${err.message}`); // 错误处理
        return;
      }
      console.info(`${this.TAG}: 音频记录器创建成功`); // 成功日志
      this.audioRecorder = recorder; // 保存记录器实例
      if (this.audioRecorder !== undefined) {
        // 监听音频数据
        this.audioRecorder.on('readData', (buffer: ArrayBuffer) => {
          this.currentDecibel = this.calculateDecibel(buffer); // 计算当前分贝值
          this.updateDisplay(); // 更新显示
        });
      }
      this.startRecording(); // 开始录音
    });
  }

  // 开始录音
  startRecording() {
    if (this.audioRecorder !== undefined) { // 检查音频记录器是否已定义
      this.audioRecorder.start((err: BusinessError) => { // 调用开始录音方法
        if (err) {
          console.error('开始录音失败'); // 记录错误信息
        } else {
          console.info('开始录音成功'); // 记录成功信息
        }
      });
    }
  }

  // 停止录音
  stopRecording() {
    if (this.audioRecorder !== undefined) { // 检查音频记录器是否已定义
      this.audioRecorder.stop((err: BusinessError) => { // 调用停止录音方法
        if (err) {
          console.error('停止录音失败'); // 记录错误信息
        } else {
          console.info('停止录音成功'); // 记录成功信息
        }
      });
    }
  }

  // 更新显示
  updateDisplay() {
    if (Date.now() - this.lastUpdateTimestamp > 1000) { // 每隔1秒更新一次显示
      this.lastUpdateTimestamp = Date.now(); // 更新最后更新时间戳
      this.displayedDecibel = Math.floor(this.currentDecibel); // 将当前分贝值取整并赋值给显示的分贝值
      // 遍历分贝类型数组,确定当前分贝值对应的类型
      for (const item of this.typeArray) {
        if (this.currentDecibel >= item.minDb && this.currentDecibel < item.maxDb) { // 检查当前分贝值是否在某个范围内
          this.displayType = item.label; // 设置当前显示类型
          break; // 找到对应类型后退出循环
        }
      }
    }
  }

  // 检查权限
  checkPermissions() {
    const atManager = abilityAccessCtrl.createAtManager(); // 创建权限管理器
    const bundleInfo =
      bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); // 获取包信息
    const tokenId = bundleInfo.appInfo.accessTokenId; // 获取应用的唯一标识
    // 检查每个权限的授权状态
    const authResults =
      this.requiredPermissions.map((permission) => atManager.checkAccessTokenSync(tokenId, permission));
    return authResults.every(v => v === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED); // 返回是否所有权限都被授予
  }

  // 构建UI
  build() {
    Column() {
      Text("分贝仪")// 显示标题
        .width('100%')// 设置宽度为100%
        .height(44)// 设置高度为44
        .backgroundColor("#fe9900")// 设置背景颜色
        .textAlign(TextAlign.Center)// 设置文本对齐方式
        .fontColor(Color.White); // 设置字体颜色

      Row() {
        Gauge({ value: this.currentDecibel, min: 1, max: 100 }) { // 创建仪表,显示当前分贝值
          Column() {
            Text(`${this.displayedDecibel}分贝`)// 显示当前分贝值
              .fontSize(25)// 设置字体大小
              .fontWeight(FontWeight.Medium)// 设置字体粗细
              .fontColor("#323232")// 设置字体颜色
              .width('40%')// 设置宽度为40%
              .height('30%')// 设置高度为30%
              .textAlign(TextAlign.Center)// 设置文本对齐方式
              .margin({ top: '22.2%' })// 设置上边距
              .textOverflow({ overflow: TextOverflow.Ellipsis })// 设置文本溢出处理
              .maxLines(1); // 设置最大行数为1

            Text(`${this.displayType}`)// 显示当前类型
              .fontSize(16)// 设置字体大小
              .fontColor("#848484")// 设置字体颜色
              .fontWeight(FontWeight.Regular)// 设置字体粗细
              .width('47.4%')// 设置宽度为47.4%
              .height('15%')// 设置高度为15%
              .textAlign(TextAlign.Center)// 设置文本对齐方式
              .backgroundColor("#e4e4e4")// 设置背景颜色
              .borderRadius(5); // 设置圆角
          }.width('100%'); // 设置列宽度为100%
        }
        .startAngle(225) // 设置仪表起始角度
        .endAngle(135) // 设置仪表结束角度
        .colors(this.gaugeColors) // 设置仪表颜色
        .height(250) // 设置仪表高度
        .strokeWidth(18) // 设置仪表边框宽度
        .description(null) // 设置描述为null
        .trackShadow({ radius: 7, offsetX: 7, offsetY: 7 }) // 设置阴影效果
        .padding({ top: 30 }); // 设置内边距
      }.width('100%').justifyContent(FlexAlign.Center); // 设置行宽度为100%并居中对齐

      Column() {
        ForEach(this.typeArray, (item: ValueBean, index: number) => { // 遍历分贝类型数组
          Row() {
            Text(item.description)// 显示每个类型的描述
              .textAlign(TextAlign.Start)// 设置文本对齐方式
              .fontColor("#3d3d3d"); // 设置字体颜色
          }.width(250) // 设置行宽度为250
          .padding({ bottom: 10, top: 10 }) // 设置上下内边距
          .borderWidth({ bottom: 1 }) // 设置下边框宽度
          .borderColor("#737977"); // 设置下边框颜色
        });
      }.width('100%'); // 设置列宽度为100%

      Row() {
        Button('开始检测').clickEffect({ level: ClickEffectLevel.LIGHT }).onClick(() => { // 创建开始检测按钮
          if (this.audioRecorder) { // 检查音频记录器是否已定义
            this.startRecording(); // 开始录音
          } else {
            this.requestPermissionsFromUser(); // 请求用户权限
          }
        });

        Button('停止检测').clickEffect({ level: ClickEffectLevel.LIGHT }).onClick(() => { // 创建停止检测按钮
          if (this.audioRecorder) { // 检查音频记录器是否已定义
            this.stopRecording(); // 停止录音
          }
        });
      }.width('100%') // 设置行宽度为100%
      .justifyContent(FlexAlign.SpaceEvenly) // 设置内容均匀分布
      .padding({
        // 设置内边距
        left: 20,
        right: 20,
        top: 40,
        bottom: 40
      });
    }.height('100%').width('100%'); // 设置列高度和宽度为100%
  }

  // 页面显示时的处理
  onPageShow(): void {
    const hasPermission = this.checkPermissions(); // 检查权限
    console.info(`麦克风权限状态: ${hasPermission ? '已开启' : '未开启'}`); // 打印权限状态
    if (hasPermission) { // 如果权限已开启
      if (this.audioRecorder) { // 检查音频记录器是否已定义
        this.startRecording(); // 开始录音
      } else {
        this.requestPermissionsFromUser(); // 请求用户权限
      }
    }
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.

分类
标签
收藏 1
回复
举报
1


回复
    相关推荐