
鸿蒙跨端噪音污染监测系统开发指南 原创
鸿蒙跨端噪音污染监测系统开发指南
一、项目概述
本文基于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在环境监测领域的强大能力,特别是在音频处理、自适应控制和多设备协同方面的优势。通过借鉴游戏同步技术,实现了高效可靠的噪音预警机制,为城市噪音污染治理提供了完整的解决方案。
