鸿蒙跨端噪音污染监测系统开发指南 原创

进修的泡芙
发布于 2025-6-23 13:06
浏览
0收藏

鸿蒙跨端噪音污染监测系统开发指南

一、项目概述

本文基于HarmonyOS的音频处理能力和分布式技术,开发一套智能噪音污染监测系统。该系统采用麦克风采样率自适应技术、优化的FFT频谱分析算法和数据分级存储策略,并借鉴《鸿蒙跨端U同步》中的多设备同步技术,实现环境噪音的精准监测、分析和多终端协同预警。

二、系统架构

±--------------------+ ±--------------------+ ±--------------------+
监测设备 <-----> 分布式数据总线 <-----> 监控终端
(智能终端/传感器) (Distributed Bus) (手机/平板/管理端)
±---------±---------+ ±---------±---------+ ±---------±---------+

±---------v----------+ ±---------v----------+ ±---------v----------+
音频采集模块 噪音分析模块 数据同步模块
(采样率自适应) (FFT优化/分级) (多设备协同/预警)

±--------------------+ ±--------------------+ ±--------------------+

三、核心代码实现
噪音监测服务实现

// src/main/ets/service/NoiseMonitorService.ts
import { distributedData } from ‘@ohos.data.distributedData’;
import { BusinessError } from ‘@ohos.base’;
import { audio } from ‘@ohos.multimedia.audio’;
import { power } from ‘@ohos.power’;
import { notification } from ‘@ohos.notification’;
import { taskpool } from ‘@ohos.taskpool’;
import { fileIo } from ‘@ohos.fileio’;
import { zlib } from ‘@ohos.zlib’;

interface NoiseData {
timestamp: number;
dB: number; // 分贝值
frequencyPeak: number; // 主要频率峰值 (Hz)
spectrum: number[]; // 频谱数据 (简化版)
location?: string; // 位置信息
deviceId: string;
isSynced: boolean;
interface NoiseLevel {

timestamp: number;
level: ‘quiet’ ‘moderate’ ‘loud’ ‘very_loud’
‘extreme’;
duration: number; // 持续时间(秒)
isSynced: boolean;
interface DeviceStatus {

batteryLevel: number;
isLowPowerMode: boolean;
sampleRate: number; // 当前采样率 (Hz)
lastSyncTime: number;
export class NoiseMonitorService {

private static instance: NoiseMonitorService;
private kvStore: distributedData.KVStore | null = null;
private readonly STORE_ID = ‘noise_monitor_store’;
private audioCapturer: audio.AudioCapturer | null = null;
private noiseData: NoiseData[] = [];
private noiseLevels: NoiseLevel[] = [];
private deviceStatus: DeviceStatus = {
batteryLevel: 100,
isLowPowerMode: false,
sampleRate: 44100, // 默认44.1kHz
lastSyncTime: 0
};
private rawAudioData: number[] = [];
private readonly SYNC_INTERVAL = 5 60 1000; // 5分钟同步一次

private constructor() {
this.initKVStore();
this.loadLocalData();
this.adjustSampleRate();
public static getInstance(): NoiseMonitorService {

if (!NoiseMonitorService.instance) {
  NoiseMonitorService.instance = new NoiseMonitorService();

return NoiseMonitorService.instance;

private async initKVStore(): Promise<void> {

try {
  const options: distributedData.KVManagerConfig = {
    bundleName: 'com.example.noisemonitor',
    userInfo: {
      userId: '0',
      userType: distributedData.UserType.SAME_USER_ID

};

  const kvManager = distributedData.createKVManager(options);
  this.kvStore = await kvManager.getKVStore({
    storeId: this.STORE_ID,
    options: {
      createIfMissing: true,
      encrypt: false,
      backup: false,
      autoSync: true,
      kvStoreType: distributedData.KVStoreType.SINGLE_VERSION

});

  this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_REMOTE, (data) => {
    this.handleRemoteDataChange(data);
  });

catch (e) {

  console.error(Failed to initialize KVStore. Code: {e.code}, message: {e.message});

}

private async loadLocalData(): Promise<void> {
try {
// 加载噪音数据
const noiseFile = await fileIo.open(‘data/noise_data.bin’, 0o666);
const noiseData = await fileIo.read(noiseFile.fd, new ArrayBuffer(0));
await fileIo.close(noiseFile.fd);

  if (noiseData) {
    const decompressed = await zlib.deflateSync(noiseData);
    this.noiseData = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(decompressed)));

// 加载噪音等级记录

  const levelFile = await fileIo.open('data/noise_levels.bin', 0o666);
  const levelData = await fileIo.read(levelFile.fd, new ArrayBuffer(0));
  await fileIo.close(levelFile.fd);
  
  if (levelData) {
    const decompressed = await zlib.deflateSync(levelData);
    this.noiseLevels = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(decompressed)));

// 加载设备状态

  const statusFile = await fileIo.open('data/device_status.bin', 0o666);
  const statusData = await fileIo.read(statusFile.fd, new ArrayBuffer(0));
  await fileIo.close(statusFile.fd);
  
  if (statusData) {
    const decompressed = await zlib.deflateSync(statusData);
    this.deviceStatus = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(decompressed)));

} catch (e) {

  console.log('No local data found or error reading file');

}

private async saveLocalData(): Promise<void> {
try {
// 确保目录存在
await fileIo.mkdir(‘data’);

  // 保存噪音数据
  const noiseStr = JSON.stringify(this.noiseData);
  const noiseCompressed = await zlib.inflateSync(new Uint8Array(noiseStr.split('').map(c => c.charCodeAt(0))));
  
  const noiseFile = await fileIo.open('data/noise_data.bin', 0o666 | fileIo.OpenMode.CREATE);
  await fileIo.write(noiseFile.fd, noiseCompressed.buffer);
  await fileIo.close(noiseFile.fd);
  
  // 保存噪音等级
  const levelStr = JSON.stringify(this.noiseLevels);
  const levelCompressed = await zlib.inflateSync(new Uint8Array(levelStr.split('').map(c => c.charCodeAt(0))));
  
  const levelFile = await fileIo.open('data/noise_levels.bin', 0o666 | fileIo.OpenMode.CREATE);
  await fileIo.write(levelFile.fd, levelCompressed.buffer);
  await fileIo.close(levelFile.fd);
  
  // 保存设备状态
  const statusStr = JSON.stringify(this.deviceStatus);
  const statusCompressed = await zlib.inflateSync(new Uint8Array(statusStr.split('').map(c => c.charCodeAt(0))));
  
  const statusFile = await fileIo.open('data/device_status.bin', 0o666 | fileIo.OpenMode.CREATE);
  await fileIo.write(statusFile.fd, statusCompressed.buffer);
  await fileIo.close(statusFile.fd);

catch (e) {

  console.error(Failed to save local data. Code: {e.code}, message: {e.message});

}

private async adjustSampleRate(): Promise<void> {
// 根据电池状态调整采样率
await this.checkBatteryStatus();

let newSampleRate = 44100; // 默认44.1kHz

if (this.deviceStatus.isLowPowerMode) {
  newSampleRate = 16000; // 低电量模式使用16kHz

else {

  // 根据最近噪音水平动态调整
  if (this.noiseLevels.length > 0) {
    const lastLevel = this.noiseLevels[this.noiseLevels.length - 1];
    if (lastLevel.level = 'quiet' || lastLevel.level = 'moderate') {
      newSampleRate = 22050; // 安静环境降低采样率

else if (lastLevel.level === ‘extreme’) {

      newSampleRate = 48000; // 极端噪音提高采样率

}

if (newSampleRate !== this.deviceStatus.sampleRate) {

  this.deviceStatus.sampleRate = newSampleRate;
  await this.restartAudioCapture();

}

private async checkBatteryStatus(): Promise<void> {
try {
const batteryInfo = await power.getBatteryInfo();
this.deviceStatus.batteryLevel = batteryInfo.batterySoc;

  // 低电量模式切换 (低于30%电量)
  const shouldLowPower = batteryInfo.batterySoc < 30;
  if (shouldLowPower !== this.deviceStatus.isLowPowerMode) {
    this.deviceStatus.isLowPowerMode = shouldLowPower;
    await this.adjustSampleRate();

} catch (e) {

  console.error(Failed to get battery info. Code: {e.code}, message: {e.message});

}

public async startMonitoring(): Promise<boolean> {
try {
// 创建音频采集器
const audioCapturerConfig: audio.AudioCapturerConfig = {
audioStreamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
channels: audio.AudioChannel.CHANNEL_1,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
},
capturerInfo: {
source: audio.SourceType.SOURCE_TYPE_MIC,
capturerFlags: 0
};

  this.audioCapturer = await audio.createAudioCapturer(audioCapturerConfig);
  await this.audioCapturer.start();
  
  // 启动音频数据采集循环
  this.startAudioCaptureLoop();
  
  return true;

catch (e) {

  console.error(Failed to start monitoring. Code: {e.code}, message: {e.message});
  return false;

}

private async restartAudioCapture(): Promise<void> {
if (this.audioCapturer) {
await this.audioCapturer.stop();
await this.audioCapturer.release();
this.audioCapturer = null;
await this.startMonitoring();

private async startAudioCaptureLoop(): Promise<void> {

if (!this.audioCapturer) return;

const bufferSize = await this.audioCapturer.getBufferSize();
const buffer = new ArrayBuffer(bufferSize);

while (this.audioCapturer) {
  try {
    const readResult = await this.audioCapturer.read(buffer, false);
    if (readResult === bufferSize) {
      const audioData = new Int16Array(buffer);
      this.processAudioData(audioData);

} catch (e) {

    console.error(Failed to read audio data. Code: {e.code}, message: {e.message});
    break;

// 根据采样率调整采集间隔

  await new Promise(resolve => setTimeout(resolve, 1000 / (this.deviceStatus.sampleRate / bufferSize * 2)));

}

private async processAudioData(audioData: Int16Array): Promise<void> {
// 1. 保存原始数据 (用于后续分析)
this.rawAudioData.push(…Array.from(audioData));

// 2. 当积累足够数据时进行处理 (约1秒数据)
if (this.rawAudioData.length >= this.deviceStatus.sampleRate) {
  // 使用任务池并行处理音频数据
  const task = new taskpool.Task(this.analyzeAudioDataTask, 
    this.rawAudioData.slice(0, this.deviceStatus.sampleRate),
    this.deviceStatus.sampleRate
  );
  
  const analysisResult = await taskpool.execute(task) as {
    dB: number, peakFreq: number, spectrum: number[]
  };
  
  // 3. 创建噪音数据记录
  const now = Date.now();
  const newData: NoiseData = {
    timestamp: now,
    dB: analysisResult.dB,
    frequencyPeak: analysisResult.peakFreq,
    spectrum: analysisResult.spectrum,
    deviceId: this.getDeviceId(),
    isSynced: false
  };
  
  this.noiseData.push(newData);
  
  // 4. 更新噪音等级记录
  this.updateNoiseLevel(newData.dB, now);
  
  // 5. 清理已处理的数据 (保留部分重叠)
  this.rawAudioData = this.rawAudioData.slice(this.deviceStatus.sampleRate / 2);
  
  // 6. 保存并同步数据
  await this.saveLocalData();
  
  if (now - this.deviceStatus.lastSyncTime > this.SYNC_INTERVAL) {
    await this.syncData();
    this.deviceStatus.lastSyncTime = now;

// 7. 根据噪音水平调整采样率

  await this.adjustSampleRate();

}

private analyzeAudioDataTask(audioData: number[], sampleRate: number): {
dB: number, peakFreq: number, spectrum: number[]
{

// 1. 计算分贝值 (简化版)
let sum = 0;
for (const sample of audioData) {
  sum += sample * sample;

const rms = Math.sqrt(sum / audioData.length);

const dB = 20 * Math.log10(rms / 32768); // 16位PCM最大值为32768

// 2. FFT频谱分析 (简化版)
const fftSize = 1024;
const spectrum = new Array(fftSize / 2).fill(0);

// 模拟FFT计算 (实际应用中应使用优化的FFT算法)
for (let i = 0; i < fftSize / 2; i++) {
  const freq = i * sampleRate / fftSize;
  if (freq < 20000) { // 人耳可听范围
    spectrum[i] = Math.random() * 10 + dB - 30; // 模拟频谱数据

}

// 3. 找出主要频率峰值
let peakFreq = 0;
let maxAmp = -Infinity;
for (let i = 1; i < spectrum.length - 1; i++) {
  if (spectrum[i] > maxAmp && spectrum[i] > spectrum[i - 1] && spectrum[i] > spectrum[i + 1]) {
    maxAmp = spectrum[i];
    peakFreq = i * sampleRate / fftSize;

}

return { dB, peakFreq, spectrum: spectrum.slice(0, 50) }; // 只返回前50个频点

private updateNoiseLevel(dB: number, timestamp: number): void {

const level = this.getNoiseLevel(dB);

// 检查是否与上一个记录相同等级
if (this.noiseLevels.length > 0) {
  const lastLevel = this.noiseLevels[this.noiseLevels.length - 1];
  if (lastLevel.level === level) {
    // 相同等级,更新持续时间
    lastLevel.duration = (timestamp - lastLevel.timestamp) / 1000;
    return;

}

// 添加新的噪音等级记录
const newLevel: NoiseLevel = {
  timestamp,
  level,
  duration: 0,
  isSynced: false
};

this.noiseLevels.push(newLevel);

private getNoiseLevel(dB: number): ‘quiet’ ‘moderate’ ‘loud’ ‘very_loud’
‘extreme’ {

if (dB < 40) return 'quiet';        // 安静: <40dB
if (dB < 60) return 'moderate';     // 中等: 40-60dB
if (dB < 80) return 'loud';         // 吵闹: 60-80dB
if (dB < 100) return 'very_loud';   // 非常吵闹: 80-100dB
return 'extreme';                    // 极端噪音: >100dB

private async triggerAlert(dB: number, timestamp: number): Promise<void> {

const level = this.getNoiseLevel(dB);
if (level = 'very_loud' || level = 'extreme') {
  const message = 噪音预警: {dB.toFixed(1)}dB ({level});
  
  try {
    await notification.publish({
      id: 1,
      contentType: notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
      normal: {
        title: '噪音污染预警',
        text: message,
        additionalText: new Date(timestamp).toLocaleTimeString()

});

catch (e) {

    console.error(Failed to publish notification. Code: {e.code}, message: {e.message});

// 通过分布式数据总线广播预警

  if (this.kvStore) {
    try {
      await this.kvStore.put('noise_alert', {
        value: {
          timestamp,
          dB,
          level,
          message,
          deviceId: this.getDeviceId()

});

catch (e) {

      console.error(Failed to sync alert. Code: {e.code}, message: {e.message});

}

}

private getDeviceId(): string {
// 实际应用中应获取真实设备ID
return ‘noise_sensor_’ + Math.random().toString(36).substr(2, 9);
private async syncData(): Promise<void> {

if (!this.kvStore) return;

try {
  // 同步噪音数据
  const unsyncedNoise = this.noiseData.filter(d => !d.isSynced);
  if (unsyncedNoise.length > 0) {
    await this.kvStore.put('noise_data', { value: unsyncedNoise });
    this.noiseData.forEach(d => {
      if (!d.isSynced) d.isSynced = true;
    });

// 同步噪音等级

  const unsyncedLevels = this.noiseLevels.filter(l => !l.isSynced);
  if (unsyncedLevels.length > 0) {
    await this.kvStore.put('noise_levels', { value: unsyncedLevels });
    this.noiseLevels.forEach(l => {
      if (!l.isSynced) l.isSynced = true;
    });

} catch (e) {

  console.error(Failed to sync data. Code: {e.code}, message: {e.message});

}

private handleRemoteDataChange(data: distributedData.ChangeData): void {
data.insertEntries.forEach((entry: distributedData.Entry) => {
if (entry.key === ‘noise_data’) {
const remoteData = entry.value.value as NoiseData[];
this.mergeNoiseData(remoteData);
else if (entry.key === ‘noise_levels’) {

    const remoteLevels = entry.value.value as NoiseLevel[];
    this.mergeNoiseLevels(remoteLevels);

else if (entry.key === ‘noise_alert’) {

    const remoteAlert = entry.value.value as {
      timestamp: number,
      dB: number,
      level: string,
      message: string,
      deviceId: string
    };
    
    this.handleRemoteAlert(remoteAlert);

});

private mergeNoiseData(remoteData: NoiseData[]): void {

remoteData.forEach(remote => {
  const existing = this.noiseData.find(local => 
    local.timestamp === remote.timestamp && 
    local.deviceId === remote.deviceId
  );
  
  if (!existing) {
    this.noiseData.push(remote);

else {

    // 合并策略:保留更高精度的数据
    if (remote.dB > existing.dB) {
      existing.dB = remote.dB;
      existing.frequencyPeak = remote.frequencyPeak;
      existing.spectrum = remote.spectrum;

}

});

// 按时间排序
this.noiseData.sort((a, b) => a.timestamp - b.timestamp);

private mergeNoiseLevels(remoteLevels: NoiseLevel[]): void {

remoteLevels.forEach(remote => {
  const existing = this.noiseLevels.find(local => 
    local.timestamp === remote.timestamp && 
    local.level === remote.level
  );
  
  if (!existing) {
    this.noiseLevels.push(remote);

else {

    // 合并持续时间
    existing.duration = Math.max(existing.duration, remote.duration);

});

// 按时间排序
this.noiseLevels.sort((a, b) => a.timestamp - b.timestamp);

private handleRemoteAlert(alert: {

timestamp: number,
dB: number,
level: string,
message: string,
deviceId: string

}): void {
// 避免处理自己的预警
if (alert.deviceId === this.getDeviceId()) return;

// 显示远程设备发来的预警
notification.publish({
  id: 2,
  contentType: notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
  normal: {
    title: 远程设备预警 (${alert.deviceId.substring(0, 6)}),
    text: alert.message,
    additionalText: new Date(alert.timestamp).toLocaleTimeString()

}).catch(e => {

  console.error(Failed to publish remote alert. Code: {e.code}, message: {e.message});
});

public getRecentNoiseData(count: number = 10): NoiseData[] {

return this.noiseData.slice(-count).reverse();

public getRecentNoiseLevels(count: number = 5): NoiseLevel[] {

return this.noiseLevels.slice(-count).reverse();

public getCurrentStatus(): DeviceStatus {

return this.deviceStatus;

public async stopMonitoring(): Promise<void> {

if (this.audioCapturer) {
  await this.audioCapturer.stop();
  await this.audioCapturer.release();
  this.audioCapturer = null;

await this.saveLocalData();

await this.syncData();

public async destroy(): Promise<void> {

await this.stopMonitoring();

if (this.kvStore) {
  this.kvStore.off('dataChange');

}

噪音监测组件实现

// src/main/ets/components/NoiseMonitor.ets
@Component
export struct NoiseMonitor {
private noiseService = NoiseMonitorService.getInstance();
@State noiseData: NoiseData[] = [];
@State noiseLevels: NoiseLevel[] = [];
@State deviceStatus: DeviceStatus = {
batteryLevel: 100,
isLowPowerMode: false,
sampleRate: 44100,
lastSyncTime: 0
};
@State isMonitoring: boolean = false;
private timer: number = 0;

aboutToAppear(): void {
this.loadData();
this.startMonitoring();
this.startAutoRefresh();
aboutToDisappear(): void {

this.stopAutoRefresh();

private loadData(): void {

this.noiseData = this.noiseService.getRecentNoiseData();
this.noiseLevels = this.noiseService.getRecentNoiseLevels();
this.deviceStatus = this.noiseService.getCurrentStatus();

private async startMonitoring(): Promise<void> {

this.isMonitoring = await this.noiseService.startMonitoring();

private startAutoRefresh(): void {

this.timer = setInterval(() => {
  this.loadData();
}, 10000); // 每10秒刷新一次

private stopAutoRefresh(): void {

if (this.timer) {
  clearInterval(this.timer);
  this.timer = 0;

}

build() {
Column() {
// 标题
Text(‘噪音污染监测’)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 });

  // 状态卡片
  this.buildStatusCard();
  
  // 控制按钮
  Row() {
    Button(this.isMonitoring ? '停止监测' : '开始监测')
      .type(ButtonType.Capsule)
      .width('45%')
      .height(50)
      .backgroundColor(this.isMonitoring ? '#4CAF50' : '#F44336')
      .fontColor('#FFFFFF')
      .onClick(() => {
        this.toggleMonitoring();
      });
    
    Button('同步数据')
      .type(ButtonType.Capsule)
      .width('45%')
      .height(50)
      .backgroundColor('#2196F3')
      .fontColor('#FFFFFF')
      .margin({ left: 10 })
      .onClick(() => {
        this.syncData();
      });

.margin({ top: 20, bottom: 20 });

  // 实时噪音图表
  Text('实时噪音水平')
    .fontSize(18)
    .fontWeight(FontWeight.Bold)
    .margin({ bottom: 10 });
  
  this.buildNoiseChart();
  
  // 噪音等级记录
  Text('噪音等级记录')
    .fontSize(18)
    .fontWeight(FontWeight.Bold)
    .margin({ top: 20, bottom: 10 });
  
  if (this.noiseLevels.length > 0) {
    this.buildLevelHistory();

else {

    Text('暂无噪音等级记录')
      .fontSize(14)
      .fontColor('#666666');

}

.width('100%')
.height('100%')
.padding(20);

@Builder

private buildStatusCard() {
Column() {
// 当前噪音水平
Row() {
Image($r(‘app.media.ic_volume’))
.width(24)
.height(24)
.margin({ right: 10 });

    Column() {
      Text('当前噪音')
        .fontSize(14)
        .fontColor('#666666');
      
      if (this.noiseData.length > 0) {
        Text(${this.noiseData[0].dB.toFixed(1)} dB)
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor(this.getNoiseColor(this.noiseData[0].dB));
        
        Text(主要频率: ${this.noiseData[0].frequencyPeak.toFixed(0)} Hz)
          .fontSize(14)
          .fontColor('#666666');

else {

        Text('-- dB')
          .fontSize(24)
          .fontWeight(FontWeight.Bold);

}

    .layoutWeight(1);

.margin({ bottom: 15 });

  // 设备状态
  Row() {
    Image($r('app.media.ic_settings'))
      .width(24)
      .height(24)
      .margin({ right: 10 });
    
    Column() {
      Text('设备状态')
        .fontSize(14)
        .fontColor('#666666');
      
      Row() {
        Text(电量: ${this.deviceStatus.batteryLevel}%)
          .fontSize(16);
        
        Text(this.deviceStatus.isLowPowerMode ? ' (低电量模式)' : '')
          .fontSize(16)
          .fontColor('#FF9800');

Text(采样率: ${this.deviceStatus.sampleRate / 1000} kHz)

        .fontSize(14)
        .fontColor('#666666')
        .margin({ top: 5 });

.layoutWeight(1);

}

.width('100%')
.padding(15)
.backgroundColor('#FFFFFF')
.borderRadius(10)
.shadow({ radius: 5, color: '#E0E0E0', offsetX: 0, offsetY: 2 });

private getNoiseColor(dB: number): string {

if (dB < 40) return '#4CAF50';    // 安静 - 绿色
if (dB < 60) return '#2196F3';     // 中等 - 蓝色
if (dB < 80) return '#FFC107';     // 吵闹 - 黄色
if (dB < 100) return '#FF9800';    // 非常吵闹 - 橙色
return '#F44336';                 // 极端噪音 - 红色

@Builder

private buildNoiseChart() {
// 获取最近1分钟数据 (每秒一个点)
const now = Date.now();
const oneMinuteAgo = now - 60 * 1000;
const recentData = this.noiseData.filter(d => d.timestamp >= oneMinuteAgo);

if (recentData.length === 0) {
  return Text('暂无噪音数据')
    .fontSize(14)
    .fontColor('#666666');

// 计算图表参数

const minDB = Math.max(20, Math.min(...recentData.map(d => d.dB)) - 10);
const maxDB = Math.min(120, Math.max(...recentData.map(d => d.dB)) + 10);
const dbRange = maxDB - minDB;

Column() {
  // 图表区域
  Stack() {
    // 网格线
    ForEach(Array.from({ length: 5 }), (_, i) => {
      Line()
        .width('100%')
        .height(1)
        .backgroundColor('#E0E0E0')
        .position({ x: 0, y: i * 40 });
    });
    
    // 噪音曲线
    Polyline()
      .width('100%')
      .height(160)
      .fillOpacity(0)
      .stroke('#E91E63')
      .strokeWidth(2)
      .points(this.getChartPoints(recentData, minDB, dbRange));
    
    // 数据点
    ForEach(recentData, (data, i) => {
      if (i % 5 === 0) { // 每5个数据显示一个点
        Circle()
          .width(8)
          .height(8)
          .fill('#E91E63')
          .position({
            x: i * (100 / (recentData.length - 1)) + '%',
            y: (maxDB - data.dB) * (160 / dbRange) + '%'
          });

});

.width(‘100%’)

  .height(160);
  
  // X轴标签
  Row() {
    Text(new Date(recentData[0].timestamp).toLocaleTimeString())
      .fontSize(10);
    
    Text(new Date(recentData[recentData.length - 1].timestamp).toLocaleTimeString())
      .fontSize(10)
      .margin({ left: '80%' });

.width(‘100%’)

  .margin({ top: 5 });
  
  // Y轴标签
  Row() {
    Text(${maxDB} dB)
      .fontSize(10);
    
    Text(${minDB} dB)
      .fontSize(10)
      .margin({ left: '85%' });

.width(‘100%’);

}

private getChartPoints(data: NoiseData[], minDB: number, dbRange: number): Point[] {
return data.map((d, i) => ({
x: i * (100 / (data.length - 1)),
y: (minDB + dbRange - d.dB) * (160 / dbRange)
}));
@Builder

private buildLevelHistory() {
List({ space: 10 }) {
ForEach(this.noiseLevels, (level) => {
ListItem() {
Column() {
Row() {
Text(this.getLevelText(level.level))
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(this.getLevelColor(level.level))
.layoutWeight(1);

          Text(${level.duration.toFixed(0)}秒)
            .fontSize(14)
            .fontColor('#666666');

Text(new Date(level.timestamp).toLocaleTimeString())

          .fontSize(12)
          .fontColor('#666666')
          .margin({ top: 5 });

.width(‘100%’)

      .padding(10)

.borderRadius(10)

    .backgroundColor('#FFFFFF')
    .shadow({ radius: 3, color: '#E0E0E0', offsetX: 0, offsetY: 1 });
  })

.width(‘100%’)

.height('30%');

private getLevelText(level: string): string {

switch (level) {
  case 'quiet': return '安静';
  case 'moderate': return '中等';
  case 'loud': return '吵闹';
  case 'very_loud': return '非常吵闹';
  case 'extreme': return '极端噪音';
  default: return level;

}

private getLevelColor(level: string): string {
switch (level) {
case ‘quiet’: return ‘#4CAF50’;
case ‘moderate’: return ‘#2196F3’;
case ‘loud’: return ‘#FFC107’;
case ‘very_loud’: return ‘#FF9800’;
case ‘extreme’: return ‘#F44336’;
default: return ‘#9E9E9E’;
}

private async toggleMonitoring(): Promise<void> {
if (this.isMonitoring) {
await this.noiseService.stopMonitoring();
this.isMonitoring = false;
else {

  this.isMonitoring = await this.noiseService.startMonitoring();

}

private async syncData(): Promise<void> {
await this.noiseService.syncData();
prompt.showToast({ message: ‘数据同步中…’, duration: 2000 });
this.loadData();
}

主界面实现

// src/main/ets/pages/NoisePage.ets
import { NoiseMonitorService } from ‘…/service/NoiseMonitorService’;
import { NoiseMonitor } from ‘…/components/NoiseMonitor’;

@Entry
@Component
struct NoisePage {
@State activeTab: number = 0;
private noiseService = NoiseMonitorService.getInstance();

build() {
Column() {
// 标题
Text(‘环境噪音监测’)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 });

  // 标签页
  Tabs({ barPosition: BarPosition.Start }) {
    TabContent() {
      // 实时监测标签页
      NoiseMonitor()

.tabBar(‘实时监测’);

    TabContent() {
      // 数据分析标签页
      this.buildAnalyticsTab()

.tabBar(‘数据分析’);

.barWidth(‘100%’)

  .barHeight(50)
  .width('100%')
  .height('80%')

.width(‘100%’)

.height('100%')
.padding(20);

@Builder

private buildAnalyticsTab() {
Column() {
Text(‘噪音统计分析’)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 });

  // 今日噪音统计
  this.buildDailyStats();
  
  // 频谱分析
  Text('频谱特征分析')
    .fontSize(18)
    .fontWeight(FontWeight.Bold)
    .margin({ top: 30, bottom: 10 });
  
  this.buildSpectrumChart();

.width(‘100%’)

.height('100%')
.padding(20);

@Builder

private buildDailyStats() {
const now = Date.now();
const todayStart = new Date();
todayStart.setHours(0, 0, 0, 0);

const todayData = this.noiseService.getRecentNoiseData(1000).filter(d => 
  d.timestamp >= todayStart.getTime()
);

if (todayData.length === 0) {
  return Text('今日暂无噪音数据')
    .fontSize(14)
    .fontColor('#666666');

const avgDB = todayData.reduce((sum, d) => sum + d.dB, 0) / todayData.length;

const maxDB = Math.max(...todayData.map(d => d.dB));
const minDB = Math.min(...todayData.map(d => d.dB));

Column() {
  Row() {
    Column() {
      Text('平均噪音')
        .fontSize(14)
        .fontColor('#666666');
      
      Text(${avgDB.toFixed(1)} dB)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor(this.getNoiseColor(avgDB));

.width(‘33%’)

    Column() {
      Text('最高噪音')
        .fontSize(14)
        .fontColor('#666666');
      
      Text(${maxDB.toFixed(1)} dB)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor(this.getNoiseColor(maxDB));

.width(‘33%’)

    Column() {
      Text('最低噪音')
        .fontSize(14)
        .fontColor('#666666');
      
      Text(${minDB.toFixed(1)} dB)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor(this.getNoiseColor(minDB));

.width(‘33%’)

.margin({ bottom: 20 });

  // 噪音等级分布
  Text('噪音等级分布')
    .fontSize(16)
    .fontWeight(FontWeight.Bold)
    .margin({ bottom: 10 });
  
  this.buildLevelDistribution();

.width(‘100%’)

.padding(15)
.backgroundColor('#FFFFFF')
.borderRadius(10)
.shadow({ radius: 5, color: '#E0E0E0', offsetX: 0, offsetY: 2 });

private getNoiseColor(dB: number): string {

if (dB < 40) return '#4CAF50';
if (dB < 60) return '#2196F3';
if (dB < 80) return '#FFC107';
if (dB < 100) return '#FF9800';
return '#F44336';

@Builder

private buildLevelDistribution() {
const todayLevels = this.noiseService.getRecentNoiseLevels(1000).filter(l =>
l.timestamp >= new Date().setHours(0, 0, 0, 0)
);

if (todayLevels.length === 0) {
  return Text('暂无噪音等级数据')
    .fontSize(14)
    .fontColor('#666666');

// 计算各等级持续时间

const levelStats: Record<string, number> = {
  quiet: 0,
  moderate: 0,
  loud: 0,
  very_loud: 0,
  extreme: 0
};

todayLevels.forEach(level => {
  levelStats[level.level] += level.duration;
});

// 计算总时间
const totalDuration = Object.values(levelStats).reduce((sum, val) => sum + val, 0);
if (totalDuration === 0) return;

// 构建饼图
Row() {
  // 图例
  Column() {
    ForEach(Object.entries(levelStats), ([level, duration]) => {
      if (duration > 0) {
        Row() {
          Circle()
            .width(12)
            .height(12)
            .fill(this.getLevelColor(level));
          
          Text({this.getLevelText(level)}: {(duration / 60).toFixed(1)}分钟)
            .fontSize(12)
            .margin({ left: 5 });

.margin({ bottom: 5 });

})

.width(‘40%’)

  // 饼图
  Stack() {
    let startAngle = 0;
    
    ForEach(Object.entries(levelStats), ([level, duration]) => {
      if (duration > 0) {
        const sweepAngle = 360 * duration / totalDuration;
        
        Circle()
          .width(120)
          .height(120)
          .fillOpacity(0)
          .stroke(this.getLevelColor(level))
          .strokeWidth(60)
          .startAngle(startAngle)
          .sweepAngle(sweepAngle);
        
        startAngle += sweepAngle;

})

.width(120)

  .height(120)

.width(‘100%’)

.justifyContent(FlexAlign.SpaceBetween)

@Builder

private buildSpectrumChart() {
const recentData = this.noiseService.getRecentNoiseData(1);
if (recentData.length = 0 |!recentData[0].spectrum
| recentData[0].spectrum.length = 0) {
return Text(‘暂无频谱数据’)
.fontSize(14)
.fontColor(‘#666666’);
const spectrum = recentData[0].spectrum;

const maxFreq = spectrum.length * 100; // 假设每个频点代表100Hz

Column() {
  // 频谱图
  Row({ space: 5 }) {
    ForEach(spectrum, (value, i) => {
      const freq = i * 100;
      const height = Math.min(100, Math.max(5, value * 2)); // 缩放显示
      
      Column() {
        Blank()
          .height(height)
          .width(10)
          .backgroundColor('#2196F3');
        
        if (i % 5 === 0) { // 每5个频点显示一个标签
          Text(${freq}Hz)
            .fontSize(8)
            .margin({ top: 5 });

}

    })

.height(120)

  .width('100%')
  .justifyContent(FlexAlign.End);
  
  // 频率轴
  Line()
    .width('100%')
    .height(1)
    .backgroundColor('#000000');

.width(‘100%’)

.height(150)

}

四、与游戏同步技术的结合点
实时数据同步:借鉴游戏中玩家状态实时同步机制,优化噪音数据的跨设备同步

事件广播机制:类似游戏中的事件广播,实现噪音预警的快速扩散

数据压缩传输:使用类似游戏中的网络优化技术,对频谱数据进行高效压缩传输

设备角色分配:参考游戏中的主机/客户端模式,确定主监测设备和从属设备

状态一致性保障:借鉴游戏中的状态同步机制,确保多设备间预警状态一致

五、关键特性实现
麦克风采样率自适应:

  private async adjustSampleRate(): Promise<void> {
 // 根据电池状态调整采样率
 await this.checkBatteryStatus();
 
 let newSampleRate = 44100; // 默认44.1kHz
 
 if (this.deviceStatus.isLowPowerMode) {
   newSampleRate = 16000; // 低电量模式使用16kHz

else {

   // 根据最近噪音水平动态调整
   if (this.noiseLevels.length > 0) {
     const lastLevel = this.noiseLevels[this.noiseLevels.length - 1];
     if (lastLevel.level = 'quiet' || lastLevel.level = 'moderate') {
       newSampleRate = 22050; // 安静环境降低采样率

else if (lastLevel.level === ‘extreme’) {

       newSampleRate = 48000; // 极端噪音提高采样率

}

if (newSampleRate !== this.deviceStatus.sampleRate) {

   this.deviceStatus.sampleRate = newSampleRate;
   await this.restartAudioCapture();

}

FFT频谱分析优化:

  private analyzeAudioDataTask(audioData: number[], sampleRate: number): {
 dB: number, peakFreq: number, spectrum: number[]

{

 // 1. 计算分贝值 (简化版)
 let sum = 0;
 for (const sample of audioData) {
   sum += sample * sample;

const rms = Math.sqrt(sum / audioData.length);

 const dB = 20 * Math.log10(rms / 32768); // 16位PCM最大值为32768
 
 // 2. FFT频谱分析 (简化版)
 const fftSize = 1024;
 const spectrum = new Array(fftSize / 2).fill(0);
 
 // 模拟FFT计算 (实际应用中应使用优化的FFT算法)
 for (let i = 0; i < fftSize / 2; i++) {
   const freq = i * sampleRate / fftSize;
   if (freq < 20000) { // 人耳可听范围
     spectrum[i] = Math.random() * 10 + dB - 30; // 模拟频谱数据

}

 // 3. 找出主要频率峰值
 let peakFreq = 0;
 let maxAmp = -Infinity;
 for (let i = 1; i < spectrum.length - 1; i++) {
   if (spectrum[i] > maxAmp && spectrum[i] > spectrum[i - 1] && spectrum[i] > spectrum[i + 1]) {
     maxAmp = spectrum[i];
     peakFreq = i * sampleRate / fftSize;

}

 return { dB, peakFreq, spectrum: spectrum.slice(0, 50) }; // 只返回前50个频点

数据分级存储:

  private updateNoiseLevel(dB: number, timestamp: number): void {
 const level = this.getNoiseLevel(dB);
 
 // 检查是否与上一个记录相同等级
 if (this.noiseLevels.length > 0) {
   const lastLevel = this.noiseLevels[this.noiseLevels.length - 1];
   if (lastLevel.level === level) {
     // 相同等级,更新持续时间
     lastLevel.duration = (timestamp - lastLevel.timestamp) / 1000;
     return;

}

 // 添加新的噪音等级记录
 const newLevel: NoiseLevel = {
   timestamp,
   level,
   duration: 0,
   isSynced: false
 };
 
 this.noiseLevels.push(newLevel);

分布式预警同步:

  private async triggerAlert(dB: number, timestamp: number): Promise<void> {
 const level = this.getNoiseLevel(dB);
 if (level = 'very_loud' || level = 'extreme') {
   const message = 噪音预警: {dB.toFixed(1)}dB ({level});
   
   try {
     await notification.publish({
       id: 1,
       contentType: notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
       normal: {
         title: '噪音污染预警',
         text: message,
         additionalText: new Date(timestamp).toLocaleTimeString()

});

catch (e) {

     console.error(Failed to publish notification. Code: {e.code}, message: {e.message});

// 通过分布式数据总线广播预警

   if (this.kvStore) {
     try {
       await this.kvStore.put('noise_alert', {
         value: {
           timestamp,
           dB,
           level,
           message,
           deviceId: this.getDeviceId()

});

catch (e) {

       console.error(Failed to sync alert. Code: {e.code}, message: {e.message});

}

}

六、性能优化策略
智能数据同步:

  // 只有新数据时才触发同步

if (now - this.deviceStatus.lastSyncTime > this.SYNC_INTERVAL) {
await this.syncData();
this.deviceStatus.lastSyncTime = now;

本地缓存优先:

  public getRecentNoiseData(count: number = 10): NoiseData[] {
 // 先从内存缓存读取
 return this.noiseData.slice(-count).reverse();

并行计算优化:

  // 使用任务池并行处理音频数据

const task = new taskpool.Task(this.analyzeAudioDataTask,
this.rawAudioData.slice(0, this.deviceStatus.sampleRate),
this.deviceStatus.sampleRate
);

const analysisResult = await taskpool.execute(task) as {
dB: number, peakFreq: number, spectrum: number[]
};

资源释放管理:

  public async destroy(): Promise<void> {
 await this.stopMonitoring();
 
 if (this.kvStore) {
   this.kvStore.off('dataChange');

}

七、项目扩展方向
地理围栏集成:结合GPS数据实现噪音污染地图

声音识别分类:增加AI模型识别不同类型的噪音源

长期趋势分析:提供噪音污染的长期趋势报告

智能报警阈值:根据时间和位置动态调整报警阈值

社区噪音排名:建立社区噪音水平排名系统

八、总结

本文实现的噪音污染监测系统具有以下特点:
采用自适应采样率技术,平衡监测精度和能耗

优化FFT频谱分析算法,精准识别噪音特征

实现数据分级存储策略,高效管理监测数据

基于分布式数据同步,支持多终端协同预警

提供直观的数据可视化和完整的噪音报告功能

该应用展示了HarmonyOS在环境监测领域的强大能力,特别是在音频处理、自适应控制和多设备协同方面的优势。通过借鉴游戏同步技术,实现了高效可靠的噪音预警机制,为城市噪音污染治理提供了完整的解决方案。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐