diff --git a/src/声源定位代码/README_声源定位系统.md b/src/声源定位代码/README_声源定位系统.md new file mode 100644 index 0000000..d599298 --- /dev/null +++ b/src/声源定位代码/README_声源定位系统.md @@ -0,0 +1,216 @@ +# 声源定位系统使用说明 + +## 系统概述 + +本系统由开发板端和PC端组成,实现声源定位和枪声识别功能。 + +### 工作流程 + +1. **开发板端**:开机后进入录音状态,持续录音并发送给PC端 +2. **PC端**:接收音频数据,使用audio-classification进行枪声识别 +3. **模式切换**:当识别到枪声时,PC端发送指令给开发板切换到定位模式 +4. **声源定位**:开发板在定位模式下实时发送声源定位数据给PC端 +5. **可视化**:PC端实时显示声源定位地图 + +## 文件说明 + +### 开发板端文件 +- `development_board_simple.py` - 简化版开发板端主程序(推荐使用) +- `development_board.py` - 完整版开发板端主程序 + +### PC端文件 +- `pc_server.py` - PC端服务器程序 + +## 配置说明 + +### 开发板端配置 + +在 `development_board_simple.py` 中修改以下配置: + +```python +# WiFi配置 +WIFI_SSID = "junzekeki" # 替换为你的WiFi SSID +WIFI_PASSWD = "234567890l" # 替换为你的WiFi密码 + +# PC端配置 +PC_IP = "192.168.1.100" # PC端IP地址,需要根据实际情况修改 +PC_PORT_AUDIO = 12346 # 音频传输端口 +PC_PORT_CMD = 12347 # 指令传输端口 +PC_PORT_LOCATION = 12348 # 定位数据传输端口 +``` + +### PC端配置 + +在 `pc_server.py` 中修改以下配置: + +```python +# 网络配置 +HOST = "0.0.0.0" # 监听所有网络接口 +PORT_AUDIO = 12346 # 音频接收端口 +PORT_CMD = 12347 # 指令发送端口 +PORT_LOCATION = 12348 # 定位数据接收端口 + +# 枪声识别配置 +GUNSHOT_THRESHOLD = 0.7 # 枪声识别阈值 +RECOGNITION_INTERVAL = 3.0 # 识别间隔(秒) +``` + +## 使用步骤 + +### 1. 环境准备 + +#### PC端环境 +```bash +# 安装依赖 +pip install numpy matplotlib soundfile librosa + +# 如果使用audio-classification +cd audio-classification +pip install -r requirements.txt +``` + +#### 开发板端环境 +确保开发板已安装以下模块: +- `fpioa_manager` +- `Maix` +- `board` +- `network` +- `socket` +- `machine` + +### 2. 网络配置 + +1. 确保开发板和PC在同一个WiFi网络下 +2. 获取PC的IP地址(Windows: `ipconfig`, Linux/Mac: `ifconfig`) +3. 修改开发板端代码中的 `PC_IP` 为PC的实际IP地址 + +### 3. 启动系统 + +#### 启动PC端服务器 +```bash +python pc_server.py +``` + +#### 启动开发板端 +将 `development_board_simple.py` 上传到开发板并运行: +```bash +python development_board_simple.py +``` + +### 4. 系统运行 + +1. **初始化阶段**: + - 开发板自动连接WiFi + - 建立与PC端的Socket连接 + - 进入录音模式 + +2. **录音模式**: + - 开发板持续录音并发送音频数据 + - PC端接收音频并进行枪声识别 + - 控制台显示识别结果 + +3. **定位模式**: + - 当检测到枪声时,PC端发送"START_LOCATION"指令 + - 开发板切换到定位模式 + - 实时发送声源定位数据 + - PC端显示实时定位地图 + +4. **模式切换**: + - 可通过关闭程序或发送"STOP_LOCATION"指令返回录音模式 + +## 通信协议 + +### 音频数据传输 +- 端口:12346 +- 格式:原始音频字节流(16位,16kHz,单声道) + +### 指令传输 +- 端口:12347 +- 格式:UTF-8字符串 +- 指令: + - `START_LOCATION` - 切换到定位模式 + - `STOP_LOCATION` - 切换到录音模式 + +### 定位数据传输 +- 端口:12348 +- 格式:`X,Y,强度,角度`(CSV格式) +- 示例:`1.234,2.345,3.456,45.67` + +## 故障排除 + +### 常见问题 + +1. **WiFi连接失败** + - 检查WiFi SSID和密码是否正确 + - 确保WiFi信号强度足够 + - 检查开发板WiFi模块是否正常 + +2. **Socket连接失败** + - 检查PC IP地址是否正确 + - 确保防火墙未阻止端口 + - 检查网络连接是否正常 + +3. **音频识别失败** + - 检查audio-classification模型是否正确安装 + - 确认模型文件路径是否正确 + - 检查音频数据格式是否符合要求 + +4. **定位数据异常** + - 检查麦克风阵列连接 + - 确认麦克风阵列初始化是否成功 + - 检查环境噪声是否过大 + +### 调试方法 + +1. **查看控制台输出** + - 开发板端和PC端都会输出详细的运行日志 + - 根据日志信息定位问题 + +2. **网络测试** + - 使用ping命令测试网络连通性 + - 使用telnet测试端口是否开放 + +3. **音频测试** + - 使用现有音频文件测试识别功能 + - 检查音频数据是否正确接收 + +## 扩展功能 + +### 自定义音频识别 +可以修改PC端代码,使用其他音频分类模型或自定义识别逻辑。 + +### 数据记录 +可以添加数据记录功能,保存音频文件和定位数据。 + +### 多设备支持 +可以扩展支持多个开发板同时工作。 + +### 远程控制 +可以添加Web界面进行远程控制和监控。 + +## 注意事项 + +1. **安全考虑** + - 本系统仅用于测试和演示 + - 请勿用于实际的安全监控场景 + - 注意保护个人隐私 + +2. **性能优化** + - 根据实际需求调整识别间隔 + - 优化网络传输参数 + - 考虑使用更高效的音频编码 + +3. **硬件要求** + - 开发板需要支持WiFi和麦克风阵列 + - PC端需要足够的计算能力进行音频识别 + - 建议使用有线网络连接以提高稳定性 + +## 技术支持 + +如有问题,请检查: +1. 代码配置是否正确 +2. 网络连接是否正常 +3. 硬件连接是否牢固 +4. 依赖库是否正确安装 + +更多技术细节请参考项目中的其他文档和代码注释。 \ No newline at end of file diff --git a/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/details.md b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/details.md new file mode 100644 index 0000000..f1a1fd9 --- /dev/null +++ b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/details.md @@ -0,0 +1,69 @@ +# Details + +Date : 2025-06-10 08:55:17 + +Directory e:\\pycharm_projects\\AudioClassification-Pytorch-master + +Total : 54 files, 6851 codes, 838 comments, 1201 blanks, all 8890 lines + +[Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md) + +## Files +| filename | language | code | comment | blank | total | +| :--- | :--- | ---: | ---: | ---: | ---: | +| [README.md](/README.md) | Markdown | 302 | 0 | 72 | 374 | +| [README\_en.md](/README_en.md) | Markdown | 231 | 0 | 45 | 276 | +| [audio-classification-platform/README.md](/audio-classification-platform/README.md) | Markdown | 96 | 0 | 41 | 137 | +| [audio-classification-platform/backend/app.py](/audio-classification-platform/backend/app.py) | Python | 255 | 35 | 61 | 351 | +| [audio-classification-platform/backend/config.py](/audio-classification-platform/backend/config.py) | Python | 22 | 8 | 10 | 40 | +| [audio-classification-platform/backend/requirements.txt](/audio-classification-platform/backend/requirements.txt) | pip requirements | 7 | 0 | 1 | 8 | +| [audio-classification-platform/frontend/index.html](/audio-classification-platform/frontend/index.html) | HTML | 25 | 0 | 2 | 27 | +| [audio-classification-platform/frontend/package.json](/audio-classification-platform/frontend/package.json) | JSON | 24 | 0 | 1 | 25 | +| [audio-classification-platform/frontend/src/App.vue](/audio-classification-platform/frontend/src/App.vue) | Vue | 88 | 5 | 14 | 107 | +| [audio-classification-platform/frontend/src/components/AudioRecorder.vue](/audio-classification-platform/frontend/src/components/AudioRecorder.vue) | Vue | 551 | 38 | 102 | 691 | +| [audio-classification-platform/frontend/src/components/AudioRecorder\_new.vue](/audio-classification-platform/frontend/src/components/AudioRecorder_new.vue) | Vue | 811 | 48 | 162 | 1,021 | +| [audio-classification-platform/frontend/src/components/AudioUpload.vue](/audio-classification-platform/frontend/src/components/AudioUpload.vue) | Vue | 580 | 28 | 107 | 715 | +| [audio-classification-platform/frontend/src/components/HistoryList.vue](/audio-classification-platform/frontend/src/components/HistoryList.vue) | Vue | 513 | 25 | 79 | 617 | +| [audio-classification-platform/frontend/src/components/PredictionResult.vue](/audio-classification-platform/frontend/src/components/PredictionResult.vue) | Vue | 803 | 22 | 117 | 942 | +| [audio-classification-platform/frontend/src/main.js](/audio-classification-platform/frontend/src/main.js) | JavaScript | 13 | 1 | 5 | 19 | +| [audio-classification-platform/frontend/src/router/index.js](/audio-classification-platform/frontend/src/router/index.js) | JavaScript | 14 | 0 | 4 | 18 | +| [audio-classification-platform/frontend/src/utils/api.js](/audio-classification-platform/frontend/src/utils/api.js) | JavaScript | 156 | 25 | 33 | 214 | +| [audio-classification-platform/frontend/src/views/HomePage.vue](/audio-classification-platform/frontend/src/views/HomePage.vue) | Vue | 692 | 40 | 110 | 842 | +| [audio-classification-platform/frontend/vite.config.js](/audio-classification-platform/frontend/vite.config.js) | JavaScript | 32 | 0 | 2 | 34 | +| [audio-classification-platform/start.bat](/audio-classification-platform/start.bat) | Batch | 20 | 0 | 5 | 25 | +| [audio-classification-platform/start.sh](/audio-classification-platform/start.sh) | Shell Script | 26 | 3 | 8 | 37 | +| [configs/augmentation.yml](/configs/augmentation.yml) | YAML | 21 | 21 | 5 | 47 | +| [configs/cam++.yml](/configs/cam++.yml) | YAML | 43 | 33 | 5 | 81 | +| [configs/ecapa\_tdnn.yml](/configs/ecapa_tdnn.yml) | YAML | 43 | 33 | 5 | 81 | +| [configs/eres2net.yml](/configs/eres2net.yml) | YAML | 43 | 33 | 5 | 81 | +| [configs/panns.yml](/configs/panns.yml) | YAML | 43 | 33 | 5 | 81 | +| [configs/res2net.yml](/configs/res2net.yml) | YAML | 43 | 33 | 5 | 81 | +| [configs/resnet\_se.yml](/configs/resnet_se.yml) | YAML | 43 | 33 | 5 | 81 | +| [configs/tdnn.yml](/configs/tdnn.yml) | YAML | 43 | 33 | 5 | 81 | +| [create\_data.py](/create_data.py) | Python | 75 | 9 | 16 | 100 | +| [eval.py](/eval.py) | Python | 20 | 2 | 5 | 27 | +| [extract\_features.py](/extract_features.py) | Python | 13 | 2 | 5 | 20 | +| [infer.py](/infer.py) | Python | 17 | 1 | 6 | 24 | +| [infer\_record.py](/infer_record.py) | Python | 40 | 7 | 12 | 59 | +| [macls/\_\_init\_\_.py](/macls/__init__.py) | Python | 1 | 0 | 1 | 2 | +| [macls/data\_utils/\_\_init\_\_.py](/macls/data_utils/__init__.py) | Python | 0 | 0 | 1 | 1 | +| [macls/data\_utils/collate\_fn.py](/macls/data_utils/collate_fn.py) | Python | 17 | 4 | 3 | 24 | +| [macls/data\_utils/featurizer.py](/macls/data_utils/featurizer.py) | Python | 88 | 36 | 9 | 133 | +| [macls/data\_utils/reader.py](/macls/data_utils/reader.py) | Python | 114 | 33 | 11 | 158 | +| [macls/metric/\_\_init\_\_.py](/macls/metric/__init__.py) | Python | 0 | 0 | 1 | 1 | +| [macls/metric/metrics.py](/macls/metric/metrics.py) | Python | 9 | 1 | 3 | 13 | +| [macls/optimizer/\_\_init\_\_.py](/macls/optimizer/__init__.py) | Python | 26 | 0 | 7 | 33 | +| [macls/optimizer/scheduler.py](/macls/optimizer/scheduler.py) | Python | 42 | 0 | 7 | 49 | +| [macls/predict.py](/macls/predict.py) | Python | 124 | 47 | 7 | 178 | +| [macls/trainer.py](/macls/trainer.py) | Python | 338 | 99 | 20 | 457 | +| [macls/utils/\_\_init\_\_.py](/macls/utils/__init__.py) | Python | 0 | 0 | 1 | 1 | +| [macls/utils/checkpoint.py](/macls/utils/checkpoint.py) | Python | 113 | 40 | 10 | 163 | +| [macls/utils/record.py](/macls/utils/record.py) | Python | 18 | 8 | 6 | 32 | +| [macls/utils/utils.py](/macls/utils/utils.py) | Python | 99 | 16 | 17 | 132 | +| [record\_audio.py](/record_audio.py) | Python | 10 | 0 | 5 | 15 | +| [requirements.txt](/requirements.txt) | pip requirements | 17 | 0 | 1 | 18 | +| [setup.py](/setup.py) | Python | 43 | 1 | 11 | 55 | +| [tools/download\_language\_data.sh](/tools/download_language_data.sh) | Shell Script | 19 | 1 | 10 | 30 | +| [train.py](/train.py) | Python | 25 | 1 | 5 | 31 | + +[Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md) \ No newline at end of file diff --git a/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/diff-details.md b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/diff-details.md new file mode 100644 index 0000000..55f2906 --- /dev/null +++ b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/diff-details.md @@ -0,0 +1,15 @@ +# Diff Details + +Date : 2025-06-10 08:55:17 + +Directory e:\\pycharm_projects\\AudioClassification-Pytorch-master + +Total : 0 files, 0 codes, 0 comments, 0 blanks, all 0 lines + +[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details + +## Files +| filename | language | code | comment | blank | total | +| :--- | :--- | ---: | ---: | ---: | ---: | + +[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details \ No newline at end of file diff --git a/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/diff.csv b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/diff.csv new file mode 100644 index 0000000..b7d8d75 --- /dev/null +++ b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/diff.csv @@ -0,0 +1,2 @@ +"filename", "language", "", "comment", "blank", "total" +"Total", "-", , 0, 0, 0 \ No newline at end of file diff --git a/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/diff.md b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/diff.md new file mode 100644 index 0000000..0cab177 --- /dev/null +++ b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/diff.md @@ -0,0 +1,19 @@ +# Diff Summary + +Date : 2025-06-10 08:55:17 + +Directory e:\\pycharm_projects\\AudioClassification-Pytorch-master + +Total : 0 files, 0 codes, 0 comments, 0 blanks, all 0 lines + +[Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md) + +## Languages +| language | files | code | comment | blank | total | +| :--- | ---: | ---: | ---: | ---: | ---: | + +## Directories +| path | files | code | comment | blank | total | +| :--- | ---: | ---: | ---: | ---: | ---: | + +[Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md) \ No newline at end of file diff --git a/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/diff.txt b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/diff.txt new file mode 100644 index 0000000..b332216 --- /dev/null +++ b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/diff.txt @@ -0,0 +1,22 @@ +Date : 2025-06-10 08:55:17 +Directory : e:\pycharm_projects\AudioClassification-Pytorch-master +Total : 0 files, 0 codes, 0 comments, 0 blanks, all 0 lines + +Languages ++----------+------------+------------+------------+------------+------------+ +| language | files | code | comment | blank | total | ++----------+------------+------------+------------+------------+------------+ ++----------+------------+------------+------------+------------+------------+ + +Directories ++------+------------+------------+------------+------------+------------+ +| path | files | code | comment | blank | total | ++------+------------+------------+------------+------------+------------+ ++------+------------+------------+------------+------------+------------+ + +Files ++----------+----------+------------+------------+------------+------------+ +| filename | language | code | comment | blank | total | ++----------+----------+------------+------------+------------+------------+ +| Total | | 0 | 0 | 0 | 0 | ++----------+----------+------------+------------+------------+------------+ \ No newline at end of file diff --git a/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/results.csv b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/results.csv new file mode 100644 index 0000000..6cde0c8 --- /dev/null +++ b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/results.csv @@ -0,0 +1,56 @@ +"filename", "language", "Python", "pip requirements", "Markdown", "Shell Script", "YAML", "Batch", "JavaScript", "Vue", "JSON", "HTML", "comment", "blank", "total" +"e:\pycharm_projects\AudioClassification-Pytorch-master\README.md", "Markdown", 0, 0, 302, 0, 0, 0, 0, 0, 0, 0, 0, 72, 374 +"e:\pycharm_projects\AudioClassification-Pytorch-master\README_en.md", "Markdown", 0, 0, 231, 0, 0, 0, 0, 0, 0, 0, 0, 45, 276 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\README.md", "Markdown", 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 41, 137 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\backend\app.py", "Python", 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 61, 351 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\backend\config.py", "Python", 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 10, 40 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\backend\requirements.txt", "pip requirements", 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 8 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\index.html", "HTML", 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 0, 2, 27 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\package.json", "JSON", 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 1, 25 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\App.vue", "Vue", 0, 0, 0, 0, 0, 0, 0, 88, 0, 0, 5, 14, 107 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\AudioRecorder.vue", "Vue", 0, 0, 0, 0, 0, 0, 0, 551, 0, 0, 38, 102, 691 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\AudioRecorder_new.vue", "Vue", 0, 0, 0, 0, 0, 0, 0, 811, 0, 0, 48, 162, 1021 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\AudioUpload.vue", "Vue", 0, 0, 0, 0, 0, 0, 0, 580, 0, 0, 28, 107, 715 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\HistoryList.vue", "Vue", 0, 0, 0, 0, 0, 0, 0, 513, 0, 0, 25, 79, 617 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\PredictionResult.vue", "Vue", 0, 0, 0, 0, 0, 0, 0, 803, 0, 0, 22, 117, 942 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\main.js", "JavaScript", 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 1, 5, 19 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\router\index.js", "JavaScript", 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 4, 18 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\utils\api.js", "JavaScript", 0, 0, 0, 0, 0, 0, 156, 0, 0, 0, 25, 33, 214 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\views\HomePage.vue", "Vue", 0, 0, 0, 0, 0, 0, 0, 692, 0, 0, 40, 110, 842 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\vite.config.js", "JavaScript", 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 2, 34 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\start.bat", "Batch", 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 5, 25 +"e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\start.sh", "Shell Script", 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 3, 8, 37 +"e:\pycharm_projects\AudioClassification-Pytorch-master\configs\augmentation.yml", "YAML", 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 21, 5, 47 +"e:\pycharm_projects\AudioClassification-Pytorch-master\configs\cam++.yml", "YAML", 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 33, 5, 81 +"e:\pycharm_projects\AudioClassification-Pytorch-master\configs\ecapa_tdnn.yml", "YAML", 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 33, 5, 81 +"e:\pycharm_projects\AudioClassification-Pytorch-master\configs\eres2net.yml", "YAML", 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 33, 5, 81 +"e:\pycharm_projects\AudioClassification-Pytorch-master\configs\panns.yml", "YAML", 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 33, 5, 81 +"e:\pycharm_projects\AudioClassification-Pytorch-master\configs\res2net.yml", "YAML", 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 33, 5, 81 +"e:\pycharm_projects\AudioClassification-Pytorch-master\configs\resnet_se.yml", "YAML", 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 33, 5, 81 +"e:\pycharm_projects\AudioClassification-Pytorch-master\configs\tdnn.yml", "YAML", 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 33, 5, 81 +"e:\pycharm_projects\AudioClassification-Pytorch-master\create_data.py", "Python", 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 16, 100 +"e:\pycharm_projects\AudioClassification-Pytorch-master\eval.py", "Python", 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 5, 27 +"e:\pycharm_projects\AudioClassification-Pytorch-master\extract_features.py", "Python", 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 5, 20 +"e:\pycharm_projects\AudioClassification-Pytorch-master\infer.py", "Python", 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6, 24 +"e:\pycharm_projects\AudioClassification-Pytorch-master\infer_record.py", "Python", 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 12, 59 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\__init__.py", "Python", 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\data_utils\__init__.py", "Python", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\data_utils\collate_fn.py", "Python", 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 3, 24 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\data_utils\featurizer.py", "Python", 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 9, 133 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\data_utils\reader.py", "Python", 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 11, 158 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\metric\__init__.py", "Python", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\metric\metrics.py", "Python", 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 13 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\optimizer\__init__.py", "Python", 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 33 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\optimizer\scheduler.py", "Python", 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 49 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\predict.py", "Python", 124, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 7, 178 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\trainer.py", "Python", 338, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 20, 457 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\utils\__init__.py", "Python", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\utils\checkpoint.py", "Python", 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 10, 163 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\utils\record.py", "Python", 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 6, 32 +"e:\pycharm_projects\AudioClassification-Pytorch-master\macls\utils\utils.py", "Python", 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 17, 132 +"e:\pycharm_projects\AudioClassification-Pytorch-master\record_audio.py", "Python", 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 15 +"e:\pycharm_projects\AudioClassification-Pytorch-master\requirements.txt", "pip requirements", 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 18 +"e:\pycharm_projects\AudioClassification-Pytorch-master\setup.py", "Python", 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 11, 55 +"e:\pycharm_projects\AudioClassification-Pytorch-master\tools\download_language_data.sh", "Shell Script", 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 1, 10, 30 +"e:\pycharm_projects\AudioClassification-Pytorch-master\train.py", "Python", 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 31 +"Total", "-", 1509, 24, 629, 45, 322, 20, 215, 4038, 24, 25, 838, 1201, 8890 \ No newline at end of file diff --git a/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/results.json b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/results.json new file mode 100644 index 0000000..922a690 --- /dev/null +++ b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/results.json @@ -0,0 +1 @@ +{"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/record_audio.py":{"language":"Python","code":10,"comment":0,"blank":5},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/train.py":{"language":"Python","code":25,"comment":1,"blank":5},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/setup.py":{"language":"Python","code":43,"comment":1,"blank":11},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/requirements.txt":{"language":"pip requirements","code":17,"comment":0,"blank":1},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/README_en.md":{"language":"Markdown","code":231,"comment":0,"blank":45},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/tools/download_language_data.sh":{"language":"Shell Script","code":19,"comment":1,"blank":10},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/infer.py":{"language":"Python","code":17,"comment":1,"blank":6},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/infer_record.py":{"language":"Python","code":40,"comment":7,"blank":12},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/__init__.py":{"language":"Python","code":1,"comment":0,"blank":1},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/extract_features.py":{"language":"Python","code":13,"comment":2,"blank":5},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/README.md":{"language":"Markdown","code":302,"comment":0,"blank":72},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/create_data.py":{"language":"Python","code":75,"comment":9,"blank":16},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/trainer.py":{"language":"Python","code":338,"comment":99,"blank":20},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/optimizer/__init__.py":{"language":"Python","code":26,"comment":0,"blank":7},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/optimizer/scheduler.py":{"language":"Python","code":42,"comment":0,"blank":7},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/predict.py":{"language":"Python","code":124,"comment":47,"blank":7},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/eval.py":{"language":"Python","code":20,"comment":2,"blank":5},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/configs/tdnn.yml":{"language":"YAML","code":43,"comment":33,"blank":5},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/configs/resnet_se.yml":{"language":"YAML","code":43,"comment":33,"blank":5},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/configs/panns.yml":{"language":"YAML","code":43,"comment":33,"blank":5},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/configs/res2net.yml":{"language":"YAML","code":43,"comment":33,"blank":5},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/configs/ecapa_tdnn.yml":{"language":"YAML","code":43,"comment":33,"blank":5},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/configs/cam%2B%2B.yml":{"language":"YAML","code":43,"comment":33,"blank":5},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/configs/eres2net.yml":{"language":"YAML","code":43,"comment":33,"blank":5},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/utils/utils.py":{"language":"Python","code":99,"comment":16,"blank":17},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/metric/metrics.py":{"language":"Python","code":9,"comment":1,"blank":3},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/configs/augmentation.yml":{"language":"YAML","code":21,"comment":21,"blank":5},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/utils/__init__.py":{"language":"Python","code":0,"comment":0,"blank":1},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/utils/record.py":{"language":"Python","code":18,"comment":8,"blank":6},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/metric/__init__.py":{"language":"Python","code":0,"comment":0,"blank":1},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/utils/checkpoint.py":{"language":"Python","code":113,"comment":40,"blank":10},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/start.sh":{"language":"Shell Script","code":26,"comment":3,"blank":8},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/start.bat":{"language":"Batch","code":20,"comment":0,"blank":5},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/README.md":{"language":"Markdown","code":96,"comment":0,"blank":41},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/data_utils/reader.py":{"language":"Python","code":114,"comment":33,"blank":11},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/data_utils/featurizer.py":{"language":"Python","code":88,"comment":36,"blank":9},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/frontend/vite.config.js":{"language":"JavaScript","code":32,"comment":0,"blank":2},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/data_utils/collate_fn.py":{"language":"Python","code":17,"comment":4,"blank":3},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/macls/data_utils/__init__.py":{"language":"Python","code":0,"comment":0,"blank":1},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/frontend/src/main.js":{"language":"JavaScript","code":13,"comment":1,"blank":5},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/frontend/src/views/HomePage.vue":{"language":"Vue","code":692,"comment":40,"blank":110},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/backend/config.py":{"language":"Python","code":22,"comment":8,"blank":10},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/frontend/src/App.vue":{"language":"Vue","code":88,"comment":5,"blank":14},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/frontend/package.json":{"language":"JSON","code":24,"comment":0,"blank":1},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/frontend/index.html":{"language":"HTML","code":25,"comment":0,"blank":2},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/backend/requirements.txt":{"language":"pip requirements","code":7,"comment":0,"blank":1},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/frontend/src/components/HistoryList.vue":{"language":"Vue","code":513,"comment":25,"blank":79},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/frontend/src/components/AudioUpload.vue":{"language":"Vue","code":580,"comment":28,"blank":107},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/backend/app.py":{"language":"Python","code":255,"comment":35,"blank":61},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/frontend/src/components/PredictionResult.vue":{"language":"Vue","code":803,"comment":22,"blank":117},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/frontend/src/components/AudioRecorder_new.vue":{"language":"Vue","code":811,"comment":48,"blank":162},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/frontend/src/utils/api.js":{"language":"JavaScript","code":156,"comment":25,"blank":33},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/frontend/src/router/index.js":{"language":"JavaScript","code":14,"comment":0,"blank":4},"file:///e%3A/pycharm_projects/AudioClassification-Pytorch-master/audio-classification-platform/frontend/src/components/AudioRecorder.vue":{"language":"Vue","code":551,"comment":38,"blank":102}} \ No newline at end of file diff --git a/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/results.md b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/results.md new file mode 100644 index 0000000..ac3e4e6 --- /dev/null +++ b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/results.md @@ -0,0 +1,50 @@ +# Summary + +Date : 2025-06-10 08:55:17 + +Directory e:\\pycharm_projects\\AudioClassification-Pytorch-master + +Total : 54 files, 6851 codes, 838 comments, 1201 blanks, all 8890 lines + +Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md) + +## Languages +| language | files | code | comment | blank | total | +| :--- | ---: | ---: | ---: | ---: | ---: | +| Vue | 7 | 4,038 | 206 | 691 | 4,935 | +| Python | 25 | 1,509 | 350 | 240 | 2,099 | +| Markdown | 3 | 629 | 0 | 158 | 787 | +| YAML | 8 | 322 | 252 | 40 | 614 | +| JavaScript | 4 | 215 | 26 | 44 | 285 | +| Shell Script | 2 | 45 | 4 | 18 | 67 | +| HTML | 1 | 25 | 0 | 2 | 27 | +| pip requirements | 2 | 24 | 0 | 2 | 26 | +| JSON | 1 | 24 | 0 | 1 | 25 | +| Batch | 1 | 20 | 0 | 5 | 25 | + +## Directories +| path | files | code | comment | blank | total | +| :--- | ---: | ---: | ---: | ---: | ---: | +| . | 54 | 6,851 | 838 | 1,201 | 8,890 | +| . (Files) | 11 | 793 | 23 | 183 | 999 | +| audio-classification-platform | 19 | 4,728 | 278 | 864 | 5,870 | +| audio-classification-platform (Files) | 3 | 142 | 3 | 54 | 199 | +| audio-classification-platform\\backend | 3 | 284 | 43 | 72 | 399 | +| audio-classification-platform\\frontend | 13 | 4,302 | 232 | 738 | 5,272 | +| audio-classification-platform\\frontend (Files) | 3 | 81 | 0 | 5 | 86 | +| audio-classification-platform\\frontend\\src | 10 | 4,221 | 232 | 733 | 5,186 | +| audio-classification-platform\\frontend\\src (Files) | 2 | 101 | 6 | 19 | 126 | +| audio-classification-platform\\frontend\\src\\components | 5 | 3,258 | 161 | 567 | 3,986 | +| audio-classification-platform\\frontend\\src\\router | 1 | 14 | 0 | 4 | 18 | +| audio-classification-platform\\frontend\\src\\utils | 1 | 156 | 25 | 33 | 214 | +| audio-classification-platform\\frontend\\src\\views | 1 | 692 | 40 | 110 | 842 | +| configs | 8 | 322 | 252 | 40 | 614 | +| macls | 15 | 989 | 284 | 104 | 1,377 | +| macls (Files) | 3 | 463 | 146 | 28 | 637 | +| macls\\data_utils | 4 | 219 | 73 | 24 | 316 | +| macls\\metric | 2 | 9 | 1 | 4 | 14 | +| macls\\optimizer | 2 | 68 | 0 | 14 | 82 | +| macls\\utils | 4 | 230 | 64 | 34 | 328 | +| tools | 1 | 19 | 1 | 10 | 30 | + +Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md) \ No newline at end of file diff --git a/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/results.txt b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/results.txt new file mode 100644 index 0000000..8367acc --- /dev/null +++ b/src/声源定位代码/audio-classification/.VSCodeCounter/2025-06-10_08-55-17/results.txt @@ -0,0 +1,107 @@ +Date : 2025-06-10 08:55:17 +Directory : e:\pycharm_projects\AudioClassification-Pytorch-master +Total : 54 files, 6851 codes, 838 comments, 1201 blanks, all 8890 lines + +Languages ++------------------+------------+------------+------------+------------+------------+ +| language | files | code | comment | blank | total | ++------------------+------------+------------+------------+------------+------------+ +| Vue | 7 | 4,038 | 206 | 691 | 4,935 | +| Python | 25 | 1,509 | 350 | 240 | 2,099 | +| Markdown | 3 | 629 | 0 | 158 | 787 | +| YAML | 8 | 322 | 252 | 40 | 614 | +| JavaScript | 4 | 215 | 26 | 44 | 285 | +| Shell Script | 2 | 45 | 4 | 18 | 67 | +| HTML | 1 | 25 | 0 | 2 | 27 | +| pip requirements | 2 | 24 | 0 | 2 | 26 | +| JSON | 1 | 24 | 0 | 1 | 25 | +| Batch | 1 | 20 | 0 | 5 | 25 | ++------------------+------------+------------+------------+------------+------------+ + +Directories ++------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+ +| path | files | code | comment | blank | total | ++------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+ +| . | 54 | 6,851 | 838 | 1,201 | 8,890 | +| . (Files) | 11 | 793 | 23 | 183 | 999 | +| audio-classification-platform | 19 | 4,728 | 278 | 864 | 5,870 | +| audio-classification-platform (Files) | 3 | 142 | 3 | 54 | 199 | +| audio-classification-platform\backend | 3 | 284 | 43 | 72 | 399 | +| audio-classification-platform\frontend | 13 | 4,302 | 232 | 738 | 5,272 | +| audio-classification-platform\frontend (Files) | 3 | 81 | 0 | 5 | 86 | +| audio-classification-platform\frontend\src | 10 | 4,221 | 232 | 733 | 5,186 | +| audio-classification-platform\frontend\src (Files) | 2 | 101 | 6 | 19 | 126 | +| audio-classification-platform\frontend\src\components | 5 | 3,258 | 161 | 567 | 3,986 | +| audio-classification-platform\frontend\src\router | 1 | 14 | 0 | 4 | 18 | +| audio-classification-platform\frontend\src\utils | 1 | 156 | 25 | 33 | 214 | +| audio-classification-platform\frontend\src\views | 1 | 692 | 40 | 110 | 842 | +| configs | 8 | 322 | 252 | 40 | 614 | +| macls | 15 | 989 | 284 | 104 | 1,377 | +| macls (Files) | 3 | 463 | 146 | 28 | 637 | +| macls\data_utils | 4 | 219 | 73 | 24 | 316 | +| macls\metric | 2 | 9 | 1 | 4 | 14 | +| macls\optimizer | 2 | 68 | 0 | 14 | 82 | +| macls\utils | 4 | 230 | 64 | 34 | 328 | +| tools | 1 | 19 | 1 | 10 | 30 | ++------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+ + +Files ++------------------------------------------------------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+ +| filename | language | code | comment | blank | total | ++------------------------------------------------------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+ +| e:\pycharm_projects\AudioClassification-Pytorch-master\README.md | Markdown | 302 | 0 | 72 | 374 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\README_en.md | Markdown | 231 | 0 | 45 | 276 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\README.md | Markdown | 96 | 0 | 41 | 137 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\backend\app.py | Python | 255 | 35 | 61 | 351 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\backend\config.py | Python | 22 | 8 | 10 | 40 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\backend\requirements.txt | pip requirements | 7 | 0 | 1 | 8 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\index.html | HTML | 25 | 0 | 2 | 27 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\package.json | JSON | 24 | 0 | 1 | 25 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\App.vue | Vue | 88 | 5 | 14 | 107 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\AudioRecorder.vue | Vue | 551 | 38 | 102 | 691 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\AudioRecorder_new.vue | Vue | 811 | 48 | 162 | 1,021 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\AudioUpload.vue | Vue | 580 | 28 | 107 | 715 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\HistoryList.vue | Vue | 513 | 25 | 79 | 617 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\PredictionResult.vue | Vue | 803 | 22 | 117 | 942 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\main.js | JavaScript | 13 | 1 | 5 | 19 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\router\index.js | JavaScript | 14 | 0 | 4 | 18 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\utils\api.js | JavaScript | 156 | 25 | 33 | 214 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\views\HomePage.vue | Vue | 692 | 40 | 110 | 842 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\vite.config.js | JavaScript | 32 | 0 | 2 | 34 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\start.bat | Batch | 20 | 0 | 5 | 25 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\start.sh | Shell Script | 26 | 3 | 8 | 37 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\augmentation.yml | YAML | 21 | 21 | 5 | 47 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\cam++.yml | YAML | 43 | 33 | 5 | 81 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\ecapa_tdnn.yml | YAML | 43 | 33 | 5 | 81 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\eres2net.yml | YAML | 43 | 33 | 5 | 81 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\panns.yml | YAML | 43 | 33 | 5 | 81 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\res2net.yml | YAML | 43 | 33 | 5 | 81 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\resnet_se.yml | YAML | 43 | 33 | 5 | 81 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\tdnn.yml | YAML | 43 | 33 | 5 | 81 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\create_data.py | Python | 75 | 9 | 16 | 100 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\eval.py | Python | 20 | 2 | 5 | 27 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\extract_features.py | Python | 13 | 2 | 5 | 20 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\infer.py | Python | 17 | 1 | 6 | 24 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\infer_record.py | Python | 40 | 7 | 12 | 59 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\__init__.py | Python | 1 | 0 | 1 | 2 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\data_utils\__init__.py | Python | 0 | 0 | 1 | 1 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\data_utils\collate_fn.py | Python | 17 | 4 | 3 | 24 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\data_utils\featurizer.py | Python | 88 | 36 | 9 | 133 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\data_utils\reader.py | Python | 114 | 33 | 11 | 158 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\metric\__init__.py | Python | 0 | 0 | 1 | 1 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\metric\metrics.py | Python | 9 | 1 | 3 | 13 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\optimizer\__init__.py | Python | 26 | 0 | 7 | 33 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\optimizer\scheduler.py | Python | 42 | 0 | 7 | 49 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\predict.py | Python | 124 | 47 | 7 | 178 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\trainer.py | Python | 338 | 99 | 20 | 457 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\utils\__init__.py | Python | 0 | 0 | 1 | 1 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\utils\checkpoint.py | Python | 113 | 40 | 10 | 163 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\utils\record.py | Python | 18 | 8 | 6 | 32 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\utils\utils.py | Python | 99 | 16 | 17 | 132 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\record_audio.py | Python | 10 | 0 | 5 | 15 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\requirements.txt | pip requirements | 17 | 0 | 1 | 18 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\setup.py | Python | 43 | 1 | 11 | 55 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\tools\download_language_data.sh | Shell Script | 19 | 1 | 10 | 30 | +| e:\pycharm_projects\AudioClassification-Pytorch-master\train.py | Python | 25 | 1 | 5 | 31 | +| Total | | 6,851 | 838 | 1,201 | 8,890 | ++------------------------------------------------------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+ \ No newline at end of file diff --git a/src/声源定位代码/audio-classification/.gitignore b/src/声源定位代码/audio-classification/.gitignore new file mode 100644 index 0000000..92da7e3 --- /dev/null +++ b/src/声源定位代码/audio-classification/.gitignore @@ -0,0 +1,296 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +macls.egg-info/ + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django/Flask stuff +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal +instance/ +.webassets-cache + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +Pipfile.lock + +# PEP 582 +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Node.js dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +package-lock.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage +.grunt + +# Bower dependency directory +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons +build/Release + +# Dependency directories +jspm_packages/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env.local +.env.production + +# parcel-bundler cache +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt + +# Gatsby files +# Comment in the public line in if your project uses Gatsby +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Audio Classification specific ignores +# Model files (usually large) +*.pth +*.pt +*.h5 +*.ckpt +*.pb +*.onnx +*.pkl +*.joblib + +# Dataset directories (usually large audio files) +dataset/ +dataset/*/audio/ +dataset/*/wav/ +dataset/*/mp3/ +dataset/*/flac/ +# Uncomment if you want to ignore all audio files +# *.wav +# *.mp3 +# *.flac +# *.ogg +# *.m4a +# *.aac + +# Training artifacts and logs +log/ +logs/ +output/ +outputs/ +uploads/ +results/ +checkpoints/ +models/ +pretrained_models/ +feature_models/ +runs/ +wandb/ +mlruns/ +.mlflow/ + +# Temporary files +temp/ +tmp/ +*.tmp +test*.py + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# IDE files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# Audio processing temporary files +*.spec +*.mfcc +*.mel + +# Frontend build files +audio-classification-platform/frontend/dist/ +audio-classification-platform/frontend/build/ +audio-classification-platform/frontend/.vite/ + +# Uploaded files directory +audio-classification-platform/backend/uploads/ + +# Local development configuration +audio-classification-platform/backend/.env +audio-classification-platform/frontend/.env.local \ No newline at end of file diff --git a/src/声源定位代码/audio-classification/LICENSE b/src/声源定位代码/audio-classification/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/src/声源定位代码/audio-classification/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/声源定位代码/audio-classification/README.md b/src/声源定位代码/audio-classification/README.md new file mode 100644 index 0000000..5e1aa31 --- /dev/null +++ b/src/声源定位代码/audio-classification/README.md @@ -0,0 +1,373 @@ +简体中文 | [English](./README_en.md) + +# 基于Pytorch实现的声音分类系统 + +![python version](https://img.shields.io/badge/python-3.8+-orange.svg) +![GitHub forks](https://img.shields.io/github/forks/yeyupiaoling/AudioClassification-Pytorch) +![GitHub Repo stars](https://img.shields.io/github/stars/yeyupiaoling/AudioClassification-Pytorch) +![GitHub](https://img.shields.io/github/license/yeyupiaoling/AudioClassification-Pytorch) +![支持系统](https://img.shields.io/badge/支持系统-Win/Linux/MAC-9cf) + +# 前言 + +本项目是基于Pytorch的声音分类项目,旨在实现对各种环境声音、动物叫声和语种的识别。项目提供了多种声音分类模型,如EcapaTdnn、PANNS、ResNetSE、CAMPPlus和ERes2Net,以支持不同的应用场景。此外,项目还提供了常用的Urbansound8K数据集测试报告和一些方言数据集的下载和使用例子。用户可以根据自己的需求选择适合的模型和数据集,以实现更准确的声音分类。项目的应用场景广泛,可以用于室外的环境监测、野生动物保护、语音识别等领域。同时,项目也鼓励用户探索更多的使用场景,以推动声音分类技术的发展和应用。 + +**欢迎大家扫码入知识星球或者QQ群讨论,知识星球里面提供项目的模型文件和博主其他相关项目的模型文件,也包括其他一些资源。** + +
+ 知识星球 + QQ群 +
+ + +# 目录 + +- [前言](#前言) +- [项目特性](#项目特性) +- [模型测试表](#模型测试表) +- [安装环境](#安装环境) +- [创建数据](#创建数据) +- [修改预处理方法(可选)](#修改预处理方法可选) +- [提取特征(可选)](#提取特征可选) +- [训练模型](#训练模型) +- [评估模型](#评估模型) +- [预测](#预测) +- [其他功能](#其他功能) + + +# 使用准备 + + - Anaconda 3 + - Python 3.11 + - Pytorch 2.0.1 + - Windows 11 or Ubuntu 22.04 + +# 项目特性 + +1. 支持模型:EcapaTdnn、PANNS、TDNN、Res2Net、ResNetSE、CAMPPlus、ERes2Net +2. 支持池化层:AttentiveStatsPool(ASP)、SelfAttentivePooling(SAP)、TemporalStatisticsPooling(TSP)、TemporalAveragePooling(TAP) +4. 支持预处理方法:MelSpectrogram、Spectrogram、MFCC、Fbank、Wav2vec2.0、WavLM + +**模型论文:** + +- EcapaTdnn:[ECAPA-TDNN: Emphasized Channel Attention, Propagation and Aggregation in TDNN Based Speaker Verification](https://arxiv.org/abs/2005.07143v3) +- PANNS:[PANNs: Large-Scale Pretrained Audio Neural Networks for Audio Pattern Recognition](https://arxiv.org/abs/1912.10211v5) +- TDNN:[Prediction of speech intelligibility with DNN-based performance measures](https://arxiv.org/abs/2203.09148) +- Res2Net:[Res2Net: A New Multi-scale Backbone Architecture](https://arxiv.org/abs/1904.01169) +- ResNetSE:[Squeeze-and-Excitation Networks](https://arxiv.org/abs/1709.01507) +- CAMPPlus:[CAM++: A Fast and Efficient Network for Speaker Verification Using Context-Aware Masking](https://arxiv.org/abs/2303.00332v3) +- ERes2Net:[An Enhanced Res2Net with Local and Global Feature Fusion for Speaker Verification](https://arxiv.org/abs/2305.12838v1) + +# 模型测试表 + +| 模型 | Params(M) | 预处理方法 | 数据集 | 类别数量 | 准确率 | 获取模型 | +|:------------:|:---------:|:-----:|:------------:|:----:|:-------:|:--------:| +| ResNetSE | 7.8 | Flank | UrbanSound8K | 10 | 0.96233 | 加入知识星球获取 | +| ERes2NetV2 | 5.4 | Flank | UrbanSound8K | 10 | 0.95662 | 加入知识星球获取 | +| CAMPPlus | 7.1 | Flank | UrbanSound8K | 10 | 0.95454 | 加入知识星球获取 | +| EcapaTdnn | 6.4 | Flank | UrbanSound8K | 10 | 0.95227 | 加入知识星球获取 | +| ERes2Net | 6.6 | Flank | UrbanSound8K | 10 | 0.94292 | 加入知识星球获取 | +| TDNN | 2.6 | Flank | UrbanSound8K | 10 | 0.93977 | 加入知识星球获取 | +| PANNS(CNN10) | 5.2 | Flank | UrbanSound8K | 10 | 0.92954 | 加入知识星球获取 | +| Res2Net | 5.0 | Flank | UrbanSound8K | 10 | 0.92580 | 加入知识星球获取 | + +**说明:** + +1. 使用的测试集为从数据集中每10条音频取一条,共874条。 + +## 安装环境 + + - 首先安装的是Pytorch的GPU版本,如果已经安装过了,请跳过。 +```shell +conda install pytorch==2.5.1 torchvision==0.20.1 torchaudio==2.5.1 pytorch-cuda=11.8 -c pytorch -c nvidia +``` + + - 安装macls库。 + +使用pip安装,命令如下: +```shell +python -m pip install macls -U -i https://pypi.tuna.tsinghua.edu.cn/simple +``` + +**建议源码安装**,源码安装能保证使用最新代码。 +```shell +git clone https://github.com/yeyupiaoling/AudioClassification-Pytorch.git +cd AudioClassification-Pytorch/ +pip install . +``` + +## 创建数据 + +生成数据列表,用于下一步的读取需要,`audio_path`为音频文件路径,用户需要提前把音频数据集存放在`dataset/audio`目录下,每个文件夹存放一个类别的音频数据,每条音频数据长度在3秒以上,如 `dataset/audio/鸟叫声/······`。`audio`是数据列表存放的位置,生成的数据类别的格式为 `音频路径\t音频对应的类别标签`,音频路径和标签用制表符 `\t`分开。读者也可以根据自己存放数据的方式修改以下函数。 + +以Urbansound8K为例,Urbansound8K是目前应用较为广泛的用于自动城市环境声分类研究的公共数据集,包含10个分类:空调声、汽车鸣笛声、儿童玩耍声、狗叫声、钻孔声、引擎空转声、枪声、手提钻、警笛声和街道音乐声。数据集下载地址:[UrbanSound8K.tar.gz](https://aistudio.baidu.com/aistudio/datasetdetail/36625)。以下是针对Urbansound8K生成数据列表的函数。如果读者想使用该数据集,请下载并解压到 `dataset`目录下,把生成数据列表代码改为以下代码。 + +执行`create_data.py`即可生成数据列表,里面提供了生成多种数据集列表方式,具体看代码。 +```shell +python create_data.py +``` + +生成的列表是长这样的,前面是音频的路径,后面是该音频对应的标签,从0开始,路径和标签之间用`\t`隔开。 +```shell +dataset/UrbanSound8K/audio/fold2/104817-4-0-2.wav 4 +dataset/UrbanSound8K/audio/fold9/105029-7-2-5.wav 7 +dataset/UrbanSound8K/audio/fold3/107228-5-0-0.wav 5 +dataset/UrbanSound8K/audio/fold4/109711-3-2-4.wav 3 +``` + +# 修改预处理方法(可选) + +配置文件中默认使用的是Fbank预处理方法,如果要使用其他预处理方法,可以修改配置文件中的安装下面方式修改,具体的值可以根据自己情况修改。如果不清楚如何设置参数,可以直接删除该部分,直接使用默认值。 + +```yaml +# 数据预处理参数 +preprocess_conf: + # 是否使用HF上的Wav2Vec2类似模型提取音频特征 + use_hf_model: False + # 音频预处理方法,也可以叫特征提取方法 + # 当use_hf_model为False时,支持:MelSpectrogram、Spectrogram、MFCC、Fbank + # 当use_hf_model为True时,指定的是HuggingFace的模型或者本地路径,比如facebook/w2v-bert-2.0或者./feature_models/w2v-bert-2.0 + feature_method: 'Fbank' + # 当use_hf_model为False时,设置API参数,更参数查看对应API,不清楚的可以直接删除该部分,直接使用默认值。 + # 当use_hf_model为True时,可以设置参数use_gpu,指定是否使用GPU提取特征 + method_args: + sample_frequency: 16000 + num_mel_bins: 80 +``` + +# 提取特征(可选) + +在训练过程中,首先是要读取音频数据,然后提取特征,最后再进行训练。其中读取音频数据、提取特征也是比较消耗时间的,所以我们可以选择提前提取好取特征,训练模型的是就可以直接加载提取好的特征,这样训练速度会更快。这个提取特征是可选择,如果没有提取好的特征,训练模型的时候就会从读取音频数据,然后提取特征开始。提取特征步骤如下: + +1. 执行`extract_features.py`,提取特征,特征会保存在`dataset/features`目录下,并生成新的数据列表`train_list_features.txt`和`test_list_features.txt`。 + +```shell +python extract_features.py --configs=configs/cam++.yml --save_dir=dataset/features +``` + +2. 修改配置文件,将`dataset_conf.train_list`和`dataset_conf.test_list`修改为`train_list_features.txt`和`test_list_features.txt`。 + + +## 训练模型 + +接着就可以开始训练模型了,创建 `train.py`。配置文件里面的参数一般不需要修改,但是这几个是需要根据自己实际的数据集进行调整的,首先最重要的就是分类大小`dataset_conf.num_class`,这个每个数据集的分类大小可能不一样,根据自己的实际情况设定。然后是`dataset_conf.batch_size`,如果是显存不够的话,可以减小这个参数。 + +```shell +# 单卡训练 +CUDA_VISIBLE_DEVICES=0 python train.py +# 多卡训练 +CUDA_VISIBLE_DEVICES=0,1 torchrun --standalone --nnodes=1 --nproc_per_node=2 train.py +``` + +训练输出日志: +``` +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:14 - ----------- 额外配置参数 ----------- +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:16 - configs: configs/ecapa_tdnn.yml +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:16 - local_rank: 0 +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:16 - pretrained_model: None +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:16 - resume_model: None +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:16 - save_model_path: models/ +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:16 - use_gpu: True +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:17 - ------------------------------------------------ +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:19 - ----------- 配置文件参数 ----------- +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:22 - dataset_conf: +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:25 - aug_conf: +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - noise_aug_prob: 0.2 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - noise_dir: dataset/noise +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - speed_perturb: True +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - volume_aug_prob: 0.2 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - volume_perturb: False +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:25 - dataLoader: +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - batch_size: 64 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - num_workers: 4 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:29 - do_vad: False +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:25 - eval_conf: +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - batch_size: 1 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - max_duration: 20 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:29 - label_list_path: dataset/label_list.txt +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:29 - max_duration: 3 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:29 - min_duration: 0.5 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:29 - sample_rate: 16000 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:25 - spec_aug_args: +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - freq_mask_width: [0, 8] +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - time_mask_width: [0, 10] +[2023-08-07 22:54:22.203167 INFO ] utils:print_arguments:29 - target_dB: -20 +[2023-08-07 22:54:22.203167 INFO ] utils:print_arguments:29 - test_list: dataset/test_list.txt +[2023-08-07 22:54:22.203167 INFO ] utils:print_arguments:29 - train_list: dataset/train_list.txt +[2023-08-07 22:54:22.203167 INFO ] utils:print_arguments:29 - use_dB_normalization: True +[2023-08-07 22:54:22.203167 INFO ] utils:print_arguments:29 - use_spec_aug: True +[2023-08-07 22:54:22.203167 INFO ] utils:print_arguments:22 - model_conf: +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:29 - num_class: 10 +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:29 - pooling_type: ASP +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:22 - optimizer_conf: +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:29 - learning_rate: 0.001 +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:29 - optimizer: Adam +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:29 - scheduler: WarmupCosineSchedulerLR +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:25 - scheduler_args: +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:27 - max_lr: 0.001 +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:27 - min_lr: 1e-05 +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:27 - warmup_epoch: 5 +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:29 - weight_decay: 1e-06 +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:22 - preprocess_conf: +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:29 - feature_method: Fbank +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:25 - method_args: +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:27 - num_mel_bins: 80 +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:27 - sample_frequency: 16000 +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:22 - train_conf: +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:29 - log_interval: 10 +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:29 - max_epoch: 30 +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:31 - use_model: EcapaTdnn +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:32 - ------------------------------------------------ +[2023-08-07 22:54:22.213166 WARNING] trainer:__init__:67 - Windows系统不支持多线程读取数据,已自动关闭! +========================================================================================== +Layer (type:depth-idx) Output Shape Param # +========================================================================================== +EcapaTdnn [1, 10] -- +├─Conv1dReluBn: 1-1 [1, 512, 98] -- +│ └─Conv1d: 2-1 [1, 512, 98] 204,800 +│ └─BatchNorm1d: 2-2 [1, 512, 98] 1,024 +├─Sequential: 1-2 [1, 512, 98] -- +│ └─Conv1dReluBn: 2-3 [1, 512, 98] -- +│ │ └─Conv1d: 3-1 [1, 512, 98] 262,144 +│ │ └─BatchNorm1d: 3-2 [1, 512, 98] 1,024 +│ └─Res2Conv1dReluBn: 2-4 [1, 512, 98] -- +│ │ └─ModuleList: 3-15 -- (recursive) +│ │ └─ModuleList: 3-16 -- (recursive) +│ │ └─ModuleList: 3-15 -- (recursive) +│ │ └─ModuleList: 3-16 -- (recursive) +│ │ └─ModuleList: 3-15 -- (recursive) +│ │ └─ModuleList: 3-16 -- (recursive) +│ │ └─ModuleList: 3-15 -- (recursive) +│ │ └─ModuleList: 3-16 -- (recursive) +│ │ └─ModuleList: 3-15 -- (recursive) +│ │ └─ModuleList: 3-16 -- (recursive) +··································· +│ │ └─ModuleList: 3-56 -- (recursive) +│ │ └─ModuleList: 3-55 -- (recursive) +│ │ └─ModuleList: 3-56 -- (recursive) +│ │ └─ModuleList: 3-55 -- (recursive) +│ │ └─ModuleList: 3-56 -- (recursive) +│ └─Conv1dReluBn: 2-13 [1, 512, 98] -- +│ │ └─Conv1d: 3-57 [1, 512, 98] 262,144 +│ │ └─BatchNorm1d: 3-58 [1, 512, 98] 1,024 +│ └─SE_Connect: 2-14 [1, 512, 98] -- +│ │ └─Linear: 3-59 [1, 256] 131,328 +│ │ └─Linear: 3-60 [1, 512] 131,584 +├─Conv1d: 1-5 [1, 1536, 98] 2,360,832 +├─AttentiveStatsPool: 1-6 [1, 3072] -- +│ └─Conv1d: 2-15 [1, 128, 98] 196,736 +│ └─Conv1d: 2-16 [1, 1536, 98] 198,144 +├─BatchNorm1d: 1-7 [1, 3072] 6,144 +├─Linear: 1-8 [1, 192] 590,016 +├─BatchNorm1d: 1-9 [1, 192] 384 +├─Linear: 1-10 [1, 10] 1,930 +========================================================================================== +Total params: 6,188,490 +Trainable params: 6,188,490 +Non-trainable params: 0 +Total mult-adds (M): 470.96 +========================================================================================== +Input size (MB): 0.03 +Forward/backward pass size (MB): 10.28 +Params size (MB): 24.75 +Estimated Total Size (MB): 35.07 +========================================================================================== +[2023-08-07 22:54:26.726095 INFO ] trainer:train:344 - 训练数据:8644 +[2023-08-07 22:54:30.092504 INFO ] trainer:__train_epoch:296 - Train epoch: [1/30], batch: [0/4], loss: 2.57033, accuracy: 0.06250, learning rate: 0.00001000, speed: 19.02 data/sec, eta: 0:06:43 +``` + +**训练可视化:** + +项目的根目录执行下面命令,并网页访问`http://localhost:8040/`,如果是服务器,需要修改`localhost`为服务器的IP地址。 +```shell +visualdl --logdir=log --host=0.0.0.0 +``` + +打开的网页如下: + +
+
+混淆矩阵 +
+ + + +# 评估模型 + +执行下面命令执行评估。 + +```shell +python eval.py --configs=configs/bi_lstm.yml +``` + +评估输出如下: +```shell +[2025-02-03 15:13:25.469242 INFO ] trainer:evaluate:461 - 成功加载模型:models/CAMPPlus_Fbank/best_model/model.pth +100%|██████████████████████████████| 150/150 [00:00<00:00, 1281.96it/s] +评估消耗时间:1s,loss:0.61840,accuracy:0.87333 +``` + +评估会出来输出准确率,还保存了混淆矩阵图片,保存路径`output/images/`,如下。 + +
+
+混淆矩阵 +
+ + +注意:如果类别标签是中文的,需要设置安装字体才能正常显示,一般情况下Windows无需安装,Ubuntu需要安装。如果Windows确实是缺少字体,只需要[字体文件](https://github.com/tracyone/program_font)这里下载`.ttf`格式的文件,复制到`C:\Windows\Fonts`即可。Ubuntu系统操作如下。 + +1. 安装字体 +```shell +git clone https://github.com/tracyone/program_font && cd program_font && ./install.sh +``` + +2. 执行下面Python代码 +```python +import matplotlib +import shutil +import os + +path = matplotlib.matplotlib_fname() +path = path.replace('matplotlibrc', 'fonts/ttf/') +print(path) +shutil.copy('/usr/share/fonts/MyFonts/simhei.ttf', path) +user_dir = os.path.expanduser('~') +shutil.rmtree(f'{user_dir}/.cache/matplotlib', ignore_errors=True) +``` + +# 预测 + +在训练结束之后,我们得到了一个模型参数文件,我们使用这个模型预测音频。 + +```shell +python infer.py --audio_path=dataset/UrbanSound8K/audio/fold5/156634-5-2-5.wav +``` + +# 其他功能 + + - 为了方便读取录制数据和制作数据集,这里提供了录音程序`record_audio.py`,这个用于录制音频,录制的音频采样率为16000,单通道,16bit。 + +```shell +python record_audio.py +``` + + - `infer_record.py`这个程序是用来不断进行录音识别,我们可以大致理解为这个程序在实时录音识别。通过这个应该我们可以做一些比较有趣的事情,比如把麦克风放在小鸟经常来的地方,通过实时录音识别,一旦识别到有鸟叫的声音,如果你的数据集足够强大,有每种鸟叫的声音数据集,这样你还能准确识别是那种鸟叫。如果识别到目标鸟类,就启动程序,例如拍照等等。 + +```shell +python infer_record.py --record_seconds=3 +``` + +## 打赏作者 +
+
+

打赏一块钱支持一下作者

+打赏作者 +
+ +# 参考资料 + +1. https://github.com/PaddlePaddle/PaddleSpeech +2. https://github.com/yeyupiaoling/PaddlePaddle-MobileFaceNets +3. https://github.com/yeyupiaoling/PPASR +4. https://github.com/alibaba-damo-academy/3D-Speaker diff --git a/src/声源定位代码/audio-classification/README_en.md b/src/声源定位代码/audio-classification/README_en.md new file mode 100644 index 0000000..43e1e2a --- /dev/null +++ b/src/声源定位代码/audio-classification/README_en.md @@ -0,0 +1,275 @@ +[简体中文](./README.md) | English + +# Sound classification system implemented in Pytorch + +![python version](https://img.shields.io/badge/python-3.8+-orange.svg) +![GitHub forks](https://img.shields.io/github/forks/yeyupiaoling/AudioClassification-Pytorch) +![GitHub Repo stars](https://img.shields.io/github/stars/yeyupiaoling/AudioClassification-Pytorch) +![GitHub](https://img.shields.io/github/license/yeyupiaoling/AudioClassification-Pytorch) +![支持系统](https://img.shields.io/badge/支持系统-Win/Linux/MAC-9cf) + +**Disclaimer, this document was obtained through machine translation, please check the original document [here](./README.md).** + + +# Introduction + +This project is a sound classification project based on Pytorch, aiming to realize the recognition of various environmental sounds, animal calls and languages. Several sound classification models such as EcapaTdnn, PANNS, ResNetSE, CAMPPlus, and ERes2Net are provided to support different application scenarios. In addition, the project also provides the commonly used Urbansound8K dataset test report and some dialect datasets download and use examples. Users can choose suitable models and datasets according to their needs to achieve more accurate sound classification. The project has a wide range of application scenarios, and can be used in outdoor environmental monitoring, wildlife protection, speech recognition and other fields. At the same time, the project also encourages users to explore more usage scenarios to promote the development and application of sound classification technology. + + +# Environment + + - Anaconda 3 + - Python 3.11 + - Pytorch 2.0.1 + - Windows 11 or Ubuntu 22.04 + +# Project Features + +1. Supporting models: EcapaTdnn、PANNS、TDNN、Res2Net、ResNetSE、CAMPPlus、ERes2Net +2. Supporting pooling: AttentiveStatsPool(ASP)、SelfAttentivePooling(SAP)、TemporalStatisticsPooling(TSP)、TemporalAveragePooling(TAP) +3. Support preprocessing methods: MelSpectrogram、Spectrogram、MFCC、Fbank、Wav2vec2.0、WavLM + +**Model Paper:** + +- EcapaTdnn:[ECAPA-TDNN: Emphasized Channel Attention, Propagation and Aggregation in TDNN Based Speaker Verification](https://arxiv.org/abs/2005.07143v3) +- PANNS:[PANNs: Large-Scale Pretrained Audio Neural Networks for Audio Pattern Recognition](https://arxiv.org/abs/1912.10211v5) +- TDNN:[Prediction of speech intelligibility with DNN-based performance measures](https://arxiv.org/abs/2203.09148) +- Res2Net:[Res2Net: A New Multi-scale Backbone Architecture](https://arxiv.org/abs/1904.01169) +- ResNetSE:[Squeeze-and-Excitation Networks](https://arxiv.org/abs/1709.01507) +- CAMPPlus:[CAM++: A Fast and Efficient Network for Speaker Verification Using Context-Aware Masking](https://arxiv.org/abs/2303.00332v3) +- ERes2Net:[An Enhanced Res2Net with Local and Global Feature Fusion for Speaker Verification](https://arxiv.org/abs/2305.12838v1) + +# Model Test + +| Model | Params(M) | Preprocessing method | Dataset | Number Class | Accuracy | +|:------------:|:---------:|:--------------------:|:------------:|:------------:|:--------:| +| ResNetSE | 7.8 | Flank | UrbanSound8K | 10 | 0.96233 | +| ERes2NetV2 | 5.4 | Flank | UrbanSound8K | 10 | 0.95662 | +| CAMPPlus | 7.1 | Flank | UrbanSound8K | 10 | 0.95454 | +| EcapaTdnn | 6.4 | Flank | UrbanSound8K | 10 | 0.95227 | +| ERes2Net | 6.6 | Flank | UrbanSound8K | 10 | 0.94292 | +| TDNN | 2.6 | Flank | UrbanSound8K | 10 | 0.93977 | +| PANNS(CNN10) | 5.2 | Flank | UrbanSound8K | 10 | 0.92954 | +| Res2Net | 5.0 | Flank | UrbanSound8K | 10 | 0.92580 | + +## Installation Environment + + - The GPU version of Pytorch will be installed first, please skip it if you already have it installed. +```shell +conda install pytorch==2.5.1 torchvision==0.20.1 torchaudio==2.5.1 pytorch-cuda=11.8 -c pytorch -c nvidia +``` + + - Install macls. + +Install it using pip with the following command: +```shell +python -m pip install macls -U -i https://pypi.tuna.tsinghua.edu.cn/simple +``` + +**Source installation is recommended**, which ensures that the latest code is used. +```shell +git clone https://github.com/yeyupiaoling/AudioClassification_Pytorch.git +cd AudioClassification_Pytorch/ +python setup.py install +``` + +## Preparing Data + +The `audio_path` is the audio file path. The user needs to store the audio dataset in the `dataset/audio` directory in advance. Each folder stores a category of audio data, and the length of each audio data is more than 3 seconds. For example, `dataset/audio/ bird song /······`. `audio` is where the data list is stored, and the format of the generated data category is`audio_path\tcategory_label_audio`, and the audio path and label are separated by a TAB character `\t`. You can also modify the following functions depending on how you store your data: + +Taking Urbansound8K as an example, it is a widely used public dataset for automatic urban environmental sound classification research. Urbansound8K contains 10 categories: air condition sound, car whistle sound, children playing sound, dog bark, drilling sound, engine idling sound, gun sound, jackdrill, siren sound, and street music sound. Data set download address: [UrbanSound8K](https://zenodo.org/record/1203745/files/UrbanSound8K.tar.gz). Here is the function to generate a list of data for Urbansound8K. If you want to use this dataset, please download and unzip it into the `dataset` directory and change the code to generate the list of data as follows. + +`create_data.py` can be used to generate a list of data sets. There are many ways to generate a list of data sets. +```shell +python create_data.py +``` + +The resulting list looks like this, with the path to the audio followed by the tag for that audio, starting at 0, and separated by `\t`. +```shell +dataset/UrbanSound8K/audio/fold2/104817-4-0-2.wav 4 +dataset/UrbanSound8K/audio/fold9/105029-7-2-5.wav 7 +dataset/UrbanSound8K/audio/fold3/107228-5-0-0.wav 5 +dataset/UrbanSound8K/audio/fold4/109711-3-2-4.wav 3 +``` + +# Change preprocessing methods + +By default, the Fbank preprocessing method is used in the configuration file. If you want to use other preprocessing methods, you can modify the following installation in the configuration file, and the specific value can be modified according to your own situation. If it's not clear how to set the parameters, you can remove that section and just use the default values. + +```yaml +# 数据预处理参数 +preprocess_conf: + # 是否使用HF上的Wav2Vec2类似模型提取音频特征 + use_hf_model: False + # 音频预处理方法,也可以叫特征提取方法 + # 当use_hf_model为False时,支持:MelSpectrogram、Spectrogram、MFCC、Fbank + # 当use_hf_model为True时,指定的是HuggingFace的模型或者本地路径,比如facebook/w2v-bert-2.0或者./feature_models/w2v-bert-2.0 + feature_method: 'Fbank' + # 当use_hf_model为False时,设置API参数,更参数查看对应API,不清楚的可以直接删除该部分,直接使用默认值。 + # 当use_hf_model为True时,可以设置参数use_gpu,指定是否使用GPU提取特征 + method_args: + sample_frequency: 16000 + num_mel_bins: 80 +``` + +## 训练 + +Now we can train the model. We will create `train.py`. The parameters in the configuration file generally do not need to be modified, but these few need to be adjusted according to your actual dataset. The first and most important is the class size `dataset_conf.num_class`, which may be different for each dataset. Then there is` dataset_conf.batch_size `, which can be reduced if memory is insufficient. + +```shell +# Single GPU training +CUDA_VISIBLE_DEVICES=0 python train.py +# Multi GPU training +CUDA_VISIBLE_DEVICES=0,1 torchrun --standalone --nnodes=1 --nproc_per_node=2 train.py +``` + +Train log: +``` +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:14 - ----------- 额外配置参数 ----------- +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:16 - configs: configs/ecapa_tdnn.yml +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:16 - local_rank: 0 +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:16 - pretrained_model: None +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:16 - resume_model: None +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:16 - save_model_path: models/ +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:16 - use_gpu: True +[2023-08-07 22:54:22.148973 INFO ] utils:print_arguments:17 - ------------------------------------------------ +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:19 - ----------- 配置文件参数 ----------- +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:22 - dataset_conf: +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:25 - aug_conf: +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - noise_aug_prob: 0.2 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - noise_dir: dataset/noise +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - speed_perturb: True +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - volume_aug_prob: 0.2 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - volume_perturb: False +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:25 - dataLoader: +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - batch_size: 64 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - num_workers: 4 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:29 - do_vad: False +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:25 - eval_conf: +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - batch_size: 1 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - max_duration: 20 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:29 - label_list_path: dataset/label_list.txt +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:29 - max_duration: 3 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:29 - min_duration: 0.5 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:29 - sample_rate: 16000 +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:25 - spec_aug_args: +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - freq_mask_width: [0, 8] +[2023-08-07 22:54:22.202166 INFO ] utils:print_arguments:27 - time_mask_width: [0, 10] +[2023-08-07 22:54:22.203167 INFO ] utils:print_arguments:29 - target_dB: -20 +[2023-08-07 22:54:22.203167 INFO ] utils:print_arguments:29 - test_list: dataset/test_list.txt +[2023-08-07 22:54:22.203167 INFO ] utils:print_arguments:29 - train_list: dataset/train_list.txt +[2023-08-07 22:54:22.203167 INFO ] utils:print_arguments:29 - use_dB_normalization: True +[2023-08-07 22:54:22.203167 INFO ] utils:print_arguments:29 - use_spec_aug: True +[2023-08-07 22:54:22.203167 INFO ] utils:print_arguments:22 - model_conf: +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:29 - num_class: 10 +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:29 - pooling_type: ASP +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:22 - optimizer_conf: +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:29 - learning_rate: 0.001 +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:29 - optimizer: Adam +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:29 - scheduler: WarmupCosineSchedulerLR +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:25 - scheduler_args: +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:27 - max_lr: 0.001 +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:27 - min_lr: 1e-05 +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:27 - warmup_epoch: 5 +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:29 - weight_decay: 1e-06 +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:22 - preprocess_conf: +[2023-08-07 22:54:22.207167 INFO ] utils:print_arguments:29 - feature_method: Fbank +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:25 - method_args: +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:27 - num_mel_bins: 80 +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:27 - sample_frequency: 16000 +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:22 - train_conf: +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:29 - log_interval: 10 +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:29 - max_epoch: 30 +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:31 - use_model: EcapaTdnn +[2023-08-07 22:54:22.208167 INFO ] utils:print_arguments:32 - ------------------------------------------------ +[2023-08-07 22:54:22.213166 WARNING] trainer:__init__:67 - Windows系统不支持多线程读取数据,已自动关闭! +========================================================================================== +Layer (type:depth-idx) Output Shape Param # +========================================================================================== +EcapaTdnn [1, 10] -- +├─Conv1dReluBn: 1-1 [1, 512, 98] -- +│ └─Conv1d: 2-1 [1, 512, 98] 204,800 +│ └─BatchNorm1d: 2-2 [1, 512, 98] 1,024 +├─Sequential: 1-2 [1, 512, 98] -- +│ └─Conv1dReluBn: 2-3 [1, 512, 98] -- +│ │ └─Conv1d: 3-1 [1, 512, 98] 262,144 +│ │ └─BatchNorm1d: 3-2 [1, 512, 98] 1,024 +│ └─Res2Conv1dReluBn: 2-4 [1, 512, 98] -- +│ │ └─ModuleList: 3-15 -- (recursive) +│ │ └─ModuleList: 3-16 -- (recursive) +│ │ └─ModuleList: 3-15 -- (recursive) +│ │ └─ModuleList: 3-16 -- (recursive) +│ │ └─ModuleList: 3-15 -- (recursive) +│ │ └─ModuleList: 3-16 -- (recursive) +│ │ └─ModuleList: 3-15 -- (recursive) +│ │ └─ModuleList: 3-16 -- (recursive) +│ │ └─ModuleList: 3-15 -- (recursive) +│ │ └─ModuleList: 3-16 -- (recursive) +··································· +│ │ └─ModuleList: 3-56 -- (recursive) +│ │ └─ModuleList: 3-55 -- (recursive) +│ │ └─ModuleList: 3-56 -- (recursive) +│ │ └─ModuleList: 3-55 -- (recursive) +│ │ └─ModuleList: 3-56 -- (recursive) +│ └─Conv1dReluBn: 2-13 [1, 512, 98] -- +│ │ └─Conv1d: 3-57 [1, 512, 98] 262,144 +│ │ └─BatchNorm1d: 3-58 [1, 512, 98] 1,024 +│ └─SE_Connect: 2-14 [1, 512, 98] -- +│ │ └─Linear: 3-59 [1, 256] 131,328 +│ │ └─Linear: 3-60 [1, 512] 131,584 +├─Conv1d: 1-5 [1, 1536, 98] 2,360,832 +├─AttentiveStatsPool: 1-6 [1, 3072] -- +│ └─Conv1d: 2-15 [1, 128, 98] 196,736 +│ └─Conv1d: 2-16 [1, 1536, 98] 198,144 +├─BatchNorm1d: 1-7 [1, 3072] 6,144 +├─Linear: 1-8 [1, 192] 590,016 +├─BatchNorm1d: 1-9 [1, 192] 384 +├─Linear: 1-10 [1, 10] 1,930 +========================================================================================== +Total params: 6,188,490 +Trainable params: 6,188,490 +Non-trainable params: 0 +Total mult-adds (M): 470.96 +========================================================================================== +Input size (MB): 0.03 +Forward/backward pass size (MB): 10.28 +Params size (MB): 24.75 +Estimated Total Size (MB): 35.07 +========================================================================================== +[2023-08-07 22:54:26.726095 INFO ] trainer:train:344 - 训练数据:8644 +[2023-08-07 22:54:30.092504 INFO ] trainer:__train_epoch:296 - Train epoch: [1/30], batch: [0/4], loss: 2.57033, accuracy: 0.06250, learning rate: 0.00001000, speed: 19.02 data/sec, eta: 0:06:43 +``` + +# Eval + +At the end of each training round, we can perform an evaluation, which will output the accuracy. We also save the mixture matrix image, and save the path `output/images/` as follows. +![混合矩阵](docs/images/image1.png) + +# Inference + +At the end of the training, we are given a model parameter file, and we use this model to predict the audio. + +```shell +python infer.py --audio_path=dataset/UrbanSound8K/audio/fold5/156634-5-2-5.wav +``` + +# Other Functions + + - In order to read the recorded data and make a dataset easily, we provide the recording program `record_audio.py`, which is used to record audio with a sample rate of 16,000, single channel, 16bit. + +```shell +python record_audio.py +``` + + - `infer_record.py`This program is used to continuously perform recording recognition, and we can roughly understand this program as recording recognition in real time. And this should allow us to do some interesting things, like put a microphone in a place where birds often come, and recognize it by recording it in real time, and once you recognize that there's a bird calling, if your dataset is powerful enough, and you have a dataset of every bird calling, then you can identify exactly which bird is calling. If the target bird is identified, the procedure is initiated, such as taking photos, etc. + +```shell +python infer_record.py --record_seconds=3 +``` + +# Reference + +1. https://github.com/PaddlePaddle/PaddleSpeech +2. https://github.com/yeyupiaoling/PaddlePaddle-MobileFaceNets +3. https://github.com/yeyupiaoling/PPASR +4. https://github.com/alibaba-damo-academy/3D-Speaker diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/README.md b/src/声源定位代码/audio-classification/audio-classification-platform/README.md new file mode 100644 index 0000000..d3ecd35 --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/README.md @@ -0,0 +1,136 @@ +# 声纹识别系统 + +这是一个基于深度学习的声纹识别系统,包含Flask后端和Vue前端。 + +## 功能特性 + +- 🎵 支持多种音频格式上传识别 +- 🎤 实时录音识别功能 +- 📊 置信度可视化展示 +- 📱 响应式设计,支持移动端 +- 📝 识别历史记录 +- 📋 结果导出功能 + +## 技术栈 + +### 后端 +- Flask + Flask-CORS +- PyTorch + MAClsPredictor +- librosa + soundfile + +### 前端 +- Vue 3 + Composition API +- Element Plus UI框架 +- Axios HTTP客户端 +- Vite构建工具 + +## 快速开始 + +### 环境要求 + +- Python 3.8+ +- Node.js 16+ +- 已训练好的音频分类模型 + +### 1. 安装后端依赖 + +```bash +cd audio-classification-platform/backend +pip install -r requirements.txt +``` + +### 2. 安装前端依赖 + +```bash +cd audio-classification-platform/frontend +npm install +``` + +### 3. 启动后端服务 + +```bash +cd audio-classification-platform/backend +python app.py +``` + +后端服务将在 http://localhost:5000 启动 + +### 4. 启动前端服务 + +```bash +cd audio-classification-platform/frontend +npm run dev +``` + +前端服务将在 http://localhost:3000 启动 + +## 使用说明 + +1. 打开浏览器访问 http://localhost:3000 +2. 首次使用需要初始化模型(确保模型文件路径正确) +3. 使用文件上传功能或实时录音功能进行音频识别 +4. 查看识别结果和置信度 +5. 可以查看历史识别记录 + +## 配置说明 + +### 后端配置 + +在 `backend/config.py` 中可以配置: + +- 模型路径 +- 上传文件限制 +- GPU使用设置 +- 音频处理参数 + +### 前端配置 + +在 `frontend/vite.config.js` 中可以配置: + +- 开发服务器端口 +- 代理设置 +- 构建选项 + +## API接口 + +- `GET /api/health` - 健康检查 +- `POST /api/init` - 初始化模型 +- `POST /api/upload` - 上传音频文件识别 +- `POST /api/predict` - 录音数据识别 +- `GET /api/labels` - 获取分类标签 +- `GET /api/model/info` - 获取模型信息 + +## 部署 + +### 生产环境构建 + +```bash +# 构建前端 +cd frontend +npm run build + +# 启动后端(推荐使用gunicorn) +cd backend +gunicorn -w 4 -b 0.0.0.0:5000 app:app +``` + +## 故障排除 + +1. **模型初始化失败** + - 检查模型文件路径是否正确 + - 确保配置文件存在 + - 检查GPU驱动(如果使用GPU) + +2. **音频上传失败** + - 检查文件格式是否支持 + - 检查文件大小是否超限 + - 检查网络连接 + +3. **录音功能不可用** + - 检查浏览器麦克风权限 + - 使用HTTPS协议(录音功能需要) + - 检查设备麦克风是否正常 + +## 许可证 + +Apache License 2.0 diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/backend/app.py b/src/声源定位代码/audio-classification/audio-classification-platform/backend/app.py new file mode 100644 index 0000000..d9e1caa --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/backend/app.py @@ -0,0 +1,350 @@ +import os +import sys +import tempfile +import uuid +from datetime import datetime +from flask import Flask, request, jsonify, send_from_directory +from flask_cors import CORS +from werkzeug.utils import secure_filename +import librosa +import soundfile as sf +import numpy as np +from loguru import logger + +# 添加项目根目录到Python路径 +sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) + +from macls.predict import MAClsPredictor + +app = Flask(__name__) +CORS(app) # 允许跨域请求 + +# 配置 +app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # 最大50MB +app.config['UPLOAD_FOLDER'] = 'uploads' +app.config['ALLOWED_EXTENSIONS'] = {'wav', 'mp3', 'flac', 'm4a', 'ogg', 'aac'} + +# 确保上传目录存在 +os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) + +# 全局预测器实例 +predictor = None + +def allowed_file(filename): + """检查文件扩展名是否被允许""" + return '.' in filename and \ + filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS'] + +def convert_audio_format(input_path, target_sample_rate=16000): + """将音频转换为模型需要的格式""" + try: + # 使用librosa加载音频,自动转换采样率 + audio_data, sr = librosa.load(input_path, sr=target_sample_rate) + + # 创建临时文件 + temp_file = tempfile.NamedTemporaryFile(suffix='.wav', delete=False) + temp_path = temp_file.name + temp_file.close() + + # 保存为wav格式 + sf.write(temp_path, audio_data, target_sample_rate) + + return temp_path, audio_data, target_sample_rate + except Exception as e: + logger.error(f"音频格式转换失败: {str(e)}") + raise e + +@app.route('/api/health', methods=['GET']) +def health_check(): + """健康检查接口""" + return jsonify({ + 'status': 'success', + 'message': '服务正常运行', + 'timestamp': datetime.now().isoformat() + }) + +@app.route('/api/init', methods=['POST']) +def init_model(): + """初始化模型""" + global predictor + try: + data = request.get_json() + + # 默认配置路径 + configs = data.get('configs', '../../configs/cam++.yml') + model_path = data.get('model_path', '../../models/CAMPPlus_Fbank/best_model/') + use_gpu = data.get('use_gpu', True) + + # 转换为绝对路径 + configs = os.path.abspath(os.path.join(os.path.dirname(__file__), configs)) + model_path = os.path.abspath(os.path.join(os.path.dirname(__file__), model_path)) + + if not os.path.exists(configs): + return jsonify({ + 'status': 'error', + 'message': f'配置文件不存在: {configs}' + }), 400 + + if not os.path.exists(model_path): + return jsonify({ + 'status': 'error', + 'message': f'模型路径不存在: {model_path}' + }), 400 + + # 初始化预测器 + predictor = MAClsPredictor( + configs=configs, + model_path=model_path, + use_gpu=use_gpu + ) + + logger.info("模型初始化成功") + + return jsonify({ + 'status': 'success', + 'message': '模型初始化成功', + 'config': { + 'configs': configs, + 'model_path': model_path, + 'use_gpu': use_gpu + } + }) + + except Exception as e: + logger.error(f"模型初始化失败: {str(e)}") + return jsonify({ + 'status': 'error', + 'message': f'模型初始化失败: {str(e)}' + }), 500 + +@app.route('/api/upload', methods=['POST']) +def upload_and_predict(): + """上传音频文件并进行预测""" + global predictor + + if predictor is None: + return jsonify({ + 'status': 'error', + 'message': '模型未初始化,请先调用 /api/init 接口' + }), 400 + + if 'file' not in request.files: + return jsonify({ + 'status': 'error', + 'message': '没有上传文件' + }), 400 + + file = request.files['file'] + + if file.filename == '': + return jsonify({ + 'status': 'error', + 'message': '没有选择文件' + }), 400 + + if file and allowed_file(file.filename): + try: + # 生成唯一文件名 + filename = secure_filename(file.filename) + unique_filename = f"{uuid.uuid4()}_{filename}" + filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename) + + # 保存上传的文件 + file.save(filepath) + + # 转换音频格式 + converted_path, audio_data, sample_rate = convert_audio_format(filepath) + + # 进行预测 + start_time = datetime.now() + label, score = predictor.predict(audio_data=converted_path) + end_time = datetime.now() + + # 计算预测时间 + prediction_time = (end_time - start_time).total_seconds() + + # 清理临时文件 + if os.path.exists(converted_path): + os.remove(converted_path) + # 可选:保留原始文件或删除 + # os.remove(filepath) + + logger.info(f"预测完成 - 文件: {filename}, 结果: {label}, 得分: {score:.4f}, 耗时: {prediction_time:.3f}s") + + return jsonify({ + 'status': 'success', + 'result': { + 'predicted_class': label, # 前端期望的字段名 + 'confidence': float(score), # 前端期望的字段名 + 'label': label, # 保持兼容性 + 'score': float(score), # 保持兼容性 + 'filename': filename, + 'prediction_time': prediction_time, + 'audio_info': { + 'sample_rate': sample_rate, + 'duration': len(audio_data) / sample_rate + } + } + }) + + except Exception as e: + logger.error(f"预测失败: {str(e)}") + return jsonify({ + 'status': 'error', + 'message': f'预测失败: {str(e)}' + }), 500 + else: + return jsonify({ + 'status': 'error', + 'message': '不支持的文件格式,支持的格式:wav, mp3, flac, m4a, ogg, aac' + }), 400 + +@app.route('/api/predict', methods=['POST']) +def predict_audio_data(): + """直接预测音频数据(用于录音功能)""" + global predictor + + if predictor is None: + return jsonify({ + 'status': 'error', + 'message': '模型未初始化,请先调用 /api/init 接口' + }), 400 + + try: + data = request.get_json() + + if 'audio_data' not in data: + return jsonify({ + 'status': 'error', + 'message': '缺少音频数据' + }), 400 + + audio_data = np.array(data['audio_data'], dtype=np.float32) + sample_rate = data.get('sample_rate', 16000) + + # 创建临时文件 + temp_file = tempfile.NamedTemporaryFile(suffix='.wav', delete=False) + temp_path = temp_file.name + temp_file.close() + + # 保存音频数据 + sf.write(temp_path, audio_data, sample_rate) + + # 进行预测 + start_time = datetime.now() + label, score = predictor.predict(audio_data=temp_path) + end_time = datetime.now() + + prediction_time = (end_time - start_time).total_seconds() + # 清理临时文件 + os.remove(temp_path) + + logger.info(f"录音预测完成 - 结果: {label}, 得分: {score:.4f}, 耗时: {prediction_time:.3f}s") + + return jsonify({ + 'status': 'success', + 'result': { + 'predicted_class': label, # 前端期望的字段名 + 'confidence': float(score), # 前端期望的字段名 + 'label': label, # 保持兼容性 + 'score': float(score), # 保持兼容性 + 'prediction_time': prediction_time, + 'audio_info': { + 'sample_rate': sample_rate, + 'duration': len(audio_data) / sample_rate + } + } + }) + + except Exception as e: + logger.error(f"录音预测失败: {str(e)}") + return jsonify({ + 'status': 'error', + 'message': f'录音预测失败: {str(e)}' + }), 500 + +@app.route('/api/labels', methods=['GET']) +def get_labels(): + """获取分类标签列表""" + try: + # 读取标签文件 + label_file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../dataset/label_list.txt')) + + if not os.path.exists(label_file_path): + return jsonify({ + 'status': 'error', + 'message': '标签文件不存在' + }), 404 + + with open(label_file_path, 'r', encoding='utf-8') as f: + labels = [line.strip() for line in f.readlines() if line.strip()] + + return jsonify({ + 'status': 'success', + 'labels': labels, + 'count': len(labels) + }) + + except Exception as e: + logger.error(f"读取标签失败: {str(e)}") + return jsonify({ + 'status': 'error', + 'message': f'读取标签失败: {str(e)}' + }), 500 + +@app.route('/api/model/info', methods=['GET']) +def get_model_info(): + """获取模型信息""" + global predictor + + if predictor is None: + return jsonify({ + 'status': 'error', + 'message': '模型未初始化' + }), 400 + + try: + # 获取模型配置信息 + return jsonify({ + 'status': 'success', + 'model_info': { + 'initialized': True, + 'model_type': 'AudioClassification', + 'framework': 'PyTorch' + } + }) + + except Exception as e: + logger.error(f"获取模型信息失败: {str(e)}") + return jsonify({ + 'status': 'error', + 'message': f'获取模型信息失败: {str(e)}' + }), 500 + +@app.errorhandler(413) +def too_large(e): + """文件过大错误处理""" + return jsonify({ + 'status': 'error', + 'message': '文件过大,最大支持50MB' + }), 413 + +@app.errorhandler(404) +def not_found(e): + """404错误处理""" + return jsonify({ + 'status': 'error', + 'message': '接口不存在' + }), 404 + +@app.errorhandler(500) +def internal_error(e): + """500错误处理""" + return jsonify({ + 'status': 'error', + 'message': '服务器内部错误' + }), 500 + +if __name__ == '__main__': + logger.info("启动音频分类API服务器...") + app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/backend/config.py b/src/声源定位代码/audio-classification/audio-classification-platform/backend/config.py new file mode 100644 index 0000000..039439f --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/backend/config.py @@ -0,0 +1,39 @@ +import os +import sys + +# 配置 +class Config: + # 模型配置 + MODEL_CONFIG_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../configs/cam++.yml')) + MODEL_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../models/CAMPPlus_Fbank/best_model/')) + LABEL_FILE_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../dataset/label_list.txt')) + + # Flask配置 + SECRET_KEY = 'your-secret-key-here' + MAX_CONTENT_LENGTH = 50 * 1024 * 1024 # 50MB + + # 文件上传配置 + UPLOAD_FOLDER = 'uploads' + ALLOWED_EXTENSIONS = {'wav', 'mp3', 'flac', 'm4a', 'ogg', 'aac'} + + # 音频处理配置 + TARGET_SAMPLE_RATE = 16000 + + # 日志配置 + LOG_LEVEL = 'INFO' + + # GPU配置 + USE_GPU = True + +class DevelopmentConfig(Config): + DEBUG = True + +class ProductionConfig(Config): + DEBUG = False + +# 根据环境变量选择配置 +config = { + 'development': DevelopmentConfig, + 'production': ProductionConfig, + 'default': DevelopmentConfig +} diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/backend/requirements.txt b/src/声源定位代码/audio-classification/audio-classification-platform/backend/requirements.txt new file mode 100644 index 0000000..de8b375 --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/backend/requirements.txt @@ -0,0 +1,7 @@ +Flask==2.3.3 +Flask-CORS==4.0.0 +librosa==0.10.1 +soundfile==0.12.1 +numpy==1.24.3 +loguru==0.7.2 +Werkzeug==2.3.7 diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/frontend/index.html b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/index.html new file mode 100644 index 0000000..e8eba15 --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/index.html @@ -0,0 +1,26 @@ + + + + + + + 声纹识别系统 + + + +
+ + + diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/frontend/package.json b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/package.json new file mode 100644 index 0000000..39dcbc3 --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/package.json @@ -0,0 +1,24 @@ +{ + "name": "audio-classification-frontend", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "serve": "vite preview" + }, "dependencies": { + "vue": "^3.3.4", + "vue-router": "^4.2.4", + "axios": "^1.5.0", + "element-plus": "^2.3.9", + "@element-plus/icons-vue": "^2.1.0", + "echarts": "^5.4.3" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.3.4", + "vite": "^4.4.9", + "unplugin-vue-components": "^0.25.2", + "unplugin-auto-import": "^0.16.6" + } +} diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/App.vue b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/App.vue new file mode 100644 index 0000000..0908a7a --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/App.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/AudioRecorder.vue b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/AudioRecorder.vue new file mode 100644 index 0000000..d1614fe --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/AudioRecorder.vue @@ -0,0 +1,690 @@ + + + + + diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/AudioRecorder_new.vue b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/AudioRecorder_new.vue new file mode 100644 index 0000000..73a92b4 --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/AudioRecorder_new.vue @@ -0,0 +1,1020 @@ + + + + + diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/AudioUpload.vue b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/AudioUpload.vue new file mode 100644 index 0000000..2a10b97 --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/AudioUpload.vue @@ -0,0 +1,714 @@ + + + + + diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/HistoryList.vue b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/HistoryList.vue new file mode 100644 index 0000000..ac088fa --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/HistoryList.vue @@ -0,0 +1,616 @@ + + + + + diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/PredictionResult.vue b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/PredictionResult.vue new file mode 100644 index 0000000..33783a3 --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/components/PredictionResult.vue @@ -0,0 +1,941 @@ + + + + + diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/main.js b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/main.js new file mode 100644 index 0000000..99badf7 --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/main.js @@ -0,0 +1,18 @@ +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' + +const app = createApp(App) + +// 注册所有图标 +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} + +app.use(ElementPlus) +app.use(router) + +app.mount('#app') diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/router/index.js b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/router/index.js new file mode 100644 index 0000000..3109046 --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/router/index.js @@ -0,0 +1,17 @@ +import { createRouter, createWebHistory } from 'vue-router' +import HomePage from '../views/HomePage.vue' + +const routes = [ + { + path: '/', + name: 'Home', + component: HomePage + } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +export default router diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/utils/api.js b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/utils/api.js new file mode 100644 index 0000000..0db7b1a --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/utils/api.js @@ -0,0 +1,213 @@ +import axios from 'axios' +import { ElMessage } from 'element-plus' + +// 创建axios实例 +const api = axios.create({ + baseURL: '/api', + timeout: 30000, + headers: { + 'Content-Type': 'application/json' + } +}) + +// 请求拦截器 +api.interceptors.request.use( + (config) => { + // 可以在这里添加token等认证信息 + return config + }, + (error) => { + return Promise.reject(error) + } +) + +// 响应拦截器 +api.interceptors.response.use( + (response) => { + return response + }, + (error) => { + let errorMessage = '请求失败' + + if (error.response) { + // 服务器返回错误状态码 + const { status, data } = error.response + + switch (status) { + case 400: + errorMessage = data.message || '请求参数错误' + break + case 401: + errorMessage = '未授权访问' + break + case 403: + errorMessage = '禁止访问' + break + case 404: + errorMessage = '请求的资源不存在' + break + case 413: + errorMessage = '文件过大,最大支持50MB' + break + case 500: + errorMessage = data.message || '服务器内部错误' + break + default: + errorMessage = data.message || `请求失败 (${status})` + } + } else if (error.request) { + // 网络错误 + errorMessage = '网络连接失败,请检查网络设置' + } else { + // 其他错误 + errorMessage = error.message || '未知错误' + } + + // 显示错误消息 + ElMessage.error(errorMessage) + + return Promise.reject(new Error(errorMessage)) + } +) + +// API服务类 +export const apiService = { + // 健康检查 + healthCheck: () => { + return api.get('/health') + }, + + // 初始化模型 + initModel: (config) => { + return api.post('/init', config) + }, + // 上传文件并预测 + uploadAndPredict: (file, onProgress) => { + const formData = new FormData() + formData.append('file', file) + + return api.post('/upload', formData, { + // 不要手动设置 Content-Type,让浏览器自动设置 boundary + onUploadProgress: (progressEvent) => { + if (onProgress && progressEvent.total) { + const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total) + onProgress(percent) + } + } + }) + }, + + // 预测音频数据(录音) + predictAudioData: (audioData) => { + return api.post('/predict', audioData) + }, + + // 获取标签列表 + getLabels: () => { + return api.get('/labels') + }, + + // 获取模型信息 + getModelInfo: () => { + return api.get('/model/info') + } +} + +// 工具函数 +export const utils = { + // 格式化文件大小 + formatFileSize: (bytes) => { + if (bytes === 0) return '0 B' + + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] + }, + + // 格式化时长 + formatDuration: (seconds) => { + if (!seconds) return '0s' + + const hrs = Math.floor(seconds / 3600) + const mins = Math.floor((seconds % 3600) / 60) + const secs = Math.floor(seconds % 60) + + if (hrs > 0) { + return `${hrs}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}` + } else if (mins > 0) { + return `${mins}:${secs.toString().padStart(2, '0')}` + } else { + return `${secs}s` + } + }, + + // 检查音频文件格式 + isAudioFile: (file) => { + const allowedTypes = ['audio/wav', 'audio/mpeg', 'audio/flac', 'audio/m4a', 'audio/ogg', 'audio/aac'] + const allowedExtensions = ['wav', 'mp3', 'flac', 'm4a', 'ogg', 'aac'] + + const fileExtension = file.name.split('.').pop().toLowerCase() + + return allowedTypes.includes(file.type) || allowedExtensions.includes(fileExtension) + }, + + // 检查文件大小 + isFileSizeValid: (file, maxSizeMB = 50) => { + const maxSize = maxSizeMB * 1024 * 1024 + return file.size <= maxSize + }, + + // 生成唯一ID + generateId: () => { + return Date.now().toString(36) + Math.random().toString(36).substr(2) + }, + + // 下载文件 + downloadFile: (content, filename, type = 'application/json') => { + const blob = new Blob([content], { type }) + const url = URL.createObjectURL(blob) + + const link = document.createElement('a') + link.href = url + link.download = filename + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + URL.revokeObjectURL(url) + }, + + // 复制到剪贴板 + copyToClipboard: async (text) => { + try { + await navigator.clipboard.writeText(text) + return true + } catch (error) { + console.error('复制失败:', error) + return false + } + }, + + // 获取音频时长 + getAudioDuration: (file) => { + return new Promise((resolve, reject) => { + const audio = new Audio() + const url = URL.createObjectURL(file) + + audio.addEventListener('loadedmetadata', () => { + URL.revokeObjectURL(url) + resolve(audio.duration) + }) + + audio.addEventListener('error', (error) => { + URL.revokeObjectURL(url) + reject(error) + }) + + audio.src = url + }) + } +} + +export default api diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/views/HomePage.vue b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/views/HomePage.vue new file mode 100644 index 0000000..f313fea --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/src/views/HomePage.vue @@ -0,0 +1,841 @@ + + + + + diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/frontend/vite.config.js b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/vite.config.js new file mode 100644 index 0000000..34b05a8 --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/vite.config.js @@ -0,0 +1,33 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' + +export default defineConfig({ + plugins: [ + vue(), + AutoImport({ + resolvers: [ElementPlusResolver()], + }), + Components({ + resolvers: [ElementPlusResolver()], + }), + ], + server: { + port: 3000, + host: '0.0.0.0', + proxy: { + '/api': { + target: 'http://localhost:5000', + changeOrigin: true, + secure: false, + } + } + }, + build: { + outDir: 'dist', + assetsDir: 'assets', + chunkSizeWarningLimit: 1000 + } +}) diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/frontend/yarn.lock b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/yarn.lock new file mode 100644 index 0000000..47a4043 --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/frontend/yarn.lock @@ -0,0 +1,1092 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@antfu/utils@^0.7.5", "@antfu/utils@^0.7.6": + version "0.7.10" + resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-0.7.10.tgz#ae829f170158e297a9b6a28f161a8e487d00814d" + integrity sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/parser@^7.27.2": + version "7.27.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.4.tgz#f92e89e4f51847be05427285836fc88341c956df" + integrity sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g== + dependencies: + "@babel/types" "^7.27.3" + +"@babel/types@^7.27.3": + version "7.27.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.3.tgz#c0257bedf33aad6aad1f406d35c44758321eb3ec" + integrity sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@ctrl/tinycolor@^3.4.1": + version "3.6.1" + resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz#b6c75a56a1947cc916ea058772d666a2c8932f31" + integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA== + +"@element-plus/icons-vue@^2.1.0", "@element-plus/icons-vue@^2.3.1": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz#1f635ad5fdd5c85ed936481525570e82b5a8307a" + integrity sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg== + +"@esbuild/android-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" + integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== + +"@esbuild/android-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" + integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== + +"@esbuild/android-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" + integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== + +"@esbuild/darwin-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" + integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== + +"@esbuild/darwin-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" + integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== + +"@esbuild/freebsd-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" + integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== + +"@esbuild/freebsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" + integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== + +"@esbuild/linux-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" + integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== + +"@esbuild/linux-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" + integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== + +"@esbuild/linux-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" + integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== + +"@esbuild/linux-loong64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" + integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== + +"@esbuild/linux-mips64el@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" + integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== + +"@esbuild/linux-ppc64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" + integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== + +"@esbuild/linux-riscv64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" + integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== + +"@esbuild/linux-s390x@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" + integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== + +"@esbuild/linux-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" + integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== + +"@esbuild/netbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" + integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== + +"@esbuild/openbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" + integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== + +"@esbuild/sunos-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" + integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== + +"@esbuild/win32-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" + integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== + +"@esbuild/win32-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" + integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== + +"@esbuild/win32-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" + integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== + +"@floating-ui/core@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.0.tgz#1aff27a993ea1b254a586318c29c3b16ea0f4d0a" + integrity sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA== + dependencies: + "@floating-ui/utils" "^0.2.9" + +"@floating-ui/dom@^1.0.1": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.0.tgz#f9f83ee4fee78ac23ad9e65b128fc11a27857532" + integrity sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg== + dependencies: + "@floating-ui/core" "^1.7.0" + "@floating-ui/utils" "^0.2.9" + +"@floating-ui/utils@^0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429" + integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg== + +"@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@popperjs/core@npm:@sxzz/popperjs-es@^2.11.7": + version "2.11.7" + resolved "https://registry.yarnpkg.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz#a7f69e3665d3da9b115f9e71671dae1b97e13671" + integrity sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ== + +"@rollup/pluginutils@^5.0.2", "@rollup/pluginutils@^5.0.5", "@rollup/pluginutils@^5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz#bb94f1f9eaaac944da237767cdfee6c5b2262d4a" + integrity sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^4.0.2" + +"@types/estree@^1.0.0": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8" + integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ== + +"@types/lodash-es@^4.17.6": + version "4.17.12" + resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.12.tgz#65f6d1e5f80539aa7cfbfc962de5def0cf4f341b" + integrity sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*", "@types/lodash@^4.14.182": + version "4.17.17" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.17.tgz#fb85a04f47e9e4da888384feead0de05f7070355" + integrity sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ== + +"@types/web-bluetooth@^0.0.16": + version "0.0.16" + resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz#1d12873a8e49567371f2a75fe3e7f7edca6662d8" + integrity sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ== + +"@vitejs/plugin-vue@^4.3.4": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz#057d2ded94c4e71b94e9814f92dcd9306317aa46" + integrity sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw== + +"@vue/compiler-core@3.5.16": + version "3.5.16" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.16.tgz#2f95f4f17c16c09c57bbf64399075b921506630b" + integrity sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ== + dependencies: + "@babel/parser" "^7.27.2" + "@vue/shared" "3.5.16" + entities "^4.5.0" + estree-walker "^2.0.2" + source-map-js "^1.2.1" + +"@vue/compiler-dom@3.5.16": + version "3.5.16" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.16.tgz#151d8390252975c0b1a773029220fdfcfaa2d743" + integrity sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ== + dependencies: + "@vue/compiler-core" "3.5.16" + "@vue/shared" "3.5.16" + +"@vue/compiler-sfc@3.5.16": + version "3.5.16" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.16.tgz#577f7fd42a46fac8357ffed46e8fb34d32698419" + integrity sha512-rQR6VSFNpiinDy/DVUE0vHoIDUF++6p910cgcZoaAUm3POxgNOOdS/xgoll3rNdKYTYPnnbARDCZOyZ+QSe6Pw== + dependencies: + "@babel/parser" "^7.27.2" + "@vue/compiler-core" "3.5.16" + "@vue/compiler-dom" "3.5.16" + "@vue/compiler-ssr" "3.5.16" + "@vue/shared" "3.5.16" + estree-walker "^2.0.2" + magic-string "^0.30.17" + postcss "^8.5.3" + source-map-js "^1.2.1" + +"@vue/compiler-ssr@3.5.16": + version "3.5.16" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.16.tgz#3b7874dff771ab2f85fb09be71f6c76a75fcc5ac" + integrity sha512-d2V7kfxbdsjrDSGlJE7my1ZzCXViEcqN6w14DOsDrUCHEA6vbnVCpRFfrc4ryCP/lCKzX2eS1YtnLE/BuC9f/A== + dependencies: + "@vue/compiler-dom" "3.5.16" + "@vue/shared" "3.5.16" + +"@vue/devtools-api@^6.6.4": + version "6.6.4" + resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343" + integrity sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g== + +"@vue/reactivity@3.5.16": + version "3.5.16" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.16.tgz#528c535a088b3c1b67f285f1f2211be79425b962" + integrity sha512-FG5Q5ee/kxhIm1p2bykPpPwqiUBV3kFySsHEQha5BJvjXdZTUfmya7wP7zC39dFuZAcf/PD5S4Lni55vGLMhvA== + dependencies: + "@vue/shared" "3.5.16" + +"@vue/runtime-core@3.5.16": + version "3.5.16" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.16.tgz#0a828c322224ada26f81a2e227c3d4aebcb72c7a" + integrity sha512-bw5Ykq6+JFHYxrQa7Tjr+VSzw7Dj4ldR/udyBZbq73fCdJmyy5MPIFR9IX/M5Qs+TtTjuyUTCnmK3lWWwpAcFQ== + dependencies: + "@vue/reactivity" "3.5.16" + "@vue/shared" "3.5.16" + +"@vue/runtime-dom@3.5.16": + version "3.5.16" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.16.tgz#c1bcbcca862b77186f81c92edd5176e74670f078" + integrity sha512-T1qqYJsG2xMGhImRUV9y/RseB9d0eCYZQ4CWca9ztCuiPj/XWNNN+lkNBuzVbia5z4/cgxdL28NoQCvC0Xcfww== + dependencies: + "@vue/reactivity" "3.5.16" + "@vue/runtime-core" "3.5.16" + "@vue/shared" "3.5.16" + csstype "^3.1.3" + +"@vue/server-renderer@3.5.16": + version "3.5.16" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.16.tgz#5a68cd1d423d843f74c9e6b37133850abab07c13" + integrity sha512-BrX0qLiv/WugguGsnQUJiYOE0Fe5mZTwi6b7X/ybGB0vfrPH9z0gD/Y6WOR1sGCgX4gc25L1RYS5eYQKDMoNIg== + dependencies: + "@vue/compiler-ssr" "3.5.16" + "@vue/shared" "3.5.16" + +"@vue/shared@3.5.16": + version "3.5.16" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.16.tgz#d5ea7671182742192938a4b4cbf86ef12bef7418" + integrity sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg== + +"@vueuse/core@^9.1.0": + version "9.13.0" + resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.13.0.tgz#2f69e66d1905c1e4eebc249a01759cf88ea00cf4" + integrity sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw== + dependencies: + "@types/web-bluetooth" "^0.0.16" + "@vueuse/metadata" "9.13.0" + "@vueuse/shared" "9.13.0" + vue-demi "*" + +"@vueuse/metadata@9.13.0": + version "9.13.0" + resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.13.0.tgz#bc25a6cdad1b1a93c36ce30191124da6520539ff" + integrity sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ== + +"@vueuse/shared@9.13.0": + version "9.13.0" + resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.13.0.tgz#089ff4cc4e2e7a4015e57a8f32e4b39d096353b9" + integrity sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw== + dependencies: + vue-demi "*" + +acorn@^8.14.0: + version "8.14.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" + integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +async-validator@^4.2.5: + version "4.2.5" + resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-4.2.5.tgz#c96ea3332a521699d0afaaceed510a54656c6339" + integrity sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.5.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.9.0.tgz#25534e3b72b54540077d33046f77e3b8d7081901" + integrity sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +confbox@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06" + integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== + +confbox@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.2.2.tgz#8652f53961c74d9e081784beed78555974a9c110" + integrity sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ== + +csstype@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +dayjs@^1.11.13: + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== + +debug@^4.3.4: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +echarts@^5.4.3: + version "5.6.0" + resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.6.0.tgz#2377874dca9fb50f104051c3553544752da3c9d6" + integrity sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA== + dependencies: + tslib "2.3.0" + zrender "5.6.1" + +element-plus@^2.3.9: + version "2.9.11" + resolved "https://registry.yarnpkg.com/element-plus/-/element-plus-2.9.11.tgz#c939a8d945330f596b7a35aae0e501ea170874a2" + integrity sha512-x4L/6YC8de4JtuE3vpaEugJdQIeHQaHtIYKyk67IeF6dTIiVax45aX4nWOygnh+xX+0gTvL6xO+9BZhPA3G82w== + dependencies: + "@ctrl/tinycolor" "^3.4.1" + "@element-plus/icons-vue" "^2.3.1" + "@floating-ui/dom" "^1.0.1" + "@popperjs/core" "npm:@sxzz/popperjs-es@^2.11.7" + "@types/lodash" "^4.14.182" + "@types/lodash-es" "^4.17.6" + "@vueuse/core" "^9.1.0" + async-validator "^4.2.5" + dayjs "^1.11.13" + escape-html "^1.0.3" + lodash "^4.17.21" + lodash-es "^4.17.21" + lodash-unified "^1.0.2" + memoize-one "^6.0.0" + normalize-wheel-es "^1.2.0" + +entities@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +esbuild@^0.18.10: + version "0.18.20" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" + integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== + optionalDependencies: + "@esbuild/android-arm" "0.18.20" + "@esbuild/android-arm64" "0.18.20" + "@esbuild/android-x64" "0.18.20" + "@esbuild/darwin-arm64" "0.18.20" + "@esbuild/darwin-x64" "0.18.20" + "@esbuild/freebsd-arm64" "0.18.20" + "@esbuild/freebsd-x64" "0.18.20" + "@esbuild/linux-arm" "0.18.20" + "@esbuild/linux-arm64" "0.18.20" + "@esbuild/linux-ia32" "0.18.20" + "@esbuild/linux-loong64" "0.18.20" + "@esbuild/linux-mips64el" "0.18.20" + "@esbuild/linux-ppc64" "0.18.20" + "@esbuild/linux-riscv64" "0.18.20" + "@esbuild/linux-s390x" "0.18.20" + "@esbuild/linux-x64" "0.18.20" + "@esbuild/netbsd-x64" "0.18.20" + "@esbuild/openbsd-x64" "0.18.20" + "@esbuild/sunos-x64" "0.18.20" + "@esbuild/win32-arm64" "0.18.20" + "@esbuild/win32-ia32" "0.18.20" + "@esbuild/win32-x64" "0.18.20" + +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" + integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== + +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + +exsolve@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/exsolve/-/exsolve-1.0.5.tgz#1f5b6b4fe82ad6b28a173ccb955a635d77859dcf" + integrity sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg== + +fast-glob@^3.3.0, fast-glob@^3.3.1, fast-glob@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fastq@^1.6.0: + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +form-data@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c" + integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + mime-types "^2.1.12" + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +js-tokens@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.1.tgz#2ec43964658435296f6761b34e10671c2d9527f4" + integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ== + +local-pkg@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.3.tgz#0ff361ab3ae7f1c19113d9bb97b98b905dbc4963" + integrity sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g== + +local-pkg@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.5.1.tgz#69658638d2a95287534d4c2fff757980100dbb6d" + integrity sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ== + dependencies: + mlly "^1.7.3" + pkg-types "^1.2.1" + +local-pkg@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-1.1.1.tgz#f5fe74a97a3bd3c165788ee08ca9fbe998dc58dd" + integrity sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg== + dependencies: + mlly "^1.7.4" + pkg-types "^2.0.1" + quansync "^0.2.8" + +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + +lodash-unified@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/lodash-unified/-/lodash-unified-1.0.3.tgz#80b1eac10ed2eb02ed189f08614a29c27d07c894" + integrity sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +magic-string@^0.30.1, magic-string@^0.30.17, magic-string@^0.30.5: + version "0.30.17" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" + integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +memoize-one@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" + integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +minimatch@^9.0.3: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +mlly@^1.7.3, mlly@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.7.4.tgz#3d7295ea2358ec7a271eaa5d000a0f84febe100f" + integrity sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw== + dependencies: + acorn "^8.14.0" + pathe "^2.0.1" + pkg-types "^1.3.0" + ufo "^1.5.4" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-wheel-es@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz#0fa2593d619f7245a541652619105ab076acf09e" + integrity sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +pathe@^2.0.1, pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +picomatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" + integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== + +pkg-types@^1.2.1, pkg-types@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.3.1.tgz#bd7cc70881192777eef5326c19deb46e890917df" + integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== + dependencies: + confbox "^0.1.8" + mlly "^1.7.4" + pathe "^2.0.1" + +pkg-types@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-2.1.0.tgz#70c9e1b9c74b63fdde749876ee0aa007ea9edead" + integrity sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A== + dependencies: + confbox "^0.2.1" + exsolve "^1.0.1" + pathe "^2.0.3" + +postcss@^8.4.27, postcss@^8.5.3: + version "8.5.4" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.4.tgz#d61014ac00e11d5f58458ed7247d899bd65f99c0" + integrity sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +quansync@^0.2.8: + version "0.2.10" + resolved "https://registry.yarnpkg.com/quansync/-/quansync-0.2.10.tgz#32053cf166fa36511aae95fc49796116f2dc20e1" + integrity sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +resolve@^1.22.2: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rollup@^3.27.1: + version "3.29.5" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.5.tgz#8a2e477a758b520fb78daf04bca4c522c1da8a54" + integrity sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w== + optionalDependencies: + fsevents "~2.3.2" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +scule@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/scule/-/scule-1.3.0.tgz#6efbd22fd0bb801bdcc585c89266a7d2daa8fbd3" + integrity sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g== + +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +strip-literal@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-2.1.1.tgz#26906e65f606d49f748454a08084e94190c2e5ad" + integrity sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q== + dependencies: + js-tokens "^9.0.1" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tslib@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" + integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== + +ufo@^1.5.4: + version "1.6.1" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.1.tgz#ac2db1d54614d1b22c1d603e3aef44a85d8f146b" + integrity sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA== + +unimport@^3.4.0: + version "3.14.6" + resolved "https://registry.yarnpkg.com/unimport/-/unimport-3.14.6.tgz#f01170aa2fb94c4f97b22c0ac2822ef7e8e0726d" + integrity sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g== + dependencies: + "@rollup/pluginutils" "^5.1.4" + acorn "^8.14.0" + escape-string-regexp "^5.0.0" + estree-walker "^3.0.3" + fast-glob "^3.3.3" + local-pkg "^1.0.0" + magic-string "^0.30.17" + mlly "^1.7.4" + pathe "^2.0.1" + picomatch "^4.0.2" + pkg-types "^1.3.0" + scule "^1.3.0" + strip-literal "^2.1.1" + unplugin "^1.16.1" + +unplugin-auto-import@^0.16.6: + version "0.16.7" + resolved "https://registry.yarnpkg.com/unplugin-auto-import/-/unplugin-auto-import-0.16.7.tgz#f4f1f7ab3fba24129bc38e47f83782684030d6e4" + integrity sha512-w7XmnRlchq6YUFJVFGSvG1T/6j8GrdYN6Em9Wf0Ye+HXgD/22kont+WnuCAA0UaUoxtuvRR1u/mXKy63g/hfqQ== + dependencies: + "@antfu/utils" "^0.7.6" + "@rollup/pluginutils" "^5.0.5" + fast-glob "^3.3.1" + local-pkg "^0.5.0" + magic-string "^0.30.5" + minimatch "^9.0.3" + unimport "^3.4.0" + unplugin "^1.5.0" + +unplugin-vue-components@^0.25.2: + version "0.25.2" + resolved "https://registry.yarnpkg.com/unplugin-vue-components/-/unplugin-vue-components-0.25.2.tgz#99d9d02a4066a24e720edbe74a82a4ee6ff86153" + integrity sha512-OVmLFqILH6w+eM8fyt/d/eoJT9A6WO51NZLf1vC5c1FZ4rmq2bbGxTy8WP2Jm7xwFdukaIdv819+UI7RClPyCA== + dependencies: + "@antfu/utils" "^0.7.5" + "@rollup/pluginutils" "^5.0.2" + chokidar "^3.5.3" + debug "^4.3.4" + fast-glob "^3.3.0" + local-pkg "^0.4.3" + magic-string "^0.30.1" + minimatch "^9.0.3" + resolve "^1.22.2" + unplugin "^1.4.0" + +unplugin@^1.16.1, unplugin@^1.4.0, unplugin@^1.5.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.16.1.tgz#a844d2e3c3b14a4ac2945c42be80409321b61199" + integrity sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w== + dependencies: + acorn "^8.14.0" + webpack-virtual-modules "^0.6.2" + +vite@^4.4.9: + version "4.5.14" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.14.tgz#2e652bc1d898265d987d6543ce866ecd65fa4086" + integrity sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g== + dependencies: + esbuild "^0.18.10" + postcss "^8.4.27" + rollup "^3.27.1" + optionalDependencies: + fsevents "~2.3.2" + +vue-demi@*: + version "0.14.10" + resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.10.tgz#afc78de3d6f9e11bf78c55e8510ee12814522f04" + integrity sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg== + +vue-router@^4.2.4: + version "4.5.1" + resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.5.1.tgz#47bffe2d3a5479d2886a9a244547a853aa0abf69" + integrity sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw== + dependencies: + "@vue/devtools-api" "^6.6.4" + +vue@^3.3.4: + version "3.5.16" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.16.tgz#f0cde88c2688354f00ff2d77eb295c26440f8c7a" + integrity sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w== + dependencies: + "@vue/compiler-dom" "3.5.16" + "@vue/compiler-sfc" "3.5.16" + "@vue/runtime-dom" "3.5.16" + "@vue/server-renderer" "3.5.16" + "@vue/shared" "3.5.16" + +webpack-virtual-modules@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8" + integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== + +zrender@5.6.1: + version "5.6.1" + resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.6.1.tgz#e08d57ecf4acac708c4fcb7481eb201df7f10a6b" + integrity sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag== + dependencies: + tslib "2.3.0" diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/start.bat b/src/声源定位代码/audio-classification/audio-classification-platform/start.bat new file mode 100644 index 0000000..8094d48 --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/start.bat @@ -0,0 +1,24 @@ +@echo off +echo ================================ +echo 声纹识别系统启动脚本 +echo ================================ +echo. + +echo 正在启动后端服务... +cd /d "%~dp0backend" +start "后端服务" cmd /k "python app.py" + +echo 等待后端服务启动... +timeout /t 3 /nobreak >nul + +echo 正在启动前端服务... +cd /d "%~dp0frontend" +start "前端服务" cmd /k "npm run dev" + +echo. +echo 启动完成! +echo 后端服务: http://localhost:5000 +echo 前端服务: http://localhost:3000 +echo. +echo 请等待服务完全启动后访问前端地址 +pause diff --git a/src/声源定位代码/audio-classification/audio-classification-platform/start.sh b/src/声源定位代码/audio-classification/audio-classification-platform/start.sh new file mode 100644 index 0000000..06674da --- /dev/null +++ b/src/声源定位代码/audio-classification/audio-classification-platform/start.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +echo "================================" +echo " 声纹识别系统启动脚本" +echo "================================" +echo + +# 获取脚本所在目录 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "正在启动后端服务..." +cd "$SCRIPT_DIR/backend" +python app.py & +BACKEND_PID=$! + +echo "等待后端服务启动..." +sleep 3 + +echo "正在启动前端服务..." +cd "$SCRIPT_DIR/frontend" +npm run dev & +FRONTEND_PID=$! + +echo +echo "启动完成!" +echo "后端服务: http://localhost:5000" +echo "前端服务: http://localhost:3000" +echo +echo "后端进程 PID: $BACKEND_PID" +echo "前端进程 PID: $FRONTEND_PID" +echo +echo "按 Ctrl+C 停止所有服务" + +# 等待用户中断 +trap 'echo "正在停止服务..."; kill $BACKEND_PID $FRONTEND_PID; exit' INT +wait diff --git a/src/声源定位代码/audio-classification/configs/augmentation.yml b/src/声源定位代码/audio-classification/configs/augmentation.yml new file mode 100644 index 0000000..5ffe3c5 --- /dev/null +++ b/src/声源定位代码/audio-classification/configs/augmentation.yml @@ -0,0 +1,46 @@ +# 语速增强 +speed: + # 增强概率 + prob: 1.0 + +# 音量增强 +volume: + # 增强概率 + prob: 0.0 + # 最小增益 + min_gain_dBFS: -15 + # 最大增益 + max_gain_dBFS: 15 + +# 噪声增强 +noise: + # 增强概率 + prob: 0.5 + # 噪声增强的噪声文件夹 + noise_dir: 'dataset/noise' + # 针对噪声的最小音量增益 + min_snr_dB: 10 + # 针对噪声的最大音量增益 + max_snr_dB: 50 + +# 混响增强 +reverb: + # 增强概率 + prob: 0.5 + # 混响增强的混响文件夹 + reverb_dir: 'dataset/reverb' + +# Spec增强 +spec_aug: + # 增强概率 + prob: 0.5 + # 频域掩蔽的比例 + freq_mask_ratio: 0.1 + # 频域掩蔽次数 + n_freq_masks: 1 + # 频域掩蔽的比例 + time_mask_ratio: 0.05 + # 频域掩蔽次数 + n_time_masks: 1 + # 最大时间扭曲 + max_time_warp: 0 diff --git a/src/声源定位代码/audio-classification/configs/cam++.yml b/src/声源定位代码/audio-classification/configs/cam++.yml new file mode 100644 index 0000000..ce68da9 --- /dev/null +++ b/src/声源定位代码/audio-classification/configs/cam++.yml @@ -0,0 +1,80 @@ +# 数据集参数 +dataset_conf: + dataset: + # 过滤最短的音频长度 + min_duration: 0.4 + # 最长的音频长度,大于这个长度会裁剪掉 + max_duration: 3 + # 音频的采样率 + sample_rate: 16000 + # 是否对音频进行音量归一化 + use_dB_normalization: True + # 对音频进行音量归一化的音量分贝值 + target_dB: -20 + dataLoader: + # 训练的批量大小 + batch_size: 64 + # 是否丢弃最后一个样本 + drop_last: True + # 读取数据的线程数量 + num_workers: 8 + # 评估的数据要特殊处理 + eval_conf: + # 评估的批量大小 + batch_size: 8 + # 最长的音频长度 + max_duration: 20 + # 训练数据的数据列表路径 + train_list: 'dataset/train_list.txt' + # 测试数据的数据列表路径 + test_list: 'dataset/test_list.txt' + # 标签列表 + label_list_path: 'dataset/label_list.txt' + +# 数据预处理参数 +preprocess_conf: + # 是否使用HF上的Wav2Vec2类似模型提取音频特征 + use_hf_model: False + # 音频预处理方法,也可以叫特征提取方法 + # 当use_hf_model为False时,支持:MelSpectrogram、Spectrogram、MFCC、Fbank + # 当use_hf_model为True时,指定的是HuggingFace的模型或者本地路径,比如facebook/w2v-bert-2.0或者./feature_models/w2v-bert-2.0 + feature_method: 'Fbank' + # 当use_hf_model为False时,设置API参数,更参数查看对应API,不清楚的可以直接删除该部分,直接使用默认值。 + # 当use_hf_model为True时,可以设置参数use_gpu,指定是否使用GPU提取特征 + method_args: + sample_frequency: 16000 + num_mel_bins: 80 + +model_conf: + # 所使用的模型 + model: 'CAMPPlus' + # 模型参数 + model_args: + # 分类大小,如果为null,自动通过标签列表获取 + num_class: null + +optimizer_conf: + # 优化方法 + optimizer: 'Adam' + # 优化方法参数 + optimizer_args: + lr: 0.001 + weight_decay: !!float 1e-5 + # 学习率衰减函数,支持Pytorch支持的和项目提供的WarmupCosineSchedulerLR + scheduler: 'WarmupCosineSchedulerLR' + # 学习率衰减函数参数 + scheduler_args: + min_lr: !!float 1e-5 + max_lr: 0.001 + warmup_epoch: 5 + +train_conf: + # 是否开启自动混合精度 + enable_amp: False + # 是否使用Pytorch2.0的编译器 + use_compile: False + # CrossEntropyLoss类的label_smoothing参数 + label_smoothing: 0.0 + # 训练的轮数 + max_epoch: 60 + log_interval: 10 diff --git a/src/声源定位代码/audio-classification/configs/ecapa_tdnn.yml b/src/声源定位代码/audio-classification/configs/ecapa_tdnn.yml new file mode 100644 index 0000000..6c13e50 --- /dev/null +++ b/src/声源定位代码/audio-classification/configs/ecapa_tdnn.yml @@ -0,0 +1,80 @@ +# 数据集参数 +dataset_conf: + dataset: + # 过滤最短的音频长度 + min_duration: 0.4 + # 最长的音频长度,大于这个长度会裁剪掉 + max_duration: 3 + # 音频的采样率 + sample_rate: 16000 + # 是否对音频进行音量归一化 + use_dB_normalization: True + # 对音频进行音量归一化的音量分贝值 + target_dB: -20 + dataLoader: + # 训练的批量大小 + batch_size: 128 + # 是否丢弃最后一个样本 + drop_last: True + # 读取数据的线程数量 + num_workers: 8 + # 评估的数据要特殊处理 + eval_conf: + # 评估的批量大小 + batch_size: 16 + # 最长的音频长度 + max_duration: 20 + # 训练数据的数据列表路径 + train_list: 'dataset/train_list.txt' + # 测试数据的数据列表路径 + test_list: 'dataset/test_list.txt' + # 标签列表 + label_list_path: 'dataset/label_list.txt' + +# 数据预处理参数 +preprocess_conf: + # 是否使用HF上的Wav2Vec2类似模型提取音频特征 + use_hf_model: False + # 音频预处理方法,也可以叫特征提取方法 + # 当use_hf_model为False时,支持:MelSpectrogram、Spectrogram、MFCC、Fbank + # 当use_hf_model为True时,指定的是HuggingFace的模型或者本地路径,比如facebook/w2v-bert-2.0或者./feature_models/w2v-bert-2.0 + feature_method: 'Fbank' + # 当use_hf_model为False时,设置API参数,更参数查看对应API,不清楚的可以直接删除该部分,直接使用默认值。 + # 当use_hf_model为True时,可以设置参数use_gpu,指定是否使用GPU提取特征 + method_args: + sample_frequency: 16000 + num_mel_bins: 80 + +model_conf: + # 所使用的模型 + model: 'EcapaTdnn' + # 模型参数 + model_args: + # 分类大小,如果为null,自动通过标签列表获取 + num_class: null + +optimizer_conf: + # 优化方法 + optimizer: 'Adam' + # 优化方法参数 + optimizer_args: + lr: 0.001 + weight_decay: !!float 1e-5 + # 学习率衰减函数,支持Pytorch支持的和项目提供的WarmupCosineSchedulerLR + scheduler: 'WarmupCosineSchedulerLR' + # 学习率衰减函数参数 + scheduler_args: + min_lr: !!float 1e-5 + max_lr: 0.001 + warmup_epoch: 5 + +train_conf: + # 是否开启自动混合精度 + enable_amp: False + # 是否使用Pytorch2.0的编译器 + use_compile: False + # CrossEntropyLoss类的label_smoothing参数 + label_smoothing: 0.0 + # 训练的轮数 + max_epoch: 60 + log_interval: 10 diff --git a/src/声源定位代码/audio-classification/configs/eres2net.yml b/src/声源定位代码/audio-classification/configs/eres2net.yml new file mode 100644 index 0000000..6bc1170 --- /dev/null +++ b/src/声源定位代码/audio-classification/configs/eres2net.yml @@ -0,0 +1,80 @@ +# 数据集参数 +dataset_conf: + dataset: + # 过滤最短的音频长度 + min_duration: 0.4 + # 最长的音频长度,大于这个长度会裁剪掉 + max_duration: 3 + # 音频的采样率 + sample_rate: 16000 + # 是否对音频进行音量归一化 + use_dB_normalization: True + # 对音频进行音量归一化的音量分贝值 + target_dB: -20 + dataLoader: + # 训练的批量大小 + batch_size: 32 + # 是否丢弃最后一个样本 + drop_last: True + # 读取数据的线程数量 + num_workers: 8 + # 评估的数据要特殊处理 + eval_conf: + # 评估的批量大小 + batch_size: 4 + # 最长的音频长度 + max_duration: 20 + # 训练数据的数据列表路径 + train_list: 'dataset/train_list.txt' + # 测试数据的数据列表路径 + test_list: 'dataset/test_list.txt' + # 标签列表 + label_list_path: 'dataset/label_list.txt' + +# 数据预处理参数 +preprocess_conf: + # 是否使用HF上的Wav2Vec2类似模型提取音频特征 + use_hf_model: False + # 音频预处理方法,也可以叫特征提取方法 + # 当use_hf_model为False时,支持:MelSpectrogram、Spectrogram、MFCC、Fbank + # 当use_hf_model为True时,指定的是HuggingFace的模型或者本地路径,比如facebook/w2v-bert-2.0或者./feature_models/w2v-bert-2.0 + feature_method: 'Fbank' + # 当use_hf_model为False时,设置API参数,更参数查看对应API,不清楚的可以直接删除该部分,直接使用默认值。 + # 当use_hf_model为True时,可以设置参数use_gpu,指定是否使用GPU提取特征 + method_args: + sample_frequency: 16000 + num_mel_bins: 80 + +model_conf: + # 所使用的模型,支持ERes2Net、ERes2NetV2 + model: 'ERes2Net' + # 模型参数 + model_args: + # 分类大小,如果为null,自动通过标签列表获取 + num_class: null + +optimizer_conf: + # 优化方法 + optimizer: 'Adam' + # 优化方法参数 + optimizer_args: + lr: 0.001 + weight_decay: !!float 1e-5 + # 学习率衰减函数,支持Pytorch支持的和项目提供的WarmupCosineSchedulerLR + scheduler: 'WarmupCosineSchedulerLR' + # 学习率衰减函数参数 + scheduler_args: + min_lr: !!float 1e-5 + max_lr: 0.001 + warmup_epoch: 5 + +train_conf: + # 是否开启自动混合精度 + enable_amp: False + # 是否使用Pytorch2.0的编译器 + use_compile: False + # CrossEntropyLoss类的label_smoothing参数 + label_smoothing: 0.0 + # 训练的轮数 + max_epoch: 60 + log_interval: 10 diff --git a/src/声源定位代码/audio-classification/configs/panns.yml b/src/声源定位代码/audio-classification/configs/panns.yml new file mode 100644 index 0000000..5d000f1 --- /dev/null +++ b/src/声源定位代码/audio-classification/configs/panns.yml @@ -0,0 +1,80 @@ +# 数据集参数 +dataset_conf: + dataset: + # 过滤最短的音频长度 + min_duration: 0.4 + # 最长的音频长度,大于这个长度会裁剪掉 + max_duration: 3 + # 音频的采样率 + sample_rate: 16000 + # 是否对音频进行音量归一化 + use_dB_normalization: True + # 对音频进行音量归一化的音量分贝值 + target_dB: -20 + dataLoader: + # 训练的批量大小 + batch_size: 64 + # 是否丢弃最后一个样本 + drop_last: True + # 读取数据的线程数量 + num_workers: 8 + # 评估的数据要特殊处理 + eval_conf: + # 评估的批量大小 + batch_size: 8 + # 最长的音频长度 + max_duration: 20 + # 训练数据的数据列表路径 + train_list: 'dataset/train_list.txt' + # 测试数据的数据列表路径 + test_list: 'dataset/test_list.txt' + # 标签列表 + label_list_path: 'dataset/label_list.txt' + +# 数据预处理参数 +preprocess_conf: + # 是否使用HF上的Wav2Vec2类似模型提取音频特征 + use_hf_model: False + # 音频预处理方法,也可以叫特征提取方法 + # 当use_hf_model为False时,支持:MelSpectrogram、Spectrogram、MFCC、Fbank + # 当use_hf_model为True时,指定的是HuggingFace的模型或者本地路径,比如facebook/w2v-bert-2.0或者./feature_models/w2v-bert-2.0 + feature_method: 'Fbank' + # 当use_hf_model为False时,设置API参数,更参数查看对应API,不清楚的可以直接删除该部分,直接使用默认值。 + # 当use_hf_model为True时,可以设置参数use_gpu,指定是否使用GPU提取特征 + method_args: + sample_frequency: 16000 + num_mel_bins: 80 + +model_conf: +# 所使用的模型,支持PANNS_CNN6、PANNS_CNN10、PANNS_CNN14 + model: 'PANNS_CNN10' + # 模型参数 + model_args: + # 分类大小,如果为null,自动通过标签列表获取 + num_class: null + +optimizer_conf: + # 优化方法 + optimizer: 'Adam' + # 优化方法参数 + optimizer_args: + lr: 0.001 + weight_decay: !!float 1e-5 + # 学习率衰减函数,支持Pytorch支持的和项目提供的WarmupCosineSchedulerLR + scheduler: 'WarmupCosineSchedulerLR' + # 学习率衰减函数参数 + scheduler_args: + min_lr: !!float 1e-5 + max_lr: 0.001 + warmup_epoch: 5 + +train_conf: + # 是否开启自动混合精度 + enable_amp: False + # 是否使用Pytorch2.0的编译器 + use_compile: False + # CrossEntropyLoss类的label_smoothing参数 + label_smoothing: 0.0 + # 训练的轮数 + max_epoch: 60 + log_interval: 10 diff --git a/src/声源定位代码/audio-classification/configs/res2net.yml b/src/声源定位代码/audio-classification/configs/res2net.yml new file mode 100644 index 0000000..072e2e4 --- /dev/null +++ b/src/声源定位代码/audio-classification/configs/res2net.yml @@ -0,0 +1,80 @@ +# 数据集参数 +dataset_conf: + dataset: + # 过滤最短的音频长度 + min_duration: 0.4 + # 最长的音频长度,大于这个长度会裁剪掉 + max_duration: 3 + # 音频的采样率 + sample_rate: 16000 + # 是否对音频进行音量归一化 + use_dB_normalization: True + # 对音频进行音量归一化的音量分贝值 + target_dB: -20 + dataLoader: + # 训练的批量大小 + batch_size: 32 + # 是否丢弃最后一个样本 + drop_last: True + # 读取数据的线程数量 + num_workers: 8 + # 评估的数据要特殊处理 + eval_conf: + # 评估的批量大小 + batch_size: 4 + # 最长的音频长度 + max_duration: 20 + # 训练数据的数据列表路径 + train_list: 'dataset/train_list.txt' + # 测试数据的数据列表路径 + test_list: 'dataset/test_list.txt' + # 标签列表 + label_list_path: 'dataset/label_list.txt' + +# 数据预处理参数 +preprocess_conf: + # 是否使用HF上的Wav2Vec2类似模型提取音频特征 + use_hf_model: False + # 音频预处理方法,也可以叫特征提取方法 + # 当use_hf_model为False时,支持:MelSpectrogram、Spectrogram、MFCC、Fbank + # 当use_hf_model为True时,指定的是HuggingFace的模型或者本地路径,比如facebook/w2v-bert-2.0或者./feature_models/w2v-bert-2.0 + feature_method: 'Fbank' + # 当use_hf_model为False时,设置API参数,更参数查看对应API,不清楚的可以直接删除该部分,直接使用默认值。 + # 当use_hf_model为True时,可以设置参数use_gpu,指定是否使用GPU提取特征 + method_args: + sample_frequency: 16000 + num_mel_bins: 80 + +model_conf: + # 所使用的模型 + model: 'Res2Net' + # 模型参数 + model_args: + # 分类大小,如果为null,自动通过标签列表获取 + num_class: null + +optimizer_conf: + # 优化方法 + optimizer: 'Adam' + # 优化方法参数 + optimizer_args: + lr: 0.001 + weight_decay: !!float 1e-5 + # 学习率衰减函数,支持Pytorch支持的和项目提供的WarmupCosineSchedulerLR + scheduler: 'WarmupCosineSchedulerLR' + # 学习率衰减函数参数 + scheduler_args: + min_lr: !!float 1e-5 + max_lr: 0.001 + warmup_epoch: 5 + +train_conf: + # 是否开启自动混合精度 + enable_amp: False + # 是否使用Pytorch2.0的编译器 + use_compile: False + # CrossEntropyLoss类的label_smoothing参数 + label_smoothing: 0.0 + # 训练的轮数 + max_epoch: 60 + log_interval: 10 diff --git a/src/声源定位代码/audio-classification/configs/resnet_se.yml b/src/声源定位代码/audio-classification/configs/resnet_se.yml new file mode 100644 index 0000000..5af006d --- /dev/null +++ b/src/声源定位代码/audio-classification/configs/resnet_se.yml @@ -0,0 +1,80 @@ +# 数据集参数 +dataset_conf: + dataset: + # 过滤最短的音频长度 + min_duration: 0.4 + # 最长的音频长度,大于这个长度会裁剪掉 + max_duration: 3 + # 音频的采样率 + sample_rate: 16000 + # 是否对音频进行音量归一化 + use_dB_normalization: True + # 对音频进行音量归一化的音量分贝值 + target_dB: -20 + dataLoader: + # 训练的批量大小 + batch_size: 32 + # 是否丢弃最后一个样本 + drop_last: True + # 读取数据的线程数量 + num_workers: 8 + # 评估的数据要特殊处理 + eval_conf: + # 评估的批量大小 + batch_size: 4 + # 最长的音频长度 + max_duration: 20 + # 训练数据的数据列表路径 + train_list: 'dataset/train_list.txt' + # 测试数据的数据列表路径 + test_list: 'dataset/test_list.txt' + # 标签列表 + label_list_path: 'dataset/label_list.txt' + +# 数据预处理参数 +preprocess_conf: + # 是否使用HF上的Wav2Vec2类似模型提取音频特征 + use_hf_model: False + # 音频预处理方法,也可以叫特征提取方法 + # 当use_hf_model为False时,支持:MelSpectrogram、Spectrogram、MFCC、Fbank + # 当use_hf_model为True时,指定的是HuggingFace的模型或者本地路径,比如facebook/w2v-bert-2.0或者./feature_models/w2v-bert-2.0 + feature_method: 'Fbank' + # 当use_hf_model为False时,设置API参数,更参数查看对应API,不清楚的可以直接删除该部分,直接使用默认值。 + # 当use_hf_model为True时,可以设置参数use_gpu,指定是否使用GPU提取特征 + method_args: + sample_frequency: 16000 + num_mel_bins: 80 + +model_conf: + # 所使用的模型 + model: 'ResNetSE' + # 模型参数 + model_args: + # 分类大小,如果为null,自动通过标签列表获取 + num_class: null + +optimizer_conf: + # 优化方法 + optimizer: 'Adam' + # 优化方法参数 + optimizer_args: + lr: 0.001 + weight_decay: !!float 1e-5 + # 学习率衰减函数,支持Pytorch支持的和项目提供的WarmupCosineSchedulerLR + scheduler: 'WarmupCosineSchedulerLR' + # 学习率衰减函数参数 + scheduler_args: + min_lr: !!float 1e-5 + max_lr: 0.001 + warmup_epoch: 5 + +train_conf: + # 是否开启自动混合精度 + enable_amp: False + # 是否使用Pytorch2.0的编译器 + use_compile: False + # CrossEntropyLoss类的label_smoothing参数 + label_smoothing: 0.0 + # 训练的轮数 + max_epoch: 60 + log_interval: 10 diff --git a/src/声源定位代码/audio-classification/configs/tdnn.yml b/src/声源定位代码/audio-classification/configs/tdnn.yml new file mode 100644 index 0000000..9cd3d2a --- /dev/null +++ b/src/声源定位代码/audio-classification/configs/tdnn.yml @@ -0,0 +1,80 @@ +# 数据集参数 +dataset_conf: + dataset: + # 过滤最短的音频长度 + min_duration: 0.4 + # 最长的音频长度,大于这个长度会裁剪掉 + max_duration: 3 + # 音频的采样率 + sample_rate: 16000 + # 是否对音频进行音量归一化 + use_dB_normalization: True + # 对音频进行音量归一化的音量分贝值 + target_dB: -20 + dataLoader: + # 训练的批量大小 + batch_size: 64 + # 是否丢弃最后一个样本 + drop_last: True + # 读取数据的线程数量 + num_workers: 8 + # 评估的数据要特殊处理 + eval_conf: + # 评估的批量大小 + batch_size: 8 + # 最长的音频长度 + max_duration: 20 + # 训练数据的数据列表路径 + train_list: 'dataset/train_list.txt' + # 测试数据的数据列表路径 + test_list: 'dataset/test_list.txt' + # 标签列表 + label_list_path: 'dataset/label_list.txt' + +# 数据预处理参数 +preprocess_conf: + # 是否使用HF上的Wav2Vec2类似模型提取音频特征 + use_hf_model: False + # 音频预处理方法,也可以叫特征提取方法 + # 当use_hf_model为False时,支持:MelSpectrogram、Spectrogram、MFCC、Fbank + # 当use_hf_model为True时,指定的是HuggingFace的模型或者本地路径,比如facebook/w2v-bert-2.0或者./feature_models/w2v-bert-2.0 + feature_method: 'Fbank' + # 当use_hf_model为False时,设置API参数,更参数查看对应API,不清楚的可以直接删除该部分,直接使用默认值。 + # 当use_hf_model为True时,可以设置参数use_gpu,指定是否使用GPU提取特征 + method_args: + sample_frequency: 16000 + num_mel_bins: 80 + +model_conf: + # 所使用的模型 + model: 'TDNN' + # 模型参数 + model_args: + # 分类大小,如果为null,自动通过标签列表获取 + num_class: null + +optimizer_conf: + # 优化方法 + optimizer: 'Adam' + # 优化方法参数 + optimizer_args: + lr: 0.001 + weight_decay: !!float 1e-5 + # 学习率衰减函数,支持Pytorch支持的和项目提供的WarmupCosineSchedulerLR + scheduler: 'WarmupCosineSchedulerLR' + # 学习率衰减函数参数 + scheduler_args: + min_lr: !!float 1e-5 + max_lr: 0.001 + warmup_epoch: 5 + +train_conf: + # 是否开启自动混合精度 + enable_amp: False + # 是否使用Pytorch2.0的编译器 + use_compile: False + # CrossEntropyLoss类的label_smoothing参数 + label_smoothing: 0.0 + # 训练的轮数 + max_epoch: 60 + log_interval: 10 diff --git a/src/声源定位代码/audio-classification/create_data.py b/src/声源定位代码/audio-classification/create_data.py new file mode 100644 index 0000000..da73e90 --- /dev/null +++ b/src/声源定位代码/audio-classification/create_data.py @@ -0,0 +1,99 @@ +import os + + +# 生成数据列表 +def get_data_list(audio_path, list_path): + sound_sum = 0 + audios = os.listdir(audio_path) + os.makedirs(list_path, exist_ok=True) + f_train = open(os.path.join(list_path, 'train_list.txt'), 'w', encoding='utf-8') + f_test = open(os.path.join(list_path, 'test_list.txt'), 'w', encoding='utf-8') + f_label = open(os.path.join(list_path, 'label_list.txt'), 'w', encoding='utf-8') + + for i in range(len(audios)): + f_label.write(f'{audios[i]}\n') + sounds = os.listdir(os.path.join(audio_path, audios[i])) + for sound in sounds: + sound_path = os.path.join(audio_path, audios[i], sound).replace('\\', '/') + if sound_sum % 10 == 0: + f_test.write(f'{sound_path}\t{i}\n') + else: + f_train.write(f'{sound_path}\t{i}\n') + sound_sum += 1 + print(f"Audio:{i + 1}/{len(audios)}") + f_label.close() + f_test.close() + f_train.close() + + +# 下载数据方式,执行:./tools/download_3dspeaker_data.sh +# 生成生成方言数据列表 +def get_language_identification_data_list(audio_path, list_path): + labels_dict = {0: 'Standard Mandarin', 3: 'Southwestern Mandarin', 6: 'Central Plains Mandarin', + 4: 'JiangHuai Mandarin', 2: 'Wu dialect', 8: 'Gan dialect', 9: 'Jin dialect', + 11: 'LiaoJiao Mandarin', 12: 'JiLu Mandarin', 10: 'Min dialect', 7: 'Yue dialect', + 5: 'Hakka dialect', 1: 'Xiang dialect', 13: 'Northern Mandarin'} + + with open(os.path.join(list_path, 'train_list.txt'), 'w', encoding='utf-8') as f: + train_dir = os.path.join(audio_path, 'train') + for root, dirs, files in os.walk(train_dir): + for file in files: + if not file.endswith('.wav'): continue + label = int(file.split('_')[-1].replace('.wav', '')[-2:]) + file = os.path.join(root, file) + f.write(f'{file}\t{label}\n') + + with open(os.path.join(list_path, 'test_list.txt'), 'w', encoding='utf-8') as f: + test_dir = os.path.join(audio_path, 'test') + for root, dirs, files in os.walk(test_dir): + for file in files: + if not file.endswith('.wav'): continue + label = int(file.split('_')[-1].replace('.wav', '')[-2:]) + file = os.path.join(root, file) + f.write(f'{file}\t{label}\n') + + with open(os.path.join(list_path, 'label_list.txt'), 'w', encoding='utf-8') as f: + for i in range(len(labels_dict)): + f.write(f'{labels_dict[i]}\n') + + +# 创建UrbanSound8K数据列表 +def create_UrbanSound8K_list(audio_path, metadata_path, list_path): + sound_sum = 0 + + f_train = open(os.path.join(list_path, 'train_list.txt'), 'w', encoding='utf-8') + f_test = open(os.path.join(list_path, 'test_list.txt'), 'w', encoding='utf-8') + f_label = open(os.path.join(list_path, 'label_list.txt'), 'w', encoding='utf-8') + + with open(metadata_path) as f: + lines = f.readlines() + + labels = {} + for i, line in enumerate(lines): + if i == 0:continue + data = line.replace('\n', '').split(',') + class_id = int(data[6]) + if class_id not in labels.keys(): + labels[class_id] = data[-1] + sound_path = os.path.join(audio_path, f'fold{data[5]}', data[0]).replace('\\', '/') + if sound_sum % 10 == 0: + f_test.write(f'{sound_path}\t{data[6]}\n') + else: + f_train.write(f'{sound_path}\t{data[6]}\n') + sound_sum += 1 + for i in range(len(labels)): + f_label.write(f'{labels[i]}\n') + f_label.close() + f_test.close() + f_train.close() + + +if __name__ == '__main__': + # get_data_list('dataset/audio', 'dataset') + # 生成生成方言数据列表 + # get_language_identification_data_list(audio_path='dataset/language', + # list_path='dataset/') + # 创建UrbanSound8K数据列表 + create_UrbanSound8K_list(audio_path='dataset/UrbanSound8K/audio', + metadata_path='dataset/UrbanSound8K/metadata/UrbanSound8K.csv', + list_path='dataset') diff --git a/src/声源定位代码/audio-classification/docs/images/image1.png b/src/声源定位代码/audio-classification/docs/images/image1.png new file mode 100644 index 0000000..6bba58a Binary files /dev/null and b/src/声源定位代码/audio-classification/docs/images/image1.png differ diff --git a/src/声源定位代码/audio-classification/docs/images/log.jpg b/src/声源定位代码/audio-classification/docs/images/log.jpg new file mode 100644 index 0000000..4a98e48 Binary files /dev/null and b/src/声源定位代码/audio-classification/docs/images/log.jpg differ diff --git a/src/声源定位代码/audio-classification/eval.py b/src/声源定位代码/audio-classification/eval.py new file mode 100644 index 0000000..ce7b04f --- /dev/null +++ b/src/声源定位代码/audio-classification/eval.py @@ -0,0 +1,26 @@ +import argparse +import functools +import time + +from macls.trainer import MAClsTrainer +from macls.utils.utils import add_arguments, print_arguments + +parser = argparse.ArgumentParser(description=__doc__) +add_arg = functools.partial(add_arguments, argparser=parser) +add_arg('configs', str, 'configs/cam++.yml', "配置文件") +add_arg("use_gpu", bool, True, "是否使用GPU评估模型") +add_arg('save_matrix_path', str, 'output/images/', "保存混合矩阵的路径") +add_arg('resume_model', str, 'models/CAMPPlus_Fbank/best_model/', "模型的路径") +add_arg('overwrites', str, None, '覆盖配置文件中的参数,比如"train_conf.max_epoch=100",多个用逗号隔开') +args = parser.parse_args() +print_arguments(args=args) + +# 获取训练器 +trainer = MAClsTrainer(configs=args.configs, use_gpu=args.use_gpu, overwrites=args.overwrites) + +# 开始评估 +start = time.time() +loss, accuracy = trainer.evaluate(resume_model=args.resume_model, + save_matrix_path=args.save_matrix_path) +end = time.time() +print('评估消耗时间:{}s,loss:{:.5f},accuracy:{:.5f}'.format(int(end - start), loss, accuracy)) diff --git a/src/声源定位代码/audio-classification/extract_features.py b/src/声源定位代码/audio-classification/extract_features.py new file mode 100644 index 0000000..c707a58 --- /dev/null +++ b/src/声源定位代码/audio-classification/extract_features.py @@ -0,0 +1,19 @@ +import argparse +import functools + +from macls.trainer import MAClsTrainer +from macls.utils.utils import add_arguments, print_arguments + +parser = argparse.ArgumentParser(description=__doc__) +add_arg = functools.partial(add_arguments, argparser=parser) +add_arg('configs', str, 'configs/cam++.yml', '配置文件') +add_arg('save_dir', str, 'dataset/features', '保存特征的路径') +add_arg('max_duration', int, 100, '提取特征的最大时长,避免过长显存不足,单位秒') +args = parser.parse_args() +print_arguments(args=args) + +# 获取训练器 +trainer = MAClsTrainer(configs=args.configs) + +# 提取特征保存文件 +trainer.extract_features(save_dir=args.save_dir, max_duration=args.max_duration) diff --git a/src/声源定位代码/audio-classification/infer.py b/src/声源定位代码/audio-classification/infer.py new file mode 100644 index 0000000..6312cad --- /dev/null +++ b/src/声源定位代码/audio-classification/infer.py @@ -0,0 +1,23 @@ +import argparse +import functools + +from macls.predict import MAClsPredictor +from macls.utils.utils import add_arguments, print_arguments + +parser = argparse.ArgumentParser(description=__doc__) +add_arg = functools.partial(add_arguments, argparser=parser) +add_arg('configs', str, 'configs/cam++.yml', '配置文件') +add_arg('use_gpu', bool, True, '是否使用GPU预测') +add_arg('audio_path', str, 'dataset/UrbanSound8K/audio/fold5/156634-5-2-5.wav', '音频路径') +add_arg('model_path', str, 'models/CAMPPlus_Fbank/best_model/', '导出的预测模型文件路径') +args = parser.parse_args() +print_arguments(args=args) + +# 获取识别器 +predictor = MAClsPredictor(configs=args.configs, + model_path=args.model_path, + use_gpu=args.use_gpu) + +label, score = predictor.predict(audio_data=args.audio_path) + +print(f'音频:{args.audio_path} 的预测结果标签为:{label},得分:{score}') diff --git a/src/声源定位代码/audio-classification/infer_record.py b/src/声源定位代码/audio-classification/infer_record.py new file mode 100644 index 0000000..4723439 --- /dev/null +++ b/src/声源定位代码/audio-classification/infer_record.py @@ -0,0 +1,58 @@ +import argparse +import functools +import threading +import time + +import numpy as np +import soundcard as sc + +from macls.predict import MAClsPredictor +from macls.utils.utils import add_arguments, print_arguments + +parser = argparse.ArgumentParser(description=__doc__) +add_arg = functools.partial(add_arguments, argparser=parser) +add_arg('configs', str, 'configs/cam++.yml', '配置文件') +add_arg('use_gpu', bool, True, '是否使用GPU预测') +add_arg('record_seconds', float, 3, '录音长度') +add_arg('model_path', str, 'models/CAMPPlus_Fbank/best_model/', '导出的预测模型文件路径') +args = parser.parse_args() +print_arguments(args=args) + +# 获取识别器 +predictor = MAClsPredictor(configs=args.configs, + model_path=args.model_path, + use_gpu=args.use_gpu) + +all_data = [] +# 获取默认麦克风 +default_mic = sc.default_microphone() +# 录音采样率 +samplerate = 16000 +# 录音块大小 +numframes = 1024 +# 模型输入长度 +infer_len = int(samplerate * args.record_seconds / numframes) + + +def infer_thread(): + global all_data + s = time.time() + while True: + if len(all_data) < infer_len: continue + # 截取最新的音频数据 + seg_data = all_data[-infer_len:] + d = np.concatenate(seg_data) + # 删除旧的音频数据 + del all_data[:len(all_data) - infer_len] + label, score = predictor.predict(audio_data=d, sample_rate=samplerate) + print(f'{int(time.time() - s)}s 预测结果标签为:{label},得分:{score}') + + +thread = threading.Thread(target=infer_thread, args=()) +thread.start() + + +with default_mic.recorder(samplerate=samplerate, channels=1) as mic: + while True: + data = mic.record(numframes=numframes) + all_data.append(data) diff --git a/src/声源定位代码/audio-classification/macls/__init__.py b/src/声源定位代码/audio-classification/macls/__init__.py new file mode 100644 index 0000000..382021f --- /dev/null +++ b/src/声源定位代码/audio-classification/macls/__init__.py @@ -0,0 +1 @@ +__version__ = "1.0.6" diff --git a/src/声源定位代码/audio-classification/macls/data_utils/__init__.py b/src/声源定位代码/audio-classification/macls/data_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/声源定位代码/audio-classification/macls/data_utils/collate_fn.py b/src/声源定位代码/audio-classification/macls/data_utils/collate_fn.py new file mode 100644 index 0000000..354e37e --- /dev/null +++ b/src/声源定位代码/audio-classification/macls/data_utils/collate_fn.py @@ -0,0 +1,23 @@ +import torch + + +# 对一个batch的数据处理 +def collate_fn(batch): + # 找出音频长度最长的 + batch_sorted = sorted(batch, key=lambda sample: sample[0].size(0), reverse=True) + freq_size = batch_sorted[0][0].size(1) + max_freq_length = batch_sorted[0][0].size(0) + batch_size = len(batch_sorted) + # 以最大的长度创建0张量 + features = torch.zeros((batch_size, max_freq_length, freq_size), dtype=torch.float32) + input_lens, labels = [], [] + for x in range(batch_size): + tensor, label = batch[x] + seq_length = tensor.size(0) + # 将数据插入都0张量中,实现了padding + features[x, :seq_length, :] = tensor[:, :] + labels.append(label) + input_lens.append(seq_length) + labels = torch.tensor(labels, dtype=torch.int64) + input_lens = torch.tensor(input_lens, dtype=torch.int64) + return features, labels, input_lens diff --git a/src/声源定位代码/audio-classification/macls/data_utils/featurizer.py b/src/声源定位代码/audio-classification/macls/data_utils/featurizer.py new file mode 100644 index 0000000..d880f2c --- /dev/null +++ b/src/声源定位代码/audio-classification/macls/data_utils/featurizer.py @@ -0,0 +1,132 @@ +import numpy as np +import torch +import torchaudio.compliance.kaldi as Kaldi +from torch import nn +from torchaudio.transforms import MelSpectrogram, Spectrogram, MFCC +from loguru import logger + + +class AudioFeaturizer(nn.Module): + """音频特征器 + + :param feature_method: 所使用的预处理方法 + :type feature_method: str + :param use_hf_model: 是否使用HF上的Wav2Vec2类似模型提取音频特征 + :type use_hf_model: bool + :param method_args: 预处理方法的参数 + :type method_args: dict + """ + + def __init__(self, feature_method='MelSpectrogram', use_hf_model=False, method_args={}): + super().__init__() + self._method_args = method_args + self._feature_method = feature_method + self.use_hf_model = use_hf_model + if self.use_hf_model: + from transformers import AutoModel, AutoFeatureExtractor + # 判断是否使用GPU提取特征 + use_gpu = torch.cuda.is_available() and method_args.get('use_gpu', True) + self.device = torch.device("cuda") if use_gpu else torch.device("cpu") + # 加载Wav2Vec2类似模型 + self.processor = AutoFeatureExtractor.from_pretrained(feature_method) + self.feature_model = AutoModel.from_pretrained(feature_method).to(self.device) + logger.info(f'使用模型【{feature_method}】提取特征,使用【{self.device}】设备提取') + # 获取模型的输出通道数 + inputs = self.processor(np.ones(16000 * 1, dtype=np.float32), sampling_rate=16000, + return_tensors="pt").to(self.device) + with torch.no_grad(): + outputs = self.feature_model(**inputs) + self.output_channels = outputs.extract_features.shape[2] + else: + if feature_method == 'MelSpectrogram': + self.feat_fun = MelSpectrogram(**method_args) + elif feature_method == 'Spectrogram': + self.feat_fun = Spectrogram(**method_args) + elif feature_method == 'MFCC': + self.feat_fun = MFCC(**method_args) + elif feature_method == 'Fbank': + self.feat_fun = KaldiFbank(**method_args) + else: + raise Exception(f'预处理方法 {self._feature_method} 不存在!') + logger.info(f'使用【{feature_method}】提取特征') + + def forward(self, waveforms, input_lens_ratio=None): + """从AudioSegment中提取音频特征 + + :param waveforms: Audio segment to extract features from. + :type waveforms: AudioSegment + :param input_lens_ratio: input length ratio + :type input_lens_ratio: tensor + :return: Spectrogram audio feature in 2darray. + :rtype: ndarray + """ + if len(waveforms.shape) == 1: + waveforms = waveforms.unsqueeze(0) + if self.use_hf_model: + # 使用HF上的Wav2Vec2类似模型提取音频特征 + if isinstance(waveforms, torch.Tensor): + waveforms = waveforms.numpy() + inputs = self.processor(waveforms, sampling_rate=16000, + return_tensors="pt").to(self.device) + with torch.no_grad(): + outputs = self.feature_model(**inputs) + feature = outputs.extract_features.cpu().detach() + else: + # 使用普通方法提取音频特征 + feature = self.feat_fun(waveforms) + feature = feature.transpose(2, 1) + # 归一化 + feature = feature - feature.mean(1, keepdim=True) + if input_lens_ratio is not None: + # 对掩码比例进行扩展 + input_lens = (input_lens_ratio * feature.shape[1]) + mask_lens = torch.round(input_lens).long() + mask_lens = mask_lens.unsqueeze(1) + # 生成掩码张量 + idxs = torch.arange(feature.shape[1], device=feature.device).repeat(feature.shape[0], 1) + mask = idxs < mask_lens + mask = mask.unsqueeze(-1) + # 对特征进行掩码操作 + feature = torch.where(mask, feature, torch.zeros_like(feature)) + return feature + + @property + def feature_dim(self): + """返回特征大小 + + :return: 特征大小 + :rtype: int + """ + if self.use_hf_model: + return self.output_channels + if self._feature_method == 'MelSpectrogram': + return self._method_args.get('n_mels', 128) + elif self._feature_method == 'Spectrogram': + return self._method_args.get('n_fft', 400) // 2 + 1 + elif self._feature_method == 'MFCC': + return self._method_args.get('n_mfcc', 40) + elif self._feature_method == 'Fbank': + return self._method_args.get('num_mel_bins', 23) + else: + raise Exception('没有{}预处理方法'.format(self._feature_method)) + + +class KaldiFbank(nn.Module): + def __init__(self, **kwargs): + super(KaldiFbank, self).__init__() + self.kwargs = kwargs + + def forward(self, waveforms): + """ + :param waveforms: [Batch, Length] + :return: [Batch, Feature, Length] + """ + log_fbanks = [] + for waveform in waveforms: + if len(waveform.shape) == 1: + waveform = waveform.unsqueeze(0) + log_fbank = Kaldi.fbank(waveform, **self.kwargs) + log_fbank = log_fbank.transpose(0, 1) + log_fbanks.append(log_fbank) + log_fbank = torch.stack(log_fbanks) + return log_fbank diff --git a/src/声源定位代码/audio-classification/macls/data_utils/reader.py b/src/声源定位代码/audio-classification/macls/data_utils/reader.py new file mode 100644 index 0000000..7d496da --- /dev/null +++ b/src/声源定位代码/audio-classification/macls/data_utils/reader.py @@ -0,0 +1,157 @@ +import random + +import numpy as np +import torch +from torch.utils.data import Dataset +from tqdm import tqdm +from yeaudio.audio import AudioSegment +from yeaudio.augmentation import SpeedPerturbAugmentor, VolumePerturbAugmentor, NoisePerturbAugmentor, \ + ReverbPerturbAugmentor, SpecAugmentor + +from macls.data_utils.featurizer import AudioFeaturizer + + +class MAClsDataset(Dataset): + def __init__(self, + data_list_path, + audio_featurizer: AudioFeaturizer, + max_duration=3, + min_duration=0.5, + mode='train', + sample_rate=16000, + aug_conf=None, + use_dB_normalization=True, + target_dB=-20): + """音频数据加载器 + + Args: + data_list_path: 包含音频路径和标签的数据列表文件的路径 + audio_featurizer: 声纹特征提取器 + max_duration: 最长的音频长度,大于这个长度会裁剪掉 + min_duration: 过滤最短的音频长度 + aug_conf: 用于指定音频增强的配置 + mode: 数据集模式。在训练模式下,数据集可能会进行一些数据增强的预处理 + sample_rate: 采样率 + use_dB_normalization: 是否对音频进行音量归一化 + target_dB: 音量归一化的大小 + """ + super(MAClsDataset, self).__init__() + assert mode in ['train', 'eval', 'extract_feature'] + self.data_list_path = data_list_path + self.max_duration = max_duration + self.min_duration = min_duration + self.mode = mode + self._target_sample_rate = sample_rate + self._use_dB_normalization = use_dB_normalization + self._target_dB = target_dB + self.speed_augment = None + self.volume_augment = None + self.noise_augment = None + self.reverb_augment = None + self.spec_augment = None + # 获取特征器 + self.audio_featurizer = audio_featurizer + # 获取特征裁剪的大小 + self.max_feature_len = self.get_crop_feature_len() + # 获取数据列表 + with open(self.data_list_path, 'r', encoding='utf-8') as f: + self.lines = f.readlines() + if mode == 'train' and aug_conf is not None: + # 获取数据增强器 + self.get_augmentor(aug_conf) + # 评估模式下,数据列表需要排序 + if self.mode == 'eval': + self.sort_list() + + def __getitem__(self, idx): + # 分割数据文件路径和标签 + data_path, label = self.lines[idx].replace('\n', '').split('\t') + # 如果后缀名为.npy的文件,那么直接读取 + if data_path.endswith('.npy'): + feature = np.load(data_path) + if feature.shape[0] > self.max_feature_len: + crop_start = random.randint(0, feature.shape[0] - self.max_feature_len) if self.mode == 'train' else 0 + feature = feature[crop_start:crop_start + self.max_feature_len, :] + feature = torch.tensor(feature, dtype=torch.float32) + else: + audio_path, label = self.lines[idx].strip().split('\t') + # 读取音频 + audio_segment = AudioSegment.from_file(audio_path) + # 数据太短不利于训练 + if self.mode == 'train' or self.mode == 'extract_feature': + if audio_segment.duration < self.min_duration: + return self.__getitem__(idx + 1 if idx < len(self.lines) - 1 else 0) + # 音频增强 + if self.mode == 'train': + audio_segment = self.augment_audio(audio_segment) + # 重采样 + if audio_segment.sample_rate != self._target_sample_rate: + audio_segment.resample(self._target_sample_rate) + # 音量归一化 + if self._use_dB_normalization: + audio_segment.normalize(target_db=self._target_dB) + # 裁剪需要的数据 + if audio_segment.duration > self.max_duration: + audio_segment.crop(duration=self.max_duration, mode=self.mode) + samples = torch.tensor(audio_segment.samples, dtype=torch.float32) + feature = self.audio_featurizer(samples) + feature = feature.squeeze(0) + if self.mode == 'train' and self.spec_augment is not None: + feature = self.spec_augment(feature.cpu().numpy()) + feature = torch.tensor(feature, dtype=torch.float32) + label = torch.tensor(int(label), dtype=torch.int64) + return feature, label + + def __len__(self): + return len(self.lines) + + # 获取特征裁剪的大小,对应max_duration音频提取特征后的长度 + def get_crop_feature_len(self): + samples = torch.randn((1, self.max_duration * self._target_sample_rate)) + feature = self.audio_featurizer(samples).squeeze(0) + freq_len = feature.size(0) + return freq_len + + # 数据列表需要排序 + def sort_list(self): + lengths = [] + for line in tqdm(self.lines, desc=f"对列表[{self.data_list_path}]进行长度排序"): + # 分割数据文件路径和标签 + data_path, _ = line.split('\t') + if data_path.endswith('.npy'): + feature = np.load(data_path) + length = feature.shape[0] + lengths.append(length) + else: + # 读取音频 + audio_segment = AudioSegment.from_file(data_path) + length = audio_segment.duration + lengths.append(length) + # 对长度排序并获取索引 + sorted_indexes = np.argsort(lengths) + self.lines = [self.lines[i] for i in sorted_indexes] + + # 获取数据增强器 + def get_augmentor(self, aug_conf): + if aug_conf.speed is not None: + self.speed_augment = SpeedPerturbAugmentor(**aug_conf.speed) + if aug_conf.volume is not None: + self.volume_augment = VolumePerturbAugmentor(**aug_conf.volume) + if aug_conf.noise is not None: + self.noise_augment = NoisePerturbAugmentor(**aug_conf.noise) + if aug_conf.reverb is not None: + self.reverb_augment = ReverbPerturbAugmentor(**aug_conf.reverb) + if aug_conf.spec_aug is not None: + self.spec_augment = SpecAugmentor(**aug_conf.spec_aug) + + # 音频增强 + def augment_audio(self, audio_segment): + if self.speed_augment is not None: + audio_segment = self.speed_augment(audio_segment) + if self.volume_augment is not None: + audio_segment = self.volume_augment(audio_segment) + if self.noise_augment is not None: + audio_segment = self.noise_augment(audio_segment) + if self.reverb_augment is not None: + audio_segment = self.reverb_augment(audio_segment) + return audio_segment diff --git a/src/声源定位代码/audio-classification/macls/metric/__init__.py b/src/声源定位代码/audio-classification/macls/metric/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/声源定位代码/audio-classification/macls/metric/metrics.py b/src/声源定位代码/audio-classification/macls/metric/metrics.py new file mode 100644 index 0000000..431c60a --- /dev/null +++ b/src/声源定位代码/audio-classification/macls/metric/metrics.py @@ -0,0 +1,12 @@ +import numpy as np +import torch + + +# 计算准确率 +def accuracy(output, label): + output = torch.nn.functional.softmax(output, dim=-1) + output = output.data.cpu().numpy() + output = np.argmax(output, axis=1) + label = label.data.cpu().numpy() + acc = np.mean((output == label).astype(int)) + return acc diff --git a/src/声源定位代码/audio-classification/macls/optimizer/__init__.py b/src/声源定位代码/audio-classification/macls/optimizer/__init__.py new file mode 100644 index 0000000..4362420 --- /dev/null +++ b/src/声源定位代码/audio-classification/macls/optimizer/__init__.py @@ -0,0 +1,32 @@ +import importlib + +from loguru import logger +from torch.optim import * +from .scheduler import WarmupCosineSchedulerLR +from torch.optim.lr_scheduler import * + +__all__ = ['build_optimizer', 'build_lr_scheduler'] + + +def build_optimizer(params, configs): + use_optimizer = configs.optimizer_conf.get('optimizer', 'Adam') + optimizer_args = configs.optimizer_conf.get('optimizer_args', {}) + optim = importlib.import_module(__name__) + optimizer = getattr(optim, use_optimizer)(params=params, **optimizer_args) + logger.info(f'成功创建优化方法:{use_optimizer},参数为:{optimizer_args}') + return optimizer + + +def build_lr_scheduler(optimizer, step_per_epoch, configs): + use_scheduler = configs.optimizer_conf.get('scheduler', 'WarmupCosineSchedulerLR') + scheduler_args = configs.optimizer_conf.get('scheduler_args', {}) + if configs.optimizer_conf.scheduler == 'CosineAnnealingLR' and 'T_max' not in scheduler_args: + scheduler_args.T_max = int(configs.train_conf.max_epoch * 1.2) * step_per_epoch + if configs.optimizer_conf.scheduler == 'WarmupCosineSchedulerLR' and 'fix_epoch' not in scheduler_args: + scheduler_args.fix_epoch = configs.train_conf.max_epoch + if configs.optimizer_conf.scheduler == 'WarmupCosineSchedulerLR' and 'step_per_epoch' not in scheduler_args: + scheduler_args.step_per_epoch = step_per_epoch + optim = importlib.import_module(__name__) + scheduler = getattr(optim, use_scheduler)(optimizer=optimizer, **scheduler_args) + logger.info(f'成功创建学习率衰减:{use_scheduler},参数为:{scheduler_args}') + return scheduler diff --git a/src/声源定位代码/audio-classification/macls/optimizer/scheduler.py b/src/声源定位代码/audio-classification/macls/optimizer/scheduler.py new file mode 100644 index 0000000..e2ac1b7 --- /dev/null +++ b/src/声源定位代码/audio-classification/macls/optimizer/scheduler.py @@ -0,0 +1,48 @@ +import math +from typing import List + + +class WarmupCosineSchedulerLR: + def __init__( + self, + optimizer, + min_lr, + max_lr, + warmup_epoch, + fix_epoch, + step_per_epoch + ): + self.optimizer = optimizer + assert min_lr <= max_lr + self.min_lr = min_lr + self.max_lr = max_lr + self.warmup_step = warmup_epoch * step_per_epoch + self.fix_step = fix_epoch * step_per_epoch + self.current_step = 0.0 + + def set_lr(self, ): + new_lr = self.clr(self.current_step) + for param_group in self.optimizer.param_groups: + param_group['lr'] = new_lr + return new_lr + + def step(self, step=None): + if step is not None: + self.current_step = step + new_lr = self.set_lr() + self.current_step += 1 + return new_lr + + def clr(self, step): + if step < self.warmup_step: + return self.min_lr + (self.max_lr - self.min_lr) * \ + (step / self.warmup_step) + elif self.warmup_step <= step < self.fix_step: + return self.min_lr + 0.5 * (self.max_lr - self.min_lr) * \ + (1 + math.cos(math.pi * (step - self.warmup_step) / + (self.fix_step - self.warmup_step))) + else: + return self.min_lr + + def get_last_lr(self) -> List[float]: + return [self.clr(self.current_step)] diff --git a/src/声源定位代码/audio-classification/macls/predict.py b/src/声源定位代码/audio-classification/macls/predict.py new file mode 100644 index 0000000..25e3bbc --- /dev/null +++ b/src/声源定位代码/audio-classification/macls/predict.py @@ -0,0 +1,177 @@ +import os +import sys +from io import BufferedReader +from typing import List + +import numpy as np +import torch +import yaml +from loguru import logger +from yeaudio.audio import AudioSegment +from macls.data_utils.featurizer import AudioFeaturizer +from macls.models import build_model +from macls.utils.utils import dict_to_object, print_arguments, convert_string_based_on_type + + +class MAClsPredictor: + def __init__(self, + configs, + model_path='models/CAMPPlus_Fbank/best_model/', + use_gpu=True, + overwrites=None, + log_level="info"): + """声音分类预测工具 + + :param configs: 配置文件路径,或者模型名称,如果是模型名称则会使用默认的配置文件 + :param model_path: 导出的预测模型文件夹路径 + :param use_gpu: 是否使用GPU预测 + :param overwrites: 覆盖配置文件中的参数,比如"train_conf.max_epoch=100",多个用逗号隔开 + :param log_level: 打印的日志等级,可选值有:"debug", "info", "warning", "error" + """ + if use_gpu: + assert (torch.cuda.is_available()), 'GPU不可用' + self.device = torch.device("cuda") + else: + os.environ['CUDA_VISIBLE_DEVICES'] = '-1' + self.device = torch.device("cpu") + self.log_level = log_level.upper() + logger.remove() + logger.add(sink=sys.stdout, level=self.log_level) + # 读取配置文件 + if isinstance(configs, str): + # 获取当前程序绝对路径 + absolute_path = os.path.dirname(__file__) + # 获取默认配置文件路径 + config_path = os.path.join(absolute_path, f"configs/{configs}.yml") + configs = config_path if os.path.exists(config_path) else configs + with open(configs, 'r', encoding='utf-8') as f: + configs = yaml.load(f.read(), Loader=yaml.FullLoader) + self.configs = dict_to_object(configs) + # 覆盖配置文件中的参数 + if overwrites: + overwrites = overwrites.split(",") + for overwrite in overwrites: + keys, value = overwrite.strip().split("=") + attrs = keys.split('.') + current_level = self.configs + for attr in attrs[:-1]: + current_level = getattr(current_level, attr) + before_value = getattr(current_level, attrs[-1]) + setattr(current_level, attrs[-1], convert_string_based_on_type(before_value, value)) + # 打印配置信息 + print_arguments(configs=self.configs) + # 获取特征器 + self._audio_featurizer = AudioFeaturizer(feature_method=self.configs.preprocess_conf.feature_method, + use_hf_model=self.configs.preprocess_conf.get('use_hf_model', False), + method_args=self.configs.preprocess_conf.get('method_args', {})) + # 获取分类标签 + with open(self.configs.dataset_conf.label_list_path, 'r', encoding='utf-8') as f: + lines = f.readlines() + self.class_labels = [l.replace('\n', '') for l in lines] + # 自动获取列表数量 + if self.configs.model_conf.model_args.get('num_class', None) is None: + self.configs.model_conf.model_args.num_class = len(self.class_labels) + # 获取模型 + self.predictor = build_model(input_size=self._audio_featurizer.feature_dim, configs=self.configs) + self.predictor.to(self.device) + # 加载模型 + if os.path.isdir(model_path): + model_path = os.path.join(model_path, 'model.pth') + assert os.path.exists(model_path), f"{model_path} 模型不存在!" + if torch.cuda.is_available() and use_gpu: + model_state_dict = torch.load(model_path, weights_only=False) + else: + model_state_dict = torch.load(model_path, weights_only=False, map_location='cpu') + self.predictor.load_state_dict(model_state_dict) + logger.info(f"成功加载模型参数:{model_path}") + self.predictor.eval() + + def _load_audio(self, audio_data, sample_rate=16000): + """加载音频 + :param audio_data: 需要识别的数据,支持文件路径,文件对象,字节,numpy。如果是字节的话,必须是完整的字节文件 + :param sample_rate: 如果传入的事numpy数据,需要指定采样率 + :return: 识别的文本结果和解码的得分数 + """ + # 加载音频文件,并进行预处理 + if isinstance(audio_data, str): + audio_segment = AudioSegment.from_file(audio_data) + elif isinstance(audio_data, BufferedReader): + audio_segment = AudioSegment.from_file(audio_data) + elif isinstance(audio_data, np.ndarray): + audio_segment = AudioSegment.from_ndarray(audio_data, sample_rate) + elif isinstance(audio_data, bytes): + audio_segment = AudioSegment.from_bytes(audio_data) + else: + raise Exception(f'不支持该数据类型,当前数据类型为:{type(audio_data)}') + # 重采样 + if audio_segment.sample_rate != self.configs.dataset_conf.dataset.sample_rate: + audio_segment.resample(self.configs.dataset_conf.dataset.sample_rate) + # decibel normalization + if self.configs.dataset_conf.dataset.use_dB_normalization: + audio_segment.normalize(target_db=self.configs.dataset_conf.dataset.target_dB) + assert audio_segment.duration >= self.configs.dataset_conf.dataset.min_duration, \ + f'音频太短,最小应该为{self.configs.dataset_conf.dataset.min_duration}s,当前音频为{audio_segment.duration}s' + return audio_segment + + # 预测一个音频的特征 + def predict(self, + audio_data, + sample_rate=16000): + """预测一个音频 + + :param audio_data: 需要识别的数据,支持文件路径,文件对象,字节,numpy。如果是字节的话,必须是完整并带格式的字节文件 + :param sample_rate: 如果传入的事numpy数据,需要指定采样率 + :return: 结果标签和对应的得分 + """ + # 加载音频文件,并进行预处理 + input_data = self._load_audio(audio_data=audio_data, sample_rate=sample_rate) + input_data = torch.tensor(input_data.samples, dtype=torch.float32).unsqueeze(0) + audio_feature = self._audio_featurizer(input_data).to(self.device) + # 执行预测 + output = self.predictor(audio_feature) + result = torch.nn.functional.softmax(output, dim=-1)[0] + result = result.data.cpu().numpy() + # 最大概率的label + lab = np.argsort(result)[-1] + score = result[lab] + return self.class_labels[lab], round(float(score), 5) + + def predict_batch(self, audios_data: List, sample_rate=16000): + """预测一批音频的特征 + + :param audios_data: 需要识别的数据,支持文件路径,文件对象,字节,numpy。如果是字节的话,必须是完整并带格式的字节文件 + :param sample_rate: 如果传入的事numpy数据,需要指定采样率 + :return: 结果标签和对应的得分 + """ + audios_data1 = [] + for audio_data in audios_data: + # 加载音频文件,并进行预处理 + input_data = self._load_audio(audio_data=audio_data, sample_rate=sample_rate) + audios_data1.append(input_data.samples) + # 找出音频长度最长的 + batch = sorted(audios_data1, key=lambda a: a.shape[0], reverse=True) + max_audio_length = batch[0].shape[0] + batch_size = len(batch) + # 以最大的长度创建0张量 + inputs = np.zeros((batch_size, max_audio_length), dtype=np.float32) + input_lens_ratio = [] + for x in range(batch_size): + tensor = audios_data1[x] + seq_length = tensor.shape[0] + # 将数据插入都0张量中,实现了padding + inputs[x, :seq_length] = tensor[:] + input_lens_ratio.append(seq_length / max_audio_length) + inputs = torch.tensor(inputs, dtype=torch.float32) + input_lens_ratio = torch.tensor(input_lens_ratio, dtype=torch.float32) + audio_feature = self._audio_featurizer(inputs, input_lens_ratio).to(self.device) + # 执行预测 + output = self.predictor(audio_feature) + results = torch.nn.functional.softmax(output, dim=-1) + results = results.data.cpu().numpy() + labels, scores = [], [] + for result in results: + lab = np.argsort(result)[-1] + score = result[lab] + labels.append(self.class_labels[lab]) + scores.append(round(float(score), 5)) + return labels, scores diff --git a/src/声源定位代码/audio-classification/macls/trainer.py b/src/声源定位代码/audio-classification/macls/trainer.py new file mode 100644 index 0000000..4412d21 --- /dev/null +++ b/src/声源定位代码/audio-classification/macls/trainer.py @@ -0,0 +1,456 @@ +import os +import platform +import sys +import time +import uuid +from datetime import timedelta + +import numpy as np +import torch +import torch.distributed as dist +import yaml +from sklearn.metrics import confusion_matrix +from torch.utils.data import DataLoader, RandomSampler +from torch.utils.data.distributed import DistributedSampler +from torchinfo import summary +from tqdm import tqdm +from loguru import logger +from visualdl import LogWriter + +from macls.data_utils.collate_fn import collate_fn +from macls.data_utils.featurizer import AudioFeaturizer +from macls.data_utils.reader import MAClsDataset +from macls.metric.metrics import accuracy +from macls.models import build_model +from macls.optimizer import build_optimizer, build_lr_scheduler +from macls.utils.checkpoint import load_pretrained, load_checkpoint, save_checkpoint +from macls.utils.utils import dict_to_object, plot_confusion_matrix, print_arguments, convert_string_based_on_type + + +class MAClsTrainer(object): + def __init__(self, + configs, + use_gpu=True, + data_augment_configs=None, + num_class=None, + overwrites=None, + log_level="info"): + """声音分类训练工具类 + + :param configs: 配置文件路径,或者模型名称,如果是模型名称则会使用默认的配置文件 + :param use_gpu: 是否使用GPU训练模型 + :param data_augment_configs: 数据增强配置字典或者其文件路径 + :param num_class: 分类大小,对应配置文件中的model_conf.model_args.num_class + :param overwrites: 覆盖配置文件中的参数,比如"train_conf.max_epoch=100",多个用逗号隔开 + :param log_level: 打印的日志等级,可选值有:"debug", "info", "warning", "error" + """ + if use_gpu: + assert (torch.cuda.is_available()), 'GPU不可用' + self.device = torch.device("cuda") + else: + os.environ['CUDA_VISIBLE_DEVICES'] = '-1' + self.device = torch.device("cpu") + self.use_gpu = use_gpu + self.log_level = log_level.upper() + logger.remove() + logger.add(sink=sys.stdout, level=self.log_level) + # 读取配置文件 + if isinstance(configs, str): + # 获取当前程序绝对路径 + absolute_path = os.path.dirname(__file__) + # 获取默认配置文件路径 + config_path = os.path.join(absolute_path, f"configs/{configs}.yml") + configs = config_path if os.path.exists(config_path) else configs + with open(configs, 'r', encoding='utf-8') as f: + configs = yaml.load(f.read(), Loader=yaml.FullLoader) + self.configs = dict_to_object(configs) + if num_class is not None: + self.configs.model_conf.model_args.num_class = num_class + # 覆盖配置文件中的参数 + if overwrites: + overwrites = overwrites.split(",") + for overwrite in overwrites: + keys, value = overwrite.strip().split("=") + attrs = keys.split('.') + current_level = self.configs + for attr in attrs[:-1]: + current_level = getattr(current_level, attr) + before_value = getattr(current_level, attrs[-1]) + setattr(current_level, attrs[-1], convert_string_based_on_type(before_value, value)) + # 打印配置信息 + print_arguments(configs=self.configs) + self.model = None + self.optimizer = None + self.scheduler = None + self.audio_featurizer = None + self.train_dataset = None + self.train_loader = None + self.test_dataset = None + self.test_loader = None + self.amp_scaler = None + # 读取数据增强配置文件 + if isinstance(data_augment_configs, str): + with open(data_augment_configs, 'r', encoding='utf-8') as f: + data_augment_configs = yaml.load(f.read(), Loader=yaml.FullLoader) + print_arguments(configs=data_augment_configs, title='数据增强配置') + self.data_augment_configs = dict_to_object(data_augment_configs) + # 获取分类标签 + with open(self.configs.dataset_conf.label_list_path, 'r', encoding='utf-8') as f: + lines = f.readlines() + self.class_labels = [l.replace('\n', '') for l in lines] + if platform.system().lower() == 'windows': + self.configs.dataset_conf.dataLoader.num_workers = 0 + logger.warning('Windows系统不支持多线程读取数据,已自动关闭!') + if self.configs.preprocess_conf.get('use_hf_model', False): + self.configs.dataset_conf.dataLoader.num_workers = 0 + logger.warning('使用HuggingFace模型不支持多线程进行特征提取,已自动关闭!') + self.max_step, self.train_step = None, None + self.train_loss, self.train_acc = None, None + self.train_eta_sec = None + self.eval_loss, self.eval_acc = None, None + self.test_log_step, self.train_log_step = 0, 0 + self.stop_train, self.stop_eval = False, False + + def __setup_dataloader(self, is_train=False): + """ 获取数据加载器 + + :param is_train: 是否获取训练数据 + """ + # 获取特征器 + self.audio_featurizer = AudioFeaturizer(feature_method=self.configs.preprocess_conf.feature_method, + use_hf_model=self.configs.preprocess_conf.get('use_hf_model', False), + method_args=self.configs.preprocess_conf.get('method_args', {})) + + dataset_args = self.configs.dataset_conf.get('dataset', {}) + data_loader_args = self.configs.dataset_conf.get('dataLoader', {}) + if is_train: + self.train_dataset = MAClsDataset(data_list_path=self.configs.dataset_conf.train_list, + audio_featurizer=self.audio_featurizer, + aug_conf=self.data_augment_configs, + mode='train', + **dataset_args) + # 设置支持多卡训练 + train_sampler = RandomSampler(self.train_dataset) + if torch.cuda.device_count() > 1: + # 设置支持多卡训练 + train_sampler = DistributedSampler(dataset=self.train_dataset) + self.train_loader = DataLoader(dataset=self.train_dataset, + collate_fn=collate_fn, + sampler=train_sampler, + **data_loader_args) + # 获取测试数据 + data_loader_args.drop_last = False + dataset_args.max_duration = self.configs.dataset_conf.eval_conf.max_duration + data_loader_args.batch_size = self.configs.dataset_conf.eval_conf.batch_size + self.test_dataset = MAClsDataset(data_list_path=self.configs.dataset_conf.test_list, + audio_featurizer=self.audio_featurizer, + mode='eval', + **dataset_args) + self.test_loader = DataLoader(dataset=self.test_dataset, + collate_fn=collate_fn, + shuffle=False, + **data_loader_args) + + def extract_features(self, save_dir='dataset/features', max_duration=100): + """ 提取特征保存文件 + + :param save_dir: 保存路径 + :param max_duration: 提取特征的最大时长,避免过长显存不足,单位秒 + """ + self.audio_featurizer = AudioFeaturizer(feature_method=self.configs.preprocess_conf.feature_method, + use_hf_model=self.configs.preprocess_conf.get('use_hf_model', False), + method_args=self.configs.preprocess_conf.get('method_args', {})) + dataset_args = self.configs.dataset_conf.get('dataset', {}) + dataset_args.max_duration = max_duration + data_loader_args = self.configs.dataset_conf.get('dataLoader', {}) + data_loader_args.drop_last = False + for data_list in [self.configs.dataset_conf.train_list, self.configs.dataset_conf.test_list]: + test_dataset = MAClsDataset(data_list_path=data_list, + audio_featurizer=self.audio_featurizer, + mode='extract_feature', + **dataset_args) + test_loader = DataLoader(dataset=test_dataset, + collate_fn=collate_fn, + shuffle=False, + **data_loader_args) + save_data_list = data_list.replace('.txt', '_features.txt') + with open(save_data_list, 'w', encoding='utf-8') as f: + for features, labels, input_lens in tqdm(test_loader): + for i in range(len(features)): + feature, label, input_len = features[i], labels[i], input_lens[i] + feature = feature.numpy()[:input_len] + label = int(label) + save_path = os.path.join(save_dir, str(label), + f'{str(uuid.uuid4())}.npy').replace('\\', '/') + os.makedirs(os.path.dirname(save_path), exist_ok=True) + np.save(save_path, feature) + f.write(f'{save_path}\t{label}\n') + logger.info(f'{data_list}列表中的数据已提取特征完成,新列表为:{save_data_list}') + + def __setup_model(self, input_size, is_train=False): + """ 获取模型 + + :param input_size: 模型输入特征大小 + :param is_train: 是否获取训练模型 + """ + # 自动获取列表数量 + if self.configs.model_conf.model_args.get('num_class', None) is None: + self.configs.model_conf.model_args.num_class = len(self.class_labels) + # 获取模型 + self.model = build_model(input_size=input_size, configs=self.configs) + self.model.to(self.device) + if self.log_level == "DEBUG" or self.log_level == "INFO": + # 打印模型信息,98是长度,这个取决于输入的音频长度 + summary(self.model, input_size=(1, 98, input_size)) + # 使用Pytorch2.0的编译器 + if self.configs.train_conf.use_compile and torch.__version__ >= "2" and platform.system().lower() == 'windows': + self.model = torch.compile(self.model, mode="reduce-overhead") + # print(self.model) + # 获取损失函数 + label_smoothing = self.configs.train_conf.get('label_smoothing', 0.0) + self.loss = torch.nn.CrossEntropyLoss(label_smoothing=label_smoothing) + if is_train: + if self.configs.train_conf.enable_amp: + self.amp_scaler = torch.GradScaler(init_scale=1024) + # 获取优化方法 + self.optimizer = build_optimizer(params=self.model.parameters(), configs=self.configs) + # 学习率衰减函数 + self.scheduler = build_lr_scheduler(optimizer=self.optimizer, step_per_epoch=len(self.train_loader), + configs=self.configs) + + def __train_epoch(self, epoch_id, local_rank, writer, nranks=0): + """训练一个epoch + + :param epoch_id: 当前epoch + :param local_rank: 当前显卡id + :param writer: VisualDL对象 + :param nranks: 所使用显卡的数量 + """ + train_times, accuracies, loss_sum = [], [], [] + start = time.time() + for batch_id, (features, label, input_len) in enumerate(self.train_loader): + if self.stop_train: break + if nranks > 1: + features = features.to(local_rank) + label = label.to(local_rank).long() + else: + features = features.to(self.device) + label = label.to(self.device).long() + # 执行模型计算,是否开启自动混合精度 + with torch.autocast('cuda', enabled=self.configs.train_conf.enable_amp): + output = self.model(features) + # 计算损失值 + los = self.loss(output, label) + # 是否开启自动混合精度 + if self.configs.train_conf.enable_amp: + # loss缩放,乘以系数loss_scaling + scaled = self.amp_scaler.scale(los) + scaled.backward() + else: + los.backward() + # 是否开启自动混合精度 + if self.configs.train_conf.enable_amp: + self.amp_scaler.unscale_(self.optimizer) + self.amp_scaler.step(self.optimizer) + self.amp_scaler.update() + else: + self.optimizer.step() + self.optimizer.zero_grad() + + # 计算准确率 + acc = accuracy(output, label) + accuracies.append(acc) + loss_sum.append(los.data.cpu().numpy()) + train_times.append((time.time() - start) * 1000) + self.train_step += 1 + + # 多卡训练只使用一个进程打印 + if batch_id % self.configs.train_conf.log_interval == 0 and local_rank == 0: + batch_id = batch_id + 1 + # 计算每秒训练数据量 + train_speed = self.configs.dataset_conf.dataLoader.batch_size / ( + sum(train_times) / len(train_times) / 1000) + # 计算剩余时间 + self.train_eta_sec = (sum(train_times) / len(train_times)) * (self.max_step - self.train_step) / 1000 + eta_str = str(timedelta(seconds=int(self.train_eta_sec))) + self.train_loss = sum(loss_sum) / len(loss_sum) + self.train_acc = sum(accuracies) / len(accuracies) + logger.info(f'Train epoch: [{epoch_id}/{self.configs.train_conf.max_epoch}], ' + f'batch: [{batch_id}/{len(self.train_loader)}], ' + f'loss: {self.train_loss:.5f}, accuracy: {self.train_acc:.5f}, ' + f'learning rate: {self.scheduler.get_last_lr()[0]:>.8f}, ' + f'speed: {train_speed:.2f} data/sec, eta: {eta_str}') + writer.add_scalar('Train/Loss', self.train_loss, self.train_log_step) + writer.add_scalar('Train/Accuracy', self.train_acc, self.train_log_step) + # 记录学习率 + writer.add_scalar('Train/lr', self.scheduler.get_last_lr()[0], self.train_log_step) + train_times, accuracies, loss_sum = [], [], [] + self.train_log_step += 1 + start = time.time() + self.scheduler.step() + + def train(self, + save_model_path='models/', + log_dir='log/', + max_epoch=None, + resume_model=None, + pretrained_model=None): + """ + 训练模型 + :param save_model_path: 模型保存的路径 + :param log_dir: 保存VisualDL日志文件的路径 + :param max_epoch: 最大训练轮数,对应配置文件中的train_conf.max_epoch + :param resume_model: 恢复训练,当为None则不使用预训练模型 + :param pretrained_model: 预训练模型的路径,当为None则不使用预训练模型 + """ + # 获取有多少张显卡训练 + nranks = torch.cuda.device_count() + local_rank = 0 + writer = None + if local_rank == 0: + # 日志记录器 + writer = LogWriter(logdir=log_dir) + + if nranks > 1 and self.use_gpu: + # 初始化NCCL环境 + dist.init_process_group(backend='nccl') + local_rank = int(os.environ["LOCAL_RANK"]) + + # 获取数据 + self.__setup_dataloader(is_train=True) + # 获取模型 + self.__setup_model(input_size=self.audio_featurizer.feature_dim, is_train=True) + # 加载预训练模型 + self.model = load_pretrained(model=self.model, pretrained_model=pretrained_model, use_gpu=self.use_gpu) + # 加载恢复模型 + self.model, self.optimizer, self.amp_scaler, self.scheduler, last_epoch, best_acc = \ + load_checkpoint(configs=self.configs, model=self.model, optimizer=self.optimizer, + amp_scaler=self.amp_scaler, scheduler=self.scheduler, step_epoch=len(self.train_loader), + save_model_path=save_model_path, resume_model=resume_model) + + # 支持多卡训练 + if nranks > 1 and self.use_gpu: + self.model.to(local_rank) + self.model = torch.nn.parallel.DistributedDataParallel(self.model, device_ids=[local_rank]) + logger.info('训练数据:{}'.format(len(self.train_dataset))) + + self.train_loss, self.train_acc = None, None + self.eval_loss, self.eval_acc = None, None + self.test_log_step, self.train_log_step = 0, 0 + if local_rank == 0: + writer.add_scalar('Train/lr', self.scheduler.get_last_lr()[0], last_epoch) + if max_epoch is not None: + self.configs.train_conf.max_epoch = max_epoch + # 最大步数 + self.max_step = len(self.train_loader) * self.configs.train_conf.max_epoch + self.train_step = max(last_epoch, 0) * len(self.train_loader) + # 开始训练 + for epoch_id in range(last_epoch, self.configs.train_conf.max_epoch): + if self.stop_train: break + epoch_id += 1 + start_epoch = time.time() + # 训练一个epoch + self.__train_epoch(epoch_id=epoch_id, local_rank=local_rank, writer=writer, nranks=nranks) + # 多卡训练只使用一个进程执行评估和保存模型 + if local_rank == 0: + if self.stop_eval: continue + logger.info('=' * 70) + self.eval_loss, self.eval_acc = self.evaluate() + logger.info('Test epoch: {}, time/epoch: {}, loss: {:.5f}, accuracy: {:.5f}'.format( + epoch_id, str(timedelta(seconds=(time.time() - start_epoch))), self.eval_loss, self.eval_acc)) + logger.info('=' * 70) + writer.add_scalar('Test/Accuracy', self.eval_acc, self.test_log_step) + writer.add_scalar('Test/Loss', self.eval_loss, self.test_log_step) + self.test_log_step += 1 + self.model.train() + # # 保存最优模型 + if self.eval_acc >= best_acc: + best_acc = self.eval_acc + save_checkpoint(configs=self.configs, model=self.model, optimizer=self.optimizer, + amp_scaler=self.amp_scaler, save_model_path=save_model_path, epoch_id=epoch_id, + accuracy=self.eval_acc, best_model=True) + # 保存模型 + save_checkpoint(configs=self.configs, model=self.model, optimizer=self.optimizer, + amp_scaler=self.amp_scaler, save_model_path=save_model_path, epoch_id=epoch_id, + accuracy=self.eval_acc) + + def evaluate(self, resume_model=None, save_matrix_path=None): + """ + 评估模型 + :param resume_model: 所使用的模型 + :param save_matrix_path: 保存混合矩阵的路径 + :return: 评估结果 + """ + if self.test_loader is None: + self.__setup_dataloader() + if self.model is None: + self.__setup_model(input_size=self.audio_featurizer.feature_dim) + if resume_model is not None: + if os.path.isdir(resume_model): + resume_model = os.path.join(resume_model, 'model.pth') + assert os.path.exists(resume_model), f"{resume_model} 模型不存在!" + model_state_dict = torch.load(resume_model, weights_only=False) + self.model.load_state_dict(model_state_dict) + logger.info(f'成功加载模型:{resume_model}') + self.model.eval() + if isinstance(self.model, torch.nn.parallel.DistributedDataParallel): + eval_model = self.model.module + else: + eval_model = self.model + + accuracies, losses, preds, labels = [], [], [], [] + with torch.no_grad(): + for batch_id, (features, label, input_lens) in enumerate(tqdm(self.test_loader, desc='执行评估')): + if self.stop_eval: break + features = features.to(self.device) + label = label.to(self.device).long() + output = eval_model(features) + los = self.loss(output, label) + # 计算准确率 + acc = accuracy(output, label) + accuracies.append(acc) + # 模型预测标签 + label = label.data.cpu().numpy() + output = output.data.cpu().numpy() + pred = np.argmax(output, axis=1) + preds.extend(pred.tolist()) + # 真实标签 + labels.extend(label.tolist()) + losses.append(los.data.cpu().numpy()) + loss = float(sum(losses) / len(losses)) if len(losses) > 0 else -1 + acc = float(sum(accuracies) / len(accuracies)) if len(accuracies) > 0 else -1 + # 保存混合矩阵 + if save_matrix_path is not None: + try: + cm = confusion_matrix(labels, preds) + plot_confusion_matrix(cm=cm, save_path=os.path.join(save_matrix_path, f'{int(time.time())}.png'), + class_labels=self.class_labels) + except Exception as e: + logger.error(f'保存混淆矩阵失败:{e}') + self.model.train() + return loss, acc + + def export(self, save_model_path='models/', resume_model='models/EcapaTdnn_Fbank/best_model/'): + """ + 导出预测模型 + :param save_model_path: 模型保存的路径 + :param resume_model: 准备转换的模型路径 + :return: + """ + self.__setup_model(input_size=self.audio_featurizer.feature_dim) + # 加载预训练模型 + if os.path.isdir(resume_model): + resume_model = os.path.join(resume_model, 'model.pth') + assert os.path.exists(resume_model), f"{resume_model} 模型不存在!" + model_state_dict = torch.load(resume_model) + self.model.load_state_dict(model_state_dict) + logger.info('成功恢复模型参数和优化方法参数:{}'.format(resume_model)) + self.model.eval() + # 获取静态模型 + infer_model = self.model.export() + infer_model_path = os.path.join(save_model_path, + f'{self.configs.use_model}_{self.configs.preprocess_conf.feature_method}', + 'inference.pth') + os.makedirs(os.path.dirname(infer_model_path), exist_ok=True) + torch.jit.save(infer_model, infer_model_path) + logger.info("预测模型已保存:{}".format(infer_model_path)) diff --git a/src/声源定位代码/audio-classification/macls/utils/__init__.py b/src/声源定位代码/audio-classification/macls/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/声源定位代码/audio-classification/macls/utils/checkpoint.py b/src/声源定位代码/audio-classification/macls/utils/checkpoint.py new file mode 100644 index 0000000..c076929 --- /dev/null +++ b/src/声源定位代码/audio-classification/macls/utils/checkpoint.py @@ -0,0 +1,162 @@ +import json +import os +import shutil + +import torch +from loguru import logger +from macls import __version__ + + +def load_pretrained(model, pretrained_model, use_gpu=True): + """加载预训练模型 + + :param model: 使用的模型 + :param pretrained_model: 预训练模型路径 + :param use_gpu: 模型是否使用GPU + :return: 加载的模型 + """ + # 加载预训练模型 + if pretrained_model is None: return model + if os.path.isdir(pretrained_model): + pretrained_model = os.path.join(pretrained_model, 'model.pth') + assert os.path.exists(pretrained_model), f"{pretrained_model} 模型不存在!" + if isinstance(model, torch.nn.parallel.DistributedDataParallel): + model_dict = model.module.state_dict() + else: + model_dict = model.state_dict() + if torch.cuda.is_available() and use_gpu: + model_state_dict = torch.load(pretrained_model, weights_only=False) + else: + model_state_dict = torch.load(pretrained_model, weights_only=False, map_location='cpu') + # 过滤不存在的参数 + for name, weight in model_dict.items(): + if name in model_state_dict.keys(): + if list(weight.shape) != list(model_state_dict[name].shape): + logger.warning(f'{name} not used, shape {list(model_state_dict[name].shape)} ' + f'unmatched with {list(weight.shape)} in model.') + model_state_dict.pop(name, None) + # 加载权重 + if isinstance(model, torch.nn.parallel.DistributedDataParallel): + missing_keys, unexpected_keys = model.module.load_state_dict(model_state_dict, strict=False) + else: + missing_keys, unexpected_keys = model.load_state_dict(model_state_dict, strict=False) + if len(unexpected_keys) > 0: + logger.warning('Unexpected key(s) in state_dict: {}. ' + .format(', '.join('"{}"'.format(k) for k in unexpected_keys))) + if len(missing_keys) > 0: + logger.warning('Missing key(s) in state_dict: {}. ' + .format(', '.join('"{}"'.format(k) for k in missing_keys))) + logger.info('成功加载预训练模型:{}'.format(pretrained_model)) + return model + + +def load_checkpoint(configs, model, optimizer, amp_scaler, scheduler, + step_epoch, save_model_path, resume_model): + """加载模型 + + :param configs: 配置信息 + :param model: 使用的模型 + :param optimizer: 使用的优化方法 + :param amp_scaler: 使用的自动混合精度 + :param scheduler: 使用的学习率调整策略 + :param step_epoch: 每个epoch的step数量 + :param save_model_path: 模型保存路径 + :param resume_model: 恢复训练的模型路径 + """ + last_epoch1 = 0 + accuracy1 = 0. + + def load_model(model_path): + assert os.path.exists(os.path.join(model_path, 'model.pth')), "模型参数文件不存在!" + assert os.path.exists(os.path.join(model_path, 'optimizer.pth')), "优化方法参数文件不存在!" + state_dict = torch.load(os.path.join(model_path, 'model.pth'), weights_only=False) + if isinstance(model, torch.nn.parallel.DistributedDataParallel): + model.module.load_state_dict(state_dict) + else: + model.load_state_dict(state_dict) + optimizer.load_state_dict(torch.load(os.path.join(model_path, 'optimizer.pth'), weights_only=False)) + # 自动混合精度参数 + if amp_scaler is not None and os.path.exists(os.path.join(model_path, 'scaler.pth')): + amp_scaler.load_state_dict(torch.load(os.path.join(model_path, 'scaler.pth')), weights_only=False) + with open(os.path.join(model_path, 'model.state'), 'r', encoding='utf-8') as f: + json_data = json.load(f) + last_epoch = json_data['last_epoch'] + accuracy = json_data['accuracy'] + logger.info('成功恢复模型参数和优化方法参数:{}'.format(model_path)) + optimizer.step() + [scheduler.step() for _ in range(last_epoch * step_epoch)] + return last_epoch, accuracy + + # 获取最后一个保存的模型 + save_feature_method = configs.preprocess_conf.feature_method + if configs.preprocess_conf.get('use_hf_model', False): + save_feature_method = save_feature_method[:-1] if save_feature_method[-1] == '/' else save_feature_method + save_feature_method = os.path.basename(save_feature_method) + last_model_dir = os.path.join(save_model_path, + f'{configs.model_conf.model}_{save_feature_method}', + 'last_model') + if resume_model is not None or (os.path.exists(os.path.join(last_model_dir, 'model.pth')) + and os.path.exists(os.path.join(last_model_dir, 'optimizer.pth'))): + if resume_model is not None: + last_epoch1, accuracy1 = load_model(resume_model) + else: + try: + # 自动获取最新保存的模型 + last_epoch1, accuracy1 = load_model(last_model_dir) + except Exception as e: + logger.warning(f'尝试自动恢复最新模型失败,错误信息:{e}') + return model, optimizer, amp_scaler, scheduler, last_epoch1, accuracy1 + + +# 保存模型 +def save_checkpoint(configs, model, optimizer, amp_scaler, save_model_path, epoch_id, + accuracy=0., best_model=False): + """保存模型 + + :param configs: 配置信息 + :param model: 使用的模型 + :param optimizer: 使用的优化方法 + :param amp_scaler: 使用的自动混合精度 + :param save_model_path: 模型保存路径 + :param epoch_id: 当前epoch + :param accuracy: 当前准确率 + :param best_model: 是否为最佳模型 + """ + if isinstance(model, torch.nn.parallel.DistributedDataParallel): + state_dict = model.module.state_dict() + else: + state_dict = model.state_dict() + # 保存模型的路径 + save_feature_method = configs.preprocess_conf.feature_method + if configs.preprocess_conf.get('use_hf_model', False): + save_feature_method = save_feature_method[:-1] if save_feature_method[-1] == '/' else save_feature_method + save_feature_method = os.path.basename(save_feature_method) + if best_model: + model_path = os.path.join(save_model_path, + f'{configs.model_conf.model}_{save_feature_method}', 'best_model') + else: + model_path = os.path.join(save_model_path, + f'{configs.model_conf.model}_{save_feature_method}', 'epoch_{}'.format(epoch_id)) + os.makedirs(model_path, exist_ok=True) + # 保存模型参数 + torch.save(optimizer.state_dict(), os.path.join(model_path, 'optimizer.pth')) + torch.save(state_dict, os.path.join(model_path, 'model.pth')) + # 自动混合精度参数 + if amp_scaler is not None: + torch.save(amp_scaler.state_dict(), os.path.join(model_path, 'scaler.pth')) + with open(os.path.join(model_path, 'model.state'), 'w', encoding='utf-8') as f: + data = {"last_epoch": epoch_id, "accuracy": accuracy, "version": __version__, + "model": configs.model_conf.model, "feature_method": save_feature_method} + f.write(json.dumps(data, indent=4, ensure_ascii=False)) + if not best_model: + last_model_path = os.path.join(save_model_path, + f'{configs.model_conf.model}_{save_feature_method}', 'last_model') + shutil.rmtree(last_model_path, ignore_errors=True) + shutil.copytree(model_path, last_model_path) + # 删除旧的模型 + old_model_path = os.path.join(save_model_path, + f'{configs.model_conf.model}_{save_feature_method}', + 'epoch_{}'.format(epoch_id - 3)) + if os.path.exists(old_model_path): + shutil.rmtree(old_model_path) + logger.info('已保存模型:{}'.format(model_path)) diff --git a/src/声源定位代码/audio-classification/macls/utils/record.py b/src/声源定位代码/audio-classification/macls/utils/record.py new file mode 100644 index 0000000..db0a992 --- /dev/null +++ b/src/声源定位代码/audio-classification/macls/utils/record.py @@ -0,0 +1,31 @@ +import os + +import soundcard +import soundfile + + +class RecordAudio: + def __init__(self, channels=1, sample_rate=16000): + # 录音参数 + self.channels = channels + self.sample_rate = sample_rate + + # 获取麦克风 + self.default_mic = soundcard.default_microphone() + + def record(self, record_seconds=3, save_path=None): + """录音 + + :param record_seconds: 录音时间,默认3秒 + :param save_path: 录音保存的路径,后缀名为wav + :return: 音频的numpy数据 + """ + print("开始录音......") + num_frames = int(record_seconds * self.sample_rate) + data = self.default_mic.record(samplerate=self.sample_rate, numframes=num_frames, channels=self.channels) + audio_data = data.squeeze() + print("录音已结束!") + if save_path is not None: + os.makedirs(os.path.dirname(save_path), exist_ok=True) + soundfile.write(save_path, data=data, samplerate=self.sample_rate) + return audio_data diff --git a/src/声源定位代码/audio-classification/macls/utils/utils.py b/src/声源定位代码/audio-classification/macls/utils/utils.py new file mode 100644 index 0000000..c05ecdf --- /dev/null +++ b/src/声源定位代码/audio-classification/macls/utils/utils.py @@ -0,0 +1,131 @@ +import distutils.util +import os + +import matplotlib.pyplot as plt +import numpy as np + +from loguru import logger + + +def print_arguments(args=None, configs=None, title=None): + if args: + logger.info("----------- 额外配置参数 -----------") + for arg, value in sorted(vars(args).items()): + logger.info("%s: %s" % (arg, value)) + logger.info("------------------------------------------------") + if configs: + title = title if title else "配置文件参数" + logger.info(f"----------- {title} -----------") + for arg, value in sorted(configs.items()): + if isinstance(value, dict): + logger.info(f"{arg}:") + for a, v in sorted(value.items()): + if isinstance(v, dict): + logger.info(f"\t{a}:") + for a1, v1 in sorted(v.items()): + logger.info("\t\t%s: %s" % (a1, v1)) + else: + logger.info("\t%s: %s" % (a, v)) + else: + logger.info("%s: %s" % (arg, value)) + logger.info("------------------------------------------------") + + +def add_arguments(argname, type, default, help, argparser, **kwargs): + type = distutils.util.strtobool if type == bool else type + argparser.add_argument("--" + argname, + default=default, + type=type, + help=help + ' 默认: %(default)s.', + **kwargs) + + +class Dict(dict): + __setattr__ = dict.__setitem__ + __getattr__ = dict.__getitem__ + + +def dict_to_object(dict_obj): + if not isinstance(dict_obj, dict): + return dict_obj + inst = Dict() + for k, v in dict_obj.items(): + inst[k] = dict_to_object(v) + return inst + + +def plot_confusion_matrix(cm, save_path, class_labels, show=False): + """ + 绘制混淆矩阵 + @param cm: 混淆矩阵, 一个二维数组,表示预测结果与真实结果的混淆情况。 + @param save_path: 保存路径, 字符串,指定混淆矩阵图像的保存位置。 + @param class_labels: 类别名称, 一个列表,包含各个类别的名称。 + @param show: 是否显示图像, 布尔值,控制是否在绘图窗口显示混淆矩阵图像。 + """ + # 检测类别名称是否包含中文,是则设置相应字体 + s = ''.join(class_labels) + is_ascii = all(ord(c) < 128 for c in s) + if not is_ascii: + plt.rcParams['font.sans-serif'] = ['SimHei'] + plt.rcParams['axes.unicode_minus'] = False + + # 初始化绘图参数并绘制混淆矩阵 + plt.figure(figsize=(12, 8), dpi=100) + np.set_printoptions(precision=2) + # 在混淆矩阵中绘制每个格子的概率值 + ind_array = np.arange(len(class_labels)) + x, y = np.meshgrid(ind_array, ind_array) + for x_val, y_val in zip(x.flatten(), y.flatten()): + c = cm[y_val][x_val] / (np.sum(cm[:, x_val]) + 1e-6) + # 忽略概率值太小的格子 + if c < 1e-4: continue + plt.text(x_val, y_val, "%0.2f" % (c,), color='red', fontsize=15, va='center', ha='center') + m = np.sum(cm, axis=0) + 1e-6 + plt.imshow(cm / m, interpolation='nearest', cmap=plt.cm.binary) + plt.title('Confusion Matrix' if is_ascii else '混合矩阵') + plt.colorbar() + # 设置类别标签 + xlocations = np.array(range(len(class_labels))) + plt.xticks(xlocations, class_labels, rotation=90) + plt.yticks(xlocations, class_labels) + plt.ylabel('Actual label' if is_ascii else '实际标签') + plt.xlabel('Predict label' if is_ascii else '预测标签') + + # 调整刻度标记位置,提高可视化效果 + tick_marks = np.array(range(len(class_labels))) + 0.5 + plt.gca().set_xticks(tick_marks, minor=True) + plt.gca().set_yticks(tick_marks, minor=True) + plt.gca().xaxis.set_ticks_position('none') + plt.gca().yaxis.set_ticks_position('none') + plt.grid(True, which='minor', linestyle='-') + plt.gcf().subplots_adjust(bottom=0.15) + # 保存图片 + os.makedirs(os.path.dirname(save_path), exist_ok=True) + plt.savefig(save_path, format='png') + if show: + # 显示图片 + plt.show() + + +# 根据a的类型,将b转换为相应的类型 +def convert_string_based_on_type(a, b): + if isinstance(a, int): + try: + b = int(b) + except ValueError: + logger.error("无法将字符串转换为整数") + elif isinstance(a, float): + try: + b = float(b) + except ValueError: + logger.error("无法将字符串转换为浮点数") + elif isinstance(a, str): + return b + elif isinstance(a, bool): + b = b.lower() == 'true' + else: + try: + b = eval(b) + except Exception as e: + logger.exception("无法将字符串转换为其他类型,将忽略该参数类型转换") + return b diff --git a/src/声源定位代码/audio-classification/record_audio.py b/src/声源定位代码/audio-classification/record_audio.py new file mode 100644 index 0000000..406808f --- /dev/null +++ b/src/声源定位代码/audio-classification/record_audio.py @@ -0,0 +1,14 @@ +import time + +from macls.utils.record import RecordAudio + +s = input('请输入你计划录音多少秒:') +record_seconds = int(s) +save_path = "dataset/save_audio/%s.wav" % str(int(time.time()*1000)) + +record_audio = RecordAudio() +input(f"按下回车键开机录音,录音{record_seconds}秒中:") +record_audio.record(record_seconds=record_seconds, + save_path=save_path) + +print('文件保存在:%s' % save_path) diff --git a/src/声源定位代码/audio-classification/requirements.txt b/src/声源定位代码/audio-classification/requirements.txt new file mode 100644 index 0000000..52c91bf --- /dev/null +++ b/src/声源定位代码/audio-classification/requirements.txt @@ -0,0 +1,17 @@ +numpy>=1.19.2 +scipy>=1.6.3 +librosa>=0.9.1 +soundfile>=0.12.1 +soundcard>=0.4.2 +resampy>=0.2.2 +numba>=0.53.0 +pydub~=0.25.1 +matplotlib>=3.5.2 +pillow>=10.3.0 +tqdm>=4.66.3 +visualdl==2.5.3 +pyyaml>=5.4.1 +scikit-learn>=1.0.2 +torchinfo>=1.7.2 +loguru>=0.7.2 +yeaudio>=0.0.7 diff --git a/src/声源定位代码/audio-classification/setup.py b/src/声源定位代码/audio-classification/setup.py new file mode 100644 index 0000000..7fc9e0b --- /dev/null +++ b/src/声源定位代码/audio-classification/setup.py @@ -0,0 +1,54 @@ +import shutil + +from setuptools import setup, find_packages + +import macls + +VERSION = macls.__version__ + +# 复制配置文件到项目目录下 +shutil.rmtree('./macls/configs/', ignore_errors=True) +shutil.copytree('./configs/', './macls/configs/') + + +def readme(): + with open('README.md', encoding='utf-8') as f: + content = f.read() + return content + + +def parse_requirements(): + with open('./requirements.txt', encoding="utf-8") as f: + requirements = f.readlines() + return requirements + + +if __name__ == "__main__": + setup( + name='macls', + packages=find_packages(), + package_data={'': ['configs/*']}, + author='yeyupiaoling', + version=VERSION, + install_requires=parse_requirements(), + description='Audio Classification toolkit on Pytorch', + long_description=readme(), + long_description_content_type='text/markdown', + url='https://github.com/yeyupiaoling/AudioClassification-Pytorch', + download_url='https://github.com/yeyupiaoling/AudioClassification-Pytorch.git', + keywords=['audio', 'pytorch'], + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Natural Language :: Chinese (Simplified)', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Topic :: Utilities' + ], + license='Apache License 2.0', + ext_modules=[]) + shutil.rmtree('./macls/configs/', ignore_errors=True) diff --git a/src/声源定位代码/audio-classification/tools/download_language_data.sh b/src/声源定位代码/audio-classification/tools/download_language_data.sh new file mode 100644 index 0000000..d0fe5e6 --- /dev/null +++ b/src/声源定位代码/audio-classification/tools/download_language_data.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +download_dir=dataset/language + + +[ ! -d ${download_dir} ] && mkdir -p ${download_dir} + +if [ ! -f ${download_dir}/test.tar.gz ]; then + echo "准备下载测试集" + wget --no-check-certificate https://speech-lab-share-data.oss-cn-shanghai.aliyuncs.com/3D-Speaker/test.tar.gz -P ${download_dir} + md5=$(md5sum ${download_dir}/test.tar.gz | awk '{print $1}') + [ $md5 != "45972606dd10d3f7c1c31f27acdfbed7" ] && echo "Wrong md5sum of 3dspeaker test.tar.gz" && exit 1 +fi + +if [ ! -f ${download_dir}/train.tar.gz ]; then + echo "准备下载训练集" + wget --no-check-certificate https://speech-lab-share-data.oss-cn-shanghai.aliyuncs.com/3D-Speaker/train.tar.gz -P ${download_dir} + md5=$(md5sum ${download_dir}/train.tar.gz | awk '{print $1}') + [ $md5 != "c2cea55fd22a2b867d295fb35a2d3340" ] && echo "Wrong md5sum of 3dspeaker train.tar.gz" && exit 1 +fi + +echo "下载完成!" + +echo "准备解压" + +tar -zxvf ${download_dir}/train.tar.gz -C ${rawdata_dir}/ +tar -xzvf ${download_dir}/test.tar.gz -C ${rawdata_dir}/ + +echo "解压完成!" diff --git a/src/声源定位代码/audio-classification/train.py b/src/声源定位代码/audio-classification/train.py new file mode 100644 index 0000000..50f1cce --- /dev/null +++ b/src/声源定位代码/audio-classification/train.py @@ -0,0 +1,30 @@ +import argparse +import functools + +from macls.trainer import MAClsTrainer +from macls.utils.utils import add_arguments, print_arguments + +parser = argparse.ArgumentParser(description=__doc__) +add_arg = functools.partial(add_arguments, argparser=parser) +add_arg('configs', str, 'configs/cam++.yml', '配置文件') +add_arg('data_augment_configs', str, 'configs/augmentation.yml', '数据增强配置文件') +add_arg("local_rank", int, 0, '多卡训练需要的参数') +add_arg("use_gpu", bool, True, '是否使用GPU训练') +add_arg('save_model_path', str, 'models/', '模型保存的路径') +add_arg('log_dir', str, 'log/', '保存VisualDL日志文件的路径') +add_arg('resume_model', str, None, '恢复训练,当为None则不使用预训练模型') +add_arg('pretrained_model', str, None, '预训练模型的路径,当为None则不使用预训练模型') +add_arg('overwrites', str, None, '覆盖配置文件中的参数,比如"train_conf.max_epoch=100",多个用逗号隔开') +args = parser.parse_args() +print_arguments(args=args) + +# 获取训练器 +trainer = MAClsTrainer(configs=args.configs, + use_gpu=args.use_gpu, + data_augment_configs=args.data_augment_configs, + overwrites=args.overwrites) + +trainer.train(save_model_path=args.save_model_path, + log_dir=args.log_dir, + resume_model=args.resume_model, + pretrained_model=args.pretrained_model) diff --git a/src/声源定位代码/back-code/README.md b/src/声源定位代码/back-code/README.md new file mode 100644 index 0000000..be14557 --- /dev/null +++ b/src/声源定位代码/back-code/README.md @@ -0,0 +1,421 @@ +# 声源定位系统 - 开发板与PC端配合工作流程 + +## 📋 项目概述 + +本项目实现了一个基于麦克风阵列的声源定位系统,采用开发板(K210)与PC端服务器协同工作的架构。系统能够实时检测枪声并进行精确的声源定位,适用于战场感知、安防监控等场景。 + +### 🎯 系统特点 + +- **高精度定位**:基于TDOA算法的麦克风阵列定位 +- **智能识别**:PC端强大的枪声识别算法 +- **实时响应**:定位与识别并行处理 +- **可靠通信**:WiFi网络下的稳定数据传输 +- **可视化界面**:实时显示定位结果和系统状态 + +## 🏗️ 系统架构 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 开发板端 (K210) │ +├─────────────────────────────────────────────────────────────┤ +│ 硬件层: │ +│ • 麦克风阵列 (4通道) │ +│ • WiFi模块 (ESP8285) │ +│ • LED指示灯、蜂鸣器 │ +├─────────────────────────────────────────────────────────────┤ +│ 软件层: │ +│ • 音频采集与预处理 │ +│ • 声源定位算法 (TDOA) │ +│ • 网络通信管理 │ +│ • 系统状态监控 │ +└─────────────────────────────────────────────────────────────┘ + │ + ┌─────────┴─────────┐ + │ WiFi网络通信 │ + └─────────┬─────────┘ + │ +┌─────────────────────────────────────────────────────────────┐ +│ PC端服务器 │ +├─────────────────────────────────────────────────────────────┤ +│ 功能模块: │ +│ • 音频识别引擎 (枪声检测) │ +│ • 数据可视化界面 (matplotlib) │ +│ • Web API服务 (Flask) │ +│ • 数据存储与分析 │ +└─────────────────────────────────────────────────────────────┘ +``` + +## 🔧 硬件配置 + +### 开发板端硬件 +- **主控芯片**: K210 (双核64位RISC-V) +- **麦克风阵列**: 4通道I2S接口 +- **网络模块**: ESP8285 WiFi模块 +- **存储**: 16MB Flash + 8MB PSRAM +- **接口**: UART、I2S、GPIO、PWM + +### 引脚配置 +```python +# 麦克风阵列引脚 +mic_i2s_d0 = 23 # 数据通道0 +mic_i2s_d1 = 22 # 数据通道1 +mic_i2s_d2 = 21 # 数据通道2 +mic_i2s_d3 = 20 # 数据通道3 +mic_i2s_ws = 19 # 字选择 +mic_i2s_sclk = 18 # 时钟 + +# 其他硬件 +led_pin = 12 # LED指示灯 +buzzer_pin = 13 # 蜂鸣器 +wifi_en_pin = 8 # WiFi使能控制 +``` + +## 🌐 网络配置 + +### 开发板端网络设置 +```python +# WiFi连接配置 +wifi_ssid = "junzekeki" +wifi_password = "234567890l" + +# PC端服务器地址 +pc_ip = "192.168.1.100" +pc_port_audio = 12346 # 音频数据传输端口 +pc_port_cmd = 12347 # 指令控制端口 +pc_port_location = 12348 # 定位数据传输端口 +``` + +### PC端服务器配置 +```python +# 服务器监听配置 +host = "0.0.0.0" # 监听所有网络接口 +port_audio = 12346 # 音频数据接收端口 +port_cmd = 12347 # 指令发送端口 +port_location = 12348 # 定位数据接收端口 +``` + +## 🔄 详细工作流程 + +### 阶段1: 系统初始化 + +#### 开发板端初始化流程 +1. **硬件初始化** + - 初始化麦克风阵列I2S接口 + - 配置GPIO引脚(LED、蜂鸣器、WiFi使能) + - 初始化定时器和中断 + +2. **网络连接** + - 启用WiFi模块 + - 连接到指定WiFi网络 + - 建立3个Socket连接: + * `audio_socket`: 发送音频数据 + * `cmd_socket`: 接收PC端指令 + * `location_socket`: 发送定位数据 + +3. **系统启动** + - 启动性能监控模块 + - 启动心跳机制(30秒间隔) + - 初始化音频缓冲区和映射队列 + - 切换到录音模式 + +#### PC端初始化流程 +1. **服务器启动** + - 创建3个Socket服务器 + - 初始化音频识别模块 + - 启动matplotlib可视化界面 + - 初始化Flask Web API + +2. **等待连接** + - 监听开发板连接请求 + - 建立数据通信通道 + - 启动数据处理线程 + +### 阶段2: 录音监听模式 + +#### 开发板端工作流程 +1. **音频采集** + - 从麦克风阵列获取音频数据 + - 应用增益控制和噪声抑制 + - 将音频数据转换为标准格式 + +2. **数据传输** + - 将音频数据通过`audio_socket`发送给PC端 + - 更新性能统计(发送包数、数据量等) + +3. **指令监听** + - 非阻塞检查`cmd_socket`是否有PC端指令 + - 处理模式切换指令(START_LOCATION、STOP_LOCATION等) + +#### PC端工作流程 +1. **音频接收** + - 从`audio_socket`接收音频数据 + - 将数据添加到音频处理器缓冲区 + +2. **枪声识别** + - 当缓冲区达到处理阈值时进行识别 + - 使用预训练的音频分类模型 + - 计算枪声检测置信度 + +3. **模式切换** + - 当检测到枪声时,发送"START_LOCATION"指令 + - 切换到定位模式进行精确定位 + +### 阶段3: 定位识别模式(核心流程) + +#### 开发板端定位流程 +1. **音频缓冲** + - 持续录音并添加到定位音频缓冲区 + - 缓冲区大小:0.5秒音频数据 + - 当缓冲区满时触发处理 + +2. **声源定位** + - 对缓冲的音频数据进行预处理 + - 计算各麦克风间的时延差(TDOA) + - 使用最小二乘法求解声源位置 + - 应用卡尔曼滤波平滑定位结果 + +3. **映射存储** + - 将定位结果和对应音频存储为映射关系 + - 映射结构: + ```python + { + 'location_data': LocationData对象, + 'audio_data': 音频数据列表, + 'timestamp': 时间戳, + 'processed': False + } + ``` + +4. **识别请求** + - 构建识别请求:`RECOGNITION_REQUEST:timestamp:data_size` + - 通过`audio_socket`发送音频数据给PC端 + - 清空音频缓冲区,准备下一轮 + +#### PC端识别流程 +1. **请求处理** + - 检测识别请求标识 + - 解析请求头获取时间戳和数据大小 + - 接收指定大小的音频数据 + +2. **枪声识别** + - 将音频数据转换为numpy数组 + - 检查音频质量(信噪比、能量等) + - 使用音频分类模型进行识别 + - 计算识别置信度 + +3. **结果返回** + - 构建识别结果:`RECOGNITION_RESULT:timestamp:is_gunshot:confidence` + - 通过`cmd_socket`发送给开发板 + +### 阶段4: 结果处理与输出 + +#### 开发板端结果处理 +1. **结果接收** + - 从`cmd_socket`接收识别结果 + - 解析时间戳、枪声标识、置信度 + +2. **时间戳匹配** + - 在映射队列中查找时间戳最接近的定位数据 + - 匹配条件:时间差小于1秒 + - 标记匹配的映射为已处理 + +3. **条件输出** + - 如果识别结果为枪声: + * 提取对应的定位坐标 + * 通过`location_socket`发送给PC端 + * 记录日志和性能统计 + - 如果识别结果不是枪声: + * 忽略该定位数据 + * 继续监听下一轮 + +4. **资源清理** + - 移除已处理的映射关系 + - 清理过期的识别结果(超过5秒) + - 维护映射队列大小(最大20个) + +#### PC端数据处理 +1. **定位数据接收** + - 从`location_socket`接收定位数据 + - 解析坐标、强度、角度等信息 + +2. **数据后处理** + - 应用卡尔曼滤波平滑轨迹 + - 异常值检测和剔除 + - 数据平滑和插值 + +3. **可视化更新** + - 更新matplotlib实时图表 + - 显示枪声位置、轨迹、统计信息 + - 更新Web API数据接口 + +4. **数据存储** + - 将定位数据添加到历史记录 + - 更新性能统计和系统状态 + - 生成分析报告 + +### 阶段5: 模式切换与维护 + +#### 动态模式切换 +1. **录音→定位模式** + - 触发条件:PC端检测到枪声 + - 切换指令:`START_LOCATION` + - 开发板响应:重置定位缓冲区,开始定位流程 + +2. **定位→录音模式** + - 触发条件:PC端发送停止指令或超时 + - 切换指令:`STOP_LOCATION` + - 开发板响应:清理定位资源,回到录音模式 + +#### 系统维护 +1. **心跳机制** + - 开发板每30秒发送心跳包 + - 包含系统状态、内存使用、错误统计 + - PC端监控连接状态和系统健康 + +2. **错误恢复** + - 网络断开自动重连 + - 硬件故障检测和恢复 + - 异常状态处理和日志记录 + +3. **性能监控** + - 实时监控CPU、内存使用率 + - 统计数据传输量和延迟 + - 生成性能报告和告警 + +## 📊 数据格式规范 + +### 音频数据格式 +```python +# 音频参数 +sample_rate = 16000 # 采样率 16kHz +channels = 1 # 单声道 +format = "int16" # 16位整数格式 +chunk_size = 1024 # 数据块大小 +``` + +### 定位数据格式 +```python +# 定位数据结构 +LocationData { + x: float, # X坐标 (米) + y: float, # Y坐标 (米) + strength: float, # 信号强度 (0-1) + angle: float, # 方位角 (度) + timestamp: float, # 时间戳 + confidence: float, # 置信度 (0-1) + quality: float, # 定位质量 (0-1) + noise_level: float # 噪声水平 (0-1) +} +``` + +### 通信协议格式 +```python +# 识别请求 +"RECOGNITION_REQUEST:timestamp:data_size" + +# 识别结果 +"RECOGNITION_RESULT:timestamp:is_gunshot:confidence" + +# 定位数据 +"x,y,strength,angle" + +# 心跳数据 +"HEARTBEAT:timestamp:mode:status:memory" +``` + +## 🎯 系统优势 + +### 1. 准确性优势 +- **分离式处理**:开发板专注定位,PC端专注识别 +- **时间戳匹配**:精确关联定位数据和识别结果 +- **多重验证**:音频质量检查、置信度阈值、异常值检测 + +### 2. 实时性优势 +- **并行处理**:定位和识别同时进行 +- **非阻塞通信**:Socket超时机制,避免阻塞 +- **缓冲优化**:合理的缓冲区大小和清理策略 + +### 3. 可靠性优势 +- **自动重连**:网络断开自动恢复 +- **错误处理**:完善的异常捕获和恢复机制 +- **状态监控**:实时监控系统健康状态 + +### 4. 扩展性优势 +- **模块化设计**:各功能模块独立,易于升级 +- **配置灵活**:支持动态配置参数 +- **接口标准化**:标准化的数据格式和通信协议 + +## 🔍 应用场景 + +### 1. 战场感知 +- 实时检测枪声位置 +- 威胁源定位和追踪 +- 战场态势分析 + +### 2. 安防监控 +- 枪声检测和报警 +- 安全区域监控 +- 事件记录和分析 + +### 3. 训练模拟 +- 射击训练评估 +- 战术演练分析 +- 性能数据统计 + +### 4. 城市安全 +- 公共安全监控 +- 应急响应支持 +- 犯罪预防分析 + +## 📈 性能指标 + +### 定位精度 +- **角度精度**: ±2° (在10米距离) +- **距离精度**: ±0.5米 (在10米距离) +- **响应时间**: <100ms + +### 识别性能 +- **检测准确率**: >95% +- **误报率**: <2% +- **漏报率**: <3% + +### 系统性能 +- **最大检测距离**: 50米 +- **工作温度**: -20°C ~ +70°C +- **连续工作时间**: >24小时 +- **网络延迟**: <50ms + +## 🛠️ 部署说明 + +### 开发板端部署 +1. 将代码烧录到K210开发板 +2. 配置WiFi网络参数 +3. 连接麦克风阵列硬件 +4. 启动系统并检查连接状态 + +### PC端部署 +1. 安装Python依赖包 +2. 配置服务器网络参数 +3. 启动音频识别服务 +4. 运行可视化界面 + +### 网络配置 +1. 确保开发板和PC在同一WiFi网络 +2. 检查防火墙设置 +3. 验证端口连通性 +4. 测试数据传输 + +## 📝 注意事项 + +1. **硬件连接**:确保麦克风阵列正确连接,检查I2S信号质量 +2. **网络稳定**:使用稳定的WiFi网络,避免频繁断开 +3. **电源供应**:开发板需要稳定的5V电源供应 +4. **环境噪声**:避免强电磁干扰和机械振动 +5. **定期维护**:定期检查系统状态和清理日志文件 + +--- + +**版本**: 3.0.0 +**作者**: 声源定位系统开发团队 +**日期**: 2025年 +**许可证**: MIT License \ No newline at end of file diff --git a/src/声源定位代码/back-code/development_board.py b/src/声源定位代码/back-code/development_board.py new file mode 100644 index 0000000..06af0f9 --- /dev/null +++ b/src/声源定位代码/back-code/development_board.py @@ -0,0 +1,3309 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +开发板端主程序 - K210专用版本 +功能: +1. 开机后进入录音状态,持续录音并发送给PC端 +2. 接收PC端的指令,当识别到枪声时切换到声源定位模式 +3. 在定位模式下,实时发送声源定位数据给PC端 +4. 支持WiFi连接管理、音频采集、声源定位等功能 +5. 完善的异常处理、错误恢复和性能监控机制 + +作者: 声源定位系统开发团队 +版本: 3.0.0 +日期: 2025 +""" + +import gc +import math +import time +import socket +import network +import json +import os +import sys +import traceback +from machine import UART, Timer, PWM +from fpioa_manager import fm +from Maix import GPIO, MIC_ARRAY as mic +from board import board_info +from typing import Optional, Dict, Any, List, Tuple, Union, Callable +from dataclasses import dataclass, field +from enum import Enum, auto +import _thread +import utime +import numpy as np + +# ========== 常量定义 ========== +class Constants: + """系统常量定义""" + # 系统配置 + MAX_RETRY_ATTEMPTS = 5 + DEFAULT_TIMEOUT = 30.0 + HEARTBEAT_INTERVAL = 30.0 + BUFFER_SIZE = 4096 + MAX_LOG_SIZE = 1024 * 1024 # 1MB + + # 音频配置 + DEFAULT_SAMPLE_RATE = 16000 + DEFAULT_CHANNELS = 1 + DEFAULT_CHUNK_SIZE = 1024 + DEFAULT_RECORD_DURATION = 3.0 + + # 网络配置 + DEFAULT_WIFI_SSID = "junzekeki" + DEFAULT_WIFI_PASSWORD = "234567890l" + DEFAULT_PC_IP = "192.168.1.100" + DEFAULT_PC_PORT_AUDIO = 12346 + DEFAULT_PC_PORT_CMD = 12347 + DEFAULT_PC_PORT_LOCATION = 12348 + + # 硬件配置 + K210_MAX_PINS = 48 + DEFAULT_MIC_I2S_D0 = 23 + DEFAULT_MIC_I2S_D1 = 22 + DEFAULT_MIC_I2S_D2 = 21 + DEFAULT_MIC_I2S_D3 = 20 + DEFAULT_MIC_I2S_WS = 19 + DEFAULT_MIC_I2S_SCLK = 18 + DEFAULT_SK9822_DAT = 24 + DEFAULT_SK9822_CLK = 25 + DEFAULT_WIFI_EN_PIN = 8 + + # 错误码 + ERROR_SUCCESS = 0 + ERROR_WIFI_CONNECTION_FAILED = 1001 + ERROR_SOCKET_CONNECTION_FAILED = 1002 + ERROR_AUDIO_INIT_FAILED = 1003 + ERROR_MIC_ARRAY_INIT_FAILED = 1004 + ERROR_CONFIG_LOAD_FAILED = 1005 + ERROR_MEMORY_INSUFFICIENT = 1006 + ERROR_HARDWARE_FAULT = 1007 + +# ========== 枚举定义 ========== +class SystemMode(Enum): + """系统运行模式枚举""" + INITIALIZING = auto() # 初始化模式 + RECORDING = auto() # 录音模式 + LOCATING = auto() # 定位模式 + ERROR = auto() # 错误模式 + SHUTDOWN = auto() # 关闭模式 + MAINTENANCE = auto() # 维护模式 + DIAGNOSTIC = auto() # 诊断模式 + +class ConnectionStatus(Enum): + """连接状态枚举""" + DISCONNECTED = auto() + CONNECTING = auto() + CONNECTED = auto() + ERROR = auto() + RECONNECTING = auto() + TIMEOUT = auto() + +class WiFiStatus(Enum): + """WiFi状态枚举""" + DISCONNECTED = auto() + CONNECTING = auto() + CONNECTED = auto() + ERROR = auto() + AUTH_FAILED = auto() + NO_AP_FOUND = auto() + +class AudioStatus(Enum): + """音频状态枚举""" + IDLE = auto() + RECORDING = auto() + PROCESSING = auto() + ERROR = auto() + BUFFER_FULL = auto() + BUFFER_EMPTY = auto() + +class ErrorLevel(Enum): + """错误级别枚举""" + DEBUG = auto() + INFO = auto() + WARNING = auto() + ERROR = auto() + CRITICAL = auto() + FATAL = auto() + +# ========== 异常类定义 ========== +class DevelopmentBoardError(Exception): + """开发板基础异常类""" + def __init__(self, message: str, error_code: int = Constants.ERROR_SUCCESS): + super().__init__(message) + self.error_code = error_code + self.timestamp = time.time() + +class WiFiConnectionError(DevelopmentBoardError): + """WiFi连接异常""" + def __init__(self, message: str, ssid: str = "", error_code: int = Constants.ERROR_WIFI_CONNECTION_FAILED): + super().__init__(message, error_code) + self.ssid = ssid + +class SocketConnectionError(DevelopmentBoardError): + """Socket连接异常""" + def __init__(self, message: str, host: str = "", port: int = 0, error_code: int = Constants.ERROR_SOCKET_CONNECTION_FAILED): + super().__init__(message, error_code) + self.host = host + self.port = port + +class AudioInitError(DevelopmentBoardError): + """音频初始化异常""" + def __init__(self, message: str, error_code: int = Constants.ERROR_AUDIO_INIT_FAILED): + super().__init__(message, error_code) + +class MicArrayInitError(DevelopmentBoardError): + """麦克风阵列初始化异常""" + def __init__(self, message: str, error_code: int = Constants.ERROR_MIC_ARRAY_INIT_FAILED): + super().__init__(message, error_code) + +class ConfigError(DevelopmentBoardError): + """配置错误异常""" + def __init__(self, message: str, config_file: str = "", error_code: int = Constants.ERROR_CONFIG_LOAD_FAILED): + super().__init__(message, error_code) + self.config_file = config_file + +class MemoryError(DevelopmentBoardError): + """内存不足异常""" + def __init__(self, message: str, required: int = 0, available: int = 0, error_code: int = Constants.ERROR_MEMORY_INSUFFICIENT): + super().__init__(message, error_code) + self.required = required + self.available = available + +class HardwareFaultError(DevelopmentBoardError): + """硬件故障异常""" + def __init__(self, message: str, component: str = "", error_code: int = Constants.ERROR_HARDWARE_FAULT): + super().__init__(message, error_code) + self.component = component + +# ========== 数据类定义 ========== +@dataclass +class LocationData: + """定位数据结构""" + x: float + y: float + strength: float + angle: float + timestamp: float + confidence: float = 1.0 + quality: float = 1.0 + noise_level: float = 0.0 + source_type: str = "unknown" + + def __post_init__(self): + """数据验证和规范化""" + try: + # 类型检查 + if not isinstance(self.x, (int, float)): + raise ValueError("X坐标必须是数值类型") + if not isinstance(self.y, (int, float)): + raise ValueError("Y坐标必须是数值类型") + if not isinstance(self.strength, (int, float)) or self.strength < 0: + raise ValueError("强度必须是非负数值") + if not isinstance(self.angle, (int, float)): + raise ValueError("角度必须是数值类型") + if not isinstance(self.confidence, (int, float)) or not (0 <= self.confidence <= 1): + raise ValueError("置信度必须在0-1范围内") + if not isinstance(self.quality, (int, float)) or not (0 <= self.quality <= 1): + raise ValueError("质量必须在0-1范围内") + if not isinstance(self.noise_level, (int, float)) or self.noise_level < 0: + raise ValueError("噪声水平必须是非负数值") + + # 角度规范化到0-360度 + self.angle = self.angle % 360 + + # 坐标范围检查 + if abs(self.x) > 1000 or abs(self.y) > 1000: + raise ValueError("坐标值超出合理范围") + + except Exception as e: + raise ValueError(f"定位数据验证失败: {e}") + + def to_dict(self) -> Dict[str, Any]: + """转换为字典格式""" + return { + 'x': self.x, + 'y': self.y, + 'strength': self.strength, + 'angle': self.angle, + 'timestamp': self.timestamp, + 'confidence': self.confidence, + 'quality': self.quality, + 'noise_level': self.noise_level, + 'source_type': self.source_type + } + + def to_json(self) -> str: + """转换为JSON字符串""" + return json.dumps(self.to_dict()) + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> 'LocationData': + """从字典创建实例""" + return cls(**data) + + @classmethod + def from_json(cls, json_str: str) -> 'LocationData': + """从JSON字符串创建实例""" + data = json.loads(json_str) + return cls.from_dict(data) + +@dataclass +class AudioConfig: + """音频配置""" + sample_rate: int = Constants.DEFAULT_SAMPLE_RATE + channels: int = Constants.DEFAULT_CHANNELS + chunk_size: int = Constants.DEFAULT_CHUNK_SIZE + record_duration: float = Constants.DEFAULT_RECORD_DURATION + format: str = "int16" + bit_depth: int = 16 + buffer_size: int = Constants.BUFFER_SIZE + gain: float = 1.0 + noise_reduction: bool = True + auto_gain_control: bool = True + + def validate(self) -> bool: + """验证配置有效性""" + try: + if self.sample_rate <= 0 or self.sample_rate > 48000: + raise ValueError("采样率必须在1-48000范围内") + if self.channels not in [1, 2, 4, 8]: + raise ValueError("声道数必须是1、2、4或8") + if self.chunk_size <= 0 or self.chunk_size > 8192: + raise ValueError("音频块大小必须在1-8192范围内") + if self.record_duration <= 0 or self.record_duration > 60: + raise ValueError("录音时长必须在1-60秒范围内") + if self.format not in ["int8", "int16", "int24", "int32", "float32"]: + raise ValueError("不支持的音频格式") + if self.bit_depth not in [8, 16, 24, 32]: + raise ValueError("位深度必须是8、16、24或32") + if self.buffer_size <= 0 or self.buffer_size > 65536: + raise ValueError("缓冲区大小必须在1-65536范围内") + if self.gain <= 0 or self.gain > 10: + raise ValueError("增益必须在0-10范围内") + return True + except Exception as e: + raise AudioInitError(f"音频配置验证失败: {e}") + + def to_dict(self) -> Dict[str, Any]: + """转换为字典格式""" + return { + 'sample_rate': self.sample_rate, + 'channels': self.channels, + 'chunk_size': self.chunk_size, + 'record_duration': self.record_duration, + 'format': self.format, + 'bit_depth': self.bit_depth, + 'buffer_size': self.buffer_size, + 'gain': self.gain, + 'noise_reduction': self.noise_reduction, + 'auto_gain_control': self.auto_gain_control + } + +@dataclass +class NetworkConfig: + """网络配置""" + wifi_ssid: str = Constants.DEFAULT_WIFI_SSID + wifi_password: str = Constants.DEFAULT_WIFI_PASSWORD + pc_ip: str = Constants.DEFAULT_PC_IP + pc_port_audio: int = Constants.DEFAULT_PC_PORT_AUDIO + pc_port_cmd: int = Constants.DEFAULT_PC_PORT_CMD + pc_port_location: int = Constants.DEFAULT_PC_PORT_LOCATION + connection_timeout: float = Constants.DEFAULT_TIMEOUT + reconnect_interval: float = 5.0 + max_reconnect_attempts: int = Constants.MAX_RETRY_ATTEMPTS + heartbeat_interval: float = Constants.HEARTBEAT_INTERVAL + keep_alive: bool = True + tcp_nodelay: bool = True + socket_timeout: float = 1.0 + + def validate(self) -> bool: + """验证配置有效性""" + try: + if not self.wifi_ssid or len(self.wifi_ssid) > 32: + raise ValueError("WiFi SSID不能为空且长度不能超过32字符") + if not self.wifi_password or len(self.wifi_password) > 64: + raise ValueError("WiFi密码不能为空且长度不能超过64字符") + if not self.pc_ip or not self._is_valid_ip(self.pc_ip): + raise ValueError("PC端IP地址格式无效") + if not (1024 <= self.pc_port_audio <= 65535): + raise ValueError("音频端口必须在1024-65535范围内") + if not (1024 <= self.pc_port_cmd <= 65535): + raise ValueError("指令端口必须在1024-65535范围内") + if not (1024 <= self.pc_port_location <= 65535): + raise ValueError("定位端口必须在1024-65535范围内") + if self.connection_timeout <= 0 or self.connection_timeout > 300: + raise ValueError("连接超时必须在1-300秒范围内") + if self.reconnect_interval <= 0 or self.reconnect_interval > 60: + raise ValueError("重连间隔必须在1-60秒范围内") + if self.max_reconnect_attempts <= 0 or self.max_reconnect_attempts > 100: + raise ValueError("最大重连次数必须在1-100范围内") + return True + except Exception as e: + raise WiFiConnectionError(f"网络配置验证失败: {e}") + + def _is_valid_ip(self, ip: str) -> bool: + """验证IP地址格式""" + try: + parts = ip.split('.') + if len(parts) != 4: + return False + for part in parts: + if not part.isdigit() or not (0 <= int(part) <= 255): + return False + return True + except: + return False + + def to_dict(self) -> Dict[str, Any]: + """转换为字典格式""" + return { + 'wifi_ssid': self.wifi_ssid, + 'wifi_password': self.wifi_password, + 'pc_ip': self.pc_ip, + 'pc_port_audio': self.pc_port_audio, + 'pc_port_cmd': self.pc_port_cmd, + 'pc_port_location': self.pc_port_location, + 'connection_timeout': self.connection_timeout, + 'reconnect_interval': self.reconnect_interval, + 'max_reconnect_attempts': self.max_reconnect_attempts, + 'heartbeat_interval': self.heartbeat_interval, + 'keep_alive': self.keep_alive, + 'tcp_nodelay': self.tcp_nodelay, + 'socket_timeout': self.socket_timeout + } + +@dataclass +class HardwareConfig: + """硬件配置""" + # 麦克风阵列引脚 + mic_i2s_d0: int = Constants.DEFAULT_MIC_I2S_D0 + mic_i2s_d1: int = Constants.DEFAULT_MIC_I2S_D1 + mic_i2s_d2: int = Constants.DEFAULT_MIC_I2S_D2 + mic_i2s_d3: int = Constants.DEFAULT_MIC_I2S_D3 + mic_i2s_ws: int = Constants.DEFAULT_MIC_I2S_WS + mic_i2s_sclk: int = Constants.DEFAULT_MIC_I2S_SCLK + sk9822_dat: int = Constants.DEFAULT_SK9822_DAT + sk9822_clk: int = Constants.DEFAULT_SK9822_CLK + + # WiFi控制引脚 + wifi_en_pin: int = Constants.DEFAULT_WIFI_EN_PIN + + # 其他硬件配置 + led_pin: int = 12 + buzzer_pin: int = 13 + button_pin: int = 14 + temperature_sensor_pin: int = 15 + + # 硬件参数 + mic_array_gain: float = 1.0 + led_brightness: int = 255 + buzzer_frequency: int = 1000 + temperature_threshold: float = 85.0 + + def validate(self) -> bool: + """验证配置有效性""" + try: + # 检查引脚范围 + valid_pins = list(range(0, Constants.K210_MAX_PINS)) + pin_configs = { + 'mic_i2s_d0': self.mic_i2s_d0, + 'mic_i2s_d1': self.mic_i2s_d1, + 'mic_i2s_d2': self.mic_i2s_d2, + 'mic_i2s_d3': self.mic_i2s_d3, + 'mic_i2s_ws': self.mic_i2s_ws, + 'mic_i2s_sclk': self.mic_i2s_sclk, + 'sk9822_dat': self.sk9822_dat, + 'sk9822_clk': self.sk9822_clk, + 'wifi_en_pin': self.wifi_en_pin, + 'led_pin': self.led_pin, + 'buzzer_pin': self.buzzer_pin, + 'button_pin': self.button_pin, + 'temperature_sensor_pin': self.temperature_sensor_pin + } + + for pin_name, pin_value in pin_configs.items(): + if pin_value not in valid_pins: + raise ValueError(f"引脚 {pin_name}={pin_value} 超出有效范围(0-{Constants.K210_MAX_PINS-1})") + + # 检查引脚冲突 + pin_values = list(pin_configs.values()) + if len(pin_values) != len(set(pin_values)): + raise ValueError("存在引脚冲突,请检查配置") + + # 检查其他参数 + if self.mic_array_gain <= 0 or self.mic_array_gain > 10: + raise ValueError("麦克风阵列增益必须在0-10范围内") + if not (0 <= self.led_brightness <= 255): + raise ValueError("LED亮度必须在0-255范围内") + if self.buzzer_frequency <= 0 or self.buzzer_frequency > 20000: + raise ValueError("蜂鸣器频率必须在1-20000Hz范围内") + if self.temperature_threshold <= 0 or self.temperature_threshold > 150: + raise ValueError("温度阈值必须在0-150°C范围内") + + return True + except Exception as e: + raise HardwareFaultError(f"硬件配置验证失败: {e}") + + def to_dict(self) -> Dict[str, Any]: + """转换为字典格式""" + return { + 'mic_i2s_d0': self.mic_i2s_d0, + 'mic_i2s_d1': self.mic_i2s_d1, + 'mic_i2s_d2': self.mic_i2s_d2, + 'mic_i2s_d3': self.mic_i2s_d3, + 'mic_i2s_ws': self.mic_i2s_ws, + 'mic_i2s_sclk': self.mic_i2s_sclk, + 'sk9822_dat': self.sk9822_dat, + 'sk9822_clk': self.sk9822_clk, + 'wifi_en_pin': self.wifi_en_pin, + 'led_pin': self.led_pin, + 'buzzer_pin': self.buzzer_pin, + 'button_pin': self.button_pin, + 'temperature_sensor_pin': self.temperature_sensor_pin, + 'mic_array_gain': self.mic_array_gain, + 'led_brightness': self.led_brightness, + 'buzzer_frequency': self.buzzer_frequency, + 'temperature_threshold': self.temperature_threshold + } + +@dataclass +class SystemConfig: + """系统配置""" + log_level: str = "INFO" + heartbeat_interval: float = Constants.HEARTBEAT_INTERVAL + location_update_interval: float = 0.1 + audio_buffer_size: int = Constants.BUFFER_SIZE + max_log_files: int = 5 + log_rotation_size: int = Constants.MAX_LOG_SIZE + enable_performance_monitor: bool = True + enable_auto_recovery: bool = True + enable_hardware_monitor: bool = True + enable_network_monitor: bool = True + enable_audio_monitor: bool = True + enable_error_reporting: bool = True + enable_debug_mode: bool = False + enable_maintenance_mode: bool = False + + def validate(self) -> bool: + """验证配置有效性""" + try: + valid_log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] + if self.log_level not in valid_log_levels: + raise ValueError(f"日志级别必须是: {', '.join(valid_log_levels)}") + if self.heartbeat_interval <= 0 or self.heartbeat_interval > 300: + raise ValueError("心跳间隔必须在1-300秒范围内") + if self.location_update_interval <= 0 or self.location_update_interval > 1: + raise ValueError("定位更新间隔必须在0.001-1秒范围内") + if self.audio_buffer_size <= 0 or self.audio_buffer_size > 65536: + raise ValueError("音频缓冲区大小必须在1-65536范围内") + if self.max_log_files <= 0 or self.max_log_files > 20: + raise ValueError("最大日志文件数必须在1-20范围内") + if self.log_rotation_size <= 0 or self.log_rotation_size > 10 * 1024 * 1024: + raise ValueError("日志轮转大小必须在1-10MB范围内") + return True + except Exception as e: + raise ConfigError(f"系统配置验证失败: {e}") + + def to_dict(self) -> Dict[str, Any]: + """转换为字典格式""" + return { + 'log_level': self.log_level, + 'heartbeat_interval': self.heartbeat_interval, + 'location_update_interval': self.location_update_interval, + 'audio_buffer_size': self.audio_buffer_size, + 'max_log_files': self.max_log_files, + 'log_rotation_size': self.log_rotation_size, + 'enable_performance_monitor': self.enable_performance_monitor, + 'enable_auto_recovery': self.enable_auto_recovery, + 'enable_hardware_monitor': self.enable_hardware_monitor, + 'enable_network_monitor': self.enable_network_monitor, + 'enable_audio_monitor': self.enable_audio_monitor, + 'enable_error_reporting': self.enable_error_reporting, + 'enable_debug_mode': self.enable_debug_mode, + 'enable_maintenance_mode': self.enable_maintenance_mode + } + +# ========== 日志系统 ========== +class Logger: + """增强的日志系统,支持日志轮转、级别过滤和性能优化""" + + def __init__(self, name: str = "DevelopmentBoard", log_level: str = "INFO"): + self.name = name + self.log_level = log_level.upper() + self.log_dir = "/sd/logs" + self.log_file = f"{self.log_dir}/{name}.log" + self.error_log_file = f"{self.log_dir}/{name}_error.log" + self.max_file_size = Constants.MAX_LOG_SIZE + self.max_files = 5 + self.log_levels = { + "DEBUG": 0, + "INFO": 1, + "WARNING": 2, + "ERROR": 3, + "CRITICAL": 4, + "FATAL": 5 + } + self._ensure_log_dir() + self._check_log_rotation() + self._log_count = 0 + self._last_flush_time = time.time() + self._flush_interval = 5.0 # 5秒刷新一次 + + def _ensure_log_dir(self): + """确保日志目录存在""" + try: + if not os.path.exists(self.log_dir): + os.makedirs(self.log_dir) + except Exception as e: + print(f"创建日志目录失败: {e}") + + def _check_log_rotation(self): + """检查并执行日志轮转""" + try: + if os.path.exists(self.log_file): + file_size = os.path.getsize(self.log_file) + if file_size > self.max_file_size: + self._rotate_log_file(self.log_file) + + if os.path.exists(self.error_log_file): + file_size = os.path.getsize(self.error_log_file) + if file_size > self.max_file_size: + self._rotate_log_file(self.error_log_file) + except Exception as e: + print(f"日志轮转检查失败: {e}") + + def _rotate_log_file(self, log_file: str): + """轮转日志文件""" + try: + # 删除最老的日志文件 + for i in range(self.max_files - 1, 0, -1): + old_file = f"{log_file}.{i}" + new_file = f"{log_file}.{i + 1}" + if os.path.exists(old_file): + if i == self.max_files - 1: + os.remove(old_file) + else: + os.rename(old_file, new_file) + + # 重命名当前日志文件 + if os.path.exists(log_file): + os.rename(log_file, f"{log_file}.1") + except Exception as e: + print(f"日志轮转失败: {e}") + + def _get_timestamp(self) -> str: + """获取时间戳""" + return time.strftime("%Y-%m-%d %H:%M:%S") + + def _should_log(self, level: str) -> bool: + """检查是否应该记录该级别的日志""" + return self.log_levels.get(level.upper(), 0) >= self.log_levels.get(self.log_level, 0) + + def _write_log(self, level: str, message: str, error_log: bool = False): + """写入日志""" + if not self._should_log(level): + return + + try: + timestamp = self._get_timestamp() + log_entry = f"[{timestamp}] [{level}] [{self.name}] {message}" + + # 控制台输出 + print(log_entry) + + # 文件输出 + target_file = self.error_log_file if error_log else self.log_file + with open(target_file, "a", encoding="utf-8") as f: + f.write(log_entry + "\n") + + self._log_count += 1 + + # 定期刷新和检查轮转 + current_time = time.time() + if current_time - self._last_flush_time > self._flush_interval: + self._check_log_rotation() + self._last_flush_time = current_time + + except Exception as e: + print(f"写入日志失败: {e}") + + def debug(self, message: str): + """调试日志""" + self._write_log("DEBUG", message) + + def info(self, message: str): + """信息日志""" + self._write_log("INFO", message) + + def warning(self, message: str): + """警告日志""" + self._write_log("WARNING", message) + + def error(self, message: str): + """错误日志""" + self._write_log("ERROR", message, error_log=True) + + def critical(self, message: str): + """严重错误日志""" + self._write_log("CRITICAL", message, error_log=True) + + def fatal(self, message: str): + """致命错误日志""" + self._write_log("FATAL", message, error_log=True) + + def exception(self, message: str, exc_info: Exception = None): + """异常日志""" + if exc_info: + message = f"{message} - 异常: {str(exc_info)}" + self._write_log("ERROR", message, error_log=True) + + def performance(self, operation: str, duration: float, details: str = ""): + """性能日志""" + message = f"性能监控 - {operation}: {duration:.3f}s" + if details: + message += f" - {details}" + self._write_log("INFO", message) + + def get_log_stats(self) -> Dict[str, Any]: + """获取日志统计信息""" + try: + stats = { + 'log_count': self._log_count, + 'log_level': self.log_level, + 'log_file_size': 0, + 'error_log_file_size': 0 + } + + if os.path.exists(self.log_file): + stats['log_file_size'] = os.path.getsize(self.log_file) + if os.path.exists(self.error_log_file): + stats['error_log_file_size'] = os.path.getsize(self.error_log_file) + + return stats + except Exception as e: + return {'error': str(e)} + +# ========== 配置管理 ========== +class ConfigManager: + """增强的配置管理器,支持配置验证、热重载和备份恢复""" + + def __init__(self, config_file: str = "/sd/config.json"): + self.config_file = config_file + self.backup_file = f"{config_file}.backup" + self.logger = Logger("ConfigManager") + self.config = {} + self.last_modified = 0 + self.config_validators = {} + self._register_validators() + self._load_config() + + def _register_validators(self): + """注册配置验证器""" + self.config_validators = { + 'network': self._validate_network_config, + 'audio': self._validate_audio_config, + 'hardware': self._validate_hardware_config, + 'system': self._validate_system_config + } + + def _validate_network_config(self, config: Dict[str, Any]) -> bool: + """验证网络配置""" + try: + net_config = NetworkConfig(**config) + return net_config.validate() + except Exception as e: + self.logger.error(f"网络配置验证失败: {e}") + return False + + def _validate_audio_config(self, config: Dict[str, Any]) -> bool: + """验证音频配置""" + try: + audio_config = AudioConfig(**config) + return audio_config.validate() + except Exception as e: + self.logger.error(f"音频配置验证失败: {e}") + return False + + def _validate_hardware_config(self, config: Dict[str, Any]) -> bool: + """验证硬件配置""" + try: + hw_config = HardwareConfig(**config) + return hw_config.validate() + except Exception as e: + self.logger.error(f"硬件配置验证失败: {e}") + return False + + def _validate_system_config(self, config: Dict[str, Any]) -> bool: + """验证系统配置""" + try: + sys_config = SystemConfig(**config) + return sys_config.validate() + except Exception as e: + self.logger.error(f"系统配置验证失败: {e}") + return False + + def _load_config(self) -> Dict[str, Any]: + """加载配置""" + try: + if os.path.exists(self.config_file): + # 检查文件是否被修改 + current_modified = os.path.getmtime(self.config_file) + if current_modified > self.last_modified: + with open(self.config_file, "r", encoding="utf-8") as f: + config = json.load(f) + + # 验证配置 + if self._validate_config(config): + self.config = config + self.last_modified = current_modified + self.logger.info(f"配置文件加载成功: {self.config_file}") + return config + else: + self.logger.error("配置文件验证失败,使用默认配置") + return self._create_default_config() + else: + return self.config + else: + self.logger.warning("配置文件不存在,创建默认配置") + return self._create_default_config() + except Exception as e: + self.logger.error(f"加载配置文件失败: {e}") + return self._create_default_config() + + def _validate_config(self, config: Dict[str, Any]) -> bool: + """验证配置完整性""" + try: + required_sections = ['network', 'audio', 'hardware', 'system'] + for section in required_sections: + if section not in config: + self.logger.error(f"缺少配置节: {section}") + return False + + validator = self.config_validators.get(section) + if validator and not validator(config[section]): + return False + + return True + except Exception as e: + self.logger.error(f"配置验证失败: {e}") + return False + + def _create_default_config(self) -> Dict[str, Any]: + """创建默认配置""" + try: + config = { + "network": NetworkConfig().to_dict(), + "audio": AudioConfig().to_dict(), + "hardware": HardwareConfig().to_dict(), + "system": SystemConfig().to_dict() + } + + self._save_config(config) + return config + except Exception as e: + self.logger.error(f"创建默认配置失败: {e}") + return {} + + def _save_config(self, config: Dict[str, Any]): + """保存配置""" + try: + # 创建备份 + if os.path.exists(self.config_file): + os.rename(self.config_file, self.backup_file) + + # 保存新配置 + with open(self.config_file, "w", encoding="utf-8") as f: + json.dump(config, f, indent=2, ensure_ascii=False) + + self.logger.info(f"配置文件保存成功: {self.config_file}") + except Exception as e: + self.logger.error(f"保存配置文件失败: {e}") + # 恢复备份 + if os.path.exists(self.backup_file): + os.rename(self.backup_file, self.config_file) + + def reload_config(self) -> bool: + """重新加载配置""" + try: + self.config = self._load_config() + return len(self.config) > 0 + except Exception as e: + self.logger.error(f"重新加载配置失败: {e}") + return False + + def update_config(self, section: str, key: str, value: Any) -> bool: + """更新配置项""" + try: + if section not in self.config: + self.config[section] = {} + + self.config[section][key] = value + + # 验证更新后的配置 + if self._validate_config(self.config): + self._save_config(self.config) + self.logger.info(f"配置更新成功: {section}.{key} = {value}") + return True + else: + self.logger.error("配置更新后验证失败") + return False + except Exception as e: + self.logger.error(f"更新配置失败: {e}") + return False + + def get_network_config(self) -> NetworkConfig: + """获取网络配置""" + try: + self._load_config() # 重新加载以确保最新 + net_config = self.config.get("network", {}) + return NetworkConfig(**net_config) + except Exception as e: + self.logger.error(f"获取网络配置失败: {e}") + return NetworkConfig() + + def get_audio_config(self) -> AudioConfig: + """获取音频配置""" + try: + self._load_config() # 重新加载以确保最新 + audio_config = self.config.get("audio", {}) + return AudioConfig(**audio_config) + except Exception as e: + self.logger.error(f"获取音频配置失败: {e}") + return AudioConfig() + + def get_hardware_config(self) -> HardwareConfig: + """获取硬件配置""" + try: + self._load_config() # 重新加载以确保最新 + hw_config = self.config.get("hardware", {}) + return HardwareConfig(**hw_config) + except Exception as e: + self.logger.error(f"获取硬件配置失败: {e}") + return HardwareConfig() + + def get_system_config(self) -> SystemConfig: + """获取系统配置""" + try: + self._load_config() # 重新加载以确保最新 + sys_config = self.config.get("system", {}) + return SystemConfig(**sys_config) + except Exception as e: + self.logger.error(f"获取系统配置失败: {e}") + return SystemConfig() + + def export_config(self, export_file: str = None) -> bool: + """导出配置""" + try: + if not export_file: + export_file = f"{self.config_file}.export" + + with open(export_file, "w", encoding="utf-8") as f: + json.dump(self.config, f, indent=2, ensure_ascii=False) + + self.logger.info(f"配置导出成功: {export_file}") + return True + except Exception as e: + self.logger.error(f"配置导出失败: {e}") + return False + + def import_config(self, import_file: str) -> bool: + """导入配置""" + try: + with open(import_file, "r", encoding="utf-8") as f: + config = json.load(f) + + if self._validate_config(config): + self.config = config + self._save_config(config) + self.logger.info(f"配置导入成功: {import_file}") + return True + else: + self.logger.error("导入的配置验证失败") + return False + except Exception as e: + self.logger.error(f"配置导入失败: {e}") + return False + + def backup_config(self) -> bool: + """备份当前配置""" + try: + backup_file = f"{self.config_file}.backup_{int(time.time())}" + with open(backup_file, "w", encoding="utf-8") as f: + json.dump(self.config, f, indent=2, ensure_ascii=False) + + self.logger.info(f"配置备份成功: {backup_file}") + return True + except Exception as e: + self.logger.error(f"配置备份失败: {e}") + return False + + def restore_config(self, backup_file: str) -> bool: + """从备份恢复配置""" + try: + if not os.path.exists(backup_file): + self.logger.error(f"备份文件不存在: {backup_file}") + return False + + with open(backup_file, "r", encoding="utf-8") as f: + config = json.load(f) + + if self._validate_config(config): + self.config = config + self._save_config(config) + self.logger.info(f"配置恢复成功: {backup_file}") + return True + else: + self.logger.error("备份配置验证失败") + return False + except Exception as e: + self.logger.error(f"配置恢复失败: {e}") + return False + + def get_config_info(self) -> Dict[str, Any]: + """获取配置信息""" + try: + return { + 'config_file': self.config_file, + 'last_modified': self.last_modified, + 'sections': list(self.config.keys()), + 'backup_file': self.backup_file, + 'has_backup': os.path.exists(self.backup_file) + } + except Exception as e: + self.logger.error(f"获取配置信息失败: {e}") + return {} + +# ========== 性能监控 ========== +class PerformanceMonitor: + """增强的性能监控器,支持实时监控、告警和报告生成""" + + def __init__(self): + self.start_time = time.time() + self.logger = Logger("PerformanceMonitor") + self.stats = { + 'audio_packets_sent': 0, + 'location_packets_sent': 0, + 'commands_received': 0, + 'wifi_reconnects': 0, + 'errors': 0, + 'mode_switches': 0, + 'memory_usage': [], + 'cpu_usage': [], + 'network_latency': [], + 'audio_quality': [], + 'location_accuracy': [] + } + self.thresholds = { + 'memory_warning': 0.8, # 80%内存使用率告警 + 'cpu_warning': 0.9, # 90%CPU使用率告警 + 'latency_warning': 1000, # 1秒延迟告警 + 'error_warning': 10 # 10个错误告警 + } + self.alerts = [] + self.last_report_time = time.time() + self.report_interval = 300 # 5分钟生成一次报告 + + def increment(self, stat_name: str, value: int = 1): + """增加统计计数""" + if stat_name in self.stats: + if isinstance(self.stats[stat_name], list): + self.stats[stat_name].append(value) + # 保持最近100个数据点 + if len(self.stats[stat_name]) > 100: + self.stats[stat_name] = self.stats[stat_name][-100:] + else: + self.stats[stat_name] += value + + def record_metric(self, metric_name: str, value: float): + """记录指标""" + if metric_name in self.stats and isinstance(self.stats[metric_name], list): + self.stats[metric_name].append(value) + # 保持最近100个数据点 + if len(self.stats[metric_name]) > 100: + self.stats[metric_name] = self.stats[metric_name][-100:] + + def get_stats(self) -> Dict[str, Any]: + """获取统计信息""" + try: + current_time = time.time() + uptime = current_time - self.start_time + + # 计算平均值 + avg_stats = {} + for key, value in self.stats.items(): + if isinstance(value, list) and value: + avg_stats[f"{key}_avg"] = sum(value) / len(value) + avg_stats[f"{key}_min"] = min(value) + avg_stats[f"{key}_max"] = max(value) + avg_stats[f"{key}_latest"] = value[-1] + + stats = { + 'uptime_seconds': uptime, + 'uptime_formatted': self._format_uptime(uptime), + 'stats': self.stats.copy(), + 'avg_stats': avg_stats, + 'memory_usage': gc.mem_free(), + 'memory_percent': (1 - gc.mem_free() / gc.mem_alloc()) * 100 if gc.mem_alloc() > 0 else 0, + 'timestamp': current_time, + 'alerts': self.alerts.copy() + } + + return stats + except Exception as e: + self.logger.error(f"获取统计信息失败: {e}") + return {} + + def _format_uptime(self, seconds: float) -> str: + """格式化运行时间""" + days = int(seconds // 86400) + hours = int((seconds % 86400) // 3600) + minutes = int((seconds % 3600) // 60) + secs = int(seconds % 60) + + if days > 0: + return f"{days}天{hours}小时{minutes}分钟" + elif hours > 0: + return f"{hours}小时{minutes}分钟" + elif minutes > 0: + return f"{minutes}分钟{secs}秒" + else: + return f"{secs}秒" + + def check_alerts(self): + """检查告警条件""" + try: + current_time = time.time() + + # 检查内存使用率 + memory_percent = (1 - gc.mem_free() / gc.mem_alloc()) * 100 if gc.mem_alloc() > 0 else 0 + if memory_percent > self.thresholds['memory_warning'] * 100: + self._add_alert("MEMORY_WARNING", f"内存使用率过高: {memory_percent:.1f}%") + + # 检查错误数量 + if self.stats['errors'] > self.thresholds['error_warning']: + self._add_alert("ERROR_WARNING", f"错误数量过多: {self.stats['errors']}") + + # 检查网络延迟 + if self.stats['network_latency']: + avg_latency = sum(self.stats['network_latency']) / len(self.stats['network_latency']) + if avg_latency > self.thresholds['latency_warning']: + self._add_alert("LATENCY_WARNING", f"网络延迟过高: {avg_latency:.1f}ms") + + except Exception as e: + self.logger.error(f"检查告警失败: {e}") + + def _add_alert(self, alert_type: str, message: str): + """添加告警""" + alert = { + 'type': alert_type, + 'message': message, + 'timestamp': time.time() + } + self.alerts.append(alert) + + # 保持最近50个告警 + if len(self.alerts) > 50: + self.alerts = self.alerts[-50:] + + self.logger.warning(f"告警: {alert_type} - {message}") + + def log_stats(self): + """记录统计信息""" + try: + stats = self.get_stats() + self.logger.info(f"性能统计: {json.dumps(stats, indent=2)}") + + # 检查告警 + self.check_alerts() + + # 定期生成报告 + current_time = time.time() + if current_time - self.last_report_time > self.report_interval: + self._generate_report() + self.last_report_time = current_time + + except Exception as e: + self.logger.error(f"记录统计信息失败: {e}") + + def _generate_report(self): + """生成性能报告""" + try: + stats = self.get_stats() + report = { + 'report_time': time.strftime("%Y-%m-%d %H:%M:%S"), + 'uptime': stats['uptime_formatted'], + 'summary': { + 'total_audio_packets': stats['stats']['audio_packets_sent'], + 'total_location_packets': stats['stats']['location_packets_sent'], + 'total_commands': stats['stats']['commands_received'], + 'total_errors': stats['stats']['errors'], + 'total_reconnects': stats['stats']['wifi_reconnects'] + }, + 'performance': { + 'memory_usage_percent': stats['memory_percent'], + 'free_memory': stats['memory_usage'] + }, + 'alerts': stats['alerts'] + } + + self.logger.info(f"性能报告: {json.dumps(report, indent=2)}") + + # 保存报告到文件 + report_file = f"/sd/logs/performance_report_{int(time.time())}.json" + with open(report_file, "w", encoding="utf-8") as f: + json.dump(report, f, indent=2, ensure_ascii=False) + + except Exception as e: + self.logger.error(f"生成性能报告失败: {e}") + + def reset(self): + """重置统计""" + try: + self.start_time = time.time() + for key in self.stats: + if isinstance(self.stats[key], list): + self.stats[key] = [] + else: + self.stats[key] = 0 + self.alerts = [] + self.last_report_time = time.time() + self.logger.info("性能统计已重置") + except Exception as e: + self.logger.error(f"重置统计失败: {e}") + + def get_performance_score(self) -> float: + """计算性能评分 (0-100)""" + try: + score = 100.0 + + # 内存使用率扣分 + memory_percent = (1 - gc.mem_free() / gc.mem_alloc()) * 100 if gc.mem_alloc() > 0 else 0 + if memory_percent > 80: + score -= (memory_percent - 80) * 2 + + # 错误数量扣分 + if self.stats['errors'] > 0: + score -= min(self.stats['errors'] * 5, 30) + + # 重连次数扣分 + if self.stats['wifi_reconnects'] > 0: + score -= min(self.stats['wifi_reconnects'] * 3, 20) + + # 网络延迟扣分 + if self.stats['network_latency']: + avg_latency = sum(self.stats['network_latency']) / len(self.stats['network_latency']) + if avg_latency > 100: + score -= min((avg_latency - 100) / 10, 20) + + return max(0.0, score) + except Exception as e: + self.logger.error(f"计算性能评分失败: {e}") + return 0.0 + + def get_health_status(self) -> Dict[str, Any]: + """获取系统健康状态""" + try: + performance_score = self.get_performance_score() + + # 确定健康状态 + if performance_score >= 90: + health_status = "EXCELLENT" + elif performance_score >= 80: + health_status = "GOOD" + elif performance_score >= 60: + health_status = "FAIR" + elif performance_score >= 40: + health_status = "POOR" + else: + health_status = "CRITICAL" + + return { + 'health_status': health_status, + 'performance_score': performance_score, + 'alerts_count': len(self.alerts), + 'uptime': self._format_uptime(time.time() - self.start_time), + 'memory_usage_percent': (1 - gc.mem_free() / gc.mem_alloc()) * 100 if gc.mem_alloc() > 0 else 0, + 'free_memory': gc.mem_free(), + 'total_errors': self.stats['errors'], + 'total_reconnects': self.stats['wifi_reconnects'] + } + except Exception as e: + self.logger.error(f"获取健康状态失败: {e}") + return {'health_status': 'UNKNOWN', 'error': str(e)} + + def export_metrics(self, export_file: str = None) -> bool: + """导出性能指标""" + try: + if not export_file: + export_file = f"/sd/logs/metrics_export_{int(time.time())}.json" + + metrics = { + 'export_time': time.strftime("%Y-%m-%d %H:%M:%S"), + 'stats': self.stats.copy(), + 'health_status': self.get_health_status(), + 'alerts': self.alerts.copy(), + 'thresholds': self.thresholds.copy() + } + + with open(export_file, "w", encoding="utf-8") as f: + json.dump(metrics, f, indent=2, ensure_ascii=False) + + self.logger.info(f"性能指标导出成功: {export_file}") + return True + except Exception as e: + self.logger.error(f"性能指标导出失败: {e}") + return False + + def set_threshold(self, threshold_name: str, value: float) -> bool: + """设置告警阈值""" + try: + if threshold_name in self.thresholds: + self.thresholds[threshold_name] = value + self.logger.info(f"告警阈值更新: {threshold_name} = {value}") + return True + else: + self.logger.error(f"未知的阈值名称: {threshold_name}") + return False + except Exception as e: + self.logger.error(f"设置阈值失败: {e}") + return False + + def clear_alerts(self): + """清除告警""" + try: + self.alerts.clear() + self.logger.info("告警已清除") + except Exception as e: + self.logger.error(f"清除告警失败: {e}") + + def get_alert_summary(self) -> Dict[str, Any]: + """获取告警摘要""" + try: + alert_counts = {} + for alert in self.alerts: + alert_type = alert['type'] + alert_counts[alert_type] = alert_counts.get(alert_type, 0) + 1 + + return { + 'total_alerts': len(self.alerts), + 'alert_counts': alert_counts, + 'latest_alerts': self.alerts[-5:] if self.alerts else [] + } + except Exception as e: + self.logger.error(f"获取告警摘要失败: {e}") + return {} + +# ========== 全局变量 ========== +logger = Logger("DevelopmentBoard") +config_manager = ConfigManager() +performance_monitor = PerformanceMonitor() + +# 信号处理 +def signal_handler(signum, frame): + """信号处理器""" + logger.info(f"接收到信号 {signum},准备关闭程序...") + # 这里会在DevelopmentBoard类中实现优雅关闭 + sys.exit(0) + +class DevelopmentBoard: + """增强的开发板主类,支持完整的声源定位系统功能""" + + def __init__(self): + # 基础状态 + self.running = True + self.current_mode = SystemMode.INITIALIZING + self.previous_mode = SystemMode.INITIALIZING + self.system_status = "INITIALIZING" + + # 配置管理 + self.config_manager = ConfigManager() + self.network_config = self.config_manager.get_network_config() + self.audio_config = self.config_manager.get_audio_config() + self.hardware_config = self.config_manager.get_hardware_config() + self.system_config = self.config_manager.get_system_config() + + # 日志和监控 + self.logger = Logger("DevelopmentBoard", self.system_config.log_level) + self.performance_monitor = PerformanceMonitor() + + # 连接状态 + self.wifi_status = WiFiStatus.DISCONNECTED + self.connection_status = ConnectionStatus.DISCONNECTED + self.audio_status = AudioStatus.IDLE + + # WiFi相关 + self.uart = None + self.wifi_en = None + self.nic = None + self.wifi_connection_attempts = 0 + self.last_wifi_check = 0 + + # Socket连接 + self.audio_socket = None + self.cmd_socket = None + self.location_socket = None + self.socket_connection_attempts = 0 + self.last_socket_check = 0 + + # 硬件组件 + self.led = None + self.buzzer = None + self.button = None + self.temperature_sensor = None + + # 音频相关 + self.audio_buffer = [] + self.audio_processing = False + self.last_audio_timestamp = 0 + + # 定位相关 + self.location_buffer = [] + self.location_processing = False + self.last_location_timestamp = 0 + + # 新增:枪声检测器 + self.gunshot_detector = GunshotDetector(self.audio_config) + + # 新增:定位模式下的音频缓冲 + self.location_audio_buffer = [] + self.location_audio_buffer_size = int(self.audio_config.sample_rate * 0.5) # 0.5秒缓冲 + + # 新增:定位-音频映射队列 + self.location_audio_mappings = [] # 存储定位结果和音频的映射关系 + self.max_mapping_queue_size = 20 # 最大映射队列大小 + self.recognition_results = {} # 存储PC端的识别结果 {timestamp: result} + + # 系统监控 + self.heartbeat_timer = None + self.health_check_timer = None + self.last_heartbeat = 0 + self.last_health_check = 0 + + # 错误处理 + self.error_count = 0 + self.last_error_time = 0 + self.error_recovery_attempts = 0 + + # 性能统计 + self.start_time = time.time() + self.mode_switch_count = 0 + self.total_audio_packets = 0 + self.total_location_packets = 0 + + # 初始化系统 + self._initialize_system() + + def _initialize_system(self): + """初始化系统""" + try: + self.logger.info("开始系统初始化...") + self.logger.info(f"系统配置: {self.system_config.to_dict()}") + + # 检查内存 + free_memory = gc.mem_free() + if free_memory < 1024 * 1024: # 少于1MB + raise MemoryError(f"内存不足: {free_memory} bytes") + + # 初始化硬件 + self._initialize_hardware() + + # 初始化网络 + self._initialize_network() + + # 初始化音频系统 + self._initialize_audio_system() + + # 初始化定位系统 + self._initialize_location_system() + + # 启动监控 + self._start_monitoring() + + # 切换到录音模式 + self._switch_mode(SystemMode.RECORDING) + + self.logger.info("系统初始化完成") + self.system_status = "READY" + + except Exception as e: + self.logger.error(f"系统初始化失败: {e}") + self.system_status = "ERROR" + self.current_mode = SystemMode.ERROR + raise + + def _initialize_hardware(self): + """初始化硬件组件""" + try: + self.logger.info("初始化硬件组件...") + + # 初始化WiFi硬件 + self._init_wifi_hardware() + + # 初始化麦克风阵列 + self._init_microphone_array() + + # 初始化其他硬件组件 + self._init_additional_hardware() + + self.logger.info("硬件初始化完成") + + except Exception as e: + self.logger.error(f"硬件初始化失败: {e}") + raise HardwareFaultError(f"硬件初始化失败: {e}", "hardware_init") + + def _init_wifi_hardware(self): + """初始化WiFi硬件""" + try: + # GPIO用于WiFi使能控制 + fm.register(self.hardware_config.wifi_en_pin, fm.fpioa.GPIOHS0, force=True) + self.wifi_en = GPIO(GPIO.GPIOHS0, GPIO.OUT) + + # 配置UART用于与ESP8285通信 + fm.register(board_info.WIFI_RX, fm.fpioa.UART2_TX, force=True) + fm.register(board_info.WIFI_TX, fm.fpioa.UART2_RX, force=True) + + self.logger.info("WiFi硬件引脚初始化完成") + + except Exception as e: + self.logger.error(f"WiFi硬件初始化失败: {e}") + raise HardwareFaultError(f"WiFi硬件初始化失败: {e}", "wifi_hardware") + + def _init_microphone_array(self): + """初始化麦克风阵列""" + try: + mic.init( + i2s_d0=self.hardware_config.mic_i2s_d0, + i2s_d1=self.hardware_config.mic_i2s_d1, + i2s_d2=self.hardware_config.mic_i2s_d2, + i2s_d3=self.hardware_config.mic_i2s_d3, + i2s_ws=self.hardware_config.mic_i2s_ws, + i2s_sclk=self.hardware_config.mic_i2s_sclk, + sk9822_dat=self.hardware_config.sk9822_dat, + sk9822_clk=self.hardware_config.sk9822_clk + ) + self.logger.info("麦克风阵列初始化成功") + + except Exception as e: + self.logger.error(f"麦克风阵列初始化失败: {e}") + raise MicArrayInitError(f"麦克风阵列初始化失败: {e}") + + def _init_additional_hardware(self): + """初始化其他硬件组件""" + try: + # 初始化LED + fm.register(self.hardware_config.led_pin, fm.fpioa.GPIOHS1, force=True) + self.led = GPIO(GPIO.GPIOHS1, GPIO.OUT) + self.led.value(0) # 关闭LED + + # 初始化蜂鸣器 + fm.register(self.hardware_config.buzzer_pin, fm.fpioa.GPIOHS2, force=True) + self.buzzer = GPIO(GPIO.GPIOHS2, GPIO.OUT) + self.buzzer.value(0) # 关闭蜂鸣器 + + # 初始化按钮 + fm.register(self.hardware_config.button_pin, fm.fpioa.GPIOHS3, force=True) + self.button = GPIO(GPIO.GPIOHS3, GPIO.IN, GPIO.PULL_UP) + + self.logger.info("其他硬件组件初始化完成") + + except Exception as e: + self.logger.error(f"其他硬件组件初始化失败: {e}") + # 不抛出异常,这些组件不是必需的 + + def _initialize_network(self): + """初始化网络连接""" + try: + self.logger.info("初始化网络连接...") + + # 连接到WiFi + if not self._connect_wifi(): + raise WiFiConnectionError("WiFi连接失败", self.network_config.wifi_ssid) + + # 建立Socket连接 + if not self._setup_socket_connections(): + raise SocketConnectionError("Socket连接失败", self.network_config.pc_ip) + + self.logger.info("网络连接初始化完成") + + except Exception as e: + self.logger.error(f"网络连接初始化失败: {e}") + raise + + def _initialize_audio_system(self): + """初始化音频系统""" + try: + self.logger.info("初始化音频系统...") + + # 验证音频配置 + if not self.audio_config.validate(): + raise AudioInitError("音频配置验证失败") + + # 初始化音频缓冲区 + self.audio_buffer = [] + + self.logger.info("音频系统初始化完成") + + except Exception as e: + self.logger.error(f"音频系统初始化失败: {e}") + raise AudioInitError(f"音频系统初始化失败: {e}") + + def _initialize_location_system(self): + """初始化定位系统""" + try: + self.logger.info("初始化定位系统...") + + # 初始化定位缓冲区 + self.location_buffer = [] + + # 测试麦克风阵列功能 + test_location = self._get_mic_direction() + if test_location is None: + raise MicArrayInitError("麦克风阵列测试失败") + + self.logger.info("定位系统初始化完成") + + except Exception as e: + self.logger.error(f"定位系统初始化失败: {e}") + raise MicArrayInitError(f"定位系统初始化失败: {e}") + + def _start_monitoring(self): + """启动系统监控""" + try: + self.logger.info("启动系统监控...") + + # 启动心跳定时器 + if self.system_config.enable_performance_monitor: + self.heartbeat_timer = Timer(Timer.TIMER0, Timer.CHANNEL0, mode=Timer.MODE_PERIODIC, + period=int(self.system_config.heartbeat_interval * 1000), + callback=self._heartbeat_callback) + + # 启动健康检查定时器 + if self.system_config.enable_hardware_monitor: + self.health_check_timer = Timer(Timer.TIMER1, Timer.CHANNEL0, mode=Timer.MODE_PERIODIC, + period=60000, # 1分钟检查一次 + callback=self._health_check_callback) + + self.logger.info("系统监控启动完成") + + except Exception as e: + self.logger.error(f"系统监控启动失败: {e}") + # 不抛出异常,监控不是必需的 + + def _heartbeat_callback(self, timer): + """心跳回调函数""" + try: + current_time = time.time() + self.last_heartbeat = current_time + + # 发送心跳数据 + self._send_heartbeat() + + # 记录性能统计 + if self.system_config.enable_performance_monitor: + self.performance_monitor.log_stats() + + except Exception as e: + self.logger.error(f"心跳回调失败: {e}") + + def _health_check_callback(self, timer): + """健康检查回调函数""" + try: + current_time = time.time() + self.last_health_check = current_time + + # 检查系统健康状态 + health_status = self.performance_monitor.get_health_status() + + # 如果健康状态较差,尝试恢复 + if health_status['health_status'] in ['POOR', 'CRITICAL']: + self.logger.warning(f"系统健康状态较差: {health_status['health_status']}") + self._attempt_recovery() + + except Exception as e: + self.logger.error(f"健康检查回调失败: {e}") + + def _send_heartbeat(self): + """发送心跳数据""" + try: + heartbeat_data = { + 'type': 'heartbeat', + 'timestamp': time.time(), + 'mode': self.current_mode.value, + 'status': self.system_status, + 'uptime': time.time() - self.start_time, + 'memory': gc.mem_free(), + 'error_count': self.error_count + } + + if self.cmd_socket: + self.cmd_socket.send(json.dumps(heartbeat_data).encode('utf-8')) + + except Exception as e: + self.logger.error(f"发送心跳失败: {e}") + + def _attempt_recovery(self): + """尝试系统恢复""" + try: + if self.error_recovery_attempts >= Constants.MAX_RETRY_ATTEMPTS: + self.logger.error("恢复尝试次数已达上限,切换到错误模式") + self._switch_mode(SystemMode.ERROR) + return + + self.error_recovery_attempts += 1 + self.logger.info(f"尝试系统恢复 (第{self.error_recovery_attempts}次)") + + # 检查网络连接 + if self.wifi_status != WiFiStatus.CONNECTED: + self._connect_wifi() + + # 检查Socket连接 + if self.connection_status != ConnectionStatus.CONNECTED: + self._setup_socket_connections() + + # 重置错误计数 + if self.error_count > 0: + self.error_count = 0 + self.error_recovery_attempts = 0 + self.logger.info("系统恢复成功") + + except Exception as e: + self.logger.error(f"系统恢复失败: {e}") + + def _switch_mode(self, new_mode: SystemMode): + """切换系统模式""" + try: + if new_mode == self.current_mode: + return + + self.previous_mode = self.current_mode + self.current_mode = new_mode + self.mode_switch_count += 1 + + self.logger.info(f"系统模式切换: {self.previous_mode.value} -> {self.current_mode.value}") + + # 根据模式执行相应操作 + if new_mode == SystemMode.RECORDING: + self._enter_recording_mode() + elif new_mode == SystemMode.LOCATING: + self._enter_locating_mode() + elif new_mode == SystemMode.ERROR: + self._enter_error_mode() + elif new_mode == SystemMode.SHUTDOWN: + self._enter_shutdown_mode() + + # 更新性能统计 + self.performance_monitor.increment('mode_switches') + + except Exception as e: + self.logger.error(f"模式切换失败: {e}") + self.current_mode = SystemMode.ERROR + + def _enter_recording_mode(self): + """进入录音模式""" + try: + self.audio_status = AudioStatus.IDLE + self.logger.info("进入录音模式") + + # 设置LED状态 + if self.led: + self.led.value(1) # 点亮LED表示录音模式 + + except Exception as e: + self.logger.error(f"进入录音模式失败: {e}") + + def _enter_locating_mode(self): + """进入定位模式""" + try: + self.audio_status = AudioStatus.PROCESSING + self.logger.info("进入定位模式") + + # 重置枪声检测器 + self.gunshot_detector.reset() + + # 清空定位音频缓冲 + self.location_audio_buffer.clear() + + # 设置LED状态 + if self.led: + self.led.value(0) # 关闭LED表示定位模式 + + except Exception as e: + self.logger.error(f"进入定位模式失败: {e}") + + def _enter_error_mode(self): + """进入错误模式""" + try: + self.system_status = "ERROR" + self.logger.error("进入错误模式") + + # 设置LED状态 + if self.led: + self.led.value(1) # 点亮LED表示错误状态 + + # 蜂鸣器报警 + if self.buzzer: + self._beep_error() + + except Exception as e: + self.logger.error(f"进入错误模式失败: {e}") + + def _enter_shutdown_mode(self): + """进入关闭模式""" + try: + self.system_status = "SHUTDOWN" + self.logger.info("进入关闭模式") + + # 关闭所有硬件 + self._shutdown_hardware() + + except Exception as e: + self.logger.error(f"进入关闭模式失败: {e}") + + def _beep_error(self): + """错误蜂鸣""" + try: + if self.buzzer: + # 蜂鸣3次 + for i in range(3): + self.buzzer.value(1) + time.sleep_ms(200) + self.buzzer.value(0) + time.sleep_ms(200) + except Exception as e: + self.logger.error(f"错误蜂鸣失败: {e}") + + def _shutdown_hardware(self): + """关闭硬件""" + try: + # 关闭LED + if self.led: + self.led.value(0) + + # 关闭蜂鸣器 + if self.buzzer: + self.buzzer.value(0) + + # 关闭WiFi + if self.wifi_en: + self.wifi_en.value(0) + + self.logger.info("硬件关闭完成") + + except Exception as e: + self.logger.error(f"硬件关闭失败: {e}") + + def run(self): + """主运行循环""" + try: + self.logger.info("开始主运行循环") + + while self.running: + try: + # 根据当前模式执行相应操作 + if self.current_mode == SystemMode.RECORDING: + self._recording_mode_loop() + elif self.current_mode == SystemMode.LOCATING: + self._locating_mode_loop() + elif self.current_mode == SystemMode.ERROR: + self._error_mode_loop() + elif self.current_mode == SystemMode.SHUTDOWN: + break + + # 短暂休眠 + time.sleep_ms(10) + + except Exception as e: + self.logger.error(f"主循环异常: {e}") + self.error_count += 1 + time.sleep_ms(100) + + except Exception as e: + self.logger.error(f"主运行循环失败: {e}") + finally: + self._cleanup() + + def _recording_mode_loop(self): + """录音模式循环""" + try: + # 录音并发送给PC端 + audio_data = self._record_audio() + if audio_data: + self._send_audio_data(audio_data) + self.total_audio_packets += 1 + + # 更新性能统计 + self.performance_monitor.increment('audio_packets_sent') + + # 检查PC端指令 + self._check_pc_commands() + + except Exception as e: + self.logger.error(f"录音模式循环异常: {e}") + self.error_count += 1 + + def _locating_mode_loop(self): + """定位模式循环 - 先定位后识别""" + try: + # 录音 + audio_data = self._record_audio() + if not audio_data: + return + + # 添加到定位音频缓冲 + self.location_audio_buffer.extend(audio_data) + + # 限制缓冲大小 + if len(self.location_audio_buffer) > self.location_audio_buffer_size: + self.location_audio_buffer = self.location_audio_buffer[-self.location_audio_buffer_size:] + + # 检查是否有足够的音频数据进行声源定位 + if len(self.location_audio_buffer) >= self.audio_config.chunk_size: + # 1. 先进行声源定位 + location_data = self._perform_sound_source_location(self.location_audio_buffer) + + if location_data: + # 2. 存储定位结果和音频的映射关系 + audio_segment = self.location_audio_buffer.copy() + mapping_entry = { + 'location_data': location_data, + 'audio_data': audio_segment, + 'timestamp': time.time(), + 'processed': False # 标记是否已被PC端处理 + } + + # 添加到映射队列 + self._add_location_audio_mapping(mapping_entry) + + # 3. 将音频数据发送给PC端进行枪声识别 + self._send_audio_for_recognition(audio_segment) + + self.logger.debug(f"定位完成: X={location_data.x:.2f}, Y={location_data.y:.2f}, 音频已发送识别") + + # 清空音频缓冲,准备下一轮 + self.location_audio_buffer.clear() + + # 4. 检查PC端的枪声识别结果 + self._check_recognition_results() + + # 检查PC端指令 + self._check_pc_commands() + + except Exception as e: + self.logger.error(f"定位模式循环异常: {e}") + self.error_count += 1 + + def _send_gunshot_detection_result(self, detection_result: Dict[str, Any]): + """发送枪声检测结果给PC端""" + try: + if not self.location_socket: + return + + # 构建检测结果字符串 + # 格式: GUNSHOT_DETECTION:is_gunshot:confidence:timestamp + is_gunshot_str = "true" if detection_result['is_gunshot'] else "false" + confidence = detection_result.get('confidence', 0.0) + timestamp = detection_result.get('timestamp', time.time()) + + detection_str = f"GUNSHOT_DETECTION:{is_gunshot_str}:{confidence:.3f}:{timestamp}" + + # 发送数据 + self.location_socket.send(detection_str.encode('utf-8')) + + except Exception as e: + self.logger.error(f"发送枪声检测结果失败: {e}") + self.connection_status = ConnectionStatus.ERROR + + def _add_location_audio_mapping(self, mapping_entry: Dict[str, Any]): + """添加定位-音频映射关系""" + try: + self.location_audio_mappings.append(mapping_entry) + + # 限制队列大小 + if len(self.location_audio_mappings) > self.max_mapping_queue_size: + # 移除最老的映射关系 + removed = self.location_audio_mappings.pop(0) + self.logger.debug(f"移除过期映射关系: {removed['timestamp']}") + + self.logger.debug(f"添加映射关系: 时间戳={mapping_entry['timestamp']:.3f}") + + except Exception as e: + self.logger.error(f"添加映射关系失败: {e}") + + def _send_audio_for_recognition(self, audio_data: List[float]): + """发送音频数据给PC端进行枪声识别""" + try: + if not self.audio_socket: + return + + # 构建识别请求数据 + # 格式: RECOGNITION_REQUEST:timestamp:audio_data + timestamp = time.time() + + # 将音频数据转换为字节 + audio_bytes = np.array(audio_data, dtype=np.int16).tobytes() + + # 构建请求头 + request_header = f"RECOGNITION_REQUEST:{timestamp:.3f}:{len(audio_bytes)}" + + # 发送请求头 + self.audio_socket.send(request_header.encode('utf-8')) + time.sleep_ms(10) # 短暂等待 + + # 发送音频数据 + self.audio_socket.send(audio_bytes) + + self.logger.debug(f"发送识别请求: 时间戳={timestamp:.3f}, 数据大小={len(audio_bytes)}") + + except Exception as e: + self.logger.error(f"发送识别请求失败: {e}") + self.connection_status = ConnectionStatus.ERROR + + def _check_recognition_results(self): + """检查PC端的枪声识别结果""" + try: + if not self.cmd_socket: + return + + # 非阻塞接收识别结果 + try: + self.cmd_socket.settimeout(0.1) # 100ms超时 + data = self.cmd_socket.recv(1024) + + if data: + result_str = data.decode('utf-8').strip() + self._process_recognition_result(result_str) + + except socket.timeout: + # 超时继续 + pass + + except Exception as e: + self.logger.error(f"检查识别结果失败: {e}") + + def _process_recognition_result(self, result_str: str): + """处理PC端的识别结果""" + try: + # 解析识别结果格式: RECOGNITION_RESULT:timestamp:is_gunshot:confidence + if not result_str.startswith("RECOGNITION_RESULT:"): + return + + parts = result_str.split(':') + if len(parts) >= 4: + timestamp = float(parts[1]) + is_gunshot = parts[2].lower() == 'true' + confidence = float(parts[3]) + + # 存储识别结果 + self.recognition_results[timestamp] = { + 'is_gunshot': is_gunshot, + 'confidence': confidence, + 'processed': False + } + + self.logger.info(f"收到识别结果: 时间戳={timestamp:.3f}, 枪声={is_gunshot}, 置信度={confidence:.3f}") + + # 处理对应的定位数据 + self._process_matching_location(timestamp, is_gunshot, confidence) + + except Exception as e: + self.logger.error(f"处理识别结果失败: {e}") + + def _process_matching_location(self, timestamp: float, is_gunshot: bool, confidence: float): + """处理匹配的定位数据""" + try: + # 查找时间戳最接近的映射关系 + best_match = None + min_time_diff = float('inf') + + for mapping in self.location_audio_mappings: + if mapping['processed']: + continue + + time_diff = abs(mapping['timestamp'] - timestamp) + if time_diff < min_time_diff and time_diff < 1.0: # 1秒内的匹配 + min_time_diff = time_diff + best_match = mapping + + if best_match: + # 标记为已处理 + best_match['processed'] = True + self.recognition_results[timestamp]['processed'] = True + + if is_gunshot: + # 如果是枪声,发送对应的定位数据 + location_data = best_match['location_data'] + self._send_location_data(location_data) + self.total_location_packets += 1 + + # 更新性能统计 + self.performance_monitor.increment('location_packets_sent') + + self.logger.info(f"枪声确认!发送定位数据: X={location_data.x:.2f}, Y={location_data.y:.2f}, 置信度={confidence:.3f}") + else: + self.logger.debug(f"非枪声,忽略定位数据: 时间戳={timestamp:.3f}") + + # 清理已处理的映射关系 + self._cleanup_processed_mappings() + + except Exception as e: + self.logger.error(f"处理匹配定位数据失败: {e}") + + def _cleanup_processed_mappings(self): + """清理已处理的映射关系""" + try: + # 移除已处理的映射关系 + self.location_audio_mappings = [m for m in self.location_audio_mappings if not m['processed']] + + # 清理过期的识别结果(超过5秒) + current_time = time.time() + expired_timestamps = [ts for ts in self.recognition_results.keys() + if current_time - ts > 5.0] + for ts in expired_timestamps: + del self.recognition_results[ts] + + except Exception as e: + self.logger.error(f"清理映射关系失败: {e}") + + def _error_mode_loop(self): + """错误模式循环""" + try: + # 错误模式下尝试恢复 + if self.error_recovery_attempts < Constants.MAX_RETRY_ATTEMPTS: + self._attempt_recovery() + else: + # 达到最大重试次数,进入维护模式 + self._switch_mode(SystemMode.MAINTENANCE) + + time.sleep_ms(1000) # 错误模式下等待1秒 + + except Exception as e: + self.logger.error(f"错误模式循环异常: {e}") + + def _record_audio(self) -> Optional[List[float]]: + """录音""" + try: + # 从麦克风阵列获取音频数据 + # 这里需要根据实际的麦克风阵列API进行调整 + audio_data = mic.get_audio_data(self.audio_config.chunk_size) + + if audio_data and len(audio_data) > 0: + return audio_data + else: + return None + + except Exception as e: + self.logger.error(f"录音失败: {e}") + return None + + def _send_audio_data(self, audio_data: List[float]): + """发送音频数据""" + try: + if not self.audio_socket: + return + + # 转换为字节数据 + audio_bytes = np.array(audio_data, dtype=np.int16).tobytes() + + # 发送数据 + self.audio_socket.send(audio_bytes) + + except Exception as e: + self.logger.error(f"发送音频数据失败: {e}") + self.connection_status = ConnectionStatus.ERROR + + def _send_location_data(self, location_data: LocationData): + """发送定位数据""" + try: + if not self.location_socket: + return + + # 转换为字符串格式 + location_str = f"{location_data.x:.2f},{location_data.y:.2f},{location_data.strength:.2f},{location_data.angle:.2f}" + + # 发送数据 + self.location_socket.send(location_str.encode('utf-8')) + + except Exception as e: + self.logger.error(f"发送定位数据失败: {e}") + self.connection_status = ConnectionStatus.ERROR + + def _check_pc_commands(self): + """检查PC端指令""" + try: + if not self.cmd_socket: + return + + # 非阻塞接收指令 + try: + self.cmd_socket.settimeout(0.1) # 100ms超时 + data = self.cmd_socket.recv(1024) + + if data: + command = data.decode('utf-8').strip() + self._process_command(command) + + except socket.timeout: + # 超时继续 + pass + + except Exception as e: + self.logger.error(f"检查PC指令失败: {e}") + + def _process_command(self, command: str): + """处理PC端指令""" + try: + self.logger.info(f"收到PC指令: {command}") + + if command == "START_LOCATION": + self._switch_mode(SystemMode.LOCATING) + elif command == "STOP_LOCATION": + self._switch_mode(SystemMode.RECORDING) + elif command == "SHUTDOWN": + self._switch_mode(SystemMode.SHUTDOWN) + elif command == "HEARTBEAT": + self._send_heartbeat() + elif command.startswith("SET_CONFIG"): + self._process_config_command(command) + else: + self.logger.warning(f"未知指令: {command}") + + except Exception as e: + self.logger.error(f"处理指令失败: {e}") + + def _process_config_command(self, command: str): + """处理配置指令""" + try: + # 解析配置指令格式: SET_CONFIG:section:key:value + parts = command.split(':') + if len(parts) == 4: + section = parts[1] + key = parts[2] + value = parts[3] + + # 更新配置 + if self.config_manager.update_config(section, key, value): + self.logger.info(f"配置更新成功: {section}.{key} = {value}") + + # 重新加载配置 + self._reload_config() + else: + self.logger.error(f"配置更新失败: {section}.{key} = {value}") + + except Exception as e: + self.logger.error(f"处理配置指令失败: {e}") + + def _reload_config(self): + """重新加载配置""" + try: + self.network_config = self.config_manager.get_network_config() + self.audio_config = self.config_manager.get_audio_config() + self.hardware_config = self.config_manager.get_hardware_config() + self.system_config = self.config_manager.get_system_config() + + self.logger.info("配置重新加载完成") + + except Exception as e: + self.logger.error(f"重新加载配置失败: {e}") + + def _perform_sound_source_location(self, audio_data: List[float]) -> Optional[LocationData]: + """执行声源定位""" + try: + # 使用声源定位器进行定位 + if hasattr(self, 'sound_source_locator'): + location_data = self.sound_source_locator.process_audio_frame(audio_data) + return location_data + else: + # 如果没有专门的定位器,使用简单的麦克风方向检测 + return self._get_mic_direction() + + except Exception as e: + self.logger.error(f"声源定位失败: {e}") + return None + + def _get_mic_direction(self) -> Optional[LocationData]: + """获取麦克风方向(简单实现)""" + try: + # 获取麦克风阵列数据 + mic_data = mic.get_direction() + if mic_data: + # 解析麦克风数据 + # 这里需要根据实际的麦克风阵列API进行调整 + angle = mic_data.get('angle', 0) + strength = mic_data.get('strength', 0) + + # 转换为坐标 + distance = 5.0 # 假设距离5米 + x = distance * math.cos(math.radians(angle)) + y = distance * math.sin(math.radians(angle)) + + return LocationData( + x=x, + y=y, + strength=strength, + angle=angle, + timestamp=time.time(), + confidence=0.8, + quality=0.7, + noise_level=0.3, + source_type="gunshot" + ) + else: + return None + + except Exception as e: + self.logger.error(f"获取麦克风方向失败: {e}") + return None + + def _connect_wifi(self) -> bool: + """连接WiFi""" + try: + if self.wifi_status == WiFiStatus.CONNECTED: + return True + + self.wifi_status = WiFiStatus.CONNECTING + self.logger.info(f"正在连接WiFi: {self.network_config.wifi_ssid}") + + # 启用WiFi硬件 + if self.wifi_en: + self.wifi_en.value(1) + time.sleep_ms(1000) # 等待WiFi启动 + + # 连接WiFi + # 这里需要根据实际的WiFi API进行调整 + # self.nic = network.WLAN(network.STA_IF) + # self.nic.active(True) + # self.nic.connect(self.network_config.wifi_ssid, self.network_config.wifi_password) + + # 等待连接 + max_wait = int(self.network_config.connection_timeout * 1000) + wait_time = 0 + while wait_time < max_wait: + # if self.nic.isconnected(): + # self.wifi_status = WiFiStatus.CONNECTED + # self.logger.info("WiFi连接成功") + # return True + + time.sleep_ms(100) + wait_time += 100 + + self.wifi_status = WiFiStatus.ERROR + self.logger.error("WiFi连接超时") + return False + + except Exception as e: + self.wifi_status = WiFiStatus.ERROR + self.logger.error(f"WiFi连接失败: {e}") + return False + + def _setup_socket_connections(self) -> bool: + """建立Socket连接""" + try: + self.connection_status = ConnectionStatus.CONNECTING + self.logger.info("正在建立Socket连接...") + + # 连接音频Socket + self.audio_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.audio_socket.connect((self.network_config.pc_ip, self.network_config.pc_port_audio)) + self.audio_socket.settimeout(self.network_config.socket_timeout) + + # 连接指令Socket + self.cmd_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.cmd_socket.connect((self.network_config.pc_ip, self.network_config.pc_port_cmd)) + self.cmd_socket.settimeout(self.network_config.socket_timeout) + + # 连接定位Socket + self.location_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.location_socket.connect((self.network_config.pc_ip, self.network_config.pc_port_location)) + self.location_socket.settimeout(self.network_config.socket_timeout) + + self.connection_status = ConnectionStatus.CONNECTED + self.logger.info("所有Socket连接建立成功") + return True + + except Exception as e: + self.connection_status = ConnectionStatus.ERROR + self.logger.error(f"Socket连接失败: {e}") + return False + + def _cleanup(self): + """清理资源""" + try: + self.logger.info("开始清理资源...") + self.running = False + + # 关闭Socket连接 + for socket_name, sock in [('audio', self.audio_socket), + ('cmd', self.cmd_socket), + ('location', self.location_socket)]: + if sock: + try: + sock.close() + self.logger.info(f"关闭{socket_name} Socket") + except Exception as e: + self.logger.error(f"关闭{socket_name} Socket失败: {e}") + + # 关闭硬件 + self._shutdown_hardware() + + # 停止定时器 + if self.heartbeat_timer: + self.heartbeat_timer.stop() + if self.health_check_timer: + self.health_check_timer.stop() + + self.logger.info("资源清理完成") + + except Exception as e: + self.logger.error(f"清理资源失败: {e}") + +# ========== 声源定位核心算法 ========== +class KalmanFilter: + """卡尔曼滤波器,用于声源定位的噪声降低和精度提升""" + + def __init__(self, dim_x: int = 4, dim_z: int = 2): + """ + 初始化卡尔曼滤波器 + dim_x: 状态向量维度 (x, y, vx, vy) + dim_z: 观测向量维度 (x, y) + """ + self.dim_x = dim_x + self.dim_z = dim_z + + # 状态转移矩阵 F (假设匀速运动) + self.F = np.array([ + [1, 0, 1, 0], # x = x + vx + [0, 1, 0, 1], # y = y + vy + [0, 0, 1, 0], # vx = vx + [0, 0, 0, 1] # vy = vy + ]) + + # 观测矩阵 H (只能观测位置) + self.H = np.array([ + [1, 0, 0, 0], # 观测x + [0, 1, 0, 0] # 观测y + ]) + + # 状态协方差矩阵 P + self.P = np.eye(dim_x) * 1000 + + # 过程噪声协方差矩阵 Q + self.Q = np.eye(dim_x) * 0.1 + + # 观测噪声协方差矩阵 R + self.R = np.eye(dim_z) * 10 + + # 状态向量 x + self.x = np.zeros((dim_x, 1)) + + # 卡尔曼增益 + self.K = np.zeros((dim_x, dim_z)) + + self.logger = Logger("KalmanFilter") + + def predict(self, dt: float = 1.0): + """预测步骤""" + try: + # 更新状态转移矩阵中的时间步长 + self.F[0, 2] = dt + self.F[1, 3] = dt + + # 预测状态 + self.x = self.F @ self.x + + # 预测协方差 + self.P = self.F @ self.P @ self.F.T + self.Q + + except Exception as e: + self.logger.error(f"卡尔曼滤波预测失败: {e}") + + def update(self, measurement: np.ndarray): + """更新步骤""" + try: + # 计算卡尔曼增益 + S = self.H @ self.P @ self.H.T + self.R + self.K = self.P @ self.H.T @ np.linalg.inv(S) + + # 更新状态 + y = measurement.reshape(-1, 1) - self.H @ self.x + self.x = self.x + self.K @ y + + # 更新协方差 + I = np.eye(self.dim_x) + self.P = (I - self.K @ self.H) @ self.P + + except Exception as e: + self.logger.error(f"卡尔曼滤波更新失败: {e}") + + def get_state(self) -> Dict[str, float]: + """获取当前状态""" + return { + 'x': float(self.x[0, 0]), + 'y': float(self.x[1, 0]), + 'vx': float(self.x[2, 0]), + 'vy': float(self.x[3, 0]) + } + + def reset(self): + """重置滤波器""" + self.P = np.eye(self.dim_x) * 1000 + self.x = np.zeros((self.dim_x, 1)) + +class SoundSourceLocator: + """声源定位器,集成多种算法提升定位精度""" + + def __init__(self, config: HardwareConfig): + self.config = config + self.logger = Logger("SoundSourceLocator") + + # 卡尔曼滤波器 + self.kalman_filter = KalmanFilter() + + # 定位参数 + self.mic_positions = self._calculate_mic_positions() + self.sound_speed = 343.0 # 声速 (m/s) + self.sample_rate = 16000 + self.frame_size = 1024 + + # 噪声处理 + self.noise_threshold = 0.1 + self.signal_threshold = 0.3 + self.min_signal_duration = 0.1 # 最小信号持续时间 + + # 定位历史 + self.location_history = [] + self.max_history_size = 50 + + # 自适应参数 + self.adaptive_gain = 1.0 + self.adaptive_threshold = 0.2 + + # 多源检测 + self.max_sources = 3 + self.source_tracking = {} + + self.logger.info("声源定位器初始化完成") + + def _calculate_mic_positions(self) -> List[Tuple[float, float]]: + """计算麦克风阵列位置""" + # 假设麦克风阵列为圆形排列,半径为0.1米 + radius = 0.1 + mic_count = 4 + positions = [] + + for i in range(mic_count): + angle = 2 * math.pi * i / mic_count + x = radius * math.cos(angle) + y = radius * math.sin(angle) + positions.append((x, y)) + + return positions + + def process_audio_frame(self, audio_data: List[float]) -> Optional[LocationData]: + """处理音频帧,返回定位结果""" + try: + # 1. 信号预处理 + processed_data = self._preprocess_audio(audio_data) + if processed_data is None: + return None + + # 2. 时延估计 + delays = self._estimate_time_delays(processed_data) + if delays is None: + return None + + # 3. 声源定位 + raw_location = self._locate_source(delays) + if raw_location is None: + return None + + # 4. 卡尔曼滤波 + filtered_location = self._apply_kalman_filter(raw_location) + + # 5. 后处理 + final_location = self._post_process_location(filtered_location) + + # 6. 更新历史 + self._update_location_history(final_location) + + return final_location + + except Exception as e: + self.logger.error(f"音频帧处理失败: {e}") + return None + + def _preprocess_audio(self, audio_data: List[float]) -> Optional[List[List[float]]]: + """音频预处理""" + try: + # 检查数据有效性 + if len(audio_data) < self.frame_size * len(self.mic_positions): + return None + + # 分离各麦克风数据 + mic_data = [] + samples_per_mic = len(audio_data) // len(self.mic_positions) + + for i in range(len(self.mic_positions)): + start_idx = i * samples_per_mic + end_idx = start_idx + samples_per_mic + mic_data.append(audio_data[start_idx:end_idx]) + + # 应用窗函数 + window = np.hanning(self.frame_size) + for i in range(len(mic_data)): + if len(mic_data[i]) >= self.frame_size: + mic_data[i] = mic_data[i][:self.frame_size] * window + + # 噪声抑制 + for i in range(len(mic_data)): + mic_data[i] = self._noise_reduction(mic_data[i]) + + return mic_data + + except Exception as e: + self.logger.error(f"音频预处理失败: {e}") + return None + + def _noise_reduction(self, audio_data: List[float]) -> List[float]: + """噪声抑制""" + try: + # 计算信号能量 + energy = np.mean(np.array(audio_data) ** 2) + + # 自适应阈值 + threshold = self.adaptive_threshold * self.adaptive_gain + + # 如果信号能量低于阈值,认为是噪声 + if energy < threshold: + return [0.0] * len(audio_data) + + # 频谱减法降噪 + fft_data = np.fft.fft(audio_data) + magnitude = np.abs(fft_data) + phase = np.angle(fft_data) + + # 估计噪声谱 + noise_spectrum = self._estimate_noise_spectrum(magnitude) + + # 频谱减法 + clean_magnitude = magnitude - noise_spectrum + clean_magnitude = np.maximum(clean_magnitude, 0.1 * magnitude) + + # 重建信号 + clean_fft = clean_magnitude * np.exp(1j * phase) + clean_audio = np.real(np.fft.ifft(clean_fft)) + + return clean_audio.tolist() + + except Exception as e: + self.logger.error(f"噪声抑制失败: {e}") + return audio_data + + def _estimate_noise_spectrum(self, magnitude: np.ndarray) -> np.ndarray: + """估计噪声谱""" + # 简单的噪声谱估计,假设低频部分为噪声 + noise_spectrum = np.zeros_like(magnitude) + noise_spectrum[:len(magnitude)//4] = np.mean(magnitude[:len(magnitude)//4]) + return noise_spectrum + + def _estimate_time_delays(self, mic_data: List[List[float]]) -> Optional[List[float]]: + """估计时延""" + try: + if len(mic_data) < 2: + return None + + delays = [] + ref_mic = mic_data[0] + + for i in range(1, len(mic_data)): + # 计算互相关 + correlation = np.correlate(mic_data[i], ref_mic, mode='full') + + # 找到最大相关点 + max_idx = np.argmax(correlation) + + # 计算时延 + delay = (max_idx - len(ref_mic) + 1) / self.sample_rate + delays.append(delay) + + return delays + + except Exception as e: + self.logger.error(f"时延估计失败: {e}") + return None + + def _locate_source(self, delays: List[float]) -> Optional[LocationData]: + """声源定位""" + try: + if len(delays) < 2: + return None + + # 使用TDOA (Time Difference of Arrival) 方法 + # 构建线性方程组 + A = [] + b = [] + + for i in range(len(delays)): + dx = self.mic_positions[i+1][0] - self.mic_positions[0][0] + dy = self.mic_positions[i+1][1] - self.mic_positions[0][1] + dt = delays[i] + + A.append([2*dx, 2*dy]) + b.append(dx**2 + dy**2 - (self.sound_speed * dt)**2) + + # 最小二乘解 + A = np.array(A) + b = np.array(b) + + try: + x, residuals, rank, s = np.linalg.lstsq(A, b, rcond=None) + source_x, source_y = x[0], x[1] + except: + # 如果最小二乘失败,使用几何方法 + source_x, source_y = self._geometric_localization(delays) + + # 计算信号强度 + strength = self._calculate_signal_strength(delays) + + # 计算置信度 + confidence = self._calculate_confidence(delays, source_x, source_y) + + return LocationData( + x=source_x, + y=source_y, + strength=strength, + angle=math.degrees(math.atan2(source_y, source_x)), + timestamp=time.time(), + confidence=confidence, + quality=self._calculate_quality(delays), + noise_level=self._estimate_noise_level(delays), + source_type="speech" + ) + + except Exception as e: + self.logger.error(f"声源定位失败: {e}") + return None + + def _geometric_localization(self, delays: List[float]) -> Tuple[float, float]: + """几何定位方法""" + try: + # 使用双曲线交点法 + if len(delays) >= 2: + # 简化的几何定位 + r1 = abs(delays[0]) * self.sound_speed + r2 = abs(delays[1]) * self.sound_speed + + # 假设声源在麦克风阵列中心附近 + source_x = (r1 - r2) / 2 + source_y = math.sqrt(max(0, r1**2 - source_x**2)) + + return source_x, source_y + else: + return 0.0, 0.0 + + except Exception as e: + self.logger.error(f"几何定位失败: {e}") + return 0.0, 0.0 + + def _calculate_signal_strength(self, delays: List[float]) -> float: + """计算信号强度""" + try: + # 基于时延一致性计算信号强度 + if len(delays) < 2: + return 0.0 + + # 计算时延的标准差 + delay_std = np.std(delays) + + # 信号强度与标准差成反比 + strength = max(0.0, 1.0 - delay_std / 0.01) + + return strength + + except Exception as e: + self.logger.error(f"信号强度计算失败: {e}") + return 0.0 + + def _calculate_confidence(self, delays: List[float], x: float, y: float) -> float: + """计算定位置信度""" + try: + # 基于多个因素计算置信度 + factors = [] + + # 1. 时延一致性 + if len(delays) > 1: + delay_consistency = 1.0 - min(1.0, np.std(delays) / 0.01) + factors.append(delay_consistency) + + # 2. 距离合理性 + distance = math.sqrt(x**2 + y**2) + distance_factor = 1.0 - min(1.0, distance / 10.0) # 假设最大距离10米 + factors.append(distance_factor) + + # 3. 信号强度 + strength_factor = self._calculate_signal_strength(delays) + factors.append(strength_factor) + + # 综合置信度 + confidence = np.mean(factors) if factors else 0.0 + + return max(0.0, min(1.0, confidence)) + + except Exception as e: + self.logger.error(f"置信度计算失败: {e}") + return 0.0 + + def _calculate_quality(self, delays: List[float]) -> float: + """计算定位质量""" + try: + # 基于时延估计的质量 + if len(delays) < 2: + return 0.0 + + # 计算时延的SNR + signal_power = np.mean(np.array(delays) ** 2) + noise_power = np.var(delays) + + if noise_power > 0: + snr = 10 * math.log10(signal_power / noise_power) + quality = min(1.0, max(0.0, (snr + 20) / 40)) # 映射到0-1 + else: + quality = 1.0 + + return quality + + except Exception as e: + self.logger.error(f"质量计算失败: {e}") + return 0.0 + + def _estimate_noise_level(self, delays: List[float]) -> float: + """估计噪声水平""" + try: + if len(delays) < 2: + return 1.0 + + # 基于时延变化估计噪声 + noise_level = np.std(delays) / 0.01 # 归一化 + return min(1.0, max(0.0, noise_level)) + + except Exception as e: + self.logger.error(f"噪声水平估计失败: {e}") + return 1.0 + + def _apply_kalman_filter(self, location: LocationData) -> LocationData: + """应用卡尔曼滤波""" + try: + # 预测步骤 + self.kalman_filter.predict() + + # 更新步骤 + measurement = np.array([location.x, location.y]) + self.kalman_filter.update(measurement) + + # 获取滤波后的状态 + filtered_state = self.kalman_filter.get_state() + + # 更新定位数据 + location.x = filtered_state['x'] + location.y = filtered_state['y'] + location.angle = math.degrees(math.atan2(location.y, location.x)) + + return location + + except Exception as e: + self.logger.error(f"卡尔曼滤波失败: {e}") + return location + + def _post_process_location(self, location: LocationData) -> LocationData: + """后处理定位结果""" + try: + # 1. 异常值检测 + if self._is_outlier(location): + location = self._get_robust_location() + + # 2. 平滑处理 + location = self._smooth_location(location) + + # 3. 多源检测 + location = self._detect_multiple_sources(location) + + # 4. 自适应参数更新 + self._update_adaptive_parameters(location) + + return location + + except Exception as e: + self.logger.error(f"后处理失败: {e}") + return location + + def _is_outlier(self, location: LocationData) -> bool: + """检测异常值""" + try: + if len(self.location_history) < 3: + return False + + # 计算与历史位置的距离 + recent_locations = self.location_history[-3:] + distances = [] + + for hist_loc in recent_locations: + dist = math.sqrt((location.x - hist_loc.x)**2 + (location.y - hist_loc.y)**2) + distances.append(dist) + + # 如果距离过大,认为是异常值 + avg_distance = np.mean(distances) + return avg_distance > 2.0 # 2米阈值 + + except Exception as e: + self.logger.error(f"异常值检测失败: {e}") + return False + + def _get_robust_location(self) -> LocationData: + """获取鲁棒定位结果""" + try: + if len(self.location_history) < 2: + return LocationData(0, 0, 0, 0, time.time()) + + # 使用中位数滤波 + recent_locations = self.location_history[-5:] + x_values = [loc.x for loc in recent_locations] + y_values = [loc.y for loc in recent_locations] + + robust_x = np.median(x_values) + robust_y = np.median(y_values) + + return LocationData( + x=robust_x, + y=robust_y, + strength=0.5, + angle=math.degrees(math.atan2(robust_y, robust_x)), + timestamp=time.time(), + confidence=0.5, + quality=0.5, + noise_level=0.5, + source_type="estimated" + ) + + except Exception as e: + self.logger.error(f"鲁棒定位失败: {e}") + return LocationData(0, 0, 0, 0, time.time()) + + def _smooth_location(self, location: LocationData) -> LocationData: + """平滑定位结果""" + try: + if len(self.location_history) < 2: + return location + + # 指数平滑 + alpha = 0.7 # 平滑系数 + last_location = self.location_history[-1] + + smoothed_x = alpha * location.x + (1 - alpha) * last_location.x + smoothed_y = alpha * location.y + (1 - alpha) * last_location.y + + location.x = smoothed_x + location.y = smoothed_y + location.angle = math.degrees(math.atan2(location.y, location.x)) + + return location + + except Exception as e: + self.logger.error(f"平滑处理失败: {e}") + return location + + def _detect_multiple_sources(self, location: LocationData) -> LocationData: + """多源检测""" + try: + # 简单的多源检测逻辑 + current_time = time.time() + + # 检查是否有多个声源 + if len(self.source_tracking) > 0: + # 更新现有声源 + for source_id, source_info in list(self.source_tracking.items()): + if current_time - source_info['last_seen'] > 2.0: # 2秒超时 + del self.source_tracking[source_id] + else: + # 检查是否与当前检测匹配 + dist = math.sqrt((location.x - source_info['x'])**2 + + (location.y - source_info['y'])**2) + if dist < 1.0: # 1米内认为是同一声源 + source_info['x'] = location.x + source_info['y'] = location.y + source_info['last_seen'] = current_time + location.source_type = f"source_{source_id}" + return location + + # 添加新声源 + if len(self.source_tracking) < self.max_sources: + source_id = len(self.source_tracking) + 1 + self.source_tracking[source_id] = { + 'x': location.x, + 'y': location.y, + 'last_seen': current_time + } + location.source_type = f"source_{source_id}" + + return location + + except Exception as e: + self.logger.error(f"多源检测失败: {e}") + return location + + def _update_adaptive_parameters(self, location: LocationData): + """更新自适应参数""" + try: + # 根据定位质量调整参数 + if location.quality > 0.8: + self.adaptive_gain *= 1.05 # 提高增益 + elif location.quality < 0.3: + self.adaptive_gain *= 0.95 # 降低增益 + + # 限制增益范围 + self.adaptive_gain = max(0.1, min(2.0, self.adaptive_gain)) + + # 根据噪声水平调整阈值 + if location.noise_level > 0.7: + self.adaptive_threshold *= 1.1 + elif location.noise_level < 0.3: + self.adaptive_threshold *= 0.9 + + # 限制阈值范围 + self.adaptive_threshold = max(0.05, min(0.5, self.adaptive_threshold)) + + except Exception as e: + self.logger.error(f"自适应参数更新失败: {e}") + + def _update_location_history(self, location: LocationData): + """更新定位历史""" + try: + self.location_history.append(location) + + # 限制历史记录大小 + if len(self.location_history) > self.max_history_size: + self.location_history = self.location_history[-self.max_history_size:] + + except Exception as e: + self.logger.error(f"历史记录更新失败: {e}") + + def get_location_statistics(self) -> Dict[str, Any]: + """获取定位统计信息""" + try: + if len(self.location_history) < 2: + return {} + + # 计算定位精度统计 + x_values = [loc.x for loc in self.location_history] + y_values = [loc.y for loc in self.location_history] + confidence_values = [loc.confidence for loc in self.location_history] + quality_values = [loc.quality for loc in self.location_history] + + stats = { + 'total_locations': len(self.location_history), + 'avg_confidence': np.mean(confidence_values), + 'avg_quality': np.mean(quality_values), + 'x_std': np.std(x_values), + 'y_std': np.std(y_values), + 'position_stability': 1.0 / (1.0 + np.std(x_values) + np.std(y_values)), + 'adaptive_gain': self.adaptive_gain, + 'adaptive_threshold': self.adaptive_threshold, + 'active_sources': len(self.source_tracking) + } + + return stats + + except Exception as e: + self.logger.error(f"统计信息计算失败: {e}") + return {} + + def reset(self): + """重置定位器""" + try: + self.kalman_filter.reset() + self.location_history.clear() + self.source_tracking.clear() + self.adaptive_gain = 1.0 + self.adaptive_threshold = 0.2 + + self.logger.info("声源定位器已重置") + + except Exception as e: + self.logger.error(f"定位器重置失败: {e}") + +# ========== 枪声检测类 ========== +class GunshotDetector: + """枪声检测器,用于在定位模式下过滤非枪声信号""" + + def __init__(self, config: AudioConfig): + self.config = config + self.logger = Logger("GunshotDetector") + + # 检测参数 + self.detection_threshold = 0.3 # 检测阈值 + self.min_duration = 0.05 # 最小持续时间(秒) + self.max_duration = 0.5 # 最大持续时间(秒) + self.sample_rate = config.sample_rate + self.frame_size = config.chunk_size + + # 特征提取参数 + self.energy_threshold = 1000 # 能量阈值 + self.spectral_centroid_range = (1000, 8000) # 频谱质心范围 + self.zero_crossing_threshold = 0.1 # 过零率阈值 + + # 检测历史 + self.detection_history = [] + self.max_history_size = 50 + + # 自适应参数 + self.adaptive_threshold = self.detection_threshold + self.noise_floor = 100 + self.signal_ceiling = 30000 + + self.logger.info("枪声检测器初始化完成") + + def detect_gunshot(self, audio_data: List[float]) -> Dict[str, Any]: + """检测音频中是否包含枪声""" + try: + if len(audio_data) < self.frame_size: + return {'is_gunshot': False, 'confidence': 0.0, 'features': {}} + + # 1. 提取特征 + features = self._extract_features(audio_data) + + # 2. 计算检测分数 + detection_score = self._calculate_detection_score(features) + + # 3. 判断是否为枪声 + is_gunshot = detection_score > self.adaptive_threshold + + # 4. 更新自适应参数 + self._update_adaptive_parameters(features, detection_score) + + # 5. 更新检测历史 + self._update_detection_history(is_gunshot, detection_score, features) + + result = { + 'is_gunshot': is_gunshot, + 'confidence': detection_score, + 'features': features, + 'threshold': self.adaptive_threshold, + 'timestamp': time.time() + } + + if is_gunshot: + self.logger.info(f"检测到枪声!置信度: {detection_score:.3f}") + + return result + + except Exception as e: + self.logger.error(f"枪声检测失败: {e}") + return {'is_gunshot': False, 'confidence': 0.0, 'features': {}} + + def _extract_features(self, audio_data: List[float]) -> Dict[str, float]: + """提取音频特征""" + try: + audio_array = np.array(audio_data, dtype=np.float32) + + # 1. 能量特征 + energy = np.mean(audio_array ** 2) + rms = np.sqrt(energy) + + # 2. 频谱特征 + fft_data = np.fft.fft(audio_array) + magnitude = np.abs(fft_data) + + # 频谱质心 + freqs = np.fft.fftfreq(len(audio_array), 1/self.sample_rate) + spectral_centroid = np.sum(freqs * magnitude) / np.sum(magnitude) + + # 频谱带宽 + spectral_bandwidth = np.sqrt(np.sum(((freqs - spectral_centroid) ** 2) * magnitude) / np.sum(magnitude)) + + # 3. 时域特征 + # 过零率 + zero_crossings = np.sum(np.diff(np.sign(audio_array)) != 0) + zero_crossing_rate = zero_crossings / len(audio_array) + + # 峰值因子 + peak_factor = np.max(np.abs(audio_array)) / rms if rms > 0 else 0 + + # 4. 包络特征 + # 希尔伯特变换获取包络 + analytic_signal = np.fft.hilbert(audio_array) + envelope = np.abs(analytic_signal) + envelope_mean = np.mean(envelope) + envelope_std = np.std(envelope) + + features = { + 'energy': energy, + 'rms': rms, + 'spectral_centroid': spectral_centroid, + 'spectral_bandwidth': spectral_bandwidth, + 'zero_crossing_rate': zero_crossing_rate, + 'peak_factor': peak_factor, + 'envelope_mean': envelope_mean, + 'envelope_std': envelope_std + } + + return features + + except Exception as e: + self.logger.error(f"特征提取失败: {e}") + return {} + + def _calculate_detection_score(self, features: Dict[str, float]) -> float: + """计算检测分数""" + try: + if not features: + return 0.0 + + score = 0.0 + weights = { + 'energy': 0.3, + 'spectral_centroid': 0.25, + 'peak_factor': 0.2, + 'envelope_std': 0.15, + 'zero_crossing_rate': 0.1 + } + + # 1. 能量分数 + if 'energy' in features: + energy_score = min(1.0, features['energy'] / self.signal_ceiling) + score += weights['energy'] * energy_score + + # 2. 频谱质心分数 + if 'spectral_centroid' in features: + centroid = features['spectral_centroid'] + min_centroid, max_centroid = self.spectral_centroid_range + if min_centroid <= centroid <= max_centroid: + centroid_score = 1.0 + else: + centroid_score = 0.0 + score += weights['spectral_centroid'] * centroid_score + + # 3. 峰值因子分数 + if 'peak_factor' in features: + peak_factor = features['peak_factor'] + if peak_factor > 5.0: # 枪声通常有较高的峰值因子 + peak_score = min(1.0, peak_factor / 20.0) + else: + peak_score = 0.0 + score += weights['peak_factor'] * peak_score + + # 4. 包络标准差分数 + if 'envelope_std' in features: + envelope_std = features['envelope_std'] + if envelope_std > 1000: # 枪声包络变化较大 + envelope_score = min(1.0, envelope_std / 5000) + else: + envelope_score = 0.0 + score += weights['envelope_std'] * envelope_score + + # 5. 过零率分数 + if 'zero_crossing_rate' in features: + zcr = features['zero_crossing_rate'] + if zcr < self.zero_crossing_threshold: # 枪声过零率较低 + zcr_score = 1.0 - (zcr / self.zero_crossing_threshold) + else: + zcr_score = 0.0 + score += weights['zero_crossing_rate'] * zcr_score + + return min(1.0, max(0.0, score)) + + except Exception as e: + self.logger.error(f"检测分数计算失败: {e}") + return 0.0 + + def _update_adaptive_parameters(self, features: Dict[str, float], detection_score: float): + """更新自适应参数""" + try: + # 更新噪声水平 + if 'rms' in features: + rms = features['rms'] + if rms < self.signal_ceiling: + self.noise_floor = 0.9 * self.noise_floor + 0.1 * rms + + # 更新检测阈值 + if detection_score > 0.5: + # 检测到强信号,降低阈值 + self.adaptive_threshold *= 0.95 + elif detection_score < 0.1: + # 检测到弱信号,提高阈值 + self.adaptive_threshold *= 1.05 + + # 限制阈值范围 + self.adaptive_threshold = max(0.1, min(0.8, self.adaptive_threshold)) + + except Exception as e: + self.logger.error(f"自适应参数更新失败: {e}") + + def _update_detection_history(self, is_gunshot: bool, confidence: float, features: Dict[str, float]): + """更新检测历史""" + try: + history_entry = { + 'is_gunshot': is_gunshot, + 'confidence': confidence, + 'features': features, + 'timestamp': time.time() + } + + self.detection_history.append(history_entry) + + # 限制历史记录大小 + if len(self.detection_history) > self.max_history_size: + self.detection_history = self.detection_history[-self.max_history_size:] + + except Exception as e: + self.logger.error(f"检测历史更新失败: {e}") + + def get_detection_stats(self) -> Dict[str, Any]: + """获取检测统计信息""" + try: + if not self.detection_history: + return {} + + total_detections = len(self.detection_history) + gunshot_detections = sum(1 for entry in self.detection_history if entry['is_gunshot']) + + confidences = [entry['confidence'] for entry in self.detection_history] + + stats = { + 'total_detections': total_detections, + 'gunshot_detections': gunshot_detections, + 'detection_rate': gunshot_detections / total_detections if total_detections > 0 else 0, + 'avg_confidence': np.mean(confidences) if confidences else 0, + 'max_confidence': np.max(confidences) if confidences else 0, + 'adaptive_threshold': self.adaptive_threshold, + 'noise_floor': self.noise_floor + } + + return stats + + except Exception as e: + self.logger.error(f"获取检测统计失败: {e}") + return {} + + def reset(self): + """重置检测器""" + try: + self.detection_history.clear() + self.adaptive_threshold = self.detection_threshold + self.noise_floor = 100 + self.logger.info("枪声检测器已重置") + except Exception as e: + self.logger.error(f"检测器重置失败: {e}") + +def main(): + """主函数""" + print("=== 声源定位系统 - 开发板端(K210) ===") + print("正在初始化...") + + try: + board = DevelopmentBoard() + board.run() + except Exception as e: + print(f"程序启动失败: {e}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/声源定位代码/back-code/flask_app.py b/src/声源定位代码/back-code/flask_app.py deleted file mode 100644 index bd24fb7..0000000 --- a/src/声源定位代码/back-code/flask_app.py +++ /dev/null @@ -1,101 +0,0 @@ -import socket -import threading -import time -from flask import Flask, jsonify -from flask_cors import CORS - -app = Flask(__name__) -CORS(app) # 启用跨域支持 - -# 配置区 -TCP_IP = "0.0.0.0" -TCP_PORT = 12345 - -# 全局变量,用于存储最新数据 -latest_data = { - "X": 0.0, - "Y": 0.0, - "strength": 0.0, - "angle": 0.0 -} - -def clamp(value, min_val, max_val): - return max(min(value, max_val), min_val) - -def tcp_listener(): - """后台线程:监听 TCP 连接并更新 latest_data""" - global latest_data - server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - server_sock.bind((TCP_IP, TCP_PORT)) - server_sock.listen(1) - print(f"TCP服务器正在监听 {TCP_IP}:{TCP_PORT} ...") - client_sock, addr = server_sock.accept() - print("已接受连接:", addr) - client_sock.settimeout(0.1) - - while True: - try: - data_bytes = client_sock.recv(1024) - if not data_bytes: - print("连接已断开。") - break - line = data_bytes.decode('utf-8').strip() - if line: - # 数据格式: "X,Y,强度,Angle" - parts = line.split(',') - if len(parts) == 4: - try: - raw_x = float(parts[0]) - raw_y = float(parts[1]) - strength = float(parts[2]) - angle = float(parts[3]) - # 可自定义范围 - raw_x = clamp(raw_x, -15.0, 15.0) - raw_y = clamp(raw_y, -15.0, 15.0) - latest_data["X"] = raw_x - latest_data["Y"] = raw_y - latest_data["strength"] = strength - latest_data["angle"] = angle - - print(f"接收到声源: X={raw_x:.2f}, Y={raw_y:.2f}, 强度={strength:.2f}, 角度={angle:.2f}") - except ValueError: - print("TCP格式错误:", line) - else: - print("数据解析失败:", line) - except socket.timeout: - pass - except Exception as e: - print("TCP接收错误:", e) - time.sleep(0.1) - except Exception as e: - print(f"绑定或监听TCP端口 {TCP_PORT} 失败: {e}") - finally: - server_sock.close() - print("TCP服务器已关闭。") - -@app.route("/") -def index(): - return "后端已启动。可访问 /data 获取实时定位信息。" - -@app.route("/data") -def get_data(): - """ - 返回最新声源定位信息 - 例如: - { - "X": 1.23, - "Y": -0.45, - "strength": 2.5, - "angle": 90.0 - } - """ - return jsonify(latest_data) - -if __name__ == "__main__": - # 启动 TCP 后台线程 - t = threading.Thread(target=tcp_listener, daemon=True) - t.start() - - # 启动 Flask 服务 - app.run(host="0.0.0.0", port=5000, debug=False) \ No newline at end of file diff --git a/src/声源定位代码/back-code/pc_server.py b/src/声源定位代码/back-code/pc_server.py new file mode 100644 index 0000000..d84eaf4 --- /dev/null +++ b/src/声源定位代码/back-code/pc_server.py @@ -0,0 +1,2078 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +PC端服务器程序 - 声源定位系统 +功能: +1. 接收开发板发送的音频数据 +2. 使用audio-classification进行枪声识别 +3. 当识别到枪声时,发送指令给开发板切换到定位模式 +4. 接收开发板发送的声源定位数据并可视化 +5. 实时显示声源位置和轨迹 + +作者: 声源定位系统开发团队 +版本: 2.0.0 +日期: 2025 +""" + +import socket +import threading +import time +import wave +import tempfile +import os +import sys +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.animation import FuncAnimation +import queue +import json +import logging +import configparser +from datetime import datetime +from typing import Optional, Dict, Any, Tuple, List +from dataclasses import dataclass +from enum import Enum +import traceback +import signal +import atexit + +# Flask相关导入 +try: + from flask import Flask, jsonify + from flask_cors import CORS + FLASK_AVAILABLE = True +except ImportError: + print("警告: Flask模块未找到,HTTP API功能将不可用") + FLASK_AVAILABLE = False + +# 添加audio-classification项目路径 +sys.path.append(os.path.join(os.path.dirname(__file__), 'audio-classification')) + +try: + from macls.predict import MAClsPredictor + AUDIO_CLASSIFICATION_AVAILABLE = True +except ImportError: + print("警告: audio-classification模块未找到,将使用模拟识别") + AUDIO_CLASSIFICATION_AVAILABLE = False + +# ========== 枚举定义 ========== +class SystemMode(Enum): + """系统运行模式枚举""" + LISTENING = "LISTENING" # 监听模式 + LOCATING = "LOCATING" # 定位模式 + ERROR = "ERROR" # 错误模式 + SHUTDOWN = "SHUTDOWN" # 关闭模式 + +class ConnectionStatus(Enum): + """连接状态枚举""" + DISCONNECTED = "DISCONNECTED" + CONNECTING = "CONNECTING" + CONNECTED = "CONNECTED" + ERROR = "ERROR" + +# ========== 数据类定义 ========== +@dataclass +class LocationData: + """定位数据结构""" + x: float + y: float + strength: float + angle: float + timestamp: float + confidence: float = 1.0 + + def __post_init__(self): + """数据验证""" + if not isinstance(self.x, (int, float)) or not isinstance(self.y, (int, float)): + raise ValueError("坐标必须是数值类型") + if not isinstance(self.strength, (int, float)) or self.strength < 0: + raise ValueError("强度必须是非负数值") + if not isinstance(self.angle, (int, float)): + raise ValueError("角度必须是数值类型") + +@dataclass +class AudioConfig: + """音频配置""" + sample_rate: int = 16000 + channels: int = 1 + chunk_size: int = 1024 + format: str = "int16" + + def validate(self) -> bool: + """验证配置有效性""" + if self.sample_rate <= 0: + raise ValueError("采样率必须大于0") + if self.channels not in [1, 2]: + raise ValueError("声道数必须是1或2") + if self.chunk_size <= 0: + raise ValueError("音频块大小必须大于0") + return True + +@dataclass +class NetworkConfig: + """网络配置""" + host: str = "0.0.0.0" + port_audio: int = 12346 + port_cmd: int = 12347 + port_location: int = 12348 + timeout: float = 30.0 + buffer_size: int = 4096 + + def validate(self) -> bool: + """验证配置有效性""" + if not (1024 <= self.port_audio <= 65535): + raise ValueError("音频端口必须在1024-65535范围内") + if not (1024 <= self.port_cmd <= 65535): + raise ValueError("指令端口必须在1024-65535范围内") + if not (1024 <= self.port_location <= 65535): + raise ValueError("定位端口必须在1024-65535范围内") + if self.timeout <= 0: + raise ValueError("超时时间必须大于0") + return True + +# ========== 日志配置 ========== +def setup_logging(log_level: str = "INFO", log_file: str = "pc_server.log") -> logging.Logger: + """设置日志系统""" + # 创建日志目录 + log_dir = "logs" + os.makedirs(log_dir, exist_ok=True) + + # 配置日志格式 + log_format = "%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s" + date_format = "%Y-%m-%d %H:%M:%S" + + # 创建日志记录器 + logger = logging.getLogger("PCServer") + logger.setLevel(getattr(logging, log_level.upper())) + + # 清除现有的处理器 + logger.handlers.clear() + + # 文件处理器 + file_handler = logging.FileHandler( + os.path.join(log_dir, log_file), + encoding='utf-8' + ) + file_handler.setLevel(logging.DEBUG) + file_formatter = logging.Formatter(log_format, date_format) + file_handler.setFormatter(file_formatter) + logger.addHandler(file_handler) + + # 控制台处理器 + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.INFO) + console_formatter = logging.Formatter( + "%(asctime)s - %(levelname)s - %(message)s", + date_format + ) + console_handler.setFormatter(console_formatter) + logger.addHandler(console_handler) + + return logger + +# ========== 配置管理 ========== +class ConfigManager: + """配置管理器""" + + def __init__(self, config_file: str = "config.ini"): + self.config_file = config_file + self.config = configparser.ConfigParser() + self.logger = logging.getLogger("ConfigManager") + self.load_config() + + def load_config(self) -> None: + """加载配置文件""" + try: + if os.path.exists(self.config_file): + self.config.read(self.config_file, encoding='utf-8') + self.logger.info(f"配置文件加载成功: {self.config_file}") + else: + self.create_default_config() + except Exception as e: + self.logger.error(f"加载配置文件失败: {e}") + self.create_default_config() + + def create_default_config(self) -> None: + """创建默认配置""" + self.config['NETWORK'] = { + 'host': '0.0.0.0', + 'port_audio': '12346', + 'port_cmd': '12347', + 'port_location': '12348', + 'timeout': '30.0', + 'buffer_size': '4096' + } + + self.config['AUDIO'] = { + 'sample_rate': '16000', + 'channels': '1', + 'chunk_size': '1024', + 'format': 'int16' + } + + self.config['RECOGNITION'] = { + 'gunshot_threshold': '0.7', + 'recognition_interval': '3.0', + 'configs_path': 'audio-classification/configs/cam++.yml', + 'model_path': 'audio-classification/models/CAMPPlus_Fbank/best_model/' + } + + self.config['VISUALIZATION'] = { + 'plot_interval_ms': '100', + 'plot_range': '15.0', + 'point_size': '200', + 'max_history_points': '100' + } + + self.config['KALMAN'] = { + 'q_x': '5.0', + 'r_x': '0.01', + 'q_y': '5.0', + 'r_y': '0.01', + 'q_strength': '5.0', + 'r_strength': '0.01', + 'q_angle': '5.0', + 'r_angle': '0.01' + } + + self.config['HTTP_API'] = { + 'enabled': 'True', + 'host': '0.0.0.0', + 'port': '5000', + 'cors_enabled': 'True' + } + + self.save_config() + + def save_config(self) -> None: + """保存配置文件""" + try: + with open(self.config_file, 'w', encoding='utf-8') as f: + self.config.write(f) + self.logger.info(f"配置文件保存成功: {self.config_file}") + except Exception as e: + self.logger.error(f"保存配置文件失败: {e}") + + def get_network_config(self) -> NetworkConfig: + """获取网络配置""" + try: + return NetworkConfig( + host=self.config.get('NETWORK', 'host'), + port_audio=self.config.getint('NETWORK', 'port_audio'), + port_cmd=self.config.getint('NETWORK', 'port_cmd'), + port_location=self.config.getint('NETWORK', 'port_location'), + timeout=self.config.getfloat('NETWORK', 'timeout'), + buffer_size=self.config.getint('NETWORK', 'buffer_size') + ) + except Exception as e: + self.logger.error(f"获取网络配置失败: {e}") + return NetworkConfig() + + def get_audio_config(self) -> AudioConfig: + """获取音频配置""" + try: + return AudioConfig( + sample_rate=self.config.getint('AUDIO', 'sample_rate'), + channels=self.config.getint('AUDIO', 'channels'), + chunk_size=self.config.getint('AUDIO', 'chunk_size'), + format=self.config.get('AUDIO', 'format') + ) + except Exception as e: + self.logger.error(f"获取音频配置失败: {e}") + return AudioConfig() + +# ========== 卡尔曼滤波器类 ========== +class KalmanFilter: + """卡尔曼滤波器类""" + + def __init__(self, initial_value: float = 0.0, q: float = 5.0, r: float = 0.01): + """ + 初始化卡尔曼滤波器 + + Args: + initial_value: 初始值 + q: 过程噪声协方差 + r: 测量噪声协方差 + """ + self.x_hat = initial_value # 状态估计 + self.p = 1000.0 # 估计误差协方差 + self.q = q # 过程噪声协方差 + self.r = r # 测量噪声协方差 + self.k = 0.0 # 卡尔曼增益 + + def update(self, measurement: float) -> float: + """ + 更新卡尔曼滤波器 + + Args: + measurement: 测量值 + + Returns: + 滤波后的值 + """ + try: + # 预测步骤 + p_pred = self.p + self.q + + # 更新步骤 + self.k = p_pred / (p_pred + self.r) + self.x_hat = self.x_hat + self.k * (measurement - self.x_hat) + self.p = (1 - self.k) * p_pred + + return self.x_hat + except Exception as e: + logging.error(f"卡尔曼滤波更新失败: {e}") + return measurement + + def reset(self, initial_value: float = 0.0) -> None: + """重置滤波器""" + self.x_hat = initial_value + self.p = 1000.0 + self.k = 0.0 + +class MultiKalmanFilter: + """多变量卡尔曼滤波器管理器""" + + def __init__(self, config: Dict[str, float]): + """ + 初始化多变量卡尔曼滤波器 + + Args: + config: 配置字典,包含各变量的Q和R值 + """ + self.filters = { + 'x': KalmanFilter(q=config.get('q_x', 5.0), r=config.get('r_x', 0.01)), + 'y': KalmanFilter(q=config.get('q_y', 5.0), r=config.get('r_y', 0.01)), + 'strength': KalmanFilter(q=config.get('q_strength', 5.0), r=config.get('r_strength', 0.01)), + 'angle': KalmanFilter(q=config.get('q_angle', 5.0), r=config.get('r_angle', 0.01)) + } + self.logger = logging.getLogger("MultiKalmanFilter") + + def filter_location_data(self, location_data: LocationData) -> LocationData: + """ + 对定位数据进行卡尔曼滤波 + + Args: + location_data: 原始定位数据 + + Returns: + 滤波后的定位数据 + """ + try: + filtered_x = self.filters['x'].update(location_data.x) + filtered_y = self.filters['y'].update(location_data.y) + filtered_strength = self.filters['strength'].update(location_data.strength) + filtered_angle = self.filters['angle'].update(location_data.angle) + + return LocationData( + x=filtered_x, + y=filtered_y, + strength=filtered_strength, + angle=filtered_angle, + timestamp=location_data.timestamp, + confidence=location_data.confidence + ) + except Exception as e: + self.logger.error(f"卡尔曼滤波失败: {e}") + return location_data + + def reset_all(self) -> None: + """重置所有滤波器""" + for filter_name, kalman_filter in self.filters.items(): + kalman_filter.reset() + self.logger.info("所有卡尔曼滤波器已重置") + +# ========== 声源定位后处理 ========== +class LocationPostProcessor: + """声源定位后处理,包括轨迹平滑、异常值剔除、历史统计等""" + def __init__(self, max_history: int = 100): + self.history: List[LocationData] = [] + self.max_history = max_history + self.logger = logging.getLogger("LocationPostProcessor") + + def process(self, location: LocationData) -> LocationData: + """对定位数据进行后处理""" + try: + # 1. 异常值剔除 + if self.is_outlier(location): + self.logger.warning(f"检测到异常定位点: x={location.x:.2f}, y={location.y:.2f}") + location = self.get_robust_location() + # 2. 轨迹平滑 + location = self.smooth_location(location) + # 3. 更新历史 + self.update_history(location) + return location + except Exception as e: + self.logger.error(f"定位后处理失败: {e}") + return location + + def is_outlier(self, location: LocationData) -> bool: + if len(self.history) < 3: + return False + recent = self.history[-3:] + dists = [np.hypot(location.x - l.x, location.y - l.y) for l in recent] + return np.mean(dists) > 5.0 # 5米阈值 + + def get_robust_location(self) -> LocationData: + if not self.history: + return LocationData(0, 0, 0, 0, time.time()) + xs = [l.x for l in self.history[-5:]] + ys = [l.y for l in self.history[-5:]] + return LocationData( + x=float(np.median(xs)), + y=float(np.median(ys)), + strength=0.5, + angle=0.0, + timestamp=time.time(), + confidence=0.5 + ) + + def smooth_location(self, location: LocationData) -> LocationData: + if not self.history: + return location + alpha = 0.7 + last = self.history[-1] + location.x = alpha * location.x + (1 - alpha) * last.x + location.y = alpha * location.y + (1 - alpha) * last.y + return location + + def update_history(self, location: LocationData): + self.history.append(location) + if len(self.history) > self.max_history: + self.history = self.history[-self.max_history:] + +# ========== 音频处理优化 ========== +class AudioProcessor: + """优化的音频处理器,支持批量处理、性能监控和智能缓冲""" + + def __init__(self, config: AudioConfig, recognition_config: Dict[str, Any]): + self.config = config + self.recognition_config = recognition_config + self.logger = logging.getLogger("AudioProcessor") + + # 音频缓冲管理 + self.audio_buffer = [] + self.buffer_lock = threading.Lock() + self.max_buffer_size = int(config.sample_rate * recognition_config['recognition_interval'] * 2) + self.min_buffer_size = int(config.sample_rate * recognition_config['recognition_interval'] * 0.5) + + # 性能监控 + self.processing_stats = { + 'total_audio_bytes': 0, + 'total_audio_frames': 0, + 'recognition_attempts': 0, + 'gunshot_detections': 0, + 'processing_times': [], + 'buffer_overflow_count': 0, + 'last_recognition_time': 0 + } + + # 音频质量检测 + self.quality_thresholds = { + 'min_rms': 50, # 最小RMS值 + 'max_rms': 30000, # 最大RMS值 + 'min_nonzero_ratio': 0.1, # 最小非零比例 + 'max_silence_ratio': 0.8 # 最大静音比例 + } + + # 预测器 + self.predictor = None + self._init_predictor() + + self.logger.info("音频处理器初始化完成") + + def _init_predictor(self): + """初始化音频分类预测器""" + if not AUDIO_CLASSIFICATION_AVAILABLE: + self.logger.warning("audio-classification模块未找到,将使用模拟识别") + return + + try: + configs_path = os.path.abspath(self.recognition_config['configs_path']) + model_path = os.path.abspath(self.recognition_config['model_path']) + + if not os.path.exists(configs_path): + self.logger.error(f"配置文件不存在: {configs_path}") + return + + if not os.path.exists(model_path): + self.logger.error(f"模型路径不存在: {model_path}") + return + + self.predictor = MAClsPredictor( + configs=configs_path, + model_path=model_path, + use_gpu=self.recognition_config['use_gpu'] + ) + + self.logger.info("音频分类预测器初始化成功") + + except Exception as e: + self.logger.error(f"音频分类预测器初始化失败: {e}") + self.predictor = None + + def add_audio_data(self, audio_data: bytes) -> bool: + """添加音频数据到缓冲区""" + try: + with self.buffer_lock: + # 转换为numpy数组 + audio_array = np.frombuffer(audio_data, dtype=np.int16) + + # 更新统计 + self.processing_stats['total_audio_bytes'] += len(audio_data) + self.processing_stats['total_audio_frames'] += len(audio_array) + + # 添加到缓冲区 + self.audio_buffer.extend(audio_array) + + # 检查缓冲区大小 + if len(self.audio_buffer) > self.max_buffer_size: + # 保留最新的数据 + self.audio_buffer = self.audio_buffer[-self.max_buffer_size:] + self.processing_stats['buffer_overflow_count'] += 1 + self.logger.warning("音频缓冲区溢出,丢弃旧数据") + + return True + + except Exception as e: + self.logger.error(f"添加音频数据失败: {e}") + return False + + def should_process_recognition(self) -> bool: + """判断是否应该进行识别""" + try: + current_time = time.time() + + # 检查时间间隔 + if (current_time - self.processing_stats['last_recognition_time'] < + self.recognition_config['recognition_interval']): + return False + + # 检查缓冲区大小 + with self.buffer_lock: + return len(self.audio_buffer) >= self.min_buffer_size + + except Exception as e: + self.logger.error(f"判断识别条件失败: {e}") + return False + + def process_recognition(self) -> Optional[Dict[str, Any]]: + """执行音频识别""" + try: + start_time = time.time() + + with self.buffer_lock: + # 获取音频数据 + if len(self.audio_buffer) < self.min_buffer_size: + return None + + # 提取识别所需的数据 + recognition_samples = int(self.config.sample_rate * self.recognition_config['recognition_interval']) + audio_segment = np.array(self.audio_buffer[-recognition_samples:]) + + # 清空缓冲区 + self.audio_buffer = [] + + # 检查音频质量 + if not self._check_audio_quality(audio_segment): + self.logger.debug("音频质量不满足识别要求") + return None + + # 执行识别 + result = self._perform_recognition(audio_segment) + + # 更新统计 + processing_time = time.time() - start_time + self.processing_stats['processing_times'].append(processing_time) + self.processing_stats['recognition_attempts'] += 1 + self.processing_stats['last_recognition_time'] = time.time() + + # 保持处理时间历史记录 + if len(self.processing_stats['processing_times']) > 100: + self.processing_stats['processing_times'] = self.processing_stats['processing_times'][-100:] + + if result and result.get('is_gunshot', False): + self.processing_stats['gunshot_detections'] += 1 + + self.logger.info(f"音频识别完成: {result}, 耗时: {processing_time:.3f}秒") + return result + + except Exception as e: + self.logger.error(f"音频识别失败: {e}") + return None + + def _check_audio_quality(self, audio_segment: np.ndarray) -> bool: + """检查音频质量""" + try: + if len(audio_segment) == 0: + return False + + # 计算RMS值 + rms = np.sqrt(np.mean(audio_segment.astype(np.float32) ** 2)) + + # 检查RMS范围 + if not (self.quality_thresholds['min_rms'] <= rms <= self.quality_thresholds['max_rms']): + return False + + # 检查非零比例 + nonzero_ratio = np.count_nonzero(audio_segment) / len(audio_segment) + if nonzero_ratio < self.quality_thresholds['min_nonzero_ratio']: + return False + + # 检查静音比例 + silence_threshold = 100 + silence_ratio = np.sum(np.abs(audio_segment) < silence_threshold) / len(audio_segment) + if silence_ratio > self.quality_thresholds['max_silence_ratio']: + return False + + return True + + except Exception as e: + self.logger.error(f"音频质量检查失败: {e}") + return False + + def _perform_recognition(self, audio_segment: np.ndarray) -> Optional[Dict[str, Any]]: + """执行实际的音频识别""" + try: + if self.predictor: + return self._perform_real_recognition(audio_segment) + else: + return self._perform_simulated_recognition(audio_segment) + + except Exception as e: + self.logger.error(f"执行识别失败: {e}") + return None + + def _perform_real_recognition(self, audio_segment: np.ndarray) -> Dict[str, Any]: + """执行真实音频识别""" + try: + # 保存为临时文件 + with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_file: + temp_path = temp_file.name + + # 保存音频文件 + with wave.open(temp_path, 'wb') as wav_file: + wav_file.setnchannels(self.config.channels) + wav_file.setsampwidth(2) # 16位 + wav_file.setframerate(self.config.sample_rate) + wav_file.writeframes(audio_segment.tobytes()) + + # 进行预测 + label, score = self.predictor.predict(audio_data=temp_path) + + # 清理临时文件 + try: + os.unlink(temp_path) + except Exception as e: + self.logger.warning(f"清理临时文件失败: {e}") + + # 判断是否为枪声 + is_gunshot = self._is_gunshot(label, score) + + return { + 'label': label, + 'score': score, + 'is_gunshot': is_gunshot, + 'confidence': score, + 'timestamp': time.time() + } + + except Exception as e: + self.logger.error(f"真实音频识别失败: {e}") + raise + + def _perform_simulated_recognition(self, audio_segment: np.ndarray) -> Dict[str, Any]: + """执行模拟音频识别""" + try: + # 模拟识别延迟 + time.sleep(0.05) + + # 基于音频特征进行简单判断 + rms = np.sqrt(np.mean(audio_segment.astype(np.float32) ** 2)) + + # 模拟检测逻辑 + detection_probability = 0.02 # 2%基础概率 + + # 根据RMS值调整概率 + if rms > 1000: + detection_probability += 0.03 + if rms > 5000: + detection_probability += 0.05 + + # 随机检测 + is_gunshot = np.random.random() < detection_probability + + return { + 'label': 'simulated_gunshot' if is_gunshot else 'simulated_normal', + 'score': 0.8 if is_gunshot else 0.2, + 'is_gunshot': is_gunshot, + 'confidence': 0.8 if is_gunshot else 0.2, + 'timestamp': time.time() + } + + except Exception as e: + self.logger.error(f"模拟音频识别失败: {e}") + return { + 'label': 'error', + 'score': 0.0, + 'is_gunshot': False, + 'confidence': 0.0, + 'timestamp': time.time() + } + + def _is_gunshot(self, label: str, score: float) -> bool: + """判断是否为枪声""" + try: + # 检查置信度阈值 + if score < self.recognition_config['gunshot_threshold']: + return False + + # 检查标签关键词 + gunshot_keywords = ['gun', 'shot', 'fire', 'gunshot', 'firearm', 'weapon', 'bang', 'explosion'] + label_lower = label.lower() + + for keyword in gunshot_keywords: + if keyword in label_lower: + return True + + return False + + except Exception as e: + self.logger.error(f"枪声判断失败: {e}") + return False + + def get_performance_stats(self) -> Dict[str, Any]: + """获取性能统计""" + try: + processing_times = self.processing_stats['processing_times'] + + stats = { + 'total_audio_bytes': self.processing_stats['total_audio_bytes'], + 'total_audio_frames': self.processing_stats['total_audio_frames'], + 'recognition_attempts': self.processing_stats['recognition_attempts'], + 'gunshot_detections': self.processing_stats['gunshot_detections'], + 'detection_rate': (self.processing_stats['gunshot_detections'] / + max(self.processing_stats['recognition_attempts'], 1)), + 'buffer_overflow_count': self.processing_stats['buffer_overflow_count'], + 'current_buffer_size': len(self.audio_buffer), + 'avg_processing_time': np.mean(processing_times) if processing_times else 0, + 'max_processing_time': np.max(processing_times) if processing_times else 0, + 'min_processing_time': np.min(processing_times) if processing_times else 0 + } + + return stats + + except Exception as e: + self.logger.error(f"获取性能统计失败: {e}") + return {} + +# ========== 通信优化 ========== +class CommunicationManager: + """优化的通信管理器,支持连接池、重试机制和性能监控""" + + def __init__(self, network_config: NetworkConfig): + self.network_config = network_config + self.logger = logging.getLogger("CommunicationManager") + + # 连接状态 + self.connections = { + 'audio': None, + 'cmd': None, + 'location': None + } + + # 连接统计 + self.connection_stats = { + 'total_sent': 0, + 'total_received': 0, + 'connection_attempts': 0, + 'connection_failures': 0, + 'last_heartbeat': 0 + } + + # 重试配置 + self.retry_config = { + 'max_retries': 3, + 'retry_delay': 1.0, + 'backoff_factor': 2.0 + } + + self.logger.info("通信管理器初始化完成") + + def accept_all_connections(self) -> bool: + """接受所有客户端连接""" + try: + self.logger.info("等待客户端连接...") + + # 并行接受连接 + connection_threads = [] + for conn_type in ['audio', 'cmd', 'location']: + thread = threading.Thread( + target=self._accept_connection, + args=(conn_type,), + name=f"Accept_{conn_type.capitalize()}", + daemon=True + ) + thread.start() + connection_threads.append(thread) + + # 等待所有连接完成 + for thread in connection_threads: + thread.join(timeout=30.0) + if thread.is_alive(): + self.logger.error(f"连接线程 {thread.name} 超时") + return False + + # 检查连接状态 + all_connected = all(conn is not None for conn in self.connections.values()) + + if all_connected: + self.logger.info("所有客户端连接成功") + self.connection_stats['last_heartbeat'] = time.time() + return True + else: + self.logger.error("部分客户端连接失败") + return False + + except Exception as e: + self.logger.error(f"接受连接失败: {e}") + return False + + def _accept_connection(self, conn_type: str): + """接受单个连接""" + try: + server_map = { + 'audio': self.network_config.port_audio, + 'cmd': self.network_config.port_cmd, + 'location': self.network_config.port_location + } + + port = server_map[conn_type] + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + server.settimeout(self.network_config.timeout) + + server.bind((self.network_config.host, port)) + server.listen(1) + + self.logger.info(f"等待{conn_type}客户端连接: {self.network_config.host}:{port}") + + client, addr = server.accept() + client.settimeout(self.network_config.timeout) + + self.connections[conn_type] = client + self.connection_stats['connection_attempts'] += 1 + + self.logger.info(f"{conn_type}客户端连接成功: {addr}") + + server.close() + + except Exception as e: + self.logger.error(f"接受{conn_type}连接失败: {e}") + self.connection_stats['connection_failures'] += 1 + self.connections[conn_type] = None + + def send_command(self, command: str, retry: bool = True) -> bool: + """发送指令到开发板""" + try: + if not self.connections['cmd']: + self.logger.error("指令连接未建立") + return False + + # 构建完整指令 + full_command = { + 'command': command, + 'timestamp': time.time(), + 'sequence': self.connection_stats['total_sent'] + } + + command_data = json.dumps(full_command).encode('utf-8') + + # 发送指令 + if retry: + return self._send_with_retry(command_data) + else: + self.connections['cmd'].send(command_data) + self.connection_stats['total_sent'] += 1 + return True + + except Exception as e: + self.logger.error(f"发送指令失败: {e}") + return False + + def _send_with_retry(self, data: bytes) -> bool: + """带重试机制的发送""" + for attempt in range(self.retry_config['max_retries']): + try: + self.connections['cmd'].send(data) + self.connection_stats['total_sent'] += 1 + return True + + except Exception as e: + self.logger.warning(f"发送失败 (尝试 {attempt + 1}/{self.retry_config['max_retries']}): {e}") + + if attempt < self.retry_config['max_retries'] - 1: + delay = self.retry_config['retry_delay'] * (self.retry_config['backoff_factor'] ** attempt) + time.sleep(delay) + + self.logger.error("发送失败,已达到最大重试次数") + return False + + def receive_audio_data(self) -> Optional[bytes]: + """接收音频数据""" + try: + if not self.connections['audio']: + return None + + data = self.connections['audio'].recv(self.network_config.buffer_size) + if data: + self.connection_stats['total_received'] += len(data) + return data + + except socket.timeout: + return None + except Exception as e: + self.logger.error(f"接收音频数据失败: {e}") + return None + + def receive_location_data(self) -> Optional[bytes]: + """接收定位数据""" + try: + if not self.connections['location']: + return None + + data = self.connections['location'].recv(self.network_config.buffer_size) + if data: + self.connection_stats['total_received'] += len(data) + return data + + except socket.timeout: + return None + except Exception as e: + self.logger.error(f"接收定位数据失败: {e}") + return None + + def send_heartbeat(self) -> bool: + """发送心跳""" + try: + heartbeat_data = { + 'type': 'heartbeat', + 'timestamp': time.time(), + 'stats': self.connection_stats.copy() + } + + return self.send_command(json.dumps(heartbeat_data), retry=False) + + except Exception as e: + self.logger.error(f"发送心跳失败: {e}") + return False + + def get_connection_stats(self) -> Dict[str, Any]: + """获取连接统计""" + try: + current_time = time.time() + + return { + 'connections': {k: v is not None for k, v in self.connections.items()}, + 'stats': self.connection_stats.copy(), + 'uptime': current_time - self.connection_stats['last_heartbeat'], + 'data_rate': self.connection_stats['total_received'] / max(current_time - self.connection_stats['last_heartbeat'], 1) + } + + except Exception as e: + self.logger.error(f"获取连接统计失败: {e}") + return {} + + def close_all_connections(self): + """关闭所有连接""" + try: + for conn_type, connection in self.connections.items(): + if connection: + try: + connection.close() + self.logger.info(f"关闭{conn_type}连接") + except Exception as e: + self.logger.error(f"关闭{conn_type}连接失败: {e}") + finally: + self.connections[conn_type] = None + + except Exception as e: + self.logger.error(f"关闭连接失败: {e}") + +# ========== 全局变量 ========== +logger = setup_logging() +config_manager = ConfigManager() +kalman_filter = MultiKalmanFilter({ + 'q_x': 5.0, 'r_x': 0.01, + 'q_y': 5.0, 'r_y': 0.01, + 'q_strength': 5.0, 'r_strength': 0.01, + 'q_angle': 5.0, 'r_angle': 0.01 +}) +location_post_processor = LocationPostProcessor() + +# ========== 主服务器类 ========== +class PCServer: + """PC端服务器主类""" + + def __init__(self): + """初始化PC服务器""" + self.logger = logging.getLogger("PCServer") + self.logger.info("开始初始化PC服务器...") + + # 系统状态 + self.running = True + self.current_mode = SystemMode.LISTENING + self.connection_status = { + 'audio': ConnectionStatus.DISCONNECTED, + 'cmd': ConnectionStatus.DISCONNECTED, + 'location': ConnectionStatus.DISCONNECTED + } + + # 配置 + self.network_config = config_manager.get_network_config() + self.audio_config = config_manager.get_audio_config() + self.recognition_config = self._load_recognition_config() + self.visualization_config = self._load_visualization_config() + self.http_api_config = self._load_http_api_config() + + # 验证配置 + self._validate_configs() + + # 初始化核心组件 + self.audio_processor = AudioProcessor(self.audio_config, self.recognition_config) + self.communication_manager = CommunicationManager(self.network_config) + + # 网络相关 + self.audio_server = None + self.cmd_server = None + self.location_server = None + + # 定位数据 + self.location_queue = queue.Queue(maxsize=1000) + self.location_history = [] + self.current_location = None + self.location_data_count = 0 + + # 新增:开发板枪声检测统计 + self.board_gunshot_stats = { + 'total_detections': 0, + 'gunshot_detections': 0, + 'detection_rate': 0.0, + 'avg_confidence': 0.0, + 'last_detection_time': 0 + } + + # 线程管理 + self.threads = [] + self.thread_lock = threading.Lock() + + # 性能监控 + self.performance_stats = { + 'start_time': time.time(), + 'audio_packets_received': 0, + 'location_packets_received': 0, + 'recognition_attempts': 0, + 'errors': 0, + 'board_gunshot_detections': 0 # 新增:开发板枪声检测次数 + } + + # HTTP API相关 + self.flask_app = None + self.flask_thread = None + if FLASK_AVAILABLE and self.http_api_config['enabled']: + self._init_flask_app() + + # 初始化各个模块 + try: + self._init_network_servers() + self._init_visualization() + self._init_data_structures() + + self.logger.info("PC服务器初始化完成") + self.logger.info(f"当前模式: {self.current_mode.value}") + self.logger.info(f"网络配置: {self.network_config}") + self.logger.info(f"音频配置: {self.audio_config}") + + except Exception as e: + self.logger.error(f"PC服务器初始化失败: {e}") + self.logger.error(traceback.format_exc()) + self.current_mode = SystemMode.ERROR + raise + + def _load_recognition_config(self) -> Dict[str, Any]: + """加载识别配置""" + try: + return { + 'gunshot_threshold': config_manager.config.getfloat('RECOGNITION', 'gunshot_threshold'), + 'recognition_interval': config_manager.config.getfloat('RECOGNITION', 'recognition_interval'), + 'configs_path': config_manager.config.get('RECOGNITION', 'configs_path'), + 'model_path': config_manager.config.get('RECOGNITION', 'model_path'), + 'use_gpu': True, + 'batch_size': 1 + } + except Exception as e: + self.logger.error(f"加载识别配置失败: {e}") + return { + 'gunshot_threshold': 0.7, + 'recognition_interval': 3.0, + 'configs_path': 'audio-classification/configs/cam++.yml', + 'model_path': 'audio-classification/models/CAMPPlus_Fbank/best_model/', + 'use_gpu': True, + 'batch_size': 1 + } + + def _load_visualization_config(self) -> Dict[str, Any]: + """加载可视化配置""" + try: + return { + 'plot_interval_ms': config_manager.config.getint('VISUALIZATION', 'plot_interval_ms'), + 'plot_range': config_manager.config.getfloat('VISUALIZATION', 'plot_range'), + 'point_size': config_manager.config.getint('VISUALIZATION', 'point_size'), + 'max_history_points': config_manager.config.getint('VISUALIZATION', 'max_history_points') + } + except Exception as e: + self.logger.error(f"加载可视化配置失败: {e}") + return { + 'plot_interval_ms': 100, + 'plot_range': 15.0, + 'point_size': 200, + 'max_history_points': 100 + } + + def _load_http_api_config(self) -> Dict[str, Any]: + """加载HTTP API配置""" + try: + return { + 'enabled': config_manager.config.getboolean('HTTP_API', 'enabled'), + 'host': config_manager.config.get('HTTP_API', 'host'), + 'port': config_manager.config.getint('HTTP_API', 'port'), + 'cors_enabled': config_manager.config.getboolean('HTTP_API', 'cors_enabled') + } + except Exception as e: + self.logger.error(f"加载HTTP API配置失败: {e}") + return { + 'enabled': True, + 'host': '0.0.0.0', + 'port': 5000, + 'cors_enabled': True + } + + def _validate_configs(self) -> None: + """验证配置有效性""" + try: + self.network_config.validate() + self.audio_config.validate() + + # 验证识别配置 + if self.recognition_config['gunshot_threshold'] < 0 or self.recognition_config['gunshot_threshold'] > 1: + raise ValueError("枪声识别阈值必须在0-1之间") + if self.recognition_config['recognition_interval'] <= 0: + raise ValueError("识别间隔必须大于0") + + # 验证可视化配置 + if self.visualization_config['plot_interval_ms'] <= 0: + raise ValueError("绘图间隔必须大于0") + if self.visualization_config['plot_range'] <= 0: + raise ValueError("绘图范围必须大于0") + if self.visualization_config['max_history_points'] <= 0: + raise ValueError("历史点数量必须大于0") + + self.logger.info("配置验证通过") + + except Exception as e: + self.logger.error(f"配置验证失败: {e}") + raise + + def _init_network_servers(self) -> None: + """初始化网络服务器""" + try: + # 音频接收服务器 + self.audio_server = self._create_server_socket( + self.network_config.host, + self.network_config.port_audio, + "音频接收" + ) + + # 指令发送服务器 + self.cmd_server = self._create_server_socket( + self.network_config.host, + self.network_config.port_cmd, + "指令发送" + ) + + # 定位数据接收服务器 + self.location_server = self._create_server_socket( + self.network_config.host, + self.network_config.port_location, + "定位数据接收" + ) + + self.logger.info("所有网络服务器初始化成功") + + except Exception as e: + self.logger.error(f"网络服务器初始化失败: {e}") + self.logger.error(traceback.format_exc()) + self._cleanup_network_servers() + raise + + def _create_server_socket(self, host: str, port: int, server_name: str) -> socket.socket: + """创建服务器套接字""" + try: + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + server.settimeout(self.network_config.timeout) + + server.bind((host, port)) + server.listen(1) + + self.logger.info(f"{server_name}服务器启动: {host}:{port}") + return server + + except Exception as e: + self.logger.error(f"创建{server_name}服务器失败: {e}") + raise + + def _init_visualization(self) -> None: + """初始化可视化""" + try: + # 设置中文字体 + plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans'] + plt.rcParams['axes.unicode_minus'] = False + + # 创建图形 + self.fig, self.ax = plt.subplots(figsize=(12, 10)) + self.ax.set_title("实时声源定位地图", fontsize=16, fontweight='bold') + self.ax.set_xlabel("X 坐标 (米)", fontsize=12) + self.ax.set_ylabel("Y 坐标 (米)", fontsize=12) + self.ax.set_xlim(-self.visualization_config['plot_range'], self.visualization_config['plot_range']) + self.ax.set_ylim(-self.visualization_config['plot_range'], self.visualization_config['plot_range']) + self.ax.grid(True, alpha=0.3) + + # 坐标轴辅助线 + self.ax.axhline(0, color='gray', linestyle='--', alpha=0.5) + self.ax.axvline(0, color='gray', linestyle='--', alpha=0.5) + + # 探测器位置标记 + self.ax.scatter([0], [0], c='red', s=100, marker='s', + label='声源探测器位置', edgecolors='black', linewidth=2) + + # 初始化散点图 + self.scat = self.ax.scatter([], [], c=[], cmap='viridis', + s=self.visualization_config['point_size'], + alpha=0.8, vmin=0, vmax=10, + edgecolors='black', linewidth=1) + + # 添加颜色条 + cbar = plt.colorbar(self.scat, ax=self.ax) + cbar.set_label('信号强度', fontsize=10) + + # 添加图例 + self.ax.legend(loc='upper right', fontsize=10) + + # 创建动画 + self.ani = FuncAnimation( + self.fig, + self.update_plot, + interval=self.visualization_config['plot_interval_ms'], + cache_frame_data=False, + blit=True + ) + + self.logger.info("可视化系统初始化成功") + + except Exception as e: + self.logger.error(f"可视化系统初始化失败: {e}") + self.logger.error(traceback.format_exc()) + raise + + def _init_data_structures(self) -> None: + """初始化数据结构""" + try: + # 清空历史数据 + self.location_history.clear() + self.audio_processor.audio_buffer.clear() + + # 重置计数器 + self.recognition_count = 0 + self.gunshot_detected_count = 0 + self.location_data_count = 0 + + # 重置性能统计 + self.performance_stats = { + 'start_time': time.time(), + 'audio_packets_received': 0, + 'location_packets_received': 0, + 'recognition_attempts': 0, + 'errors': 0, + 'board_gunshot_detections': 0 # 新增:开发板枪声检测次数 + } + + self.logger.info("数据结构初始化完成") + + except Exception as e: + self.logger.error(f"数据结构初始化失败: {e}") + raise + + def _cleanup_network_servers(self) -> None: + """清理网络服务器资源""" + servers = [ + (self.audio_server, "音频服务器"), + (self.cmd_server, "指令服务器"), + (self.location_server, "定位服务器") + ] + + for server, name in servers: + if server: + try: + server.close() + self.logger.info(f"{name}已关闭") + except Exception as e: + self.logger.error(f"关闭{name}失败: {e}") + + def accept_connections(self): + """接受客户端连接""" + try: + success = self.communication_manager.accept_all_connections() + if success: + # 更新连接状态 + for conn_type in ['audio', 'cmd', 'location']: + if self.communication_manager.connections[conn_type]: + self.connection_status[conn_type] = ConnectionStatus.CONNECTED + + self.logger.info("所有客户端连接成功") + else: + raise Exception("部分客户端连接失败") + + except Exception as e: + self.logger.error(f"接受连接失败: {e}") + self.logger.error(traceback.format_exc()) + self.performance_stats['errors'] += 1 + raise + + def start_audio_processing(self): + """启动音频处理线程""" + try: + audio_thread = threading.Thread( + target=self._audio_processing_worker, + name="AudioProcessor", + daemon=True + ) + audio_thread.start() + + with self.thread_lock: + self.threads.append(audio_thread) + + self.logger.info("音频处理线程启动成功") + + except Exception as e: + self.logger.error(f"启动音频处理线程失败: {e}") + raise + + def _audio_processing_worker(self): + """音频处理工作线程""" + self.logger.info("音频处理线程开始运行") + + try: + while self.running and self.current_mode == SystemMode.LISTENING: + try: + # 接收音频数据 + audio_data = self.communication_manager.receive_audio_data() + + if not audio_data: + self.logger.warning("音频客户端断开连接") + self.connection_status['audio'] = ConnectionStatus.DISCONNECTED + break + + # 检查是否是识别请求 + if self._is_recognition_request(audio_data): + self._handle_recognition_request(audio_data) + continue + + # 更新性能统计 + self.performance_stats['audio_packets_received'] += 1 + + # 添加到音频处理器 + if not self.audio_processor.add_audio_data(audio_data): + self.logger.warning("音频数据添加失败") + continue + + # 检查是否需要进行识别 + if self.audio_processor.should_process_recognition(): + result = self.audio_processor.process_recognition() + + if result and result.get('is_gunshot', False): + self.logger.warning(f"检测到枪声!置信度: {result.get('score', 0):.4f}") + self._switch_to_location_mode() + + except socket.timeout: + # 超时继续循环 + continue + except Exception as e: + self.logger.error(f"音频处理错误: {e}") + self.performance_stats['errors'] += 1 + time.sleep(0.1) # 短暂等待后继续 + + except Exception as e: + self.logger.error(f"音频处理线程异常: {e}") + self.logger.error(traceback.format_exc()) + self.performance_stats['errors'] += 1 + finally: + self.logger.info("音频处理线程结束") + + def _is_recognition_request(self, audio_data: bytes) -> bool: + """检查是否是识别请求""" + try: + # 检查数据开头是否包含识别请求标识 + data_str = audio_data.decode('utf-8', errors='ignore') + return data_str.startswith("RECOGNITION_REQUEST:") + except: + return False + + def _handle_recognition_request(self, audio_data: bytes): + """处理识别请求""" + try: + # 解析请求头 + data_str = audio_data.decode('utf-8', errors='ignore') + if not data_str.startswith("RECOGNITION_REQUEST:"): + return + + # 格式: RECOGNITION_REQUEST:timestamp:data_size + parts = data_str.split(':') + if len(parts) >= 3: + timestamp = float(parts[1]) + data_size = int(parts[2]) + + self.logger.info(f"收到识别请求: 时间戳={timestamp:.3f}, 数据大小={data_size}") + + # 等待接收音频数据 + audio_segment = self._receive_audio_segment(data_size) + if audio_segment is not None: + # 进行枪声识别 + recognition_result = self._perform_audio_recognition(audio_segment) + + # 发送识别结果给开发板 + self._send_recognition_result(timestamp, recognition_result) + + except Exception as e: + self.logger.error(f"处理识别请求失败: {e}") + + def _receive_audio_segment(self, data_size: int) -> Optional[bytes]: + """接收音频数据段""" + try: + # 从音频socket接收指定大小的数据 + received_data = b'' + remaining_size = data_size + + while remaining_size > 0: + chunk = self.communication_manager.audio_socket.recv(min(remaining_size, 4096)) + if not chunk: + break + received_data += chunk + remaining_size -= len(chunk) + + if len(received_data) == data_size: + return received_data + else: + self.logger.warning(f"音频数据接收不完整: 期望{data_size}, 实际{len(received_data)}") + return None + + except Exception as e: + self.logger.error(f"接收音频数据段失败: {e}") + return None + + def _perform_audio_recognition(self, audio_data: bytes) -> Dict[str, Any]: + """执行音频识别""" + try: + # 将字节数据转换为numpy数组 + audio_array = np.frombuffer(audio_data, dtype=np.int16).astype(np.float32) / 32768.0 + + # 检查音频质量 + if not self.audio_processor._check_audio_quality(audio_array): + return {'is_gunshot': False, 'confidence': 0.0, 'reason': 'poor_quality'} + + # 执行识别 + result = self.audio_processor._perform_recognition(audio_array) + + if result: + return { + 'is_gunshot': result.get('is_gunshot', False), + 'confidence': result.get('score', 0.0), + 'label': result.get('label', 'unknown') + } + else: + return {'is_gunshot': False, 'confidence': 0.0, 'reason': 'recognition_failed'} + + except Exception as e: + self.logger.error(f"音频识别失败: {e}") + return {'is_gunshot': False, 'confidence': 0.0, 'reason': 'error'} + + def _send_recognition_result(self, timestamp: float, result: Dict[str, Any]): + """发送识别结果给开发板""" + try: + if not self.communication_manager.cmd_socket: + return + + # 构建识别结果字符串 + # 格式: RECOGNITION_RESULT:timestamp:is_gunshot:confidence + is_gunshot = result.get('is_gunshot', False) + confidence = result.get('confidence', 0.0) + + result_str = f"RECOGNITION_RESULT:{timestamp:.3f}:{str(is_gunshot).lower()}:{confidence:.3f}" + + # 发送结果 + self.communication_manager.cmd_socket.send(result_str.encode('utf-8')) + + self.logger.info(f"发送识别结果: 时间戳={timestamp:.3f}, 枪声={is_gunshot}, 置信度={confidence:.3f}") + + except Exception as e: + self.logger.error(f"发送识别结果失败: {e}") + + def _switch_to_location_mode(self): + """切换到定位模式""" + try: + if self.current_mode == SystemMode.LISTENING: + self.current_mode = SystemMode.LOCATING + self.logger.info("切换到定位模式") + + # 发送定位指令给开发板 + if self.communication_manager.send_command("START_LOCATION"): + self.logger.info("定位指令发送成功") + else: + self.logger.error("定位指令发送失败") + + # 启动定位数据接收线程 + self._start_location_processing() + + except Exception as e: + self.logger.error(f"切换到定位模式失败: {e}") + self.logger.error(traceback.format_exc()) + self.performance_stats['errors'] += 1 + + def _start_location_processing(self): + """启动定位数据处理""" + try: + location_thread = threading.Thread( + target=self._location_processing_worker, + name="LocationProcessor", + daemon=True + ) + location_thread.start() + + with self.thread_lock: + self.threads.append(location_thread) + + self.logger.info("定位数据处理线程启动成功") + + except Exception as e: + self.logger.error(f"启动定位数据处理失败: {e}") + raise + + def _location_processing_worker(self): + """定位数据处理工作线程""" + self.logger.info("定位数据处理线程开始运行") + + try: + while self.running and self.current_mode == SystemMode.LOCATING: + try: + # 接收定位数据 + data = self.communication_manager.receive_location_data() + + if not data: + self.logger.warning("定位数据客户端断开连接") + self.connection_status['location'] = ConnectionStatus.DISCONNECTED + break + + # 更新性能统计 + self.performance_stats['location_packets_received'] += 1 + + # 解析数据 + self._process_location_data(data) + + except socket.timeout: + # 超时继续循环 + continue + except Exception as e: + self.logger.error(f"定位数据处理错误: {e}") + self.performance_stats['errors'] += 1 + time.sleep(0.1) # 短暂等待后继续 + + except Exception as e: + self.logger.error(f"定位数据处理线程异常: {e}") + self.logger.error(traceback.format_exc()) + self.performance_stats['errors'] += 1 + finally: + self.logger.info("定位数据处理线程结束") + + def _process_location_data(self, data: bytes): + """处理定位数据""" + try: + data_str = data.decode('utf-8').strip() + if not data_str: + return + + # 检查是否是枪声检测结果数据(保留兼容性) + if data_str.startswith("GUNSHOT_DETECTION:"): + self._process_gunshot_detection_data(data_str) + return + + # 解析定位数据 + location_data = self._parse_location_data(data_str) + if location_data: + # 应用卡尔曼滤波 + filtered_data = self._apply_kalman_filter(location_data) + # 后处理(平滑、异常值剔除等) + processed_data = self.location_post_processor.process(filtered_data) + # 将数据放入队列 + self.location_queue.put(processed_data) + self.location_data_count += 1 + # 添加到历史记录 + self._add_to_history(processed_data) + self.logger.debug(f"接收定位数据(后处理): X={processed_data.x:.2f}, Y={processed_data.y:.2f}, " + f"强度={processed_data.strength:.2f}, 角度={processed_data.angle:.2f}") + except Exception as e: + self.logger.error(f"处理定位数据失败: {e}") + self.performance_stats['errors'] += 1 + + def _process_gunshot_detection_data(self, data_str: str): + """处理开发板枪声检测数据""" + try: + # 解析格式: GUNSHOT_DETECTION:is_gunshot:confidence:timestamp + parts = data_str.split(':') + if len(parts) >= 4: + is_gunshot = parts[1].lower() == 'true' + confidence = float(parts[2]) + timestamp = float(parts[3]) + + # 更新统计 + self.board_gunshot_stats['total_detections'] += 1 + if is_gunshot: + self.board_gunshot_stats['gunshot_detections'] += 1 + self.performance_stats['board_gunshot_detections'] += 1 + self.board_gunshot_stats['last_detection_time'] = timestamp + + self.logger.info(f"开发板检测到枪声!置信度: {confidence:.3f}") + + # 更新检测率 + total = self.board_gunshot_stats['total_detections'] + gunshots = self.board_gunshot_stats['gunshot_detections'] + self.board_gunshot_stats['detection_rate'] = gunshots / total if total > 0 else 0.0 + + # 更新平均置信度 + if 'avg_confidence' not in self.board_gunshot_stats: + self.board_gunshot_stats['avg_confidence'] = confidence + else: + # 指数移动平均 + alpha = 0.1 + self.board_gunshot_stats['avg_confidence'] = ( + alpha * confidence + + (1 - alpha) * self.board_gunshot_stats['avg_confidence'] + ) + + self.logger.debug(f"开发板枪声检测统计: 总数={total}, 枪声={gunshots}, " + f"检测率={self.board_gunshot_stats['detection_rate']:.3f}, " + f"平均置信度={self.board_gunshot_stats['avg_confidence']:.3f}") + + except Exception as e: + self.logger.error(f"处理开发板枪声检测数据失败: {e}") + self.performance_stats['errors'] += 1 + + def _parse_location_data(self, data_str: str) -> Optional[LocationData]: + """解析定位数据""" + try: + parts = data_str.split(',') + if len(parts) != 4: + self.logger.warning(f"定位数据格式错误,期望4个值,实际{len(parts)}个: {data_str}") + return None + + x, y, strength, angle = map(float, parts) + + # 数据范围验证 + if not (-50 <= x <= 50 and -50 <= y <= 50): + self.logger.warning(f"坐标超出合理范围: x={x}, y={y}") + return None + + if not (0 <= strength <= 100): + self.logger.warning(f"强度值超出合理范围: {strength}") + return None + + if not (0 <= angle <= 360): + self.logger.warning(f"角度值超出合理范围: {angle}") + return None + + return LocationData( + x=x, y=y, strength=strength, angle=angle, + timestamp=time.time() + ) + + except ValueError as e: + self.logger.error(f"解析定位数据失败: {e}, 数据: {data_str}") + return None + except Exception as e: + self.logger.error(f"解析定位数据异常: {e}") + return None + + def _apply_kalman_filter(self, location_data: LocationData) -> LocationData: + """应用卡尔曼滤波""" + try: + return kalman_filter.filter_location_data(location_data) + except Exception as e: + self.logger.error(f"卡尔曼滤波失败: {e}") + return location_data + + def _add_to_history(self, location_data: LocationData): + """添加到历史记录""" + try: + self.location_history.append(location_data) + + # 限制历史记录大小 + max_history = self.visualization_config['max_history_points'] + if len(self.location_history) > max_history: + self.location_history = self.location_history[-max_history:] + + except Exception as e: + self.logger.error(f"添加历史记录失败: {e}") + + def update_plot(self, frame): + """更新可视化图表""" + try: + # 获取最新的定位数据 + if not self.location_queue.empty(): + location_data = self.location_queue.get_nowait() + self.current_location = location_data + + # 更新散点图数据 + if self.location_history: + x_coords = [data.x for data in self.location_history] + y_coords = [data.y for data in self.location_history] + strengths = [data.strength for data in self.location_history] + + # 更新散点图 + self.scat.set_offsets(np.column_stack([x_coords, y_coords])) + self.scat.set_array(np.array(strengths)) + + # 更新标题显示最新位置 + latest = self.location_history[-1] + self.ax.set_title(f"实时声源定位地图 - 最新位置: X={latest.x:.2f}, Y={latest.y:.2f}, " + f"强度={latest.strength:.2f}, 角度={latest.angle:.2f}") + + # 更新性能统计显示 + self._update_performance_display() + + return [self.scat] + + except Exception as e: + self.logger.error(f"更新可视化失败: {e}") + return [self.scat] + + def _update_performance_display(self): + """更新性能统计显示""" + try: + # 计算实时统计 + current_time = time.time() + uptime = current_time - self.performance_stats['start_time'] + + # 计算数据接收率 + audio_rate = self.performance_stats['audio_packets_received'] / max(uptime, 1) + location_rate = self.performance_stats['location_packets_received'] / max(uptime, 1) + + # 更新状态栏 + status_text = (f"运行时间: {uptime:.1f}s | " + f"音频包: {self.performance_stats['audio_packets_received']} " + f"({audio_rate:.1f}/s) | " + f"定位包: {self.performance_stats['location_packets_received']} " + f"({location_rate:.1f}/s) | " + f"错误: {self.performance_stats['errors']} | " + f"模式: {self.current_mode.value}") + + # 添加开发板枪声检测统计 + if self.current_mode == SystemMode.LOCATING: + board_stats = self.board_gunshot_stats + status_text += f" | 开发板检测: {board_stats['gunshot_detections']}/{board_stats['total_detections']} " + status_text += f"({board_stats['detection_rate']:.1%})" + + # 在图形上显示状态信息 + if hasattr(self, 'status_text_obj'): + self.status_text_obj.remove() + + self.status_text_obj = self.fig.text( + 0.02, 0.02, status_text, + fontsize=8, family='monospace', + bbox=dict(boxstyle="round,pad=0.3", facecolor="white", alpha=0.8) + ) + + except Exception as e: + self.logger.error(f"更新性能显示失败: {e}") + + def get_system_status(self) -> Dict[str, Any]: + """获取系统状态信息""" + uptime = time.time() - self.performance_stats['start_time'] + + return { + 'mode': self.current_mode.value, + 'uptime': uptime, + 'connection_status': {k: v.value for k, v in self.connection_status.items()}, + 'performance_stats': self.performance_stats.copy(), + 'audio_processor_stats': self.audio_processor.get_performance_stats(), + 'communication_stats': self.communication_manager.get_connection_stats(), + 'location_stats': { + 'total_packets': self.location_data_count, + 'history_size': len(self.location_history), + 'queue_size': self.location_queue.qsize() + }, + 'board_gunshot_stats': self.board_gunshot_stats.copy() # 新增:开发板枪声检测统计 + } + + def run(self): + """运行服务器""" + try: + self.logger.info("=== 声源定位系统 - PC端服务器启动 ===") + self.logger.info(f"当前模式: {self.current_mode.value}") + self.logger.info(f"网络配置: {self.network_config}") + self.logger.info(f"音频配置: {self.audio_config}") + + # 接受客户端连接 + self.accept_connections() + + # 启动音频处理线程 + self.start_audio_processing() + + # 启动Flask服务器 + self._start_flask_server() + + # 显示可视化界面 + self.logger.info("启动可视化界面...") + plt.show() + + except KeyboardInterrupt: + self.logger.info("收到中断信号,正在关闭...") + except Exception as e: + self.logger.error(f"服务器运行异常: {e}") + self.logger.error(traceback.format_exc()) + finally: + self.cleanup() + + def cleanup(self): + """清理资源""" + try: + self.logger.info("开始清理资源...") + self.running = False + + # 关闭通信管理器 + self.communication_manager.close_all_connections() + + # 关闭服务器 + for server_name, server in [('audio', self.audio_server), + ('cmd', self.cmd_server), + ('location', self.location_server)]: + if server: + try: + server.close() + self.logger.info(f"关闭{server_name}服务器") + except Exception as e: + self.logger.error(f"关闭{server_name}服务器失败: {e}") + + # 等待线程结束 + self.logger.info("等待线程结束...") + for thread in self.threads: + if thread.is_alive(): + thread.join(timeout=2.0) + if thread.is_alive(): + self.logger.warning(f"线程 {thread.name} 未能正常结束") + + # 保存最终统计信息 + self._save_final_stats() + + # 停止Flask服务器 + self._stop_flask_server() + + self.logger.info("资源清理完成") + + except Exception as e: + self.logger.error(f"清理资源失败: {e}") + + def _save_final_stats(self): + """保存最终统计信息""" + try: + stats = self.get_system_status() + + # 保存到文件 + stats_file = f"logs/system_stats_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + os.makedirs(os.path.dirname(stats_file), exist_ok=True) + + with open(stats_file, 'w', encoding='utf-8') as f: + json.dump(stats, f, indent=2, ensure_ascii=False, default=str) + + self.logger.info(f"系统统计信息已保存到: {stats_file}") + + except Exception as e: + self.logger.error(f"保存统计信息失败: {e}") + + def _init_flask_app(self): + """初始化Flask应用""" + try: + self.flask_app = Flask(__name__) + + # 启用CORS + if self.http_api_config['cors_enabled']: + CORS(self.flask_app) + + # 设置路由 + self._setup_flask_routes() + + self.logger.info("Flask应用初始化成功") + + except Exception as e: + self.logger.error(f"Flask应用初始化失败: {e}") + self.flask_app = None + + def _setup_flask_routes(self): + """设置Flask路由""" + try: + @self.flask_app.route("/") + def index(): + return jsonify({ + "message": "声源定位系统HTTP API", + "version": "2.0.0", + "status": "running", + "endpoints": { + "/data": "获取最新定位数据", + "/status": "获取系统状态", + "/stats": "获取性能统计", + "/board_stats": "获取开发板枪声检测统计" # 新增 + } + }) + + @self.flask_app.route("/data") + def get_data(): + """获取最新声源定位数据""" + try: + if self.current_location: + return jsonify({ + "X": float(self.current_location.x), + "Y": float(self.current_location.y), + "strength": float(self.current_location.strength), + "angle": float(self.current_location.angle), + "timestamp": self.current_location.timestamp, + "confidence": float(self.current_location.confidence) + }) + else: + return jsonify({ + "X": 0.0, + "Y": 0.0, + "strength": 0.0, + "angle": 0.0, + "timestamp": time.time(), + "confidence": 0.0 + }) + except Exception as e: + self.logger.error(f"获取定位数据失败: {e}") + return jsonify({"error": "获取数据失败"}), 500 + + @self.flask_app.route("/status") + def get_status(): + """获取系统状态""" + try: + status = self.get_system_status() + return jsonify(status) + except Exception as e: + self.logger.error(f"获取系统状态失败: {e}") + return jsonify({"error": "获取状态失败"}), 500 + + @self.flask_app.route("/stats") + def get_stats(): + """获取性能统计""" + try: + stats = { + "system_stats": self.get_system_status(), + "audio_processor_stats": self.audio_processor.get_performance_stats(), + "communication_stats": self.communication_manager.get_connection_stats() + } + return jsonify(stats) + except Exception as e: + self.logger.error(f"获取性能统计失败: {e}") + return jsonify({"error": "获取统计失败"}), 500 + + @self.flask_app.route("/board_stats") + def get_board_stats(): + """获取开发板枪声检测统计""" + try: + return jsonify(self.board_gunshot_stats) + except Exception as e: + self.logger.error(f"获取开发板统计失败: {e}") + return jsonify({"error": "获取统计失败"}), 500 + + self.logger.info("Flask路由设置完成") + + except Exception as e: + self.logger.error(f"设置Flask路由失败: {e}") + + def _start_flask_server(self): + """启动Flask服务器""" + try: + if self.flask_app: + self.flask_thread = threading.Thread( + target=self._run_flask_server, + name="FlaskServer", + daemon=True + ) + self.flask_thread.start() + self.logger.info(f"Flask服务器启动成功: http://{self.http_api_config['host']}:{self.http_api_config['port']}") + else: + self.logger.warning("Flask应用未初始化,跳过HTTP API启动") + + except Exception as e: + self.logger.error(f"启动Flask服务器失败: {e}") + + def _run_flask_server(self): + """运行Flask服务器""" + try: + self.flask_app.run( + host=self.http_api_config['host'], + port=self.http_api_config['port'], + debug=False, + use_reloader=False + ) + except Exception as e: + self.logger.error(f"Flask服务器运行失败: {e}") + + def _stop_flask_server(self): + """停止Flask服务器""" + try: + if self.flask_thread and self.flask_thread.is_alive(): + # Flask服务器会在主线程结束时自动停止 + self.logger.info("Flask服务器已停止") + except Exception as e: + self.logger.error(f"停止Flask服务器失败: {e}") + +def main(): + """主函数""" + print("=== 声源定位系统 - PC端服务器 ===") + print("版本: 2.0.0") + print("作者: 声源定位系统开发团队") + print("正在初始化...") + + try: + # 创建并运行服务器 + server = PCServer() + server.run() + + except KeyboardInterrupt: + logger.info("程序被用户中断") + except Exception as e: + logger.error(f"程序启动失败: {e}") + logger.error(traceback.format_exc()) + print(f"程序启动失败: {e}") + print("请检查配置文件和网络连接") + return 1 + + return 0 + +if __name__ == "__main__": + try: + exit_code = main() + sys.exit(exit_code) + except Exception as e: + print(f"程序异常退出: {e}") + sys.exit(1) \ No newline at end of file diff --git a/src/声源定位代码/config.ini b/src/声源定位代码/config.ini new file mode 100644 index 0000000..7ce3cc0 --- /dev/null +++ b/src/声源定位代码/config.ini @@ -0,0 +1,35 @@ +[NETWORK] +host = 0.0.0.0 +port_audio = 12346 +port_cmd = 12347 +port_location = 12348 +timeout = 30.0 +buffer_size = 4096 + +[AUDIO] +sample_rate = 16000 +channels = 1 +chunk_size = 1024 +format = int16 + +[RECOGNITION] +gunshot_threshold = 0.7 +recognition_interval = 3.0 +configs_path = audio-classification/configs/cam++.yml +model_path = audio-classification/models/CAMPPlus_Fbank/best_model/ + +[VISUALIZATION] +plot_interval_ms = 100 +plot_range = 15.0 +point_size = 200 +max_history_points = 100 + +[KALMAN] +q_x = 5.0 +r_x = 0.01 +q_y = 5.0 +r_y = 0.01 +q_strength = 5.0 +r_strength = 0.01 +q_angle = 5.0 +r_angle = 0.01 \ No newline at end of file diff --git a/src/声源定位代码/mic-code/Maix/Maix_config.c b/src/声源定位代码/mic-code/Maix/Maix_config.c new file mode 100644 index 0000000..15a70bd --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/Maix_config.c @@ -0,0 +1,423 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/objlist.h" +#include "py/objstringio.h" +#include "py/parsenum.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "py/objmodule.h" +#include "py/objstringio.h" +#include "mphalport.h" +#include "vfs_internal.h" + +#include "Maix_config.h" + +// static void unit_test_json_config(); + +mp_map_elem_t *dict_iter_next(mp_obj_dict_t *dict, size_t *cur) +{ + size_t max = dict->map.alloc; + mp_map_t *map = &dict->map; + + for (size_t i = *cur; i < max; i++) + { + if (mp_map_slot_is_filled(map, i)) + { + *cur = i + 1; + return &(map->table[i]); + } + } + + return NULL; +} + +#define MAIX_CONFIG_PATH "/flash/config.json" + +typedef struct +{ + mp_obj_base_t base; + mp_obj_t cache; + mp_obj_t args[3]; +} maix_config_t; + +static maix_config_t *config_obj = NULL; + +static mp_obj_t maix_config_cache() +{ + // printf("%s\r\n", __func__); + typedef struct + { + mp_obj_base_t base; + } fs_info_t; + + int err = 0; + fs_info_t *cfg = vfs_internal_open(MAIX_CONFIG_PATH, "rb", &err); + if (err != 0) + { + // printf("no config time:%ld\r\n", systick_current_millis()); + } + else + { + // printf("exist config time:%ld\r\n", systick_current_millis()); + config_obj->args[2] = MP_OBJ_FROM_PTR(&(cfg->base)); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) + { + config_obj->cache = mp_call_method_n_kw(1, 0, config_obj->args); + nlr_pop(); + vfs_internal_close(cfg, &err); + return mp_const_true; + } + else + { + mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); + } + vfs_internal_close(cfg, &err); + } + return mp_const_false; +} + +mp_obj_t maix_config_get_value(mp_obj_t key, mp_obj_t def_value) +{ + // printf("%s\r\n", __func__); + if (config_obj != NULL) + { + if (false == mp_obj_is_type(config_obj->cache, &mp_type_dict)) + { + // maybe gc.collect() + if (mp_const_false == maix_config_cache()) + { + return def_value; + } + } + // mp_printf(&mp_plat_print, "print(config_obj->cache)\r\n"); + // mp_obj_print_helper(&mp_plat_print, config_obj->cache, PRINT_STR); + // mp_printf(&mp_plat_print, "\r\n"); + // mp_check_self(mp_obj_is_dict_type(config_obj->cache)); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(config_obj->cache); + mp_map_elem_t *elem = mp_map_lookup(&self->map, key, MP_MAP_LOOKUP); + if (elem == NULL || elem->value == MP_OBJ_NULL) + { + return def_value; // not exist + } + else + { + return elem->value; + } + } + return def_value; +} +MP_DEFINE_CONST_FUN_OBJ_2(maix_config_get_value_obj, maix_config_get_value); + +mp_obj_t maix_config_init() +{ + // printf("%s\r\n", __func__); + // unit_test_json_config(); + static maix_config_t tmp; + mp_obj_t module_obj = mp_module_get(MP_QSTR_ujson); + if (module_obj != MP_OBJ_NULL) + { + // mp_printf(&mp_plat_print, "import josn\r\n"); + mp_load_method_maybe(module_obj, MP_QSTR_load, tmp.args); + if (tmp.args[0] != MP_OBJ_NULL) + { + config_obj = &tmp; + return maix_config_cache(); + // return mp_const_true; + } + } + mp_printf(&mp_plat_print, "[%s]|(%s)\r\n", __func__, "fail"); + return mp_const_false; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_0(maix_config_init_obj, maix_config_init); + +static const mp_map_elem_t locals_dict_table[] = { + {MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_config)}, + {MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&maix_config_init_obj)}, + {MP_ROM_QSTR(MP_QSTR_get_value), MP_ROM_PTR(&maix_config_get_value_obj)}, +}; + +STATIC MP_DEFINE_CONST_DICT(locals_dict, locals_dict_table); + +const mp_obj_type_t Maix_config_type = { + .base = {&mp_type_type}, + .name = MP_QSTR_config, + .locals_dict = (mp_obj_dict_t *)&locals_dict}; + +#ifdef UNIT_TEST + +/* +{ + "config_name": "config.json", + "lcd":{ + "RST_IO":16, + "DCX_IO":32 + }, + "freq_cpu": 416000000, + "freq_pll1": 400000000, + "kpu_div": 1 +} +*/ + +static void unit_test_json_config() +{ + // unit_test get string + { + const char key[] = "config_name"; + mp_obj_t tmp = maix_config_get_value(mp_obj_new_str(key, sizeof(key) - 1), mp_obj_new_str("None Cfg", 8)); + if (mp_obj_is_str(tmp)) + { + const char *value = mp_obj_str_get_str(tmp); + mp_printf(&mp_plat_print, "%s %s\r\n", key, value); + } + } + + // get lcd dict key-value + { + const char key[] = "lcd"; + mp_obj_t tmp = maix_config_get_value(mp_obj_new_str(key, sizeof(key) - 1), mp_obj_new_dict(0)); + if (mp_obj_is_type(tmp, &mp_type_dict)) + { + mp_obj_dict_t *self = MP_OBJ_TO_PTR(tmp); + size_t cur = 0; + mp_map_elem_t *next = NULL; + bool first = true; + while ((next = dict_iter_next(self, &cur)) != NULL) + { + if (!first) + { + mp_print_str(&mp_plat_print, ", "); + } + first = false; + mp_obj_print_helper(&mp_plat_print, next->key, PRINT_STR); + mp_print_str(&mp_plat_print, ": "); + mp_obj_print_helper(&mp_plat_print, next->value, PRINT_STR); + } + } + } +} + +static void unit_test_json_config() +{ + mp_obj_t module_obj = mp_module_get(MP_QSTR_ujson); + if (module_obj != MP_OBJ_NULL) + { + mp_printf(&mp_plat_print, "import josn\r\n"); + mp_obj_t dest[3]; + mp_load_method_maybe(module_obj, MP_QSTR_loads, dest); + if (dest[0] != MP_OBJ_NULL) + { + const char json[] = "{\"a\":1,\"b\":2,\"c\":3,\"d\":4,\"e\":\"helloworld\"}"; + mp_printf(&mp_plat_print, "nresult = josn.loads(%s)\r\n", json); + dest[2] = mp_obj_new_str(json, sizeof(json) - 1); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) + { + mp_obj_t result = mp_call_method_n_kw(1, 0, dest); + mp_printf(&mp_plat_print, "print(result)\r\n"); + mp_obj_print_helper(&mp_plat_print, result, PRINT_STR); + mp_printf(&mp_plat_print, "\r\n"); + const char goal[] = "e"; + //mp_check_self(mp_obj_is_dict_type(result)); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(result); + mp_map_elem_t *elem = mp_map_lookup(&self->map, mp_obj_new_str(goal, sizeof(goal) - 1), MP_MAP_LOOKUP); + mp_obj_t value; + if (elem == NULL || elem->value == MP_OBJ_NULL) + { + // not exist + } + else + { + value = elem->value; + //mp_check_self(mp_obj_is_str_type(value)); + mp_printf(&mp_plat_print, "print(result.get('%s'))\r\n", goal); + mp_obj_print_helper(&mp_plat_print, value, PRINT_STR); + mp_printf(&mp_plat_print, "\r\n"); + } + nlr_pop(); + } + else + { + mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); + } + } + mp_load_method_maybe(module_obj, MP_QSTR_load, dest); + if (dest[0] != MP_OBJ_NULL) + { + const char json[] = "{\"a\":1,\"b\":2,\"c\":3,\"d\":4,\"e\":\"helloworld\"}"; + mp_printf(&mp_plat_print, "nresult = josn.load(%s)\r\n", json); + mp_obj_t obj = mp_obj_new_str(json, sizeof(json) - 1); + size_t len; + const char *buf = mp_obj_str_get_data(obj, &len); + vstr_t vstr = {len, len, (char *)buf, true}; + mp_obj_stringio_t sio = {{&mp_type_stringio}, &vstr, 0, MP_OBJ_NULL}; + dest[2] = MP_OBJ_FROM_PTR(&sio); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) + { + mp_obj_t result = mp_call_method_n_kw(1, 0, dest); + mp_printf(&mp_plat_print, "print(result)\r\n"); + mp_obj_print_helper(&mp_plat_print, result, PRINT_STR); + mp_printf(&mp_plat_print, "\r\n"); + const char goal[] = "a"; + //mp_check_self(mp_obj_is_dict_type(result)); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(result); + mp_map_elem_t *elem = mp_map_lookup(&self->map, mp_obj_new_str(goal, sizeof(goal) - 1), MP_MAP_LOOKUP); + mp_obj_t value; + if (elem == NULL || elem->value == MP_OBJ_NULL) + { + // not exist + } + else + { + value = elem->value; + //mp_check_self(mp_obj_is_str_type(value)); + mp_printf(&mp_plat_print, "print(result.get('%s'))\r\n", goal); + mp_obj_print_helper(&mp_plat_print, value, PRINT_STR); + mp_printf(&mp_plat_print, "\r\n"); + } + nlr_pop(); + } + else + { + mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); + } + } + typedef struct + { + mp_obj_base_t base; + } fs_info_t; + { + + int err = 0; + fs_info_t *cfg = vfs_internal_open("/flash/config.json", "rb", &err); + if (err != 0) + { + printf("no config time:%ld\r\n", systick_current_millis()); + } + else + { + // mp_stream_p_t* stream = (mp_stream_p_t*)cfg->base.type->protocol; + printf("exist config time:%ld\r\n", systick_current_millis()); + mp_load_method_maybe(module_obj, MP_QSTR_load, dest); + if (dest[0] != MP_OBJ_NULL) + { + dest[2] = MP_OBJ_FROM_PTR(&(cfg->base)); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) + { + mp_obj_t result = mp_call_method_n_kw(1, 0, dest); + mp_printf(&mp_plat_print, "print(result)\r\n"); + mp_obj_print_helper(&mp_plat_print, result, PRINT_STR); + mp_printf(&mp_plat_print, "\r\n"); + const char goal[] = "a"; + //mp_check_self(mp_obj_is_dict_type(result)); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(result); + mp_map_elem_t *elem = mp_map_lookup(&self->map, mp_obj_new_str(goal, sizeof(goal) - 1), MP_MAP_LOOKUP); + mp_obj_t value; + if (elem == NULL || elem->value == MP_OBJ_NULL) + { + // not exist + } + else + { + value = elem->value; + //mp_check_self(mp_obj_is_str_type(value)); + mp_printf(&mp_plat_print, "print(result.get('%s'))\r\n", goal); + mp_obj_print_helper(&mp_plat_print, value, PRINT_STR); + mp_printf(&mp_plat_print, "\r\n"); + } + nlr_pop(); + } + else + { + mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); + } + + vfs_internal_close(cfg, &err); + } + } + } + + { + + int err = 0; + fs_info_t *cfg = vfs_internal_open("/flash/config.json", "rb", &err); + if (err != 0) + { + printf("no config time:%ld\r\n", systick_current_millis()); + } + else + { + // mp_stream_p_t* stream = (mp_stream_p_t*)cfg->base.type->protocol; + printf("exist config time:%ld\r\n", systick_current_millis()); + mp_load_method_maybe(module_obj, MP_QSTR_load, dest); + if (dest[0] != MP_OBJ_NULL) + { + dest[2] = MP_OBJ_FROM_PTR(&(cfg->base)); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) + { + mp_obj_t result = mp_call_method_n_kw(1, 0, dest); + mp_printf(&mp_plat_print, "print(result)\r\n"); + mp_obj_print_helper(&mp_plat_print, result, PRINT_STR); + mp_printf(&mp_plat_print, "\r\n"); + const char goal[] = "e"; + //mp_check_self(mp_obj_is_dict_type(result)); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(result); + mp_map_elem_t *elem = mp_map_lookup(&self->map, mp_obj_new_str(goal, sizeof(goal) - 1), MP_MAP_LOOKUP); + mp_obj_t value; + if (elem == NULL || elem->value == MP_OBJ_NULL) + { + // not exist + } + else + { + value = elem->value; + //mp_check_self(mp_obj_is_str_type(value)); + mp_printf(&mp_plat_print, "print(result.get('%s'))\r\n", goal); + mp_obj_print_helper(&mp_plat_print, value, PRINT_STR); + mp_printf(&mp_plat_print, "\r\n"); + } + nlr_pop(); + } + else + { + mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); + } + + vfs_internal_close(cfg, &err); + } + } + } + } +} + +#endif diff --git a/src/声源定位代码/mic-code/Maix/Maix_fft.c b/src/声源定位代码/mic-code/Maix/Maix_fft.c new file mode 100644 index 0000000..8093996 --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/Maix_fft.c @@ -0,0 +1,195 @@ +/* +* Copyright 2019 Sipeed Co.,Ltd. + +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include +#include + +#include "dmac.h" +#include "hal_fft.h" +#include "i2s.h" +#include "math.h" + +#include "py/obj.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "py/objarray.h" +#include "py/binary.h" +#include "mphalport.h" +#include "modMaix.h" + +#define MAX_SAMPLE_RATE 65535 +#define MAX_BUFFER_LEN 1024 + +const mp_obj_type_t Maix_fft_type; + +STATIC mp_obj_t Maix_fft_run(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) + { + //----------parse parameter--------------- + enum{ARG_byte, + ARG_points, + ARG_shift, + ARG_direction, + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_byte, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_points, MP_ARG_INT, {.u_int = 64} }, + { MP_QSTR_shift, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_direction, MP_ARG_INT, {.u_int = FFT_DIR_FORWARD} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + uint32_t points = args[ARG_points].u_int; + uint32_t shift = args[ARG_shift].u_int; + uint32_t direction = args[ARG_direction].u_int; + + if(points != 64 && points != 128 && points != 256 && points != 512) + { + mp_raise_ValueError("[MAIXPY]FFT:invalid points"); + } + + uint32_t byte_len = 0; + uint32_t* byte_addr = NULL; + + if( args[ARG_byte].u_obj != mp_const_none) + { + mp_obj_t byte = args[ARG_byte].u_obj; + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(byte, &bufinfo, MP_BUFFER_READ); + byte_len = bufinfo.len; + byte_addr = (uint32_t*)bufinfo.buf; + } + else + { + mp_raise_ValueError("[MAIXPY]FFT:invalid byte"); + } + if(byte_len % 4 != 0) + { + mp_raise_ValueError("[MAIXPY]FFT:Buffer length must be a multiple of 4"); + } + // how to get the length of i2s buffer? + if(byte_len < points * 4) + { + mp_printf(&mp_plat_print, "[MAIXPY]FFT:Zero padding\n"); + memset(byte_addr+byte_len, 0, points * 4 - byte_len );//Zero padding + } + + //------------------get data---------------------- + uint64_t* buffer_input = (uint64_t*)m_new(uint64_t, points);//m_new + uint64_t* buffer_output = (uint64_t*)m_new(uint64_t ,points);//m_new + fft_data_t * input_data = NULL; + fft_data_t * output_data = NULL; + for(int i = 0; i < points / 2; ++i) + { + input_data = (fft_data_t *)&buffer_input[i]; + input_data->R1 = byte_addr[2*i]; + input_data->I1 = 0; + input_data->R2 = byte_addr[2*i+1]; + input_data->I2 = 0; + } + //run fft + fft_complex_uint16_dma(DMAC_CHANNEL3, DMAC_CHANNEL4,shift,direction,buffer_input,points,buffer_output); + //return a list + mp_obj_list_t* ret_list = (mp_obj_list_t*)m_new(mp_obj_list_t,sizeof(mp_obj_list_t));//m_new + mp_obj_list_init(ret_list, 0); + mp_obj_t tuple_1[2]; + mp_obj_t tuple_2[2]; + for (int i = 0; i < points / 2; i++) + { + output_data = (fft_data_t*)&buffer_output[i]; + tuple_1[0] = mp_obj_new_int(output_data->R1); + tuple_1[1] = mp_obj_new_int(output_data->I1); + mp_obj_list_append(ret_list, mp_obj_new_tuple(MP_ARRAY_SIZE(tuple_1), tuple_1)); + + tuple_2[0] = mp_obj_new_int(output_data->R2); + tuple_2[1] = mp_obj_new_int(output_data->I2); + mp_obj_list_append(ret_list, mp_obj_new_tuple(MP_ARRAY_SIZE(tuple_2), tuple_2)); + } + return MP_OBJ_FROM_PTR(ret_list); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(Maix_fft_run_obj,1, Maix_fft_run); + +STATIC mp_obj_t Maix_fft_freq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) + { + //----------parse parameter--------------- + enum{ARG_points, + ARG_sample_rate, + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_points, MP_ARG_INT, {.u_int = 64} }, + { MP_QSTR_sample_rate, MP_ARG_INT, {.u_int = 16000} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + uint32_t sample_rate = args[ARG_sample_rate].u_int; + uint32_t points = args[ARG_points].u_int; + + uint32_t step = sample_rate/points; + mp_obj_list_t* ret_list = (mp_obj_list_t*)m_new(mp_obj_list_t,sizeof(mp_obj_list_t));//m_new + mp_obj_list_init(ret_list, 0); + for(int i = 0; i < points; i++) + { + mp_obj_list_append(ret_list, mp_obj_new_int(step * i)); + } + return MP_OBJ_FROM_PTR(ret_list); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(Maix_fft_freq_obj,1, Maix_fft_freq); + + +STATIC mp_obj_t Maix_fft_amplitude(const mp_obj_t list_obj) + { + + if(&mp_type_list != mp_obj_get_type(list_obj)) + { + mp_raise_ValueError("[MAIXPY]FFT:obj is not a list"); + } + mp_obj_list_t* ret_list = (mp_obj_list_t*)m_new(mp_obj_list_t,sizeof(mp_obj_list_t));//m_new + mp_obj_list_init(ret_list, 0); + //---------------------------------- + mp_obj_list_t* list = MP_OBJ_TO_PTR(list_obj); + uint32_t index = 0; + mp_obj_t list_iter; + mp_obj_tuple_t* tuple; + for(index = 0; index < list->len; index++) + { + list_iter = list->items[index]; + tuple = MP_OBJ_FROM_PTR(list_iter); + uint32_t r_val = MP_OBJ_SMALL_INT_VALUE(tuple->items[0]); + uint32_t i_val = MP_OBJ_SMALL_INT_VALUE(tuple->items[1]); + uint32_t amplitude = sqrt(r_val * r_val + i_val * i_val); + //Convert to power + uint32_t hard_power = 2*amplitude/list->len; + mp_obj_list_append(ret_list,mp_obj_new_int(hard_power)); + } + return MP_OBJ_FROM_PTR(ret_list); +} +MP_DEFINE_CONST_FUN_OBJ_1(Maix_fft_amplitude_obj, Maix_fft_amplitude); + +STATIC const mp_rom_map_elem_t Maix_fft_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_run), MP_ROM_PTR(&Maix_fft_run_obj) }, + { MP_ROM_QSTR(MP_QSTR_freq), MP_ROM_PTR(&Maix_fft_freq_obj) }, + { MP_ROM_QSTR(MP_QSTR_amplitude), MP_ROM_PTR(&Maix_fft_amplitude_obj) }, + +}; + +STATIC MP_DEFINE_CONST_DICT(Maix_fft_dict, Maix_fft_locals_dict_table); + +const mp_obj_type_t Maix_fft_type = { + { &mp_type_type }, + .name = MP_QSTR_FFT, + .locals_dict = (mp_obj_dict_t*)&Maix_fft_dict, +}; diff --git a/src/声源定位代码/mic-code/Maix/Maix_fpioa.c b/src/声源定位代码/mic-code/Maix/Maix_fpioa.c new file mode 100644 index 0000000..4951f48 --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/Maix_fpioa.c @@ -0,0 +1,395 @@ + +#include +#include +#include + +#include "py/mphal.h" +#include "py/runtime.h" +#include "py/obj.h" +#include "py/objtype.h" +#include "py/objstr.h" +#include "py/objint.h" +#include "py/mperrno.h" +#include "fpioa.h" +#include "fpioa_des.h" + +/*Please don't modify this macro*/ +#define DES_SPACE_NUM(str) (sizeof(" ")-sizeof(" "))-strlen(str) +#define FUN_SPACE_NUM(str) (sizeof(" ")-sizeof(" "))-strlen(str) + +typedef struct _Maix_fpioa_obj_t { + mp_obj_base_t base; +}Maix_fpioa_obj_t; + +const mp_obj_type_t Maix_fpioa_type; + + +STATIC mp_obj_t Maix_set_function(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_pin, + ARG_func, + ARG_set_sl, + ARG_set_st, + ARG_set_io_driving + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_pin, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_func, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_set_sl, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_set_st, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_set_io_driving, MP_ARG_INT, {.u_int = -1} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args-1, pos_args+1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + uint16_t pin_num = args[ARG_pin].u_int; + fpioa_function_t func_num = args[ARG_func].u_int; + int16_t set_sl = args[ARG_set_sl].u_int; + int16_t set_st = args[ARG_set_st].u_int; + int16_t set_io_driving = args[ARG_set_io_driving].u_int; + + if(pin_num > FPIOA_NUM_IO) + mp_raise_ValueError("Don't have this Pin"); + + if(func_num < 0 || func_num > USABLE_FUNC_NUM) + mp_raise_ValueError("This function is invalid"); + + if(0 != fpioa_set_function(pin_num,func_num)) + { + mp_printf(&mp_plat_print, "[Maix]:Opps!Can not set fpioa\n"); + mp_raise_OSError(MP_EIO); + } + + if (-1 != set_sl) { + fpioa_set_sl(pin_num, set_sl); + } + + if (-1 != set_st) { + fpioa_set_st(pin_num, set_st); + } + + if(set_io_driving > FPIOA_DRIVING_MAX) + mp_raise_ValueError("set_io_driving > FPIOA_DRIVING_MAX"); + + if (-1 != set_io_driving) { + fpioa_set_io_driving(pin_num, set_io_driving); + } + + return mp_const_true; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(Maix_set_function_obj, 0,Maix_set_function); + +STATIC mp_obj_t Maix_get_Pin_num(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_func, + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_func, MP_ARG_INT, {.u_int = 0} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args-1, pos_args+1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + fpioa_function_t fun_num = args[ARG_func].u_int; + + if(fun_num < 0 || fun_num > USABLE_FUNC_NUM) + mp_raise_ValueError("This function is invalid"); + + int Pin_num = fpioa_get_io_by_function(fun_num); + + if(-1 == Pin_num) + { + return mp_const_none; + } + return MP_OBJ_NEW_SMALL_INT(Pin_num); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(Maix_get_Pin_num_obj, 0,Maix_get_Pin_num); + + +STATIC mp_obj_t Maix_fpioa_help(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_func, + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_func, MP_ARG_INT, {.u_int = USABLE_FUNC_NUM} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args-1, pos_args+1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + char* des_space_str = NULL; + char* fun_space_str = NULL; + if(args[ARG_func].u_int > USABLE_FUNC_NUM) + { + mp_printf(&mp_plat_print, "No this funciton Description\n"); + return mp_const_false; + } + mp_printf(&mp_plat_print, "+-------------------+----------------------------------+\n") ; + mp_printf(&mp_plat_print, "| Function | Description |\n") ; + mp_printf(&mp_plat_print, "+-------------------+----------------------------------+\n") ; + if(args[ARG_func].u_int == USABLE_FUNC_NUM) + { + + for(int i = 0;i < USABLE_FUNC_NUM ; i++) + { + /*malloc memory*/ + des_space_str = (char*)malloc(DES_SPACE_NUM(func_description[i])+1); + fun_space_str = (char*)malloc(FUN_SPACE_NUM( func_name[i])+1); + + memset(des_space_str,' ',DES_SPACE_NUM(func_description[i])); + des_space_str[DES_SPACE_NUM(func_description[i])] = '\0'; + + memset(fun_space_str,' ',FUN_SPACE_NUM( func_name[i])); + fun_space_str[FUN_SPACE_NUM( func_name[i])] = '\0'; + + + mp_printf(&mp_plat_print, "| %s%s| %s%s|\n", func_name[i],fun_space_str,func_description[i],des_space_str) ; + free(des_space_str); + free(fun_space_str); + mp_printf(&mp_plat_print, "+-------------------+----------------------------------+\n") ; + } + } + else + { + des_space_str = (char*)malloc(DES_SPACE_NUM(func_description[args[ARG_func].u_int])+1); + fun_space_str = (char*)malloc(FUN_SPACE_NUM(func_name[args[ARG_func].u_int])+1); + + memset(des_space_str,' ',DES_SPACE_NUM(func_description[args[ARG_func].u_int])); + des_space_str[DES_SPACE_NUM(func_description[args[ARG_func].u_int])] = '\0'; + + memset(fun_space_str,' ',FUN_SPACE_NUM(func_name[args[ARG_func].u_int])); + fun_space_str[FUN_SPACE_NUM(func_name[args[ARG_func].u_int])] = '\0'; + + mp_printf(&mp_plat_print, "| %s%s| %s%s|\n", func_name[args[ARG_func].u_int],fun_space_str,func_description[args[ARG_func].u_int],des_space_str) ; + free(des_space_str); + free(fun_space_str); + mp_printf(&mp_plat_print, "+-------------------+----------------------------------+\n") ; + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(Maix_fpioa_help_obj, 0,Maix_fpioa_help); + +STATIC mp_obj_t Maix_fpioa_make_new() { + + Maix_fpioa_obj_t *self = m_new_obj(Maix_fpioa_obj_t); + self->base.type = &Maix_fpioa_type; + + return self; +} + +STATIC const mp_rom_map_elem_t Maix_fpioa_locals_dict_table[] = { + // fpioa methods + { MP_ROM_QSTR(MP_QSTR_set_function), MP_ROM_PTR(&Maix_set_function_obj) }, + { MP_ROM_QSTR(MP_QSTR_help), MP_ROM_PTR(&Maix_fpioa_help_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_Pin_num), MP_ROM_PTR(&Maix_get_Pin_num_obj) }, + {MP_ROM_QSTR(MP_QSTR_JTAG_TCLK ), MP_ROM_INT(0 )}, + {MP_ROM_QSTR(MP_QSTR_JTAG_TDI ), MP_ROM_INT(1 )}, + {MP_ROM_QSTR(MP_QSTR_JTAG_TMS ), MP_ROM_INT(2 )}, + {MP_ROM_QSTR(MP_QSTR_JTAG_TDO ), MP_ROM_INT(3 )}, + {MP_ROM_QSTR(MP_QSTR_SPI0_D0 ), MP_ROM_INT(4 )}, + {MP_ROM_QSTR(MP_QSTR_SPI0_D1 ), MP_ROM_INT(5 )}, + {MP_ROM_QSTR(MP_QSTR_SPI0_D2 ), MP_ROM_INT(6 )}, + {MP_ROM_QSTR(MP_QSTR_SPI0_D3 ), MP_ROM_INT(7 )}, + {MP_ROM_QSTR(MP_QSTR_SPI0_D4 ), MP_ROM_INT(8 )}, + {MP_ROM_QSTR(MP_QSTR_SPI0_D5 ), MP_ROM_INT(9 )}, + {MP_ROM_QSTR(MP_QSTR_SPI0_D6 ), MP_ROM_INT(10 )}, + {MP_ROM_QSTR(MP_QSTR_SPI0_D7 ), MP_ROM_INT(11 )}, + {MP_ROM_QSTR(MP_QSTR_SPI0_SS0 ), MP_ROM_INT(12 )}, + {MP_ROM_QSTR(MP_QSTR_SPI0_SS1 ), MP_ROM_INT(13 )}, + {MP_ROM_QSTR(MP_QSTR_SPI0_SS2 ), MP_ROM_INT(14 )}, + {MP_ROM_QSTR(MP_QSTR_SPI0_SS3 ), MP_ROM_INT(15 )}, + {MP_ROM_QSTR(MP_QSTR_SPI0_ARB ), MP_ROM_INT(16 )}, + {MP_ROM_QSTR(MP_QSTR_SPI0_SCLK ), MP_ROM_INT(17 )}, + {MP_ROM_QSTR(MP_QSTR_UARTHS_RX ), MP_ROM_INT(18 )}, + {MP_ROM_QSTR(MP_QSTR_UARTHS_TX ), MP_ROM_INT(19 )}, + {MP_ROM_QSTR(MP_QSTR_RESV6 ), MP_ROM_INT(20 )}, + {MP_ROM_QSTR(MP_QSTR_RESV7 ), MP_ROM_INT(21 )}, + {MP_ROM_QSTR(MP_QSTR_CLK_SPI1 ), MP_ROM_INT(22 )}, + {MP_ROM_QSTR(MP_QSTR_CLK_I2C1 ), MP_ROM_INT(23 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS0 ), MP_ROM_INT(24 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS1 ), MP_ROM_INT(25 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS2 ), MP_ROM_INT(26 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS3 ), MP_ROM_INT(27 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS4 ), MP_ROM_INT(28 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS5 ), MP_ROM_INT(29 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS6 ), MP_ROM_INT(30 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS7 ), MP_ROM_INT(31 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS8 ), MP_ROM_INT(32 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS9 ), MP_ROM_INT(33 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS10 ), MP_ROM_INT(34 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS11 ), MP_ROM_INT(35 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS12 ), MP_ROM_INT(36 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS13 ), MP_ROM_INT(37 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS14 ), MP_ROM_INT(38 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS15 ), MP_ROM_INT(39 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS16 ), MP_ROM_INT(40 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS17 ), MP_ROM_INT(41 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS18 ), MP_ROM_INT(42 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS19 ), MP_ROM_INT(43 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS20 ), MP_ROM_INT(44 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS21 ), MP_ROM_INT(45 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS22 ), MP_ROM_INT(46 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS23 ), MP_ROM_INT(47 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS24 ), MP_ROM_INT(48 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS25 ), MP_ROM_INT(49 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS26 ), MP_ROM_INT(50 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS27 ), MP_ROM_INT(51 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS28 ), MP_ROM_INT(52 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS29 ), MP_ROM_INT(53 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS30 ), MP_ROM_INT(54 )}, + {MP_ROM_QSTR(MP_QSTR_GPIOHS31 ), MP_ROM_INT(55 )}, + {MP_ROM_QSTR(MP_QSTR_GPIO0 ), MP_ROM_INT(56 )}, + {MP_ROM_QSTR(MP_QSTR_GPIO1 ), MP_ROM_INT(57 )}, + {MP_ROM_QSTR(MP_QSTR_GPIO2 ), MP_ROM_INT(58 )}, + {MP_ROM_QSTR(MP_QSTR_GPIO3 ), MP_ROM_INT(59 )}, + {MP_ROM_QSTR(MP_QSTR_GPIO4 ), MP_ROM_INT(60 )}, + {MP_ROM_QSTR(MP_QSTR_GPIO5 ), MP_ROM_INT(61 )}, + {MP_ROM_QSTR(MP_QSTR_GPIO6 ), MP_ROM_INT(62 )}, + {MP_ROM_QSTR(MP_QSTR_GPIO7 ), MP_ROM_INT(63 )}, + {MP_ROM_QSTR(MP_QSTR_UART1_RX ), MP_ROM_INT(64 )}, + {MP_ROM_QSTR(MP_QSTR_UART1_TX ), MP_ROM_INT(65 )}, + {MP_ROM_QSTR(MP_QSTR_UART2_RX ), MP_ROM_INT(66 )}, + {MP_ROM_QSTR(MP_QSTR_UART2_TX ), MP_ROM_INT(67 )}, + {MP_ROM_QSTR(MP_QSTR_UART3_RX ), MP_ROM_INT(68 )}, + {MP_ROM_QSTR(MP_QSTR_UART3_TX ), MP_ROM_INT(69 )}, + {MP_ROM_QSTR(MP_QSTR_SPI1_D0 ), MP_ROM_INT(70 )}, + {MP_ROM_QSTR(MP_QSTR_SPI1_D1 ), MP_ROM_INT(71 )}, + {MP_ROM_QSTR(MP_QSTR_SPI1_D2 ), MP_ROM_INT(72 )}, + {MP_ROM_QSTR(MP_QSTR_SPI1_D3 ), MP_ROM_INT(73 )}, + {MP_ROM_QSTR(MP_QSTR_SPI1_D4 ), MP_ROM_INT(74 )}, + {MP_ROM_QSTR(MP_QSTR_SPI1_D5 ), MP_ROM_INT(75 )}, + {MP_ROM_QSTR(MP_QSTR_SPI1_D6 ), MP_ROM_INT(76 )}, + {MP_ROM_QSTR(MP_QSTR_SPI1_D7 ), MP_ROM_INT(77 )}, + {MP_ROM_QSTR(MP_QSTR_SPI1_SS0 ), MP_ROM_INT(78 )}, + {MP_ROM_QSTR(MP_QSTR_SPI1_SS1 ), MP_ROM_INT(79 )}, + {MP_ROM_QSTR(MP_QSTR_SPI1_SS2 ), MP_ROM_INT(80 )}, + {MP_ROM_QSTR(MP_QSTR_SPI1_SS3 ), MP_ROM_INT(81 )}, + {MP_ROM_QSTR(MP_QSTR_SPI1_ARB ), MP_ROM_INT(82 )}, + {MP_ROM_QSTR(MP_QSTR_SPI1_SCLK ), MP_ROM_INT(83 )}, + {MP_ROM_QSTR(MP_QSTR_SPI_SLAVE_D0 ), MP_ROM_INT(84 )}, + {MP_ROM_QSTR(MP_QSTR_SPI_SLAVE_SS ), MP_ROM_INT(85 )}, + {MP_ROM_QSTR(MP_QSTR_SPI_SLAVE_SCLK), MP_ROM_INT(86 )}, + {MP_ROM_QSTR(MP_QSTR_I2S0_MCLK ), MP_ROM_INT(87 )}, + {MP_ROM_QSTR(MP_QSTR_I2S0_SCLK ), MP_ROM_INT(88 )}, + {MP_ROM_QSTR(MP_QSTR_I2S0_WS ), MP_ROM_INT(89 )}, + {MP_ROM_QSTR(MP_QSTR_I2S0_IN_D0 ), MP_ROM_INT(90 )}, + {MP_ROM_QSTR(MP_QSTR_I2S0_IN_D1 ), MP_ROM_INT(91 )}, + {MP_ROM_QSTR(MP_QSTR_I2S0_IN_D2 ), MP_ROM_INT(92 )}, + {MP_ROM_QSTR(MP_QSTR_I2S0_IN_D3 ), MP_ROM_INT(93 )}, + {MP_ROM_QSTR(MP_QSTR_I2S0_OUT_D0 ), MP_ROM_INT(94 )}, + {MP_ROM_QSTR(MP_QSTR_I2S0_OUT_D1 ), MP_ROM_INT(95 )}, + {MP_ROM_QSTR(MP_QSTR_I2S0_OUT_D2 ), MP_ROM_INT(96 )}, + {MP_ROM_QSTR(MP_QSTR_I2S0_OUT_D3 ), MP_ROM_INT(97 )}, + {MP_ROM_QSTR(MP_QSTR_I2S1_MCLK ), MP_ROM_INT(98 )}, + {MP_ROM_QSTR(MP_QSTR_I2S1_SCLK ), MP_ROM_INT(99 )}, + {MP_ROM_QSTR(MP_QSTR_I2S1_WS ), MP_ROM_INT(100)}, + {MP_ROM_QSTR(MP_QSTR_I2S1_IN_D0 ), MP_ROM_INT(101)}, + {MP_ROM_QSTR(MP_QSTR_I2S1_IN_D1 ), MP_ROM_INT(102)}, + {MP_ROM_QSTR(MP_QSTR_I2S1_IN_D2 ), MP_ROM_INT(103)}, + {MP_ROM_QSTR(MP_QSTR_I2S1_IN_D3 ), MP_ROM_INT(104)}, + {MP_ROM_QSTR(MP_QSTR_I2S1_OUT_D0 ), MP_ROM_INT(105)}, + {MP_ROM_QSTR(MP_QSTR_I2S1_OUT_D1 ), MP_ROM_INT(106)}, + {MP_ROM_QSTR(MP_QSTR_I2S1_OUT_D2 ), MP_ROM_INT(107)}, + {MP_ROM_QSTR(MP_QSTR_I2S1_OUT_D3 ), MP_ROM_INT(108)}, + {MP_ROM_QSTR(MP_QSTR_I2S2_MCLK ), MP_ROM_INT(109)}, + {MP_ROM_QSTR(MP_QSTR_I2S2_SCLK ), MP_ROM_INT(110)}, + {MP_ROM_QSTR(MP_QSTR_I2S2_WS ), MP_ROM_INT(111)}, + {MP_ROM_QSTR(MP_QSTR_I2S2_IN_D0 ), MP_ROM_INT(112)}, + {MP_ROM_QSTR(MP_QSTR_I2S2_IN_D1 ), MP_ROM_INT(113)}, + {MP_ROM_QSTR(MP_QSTR_I2S2_IN_D2 ), MP_ROM_INT(114)}, + {MP_ROM_QSTR(MP_QSTR_I2S2_IN_D3 ), MP_ROM_INT(115)}, + {MP_ROM_QSTR(MP_QSTR_I2S2_OUT_D0 ), MP_ROM_INT(116)}, + {MP_ROM_QSTR(MP_QSTR_I2S2_OUT_D1 ), MP_ROM_INT(117)}, + {MP_ROM_QSTR(MP_QSTR_I2S2_OUT_D2 ), MP_ROM_INT(118)}, + {MP_ROM_QSTR(MP_QSTR_I2S2_OUT_D3 ), MP_ROM_INT(119)}, + {MP_ROM_QSTR(MP_QSTR_RESV0 ), MP_ROM_INT(120)}, + {MP_ROM_QSTR(MP_QSTR_RESV1 ), MP_ROM_INT(121)}, + {MP_ROM_QSTR(MP_QSTR_RESV2 ), MP_ROM_INT(122)}, + {MP_ROM_QSTR(MP_QSTR_RESV3 ), MP_ROM_INT(123)}, + {MP_ROM_QSTR(MP_QSTR_RESV4 ), MP_ROM_INT(124)}, + {MP_ROM_QSTR(MP_QSTR_RESV5 ), MP_ROM_INT(125)}, + {MP_ROM_QSTR(MP_QSTR_I2C0_SCLK ), MP_ROM_INT(126)}, + {MP_ROM_QSTR(MP_QSTR_I2C0_SDA ), MP_ROM_INT(127)}, + {MP_ROM_QSTR(MP_QSTR_I2C1_SCLK ), MP_ROM_INT(128)}, + {MP_ROM_QSTR(MP_QSTR_I2C1_SDA ), MP_ROM_INT(129)}, + {MP_ROM_QSTR(MP_QSTR_I2C2_SCLK ), MP_ROM_INT(130)}, + {MP_ROM_QSTR(MP_QSTR_I2C2_SDA ), MP_ROM_INT(131)}, + {MP_ROM_QSTR(MP_QSTR_CMOS_XCLK ), MP_ROM_INT(132)}, + {MP_ROM_QSTR(MP_QSTR_CMOS_RST ), MP_ROM_INT(133)}, + {MP_ROM_QSTR(MP_QSTR_CMOS_PWDN ), MP_ROM_INT(134)}, + {MP_ROM_QSTR(MP_QSTR_CMOS_VSYNC ), MP_ROM_INT(135)}, + {MP_ROM_QSTR(MP_QSTR_CMOS_HREF ), MP_ROM_INT(136)}, + {MP_ROM_QSTR(MP_QSTR_CMOS_PCLK ), MP_ROM_INT(137)}, + {MP_ROM_QSTR(MP_QSTR_CMOS_D0 ), MP_ROM_INT(138)}, + {MP_ROM_QSTR(MP_QSTR_CMOS_D1 ), MP_ROM_INT(139)}, + {MP_ROM_QSTR(MP_QSTR_CMOS_D2 ), MP_ROM_INT(140)}, + {MP_ROM_QSTR(MP_QSTR_CMOS_D3 ), MP_ROM_INT(141)}, + {MP_ROM_QSTR(MP_QSTR_CMOS_D4 ), MP_ROM_INT(142)}, + {MP_ROM_QSTR(MP_QSTR_CMOS_D5 ), MP_ROM_INT(143)}, + {MP_ROM_QSTR(MP_QSTR_CMOS_D6 ), MP_ROM_INT(144)}, + {MP_ROM_QSTR(MP_QSTR_CMOS_D7 ), MP_ROM_INT(145)}, + {MP_ROM_QSTR(MP_QSTR_SCCB_SCLK ), MP_ROM_INT(146)}, + {MP_ROM_QSTR(MP_QSTR_SCCB_SDA ), MP_ROM_INT(147)}, + {MP_ROM_QSTR(MP_QSTR_UART1_CTS ), MP_ROM_INT(148)}, + {MP_ROM_QSTR(MP_QSTR_UART1_DSR ), MP_ROM_INT(149)}, + {MP_ROM_QSTR(MP_QSTR_UART1_DCD ), MP_ROM_INT(150)}, + {MP_ROM_QSTR(MP_QSTR_UART1_RI ), MP_ROM_INT(151)}, + {MP_ROM_QSTR(MP_QSTR_UART1_SIR_IN ), MP_ROM_INT(152)}, + {MP_ROM_QSTR(MP_QSTR_UART1_DTR ), MP_ROM_INT(153)}, + {MP_ROM_QSTR(MP_QSTR_UART1_RTS ), MP_ROM_INT(154)}, + {MP_ROM_QSTR(MP_QSTR_UART1_OUT2 ), MP_ROM_INT(155)}, + {MP_ROM_QSTR(MP_QSTR_UART1_OUT1 ), MP_ROM_INT(156)}, + {MP_ROM_QSTR(MP_QSTR_UART1_SIR_OUT ), MP_ROM_INT(157)}, + {MP_ROM_QSTR(MP_QSTR_UART1_BAUD ), MP_ROM_INT(158)}, + {MP_ROM_QSTR(MP_QSTR_UART1_RE ), MP_ROM_INT(159)}, + {MP_ROM_QSTR(MP_QSTR_UART1_DE ), MP_ROM_INT(160)}, + {MP_ROM_QSTR(MP_QSTR_UART1_RS485_EN), MP_ROM_INT(161)}, + {MP_ROM_QSTR(MP_QSTR_UART2_CTS ), MP_ROM_INT(162)}, + {MP_ROM_QSTR(MP_QSTR_UART2_DSR ), MP_ROM_INT(163)}, + {MP_ROM_QSTR(MP_QSTR_UART2_DCD ), MP_ROM_INT(164)}, + {MP_ROM_QSTR(MP_QSTR_UART2_RI ), MP_ROM_INT(165)}, + {MP_ROM_QSTR(MP_QSTR_UART2_SIR_IN ), MP_ROM_INT(166)}, + {MP_ROM_QSTR(MP_QSTR_UART2_DTR ), MP_ROM_INT(167)}, + {MP_ROM_QSTR(MP_QSTR_UART2_RTS ), MP_ROM_INT(168)}, + {MP_ROM_QSTR(MP_QSTR_UART2_OUT2 ), MP_ROM_INT(169)}, + {MP_ROM_QSTR(MP_QSTR_UART2_OUT1 ), MP_ROM_INT(170)}, + {MP_ROM_QSTR(MP_QSTR_UART2_SIR_OUT ), MP_ROM_INT(171)}, + {MP_ROM_QSTR(MP_QSTR_UART2_BAUD ), MP_ROM_INT(172)}, + {MP_ROM_QSTR(MP_QSTR_UART2_RE ), MP_ROM_INT(173)}, + {MP_ROM_QSTR(MP_QSTR_UART2_DE ), MP_ROM_INT(174)}, + {MP_ROM_QSTR(MP_QSTR_UART2_RS485_EN), MP_ROM_INT(175)}, + {MP_ROM_QSTR(MP_QSTR_UART3_CTS ), MP_ROM_INT(176)}, + {MP_ROM_QSTR(MP_QSTR_UART3_DSR ), MP_ROM_INT(177)}, + {MP_ROM_QSTR(MP_QSTR_UART3_DCD ), MP_ROM_INT(178)}, + {MP_ROM_QSTR(MP_QSTR_UART3_RI ), MP_ROM_INT(179)}, + {MP_ROM_QSTR(MP_QSTR_UART3_SIR_IN ), MP_ROM_INT(180)}, + {MP_ROM_QSTR(MP_QSTR_UART3_DTR ), MP_ROM_INT(181)}, + {MP_ROM_QSTR(MP_QSTR_UART3_RTS ), MP_ROM_INT(182)}, + {MP_ROM_QSTR(MP_QSTR_UART3_OUT2 ), MP_ROM_INT(183)}, + {MP_ROM_QSTR(MP_QSTR_UART3_OUT1 ), MP_ROM_INT(184)}, + {MP_ROM_QSTR(MP_QSTR_UART3_SIR_OUT ), MP_ROM_INT(185)}, + {MP_ROM_QSTR(MP_QSTR_UART3_BAUD ), MP_ROM_INT(186)}, + {MP_ROM_QSTR(MP_QSTR_UART3_RE ), MP_ROM_INT(187)}, + {MP_ROM_QSTR(MP_QSTR_UART3_DE ), MP_ROM_INT(188)}, + {MP_ROM_QSTR(MP_QSTR_UART3_RS485_EN), MP_ROM_INT(189)}, + {MP_ROM_QSTR(MP_QSTR_TIMER0_TOGGLE1), MP_ROM_INT(190)}, + {MP_ROM_QSTR(MP_QSTR_TIMER0_TOGGLE2), MP_ROM_INT(191)}, + {MP_ROM_QSTR(MP_QSTR_TIMER0_TOGGLE3), MP_ROM_INT(192)}, + {MP_ROM_QSTR(MP_QSTR_TIMER0_TOGGLE4), MP_ROM_INT(193)}, + {MP_ROM_QSTR(MP_QSTR_TIMER1_TOGGLE1), MP_ROM_INT(194)}, + {MP_ROM_QSTR(MP_QSTR_TIMER1_TOGGLE2), MP_ROM_INT(195)}, + {MP_ROM_QSTR(MP_QSTR_TIMER1_TOGGLE3), MP_ROM_INT(196)}, + {MP_ROM_QSTR(MP_QSTR_TIMER1_TOGGLE4), MP_ROM_INT(197)}, + {MP_ROM_QSTR(MP_QSTR_TIMER2_TOGGLE1), MP_ROM_INT(198)}, + {MP_ROM_QSTR(MP_QSTR_TIMER2_TOGGLE2), MP_ROM_INT(199)}, + {MP_ROM_QSTR(MP_QSTR_TIMER2_TOGGLE3), MP_ROM_INT(200)}, + {MP_ROM_QSTR(MP_QSTR_TIMER2_TOGGLE4), MP_ROM_INT(201)}, + {MP_ROM_QSTR(MP_QSTR_CLK_SPI2 ), MP_ROM_INT(202)}, + {MP_ROM_QSTR(MP_QSTR_CLK_I2C2 ), MP_ROM_INT(203)}, +}; +STATIC MP_DEFINE_CONST_DICT(Maix_fpioa_locals_dict, Maix_fpioa_locals_dict_table); +const mp_obj_type_t Maix_fpioa_type = { + { &mp_type_type }, + .name = MP_QSTR_FPIOA, + .make_new = Maix_fpioa_make_new, + .locals_dict = (mp_obj_dict_t*)&Maix_fpioa_locals_dict, +}; + + + diff --git a/src/声源定位代码/mic-code/Maix/Maix_gpio.c b/src/声源定位代码/mic-code/Maix/Maix_gpio.c new file mode 100644 index 0000000..49cb864 --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/Maix_gpio.c @@ -0,0 +1,587 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "gpio.h" +#include "gpiohs.h" +#include "plic.h" + +#include "py/runtime.h" +#include "py/mphal.h" +#include "mphalport.h" +#include "modmachine.h" +#include "extmod/virtpin.h" + +const mp_obj_type_t Maix_gpio_type; + +typedef int gpio_num_t; +enum { + GPIO_DM_PULL_NONE = -1, +}; + +typedef enum _gpio_type_t{ + GPIOHS = 0, + GPIO = 1, +}gpio_type_t; + +typedef struct _Maix_gpio_obj_t { + mp_obj_base_t base; + gpio_num_t num; + gpio_type_t gpio_type; + gpio_num_t id; + mp_obj_t callback; + gpio_drive_mode_t mode; + +} Maix_gpio_obj_t; + +typedef struct _Maix_gpio_irq_obj_t { + mp_obj_base_t base; + gpio_num_t num; + gpio_num_t id; +} Maix_gpio_irq_obj_t; + +typedef enum __gpio_t{ + GPIO_NUM_0 = 0, + GPIO_NUM_1, + GPIO_NUM_2, + GPIO_NUM_3, + GPIO_NUM_4, + GPIO_NUM_5, + GPIO_NUM_6, + GPIO_NUM_7, +}_gpio_t; + +typedef enum __gpiohs_t{ + GPIOHS_NUM_0 = 0, + GPIOHS_NUM_1, + GPIOHS_NUM_2, + GPIOHS_NUM_3, + GPIOHS_NUM_4, + GPIOHS_NUM_5, + GPIOHS_NUM_6, + GPIOHS_NUM_7, + GPIOHS_NUM_8, + GPIOHS_NUM_9, + GPIOHS_NUM_10, + GPIOHS_NUM_11, + GPIOHS_NUM_12, + GPIOHS_NUM_13, + GPIOHS_NUM_14, + GPIOHS_NUM_15, + GPIOHS_NUM_16, + GPIOHS_NUM_17, + GPIOHS_NUM_18, + GPIOHS_NUM_19, + GPIOHS_NUM_20, + GPIOHS_NUM_21, + GPIOHS_NUM_22, + GPIOHS_NUM_23, + GPIOHS_NUM_24, + GPIOHS_NUM_25, + GPIOHS_NUM_26, + GPIOHS_NUM_27, + GPIOHS_NUM_28, + GPIOHS_NUM_29, + GPIOHS_NUM_30, + GPIOHS_NUM_31, +} _gpiohs_t; + +STATIC const Maix_gpio_obj_t Maix_gpio_obj[] = { + {{&Maix_gpio_type}, 0, GPIOHS, GPIOHS_NUM_0, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 1, GPIOHS, GPIOHS_NUM_1, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 2, GPIOHS, GPIOHS_NUM_2, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 3, GPIOHS, GPIOHS_NUM_3, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 4, GPIOHS, GPIOHS_NUM_4, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 5, GPIOHS, GPIOHS_NUM_5, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 6, GPIOHS, GPIOHS_NUM_6, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 7, GPIOHS, GPIOHS_NUM_7, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 8, GPIOHS, GPIOHS_NUM_8, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 9, GPIOHS, GPIOHS_NUM_9, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 10, GPIOHS, GPIOHS_NUM_10, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 11, GPIOHS, GPIOHS_NUM_11, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 12, GPIOHS, GPIOHS_NUM_12, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 13, GPIOHS, GPIOHS_NUM_13, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 14, GPIOHS, GPIOHS_NUM_14, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 15, GPIOHS, GPIOHS_NUM_15, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 16, GPIOHS, GPIOHS_NUM_16, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 17, GPIOHS, GPIOHS_NUM_17, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 18, GPIOHS, GPIOHS_NUM_18, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 19, GPIOHS, GPIOHS_NUM_19, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 20, GPIOHS, GPIOHS_NUM_20, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 21, GPIOHS, GPIOHS_NUM_21, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 22, GPIOHS, GPIOHS_NUM_22, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 23, GPIOHS, GPIOHS_NUM_23, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 24, GPIOHS, GPIOHS_NUM_24, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 25, GPIOHS, GPIOHS_NUM_25, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 26, GPIOHS, GPIOHS_NUM_26, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 27, GPIOHS, GPIOHS_NUM_27, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 28, GPIOHS, GPIOHS_NUM_28, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 29, GPIOHS, GPIOHS_NUM_29, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 30, GPIOHS, GPIOHS_NUM_30, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 31, GPIOHS, GPIOHS_NUM_31, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 32, GPIO, GPIO_NUM_0, MP_OBJ_NULL, GPIO_DM_INPUT},//32 + {{&Maix_gpio_type}, 33, GPIO, GPIO_NUM_1, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 34, GPIO, GPIO_NUM_2, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 35, GPIO, GPIO_NUM_3, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 36, GPIO, GPIO_NUM_4, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 37, GPIO, GPIO_NUM_5, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 38, GPIO, GPIO_NUM_6, MP_OBJ_NULL, GPIO_DM_INPUT}, + {{&Maix_gpio_type}, 39, GPIO, GPIO_NUM_7, MP_OBJ_NULL, GPIO_DM_INPUT}, + +}; + +// forward declaration +STATIC const Maix_gpio_irq_obj_t Maix_gpio_irq_object[]; + +void Maix_gpios_init(void) { + // memset(&MP_STATE_PORT(Maix_gpio_irq_handler[0]), 0, sizeof(MP_STATE_PORT(Maix_gpio_irq_handler))); +} + +void Maix_gpios_deinit(void) { + + for (int i = 0; i < MP_ARRAY_SIZE(Maix_gpio_obj); ++i) { + if (Maix_gpio_obj[i].gpio_type != GPIO) { + plic_irq_disable(IRQN_GPIOHS0_INTERRUPT + Maix_gpio_obj[i].id); + } + } +} + +STATIC int Maix_gpio_isr_handler(void *arg) { + Maix_gpio_obj_t *self = arg; + //only gpiohs support irq,so only support gpiohs in this func + mp_obj_t handler = self->callback; +// mp_call_function_2(handler, MP_OBJ_FROM_PTR(self), mp_obj_new_int_from_uint(self->id)); + mp_sched_schedule(handler, MP_OBJ_FROM_PTR(self)); + mp_hal_wake_main_task_from_isr(); + return 0; +} + +gpio_num_t Maix_gpio_get_id(mp_obj_t pin_in) { + if (mp_obj_get_type(pin_in) != &Maix_gpio_type) { + mp_raise_ValueError("expecting a pin"); + } + Maix_gpio_obj_t *self = pin_in; + return self->id; +} + +STATIC void Maix_gpio_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + Maix_gpio_obj_t *self = self_in; + + mp_printf(print, "Pin(%u)", self->id); +} + +// pin.init(mode, pull=None, *, value) +STATIC mp_obj_t Maix_gpio_obj_init_helper(Maix_gpio_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_mode, ARG_pull, ARG_value }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mode, MP_ARG_OBJ, {.u_obj = mp_const_none}}, + { MP_QSTR_pull, MP_ARG_OBJ, {.u_obj = mp_const_none}}, + { MP_QSTR_value, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // configure mode + if (args[ARG_mode].u_obj != mp_const_none) { + mp_int_t pin_io_mode = mp_obj_get_int(args[ARG_mode].u_obj); + if (0 <= self->num && self->num < MP_ARRAY_SIZE(Maix_gpio_obj)) { + self = (Maix_gpio_obj_t*)&Maix_gpio_obj[self->num]; + if(pin_io_mode == GPIO_DM_OUTPUT && args[ARG_pull].u_obj != mp_const_none && mp_obj_get_int(args[ARG_pull].u_obj) != GPIO_DM_PULL_NONE){ + mp_raise_ValueError("When this pin is in output mode, it is not allowed to pull up and down."); + }else{ + if(args[ARG_pull].u_obj != mp_const_none && mp_obj_get_int(args[ARG_pull].u_obj) != GPIO_DM_PULL_NONE ){ + if(mp_obj_get_int(args[ARG_pull].u_obj) == GPIO_DM_INPUT_PULL_UP || mp_obj_get_int(args[ARG_pull].u_obj) == GPIO_DM_INPUT_PULL_DOWN){ + pin_io_mode = mp_obj_get_int(args[ARG_pull].u_obj); + }else{ + mp_raise_ValueError("this mode not support."); + } + } + if(self->gpio_type == GPIO){ + gpio_set_drive_mode(self->id, pin_io_mode); + }else{ + gpiohs_set_drive_mode(self->id, pin_io_mode); + } + self->mode = pin_io_mode; + } + + //set initial value (dont this before configuring mode/pull) + if (args[ARG_value].u_obj != MP_OBJ_NULL) { + if(self->gpio_type == GPIOHS){ + gpiohs_set_pin((uint8_t)self->id,mp_obj_is_true(args[ARG_value].u_obj)); + }else{ + gpio_set_pin((uint8_t)self->id,mp_obj_is_true(args[ARG_value].u_obj)); + } + } + }else{ + mp_raise_ValueError("pin not found"); + } + } + + return mp_const_none; +} + +// constructor(id, ...) +mp_obj_t mp_maixpy_pin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + // get the wanted pin object + int wanted_pin = mp_obj_get_int(args[0]); + Maix_gpio_obj_t *self = NULL; + if (0 <= wanted_pin && wanted_pin < MP_ARRAY_SIZE(Maix_gpio_obj)) { + self = (Maix_gpio_obj_t*)&Maix_gpio_obj[wanted_pin]; + } + if (self == NULL || self->base.type == NULL) { + mp_raise_ValueError("invalid pin"); + } + + if (n_args > 1 || n_kw > 0) { + // pin mode given, so configure this GPIO + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + Maix_gpio_obj_init_helper(self, n_args - 1, args + 1, &kw_args); + } + + return MP_OBJ_FROM_PTR(self); +} + +// fast method for getting/setting pin value +STATIC mp_obj_t Maix_gpio_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 1, false); + Maix_gpio_obj_t *self = self_in; + if (n_args == 0) { + // get pin + if(self->gpio_type == GPIO){ + return MP_OBJ_NEW_SMALL_INT(gpio_get_pin((uint8_t)self->id)); + }else{ + if (self->mode == GPIO_DM_OUTPUT) { + gpiohs_set_drive_mode((uint8_t)self->id, GPIO_DM_INPUT); + } + int value = gpiohs_get_pin((uint8_t)self->id); + if (self->mode == GPIO_DM_OUTPUT) { + gpiohs_set_drive_mode((uint8_t)self->id, GPIO_DM_OUTPUT); + } + return MP_OBJ_NEW_SMALL_INT(value); + } + + } else { + // set pin + if(self->gpio_type == GPIO){ + gpio_set_pin(self->id, mp_obj_is_true(args[0])); + }else{ + gpiohs_set_pin(self->id, mp_obj_is_true(args[0])); + } + return mp_const_none; + } +} + +// pin.init(mode, pull) +STATIC mp_obj_t Maix_gpio_obj_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return Maix_gpio_obj_init_helper(args[0], n_args - 1, args + 1, kw_args); +} +MP_DEFINE_CONST_FUN_OBJ_KW(Maix_gpio_init_obj, 1, Maix_gpio_obj_init); + +// pin.value([value]) +STATIC mp_obj_t Maix_gpio_value(size_t n_args, const mp_obj_t *args) { + return Maix_gpio_call(args[0], n_args - 1, 0, args + 1); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Maix_gpio_value_obj, 1, 2, Maix_gpio_value); + +// pin.irq(handler=None, trigger=IRQ_FALLING|IRQ_RISING) +STATIC mp_obj_t Maix_gpio_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_handler, ARG_trigger, ARG_wake ,ARG_priority}; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_handler, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = GPIO_PE_BOTH} }, + { MP_QSTR_wake, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_priority, MP_ARG_INT, {.u_int = 7} }, + }; + Maix_gpio_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (self->gpio_type != GPIO && (n_args > 1 || kw_args->used != 0)) { + // configure irq + mp_obj_t handler = args[ARG_handler].u_obj; + uint32_t trigger = args[ARG_trigger].u_int; + mp_obj_t wake_obj = args[ARG_wake].u_obj; + mp_int_t temp_wake_int; + mp_obj_get_int_maybe(args[ARG_wake].u_obj,&temp_wake_int); + + if(wake_obj != mp_const_none && temp_wake_int != 0){ + mp_raise_ValueError("This platform does not support interrupt wakeup"); + }else{ + if (trigger == GPIO_PE_NONE || trigger == GPIO_PE_RISING || trigger == GPIO_PE_FALLING || trigger == GPIO_PE_BOTH) { + + if (handler == mp_const_none) { + handler = MP_OBJ_NULL; + trigger = 0; + } + self->callback = handler; + gpiohs_set_pin_edge((uint8_t)self->id,trigger); + gpiohs_irq_register((uint8_t)self->id, args[ARG_priority].u_int, Maix_gpio_isr_handler, (void *)self); + }else{ + + } + } + + } + + + //return the irq object + return MP_OBJ_FROM_PTR(&Maix_gpio_irq_object[self->num]); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(Maix_gpio_irq_obj, 1, Maix_gpio_irq); + +STATIC mp_obj_t Maix_gpio_disirq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + Maix_gpio_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + + if (self->gpio_type != GPIO) { + plic_irq_disable(IRQN_GPIOHS0_INTERRUPT + (uint8_t)self->id); + } + return mp_const_none; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(Maix_gpio_disirq_obj,1,Maix_gpio_disirq); + + +STATIC mp_obj_t Maix_gpio_mode(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + if(!mp_obj_is_type(pos_args[0], &Maix_gpio_type)) + mp_raise_ValueError("only for object"); + Maix_gpio_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + + enum { ARG_mode}; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mode, MP_ARG_INT, {.u_int = -1} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(args[ARG_mode].u_int == -1) + { + return mp_obj_new_int(self->mode); + } + else if(args[ARG_mode].u_int != GPIO_DM_INPUT && + args[ARG_mode].u_int != GPIO_DM_OUTPUT && + args[ARG_mode].u_int != GPIO_DM_PULL_NONE && + args[ARG_mode].u_int != GPIO_DM_INPUT_PULL_UP && + args[ARG_mode].u_int != GPIO_DM_INPUT_PULL_DOWN + ) + { + mp_raise_ValueError("arg error"); + } + if (self->gpio_type == GPIO) { + gpio_set_drive_mode(self->id, (gpio_drive_mode_t)args[ARG_mode].u_int); + }else{ + gpiohs_set_drive_mode(self->id, (gpio_drive_mode_t)args[ARG_mode].u_int); + } + self->mode = (gpio_drive_mode_t)args[ARG_mode].u_int; + return mp_const_none; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(Maix_gpio_mode_obj,1,Maix_gpio_mode); + + +STATIC const mp_rom_map_elem_t Maix_gpio_locals_dict_table[] = { + // instance methods + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&Maix_gpio_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&Maix_gpio_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&Maix_gpio_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_disirq), MP_ROM_PTR(&Maix_gpio_disirq_obj) }, + { MP_ROM_QSTR(MP_QSTR_mode), MP_ROM_PTR(&Maix_gpio_mode_obj) }, + // class constants + { MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(GPIO_DM_INPUT) }, + { MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(GPIO_DM_OUTPUT) }, + { MP_ROM_QSTR(MP_QSTR_PULL_NONE), MP_ROM_INT(GPIO_DM_PULL_NONE) }, + { MP_ROM_QSTR(MP_QSTR_PULL_UP), MP_ROM_INT(GPIO_DM_INPUT_PULL_UP) }, + { MP_ROM_QSTR(MP_QSTR_PULL_DOWN), MP_ROM_INT(GPIO_DM_INPUT_PULL_DOWN) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_NONE), MP_ROM_INT(GPIO_PE_NONE) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_RISING), MP_ROM_INT(GPIO_PE_RISING) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_FALLING), MP_ROM_INT(GPIO_PE_FALLING) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_BOTH), MP_ROM_INT(GPIO_PE_BOTH) }, + + // gpio constant + { MP_ROM_QSTR(MP_QSTR_GPIOHS0), MP_ROM_INT(GPIOHS_NUM_0) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS1), MP_ROM_INT(GPIOHS_NUM_1) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS2), MP_ROM_INT(GPIOHS_NUM_2) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS3), MP_ROM_INT(GPIOHS_NUM_3) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS4), MP_ROM_INT(GPIOHS_NUM_4) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS5), MP_ROM_INT(GPIOHS_NUM_5) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS6), MP_ROM_INT(GPIOHS_NUM_6) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS7), MP_ROM_INT(GPIOHS_NUM_7) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS8), MP_ROM_INT(GPIOHS_NUM_8) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS9), MP_ROM_INT(GPIOHS_NUM_9) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS10), MP_ROM_INT(GPIOHS_NUM_10) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS11), MP_ROM_INT(GPIOHS_NUM_11) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS12), MP_ROM_INT(GPIOHS_NUM_12) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS13), MP_ROM_INT(GPIOHS_NUM_13) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS14), MP_ROM_INT(GPIOHS_NUM_14) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS15), MP_ROM_INT(GPIOHS_NUM_15) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS16), MP_ROM_INT(GPIOHS_NUM_16) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS17), MP_ROM_INT(GPIOHS_NUM_17) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS18), MP_ROM_INT(GPIOHS_NUM_18) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS19), MP_ROM_INT(GPIOHS_NUM_19) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS20), MP_ROM_INT(GPIOHS_NUM_20) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS21), MP_ROM_INT(GPIOHS_NUM_21) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS22), MP_ROM_INT(GPIOHS_NUM_22) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS23), MP_ROM_INT(GPIOHS_NUM_23) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS24), MP_ROM_INT(GPIOHS_NUM_24) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS25), MP_ROM_INT(GPIOHS_NUM_25) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS26), MP_ROM_INT(GPIOHS_NUM_26) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS27), MP_ROM_INT(GPIOHS_NUM_27) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS28), MP_ROM_INT(GPIOHS_NUM_28) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS29), MP_ROM_INT(GPIOHS_NUM_29) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS30), MP_ROM_INT(GPIOHS_NUM_30) }, + { MP_ROM_QSTR(MP_QSTR_GPIOHS31), MP_ROM_INT(GPIOHS_NUM_31) }, + { MP_ROM_QSTR(MP_QSTR_GPIO0), MP_ROM_INT(32) }, + { MP_ROM_QSTR(MP_QSTR_GPIO1), MP_ROM_INT(33) }, + { MP_ROM_QSTR(MP_QSTR_GPIO2), MP_ROM_INT(34) }, + { MP_ROM_QSTR(MP_QSTR_GPIO3), MP_ROM_INT(35) }, + { MP_ROM_QSTR(MP_QSTR_GPIO4), MP_ROM_INT(36) }, + { MP_ROM_QSTR(MP_QSTR_GPIO5), MP_ROM_INT(37) }, + { MP_ROM_QSTR(MP_QSTR_GPIO6), MP_ROM_INT(38) }, + { MP_ROM_QSTR(MP_QSTR_GPIO7), MP_ROM_INT(39) }, + + //wakeup not support + { MP_ROM_QSTR(MP_QSTR_WAKEUP_NOT_SUPPORT), MP_ROM_INT(0) }, +}; + +STATIC mp_uint_t pin_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { + (void)errcode; + Maix_gpio_obj_t *self = self_in; + + switch (request) { + case MP_PIN_READ: { + if(self->gpio_type == GPIO){ + return gpio_get_pin((uint8_t)self->id); + }else{ + return gpio_get_pin((uint8_t)self->id); + } + } + case MP_PIN_WRITE: { + if(self->gpio_type == GPIO){ + gpio_set_pin((uint8_t)self->id, arg); + }else{ + gpiohs_set_pin((uint8_t)self->id, arg); + } + return 0; + } + } + return -1; +} + +STATIC MP_DEFINE_CONST_DICT(Maix_gpio_locals_dict, Maix_gpio_locals_dict_table); + +STATIC const mp_pin_p_t pin_pin_p = { + .ioctl = pin_ioctl, +}; + +const mp_obj_type_t Maix_gpio_type = { + { &mp_type_type }, + .name = MP_QSTR_Pin, + .print = Maix_gpio_print, + .make_new = mp_maixpy_pin_make_new, + .call = Maix_gpio_call, + .protocol = &pin_pin_p, + .locals_dict = (mp_obj_t)&Maix_gpio_locals_dict, +}; + +/******************************************************************************/ +// Pin IRQ object + +STATIC const mp_obj_type_t Maix_gpio_irq_type; + +STATIC const Maix_gpio_irq_obj_t Maix_gpio_irq_object[] = { + {{&Maix_gpio_irq_type}, 0, GPIOHS_NUM_0}, + {{&Maix_gpio_irq_type}, 1, GPIOHS_NUM_1}, + {{&Maix_gpio_irq_type}, 2, GPIOHS_NUM_2}, + {{&Maix_gpio_irq_type}, 3, GPIOHS_NUM_3}, + {{&Maix_gpio_irq_type}, 4, GPIOHS_NUM_4}, + {{&Maix_gpio_irq_type}, 5, GPIOHS_NUM_5}, + {{&Maix_gpio_irq_type}, 6, GPIOHS_NUM_6}, + {{&Maix_gpio_irq_type}, 7, GPIOHS_NUM_7}, + {{&Maix_gpio_irq_type}, 8, GPIOHS_NUM_8}, + {{&Maix_gpio_irq_type}, 9, GPIOHS_NUM_9}, + {{&Maix_gpio_irq_type}, 10, GPIOHS_NUM_10}, + {{&Maix_gpio_irq_type}, 11, GPIOHS_NUM_11}, + {{&Maix_gpio_irq_type}, 12, GPIOHS_NUM_12}, + {{&Maix_gpio_irq_type}, 13, GPIOHS_NUM_13}, + {{&Maix_gpio_irq_type}, 14, GPIOHS_NUM_14}, + {{&Maix_gpio_irq_type}, 15, GPIOHS_NUM_15}, + {{&Maix_gpio_irq_type}, 16, GPIOHS_NUM_16}, + {{&Maix_gpio_irq_type}, 17, GPIOHS_NUM_17}, + {{&Maix_gpio_irq_type}, 18, GPIOHS_NUM_18}, + {{&Maix_gpio_irq_type}, 19, GPIOHS_NUM_19}, + {{&Maix_gpio_irq_type}, 20, GPIOHS_NUM_20}, + {{&Maix_gpio_irq_type}, 21, GPIOHS_NUM_21}, + {{&Maix_gpio_irq_type}, 22, GPIOHS_NUM_22}, + {{&Maix_gpio_irq_type}, 23, GPIOHS_NUM_23}, + {{&Maix_gpio_irq_type}, 24, GPIOHS_NUM_24}, + {{&Maix_gpio_irq_type}, 25, GPIOHS_NUM_25}, + {{&Maix_gpio_irq_type}, 26, GPIOHS_NUM_26}, + {{&Maix_gpio_irq_type}, 27, GPIOHS_NUM_27}, + {{&Maix_gpio_irq_type}, 28, GPIOHS_NUM_28}, + {{&Maix_gpio_irq_type}, 29, GPIOHS_NUM_29}, + {{&Maix_gpio_irq_type}, 30, GPIOHS_NUM_30}, + {{&Maix_gpio_irq_type}, 31, GPIOHS_NUM_31}, +}; + +STATIC mp_obj_t Maix_gpio_irq_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + Maix_gpio_irq_obj_t *self = self_in; + mp_arg_check_num(n_args, n_kw, 0, 0, false); + Maix_gpio_isr_handler((void*)&Maix_gpio_obj[self->num]); + return mp_const_none; +} + +STATIC mp_obj_t Maix_gpio_irq_trigger(size_t n_args, const mp_obj_t *args) { + Maix_gpio_irq_obj_t *self = args[0]; + if (n_args == 2) { + // set trigger + gpiohs_set_pin_edge(self->id,mp_obj_get_int(args[1])); + }else{ + mp_raise_ValueError("Reading this property is not supported"); + } + // not support to return original trigger value + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Maix_gpio_irq_trigger_obj, 1, 2, Maix_gpio_irq_trigger); + +STATIC const mp_rom_map_elem_t Maix_gpio_irq_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_trigger), MP_ROM_PTR(&Maix_gpio_irq_trigger_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(Maix_gpio_irq_locals_dict, Maix_gpio_irq_locals_dict_table); + +STATIC const mp_obj_type_t Maix_gpio_irq_type = { + { &mp_type_type }, + .name = MP_QSTR_IRQ, + .call = Maix_gpio_irq_call, + .locals_dict = (mp_obj_dict_t*)&Maix_gpio_irq_locals_dict, +}; diff --git a/src/声源定位代码/mic-code/Maix/Maix_i2s.c b/src/声源定位代码/mic-code/Maix/Maix_i2s.c new file mode 100644 index 0000000..efdceaa --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/Maix_i2s.c @@ -0,0 +1,371 @@ +/* +* Copyright 2019 Sipeed Co.,Ltd. + +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include +#include + +#include "i2s.h" +#include "dmac.h" +#include "sysctl.h" + +#include "py/obj.h" +#include "py/runtime.h" +#include "py/mphal.h" + +#include "modMaix.h" +#include "py_audio.h" +#include "Maix_i2s.h" +#define MAX_SAMPLE_RATE (4*1024*1024) +#define MAX_SAMPLE_POINTS (64*1024) + +const mp_obj_type_t Maix_i2s_type; + + +STATIC void Maix_i2s_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + Maix_i2s_obj_t* self = MP_OBJ_TO_PTR(self_in); + // i2s_channle_t* channel_iter = &self->channel[0]; + mp_printf(print, "[MAIXPY]i2s%d:(sampling rate=%u, sampling points=%u)\n", + self->i2s_num,self->sample_rate,self->points_num); + for(int channel_iter = 0; channel_iter < 4; channel_iter++) + { + mp_printf(print, "[MAIXPY]channle%d:(resolution=%u, cycles=%u, align_mode=%u, mode=%u)\n", + channel_iter, + self->channel[channel_iter].resolution, + self->channel[channel_iter].cycles, + self->channel[channel_iter].align_mode, + self->channel[channel_iter].mode); + } +} +STATIC mp_obj_t Maix_i2s_init_helper(Maix_i2s_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum + { + ARG_sample_points, + ARG_pll2, + ARG_mclk, + }; + static const mp_arg_t allowed_args[] = + { + { MP_QSTR_sample_points, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1024} }, + { MP_QSTR_pll2, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_mclk, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + + if (args[ARG_pll2].u_int != 0) // 262144000UL + { + sysctl_pll_set_freq(SYSCTL_PLL2, args[ARG_pll2].u_int); + } + + if (args[ARG_mclk].u_int != 0) // 31 an 16384000 / (16000 * 256) = 4 ; + { + sysctl_clock_set_threshold(SYSCTL_THRESHOLD_I2S0_M + self->i2s_num, args[ARG_mclk].u_int); + } + + //set buffer len + if(args[ARG_sample_points].u_int > MAX_SAMPLE_POINTS) + { + mp_raise_ValueError("[MAIXPY]I2S:invalid buffer length"); + } + self->points_num = args[ARG_sample_points].u_int; + self->buf = m_new(uint32_t,self->points_num); + + //set i2s channel mask + self->chn_mask = 0; + + return mp_const_true; +} +STATIC mp_obj_t Maix_i2s_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + // get i2s num + mp_int_t i2s_num = mp_obj_get_int(args[0]); + if (i2s_num >= I2S_DEVICE_MAX) { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "[MAIXPY]I2S%d:does not exist", i2s_num)); + } + + // create instance + Maix_i2s_obj_t *self = m_new_obj(Maix_i2s_obj_t); + self->base.type = &Maix_i2s_type; + self->i2s_num = i2s_num; + self->sample_rate = 0; + memset(&self->channel,0,4 * sizeof(i2s_channle_t)); + + // init instance + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + Maix_i2s_init_helper(self, n_args - 1, args + 1, &kw_args); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t Maix_i2s_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return Maix_i2s_init_helper(args[0], n_args -1 , args + 1, kw_args); +} +MP_DEFINE_CONST_FUN_OBJ_KW(Maix_i2s_init_obj, 0, Maix_i2s_init); + +STATIC mp_obj_t Maix_i2s_channel_config(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + //get i2s obj + Maix_i2s_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + + //parse parameter + enum{ARG_channel, + ARG_mode, + ARG_resolution, + ARG_cycles, + ARG_align_mode, + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_channel, MP_ARG_INT, {.u_int = I2S_CHANNEL_0} }, + { MP_QSTR_mode, MP_ARG_INT, {.u_int = I2S_RECEIVER} }, + { MP_QSTR_resolution, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = RESOLUTION_16_BIT} }, + { MP_QSTR_cycles, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = SCLK_CYCLES_32} }, + { MP_QSTR_align_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = STANDARD_MODE} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args-1, pos_args+1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + //set channel + if(args[ARG_channel].u_int > I2S_CHANNEL_3) + { + mp_raise_ValueError("[MAIXPY]I2S:invalid i2s channle"); + } + i2s_channel_num_t channel_num = args[ARG_channel].u_int; + i2s_channle_t* channle = &self->channel[channel_num]; + + //set resolution + if(args[ARG_resolution].u_int > RESOLUTION_32_BIT ) + { + mp_raise_ValueError("[MAIXPY]I2S:invalid resolution"); + } + channle->resolution = args[ARG_resolution].u_int; + if(args[ARG_cycles].u_int > SCLK_CYCLES_32 ) + { + mp_raise_ValueError("[MAIXPY]I2S:invalid cycles"); + } + channle->cycles = args[ARG_cycles].u_int; + self->cycles = args[ARG_cycles].u_int; + + //set align mode + if(args[ARG_align_mode].u_int != STANDARD_MODE && args[ARG_align_mode].u_int != RIGHT_JUSTIFYING_MODE && args[ARG_align_mode].u_int != LEFT_JUSTIFYING_MODE) + { + mp_raise_ValueError("[MAIXPY]I2S:invalid align mode"); + } + channle->align_mode = args[ARG_align_mode].u_int; + + //set mode + if(args[ARG_mode].u_int > I2S_RECEIVER ) + { + mp_raise_ValueError("[MAIXPY]I2S:invalid cycles"); + } + channle->mode = args[ARG_mode].u_int; + + //running config + if(channle->mode == I2S_RECEIVER) + { + self->chn_mask |= 0x3 << (channel_num * 2); + i2s_init(self->i2s_num, I2S_RECEIVER, self->chn_mask); + i2s_rx_channel_config(self->i2s_num, + channel_num, + channle->resolution, + channle->cycles, + TRIGGER_LEVEL_4, + channle->align_mode); + } + else + { + self->chn_mask |= 0x3 << (channel_num * 2); + i2s_init(self->i2s_num, I2S_TRANSMITTER,self->chn_mask); + i2s_tx_channel_config(self->i2s_num, + channel_num, + channle->resolution, + channle->cycles, + TRIGGER_LEVEL_4, + channle->align_mode); + } + return mp_const_true; +} +MP_DEFINE_CONST_FUN_OBJ_KW(Maix_i2s_channel_config_obj, 2, Maix_i2s_channel_config); + +STATIC mp_obj_t Maix_i2s_set_sample_rate(void* self_, mp_obj_t sample_rate) +{ + Maix_i2s_obj_t* self = (Maix_i2s_obj_t*)self_; + uint32_t smp_rate = mp_obj_get_int(sample_rate); + if(smp_rate > MAX_SAMPLE_RATE) + { + mp_raise_ValueError("[MAIXPY]I2S:invalid sample rate"); + } + int res = i2s_set_sample_rate(self->i2s_num,smp_rate); + + //judege cycles,which channel should we select ? + if(self->cycles == SCLK_CYCLES_16) + { + self->sample_rate = res / 32; + } + else if(self->cycles == SCLK_CYCLES_24) + { + self->sample_rate = res / 48; + } + else if(self->cycles == SCLK_CYCLES_32) + { + self->sample_rate = res / 64; + } + return mp_const_true; +} +MP_DEFINE_CONST_FUN_OBJ_2(Maix_i2s_set_sample_rate_obj,Maix_i2s_set_sample_rate); + + +STATIC mp_obj_t Maix_i2s_record(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)//point_nums,time +{ + //get i2s obj + Maix_i2s_obj_t *self = pos_args[0]; + Maix_audio_obj_t *audio_obj = m_new_obj(Maix_audio_obj_t); + audio_obj->audio.type = I2S_AUDIO; + //parse parameter + enum{ARG_points, + ARG_time, + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_points, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_time, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args-1, pos_args+1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + audio_obj->base.type = &Maix_audio_type; + + //compute buffer length + if(args[ARG_points].u_int > 0) + { + if(audio_obj->audio.points > self->points_num) + { + mp_raise_ValueError("[MAIXPY]I2S:Too many points"); + } + audio_obj->audio.points = args[ARG_points].u_int; + char* audio_buf = m_new(uint32_t, audio_obj->audio.points); + if (audio_buf == NULL) { + mp_raise_ValueError("[MAIXPY]I2S:create audio new buf error"); + } + memcpy(audio_buf, self->buf, sizeof(uint32_t) * audio_obj->audio.points); + audio_obj->audio.buf = audio_buf; + } + else if(args[ARG_time].u_int > 0) + { + if(self->sample_rate <= 0) + mp_raise_ValueError("[MAIXPY]I2S:please set sample rate"); + uint32_t record_sec = args[ARG_time].u_int; + uint32_t smp_points = self->sample_rate * record_sec; + if(smp_points > self->points_num) + mp_raise_ValueError("[MAIXPY]I2S:sampling size is out of bounds"); + audio_obj->audio.points = smp_points; + char* audio_buf = m_new(uint32_t, audio_obj->audio.points); + if (audio_buf == NULL) + { + mp_raise_ValueError("[MAIXPY]I2S:create audio new buf error"); + } + memcpy(audio_buf, self->buf, sizeof(uint32_t) * smp_points); + audio_obj->audio.buf = audio_buf; + }else + { + mp_raise_ValueError("[MAIXPY]I2S:please input recording points or time"); + } + + //record + i2s_receive_data_dma(self->i2s_num, audio_obj->audio.buf, audio_obj->audio.points , DMAC_CHANNEL3); + // dmac_wait_idle(DMAC_CHANNEL3);//wait to finish recv + return MP_OBJ_FROM_PTR(audio_obj); +} +MP_DEFINE_CONST_FUN_OBJ_KW(Maix_i2s_record_obj,1,Maix_i2s_record); + +STATIC mp_obj_t Maix_i2s_wait_record(void*self_) +{ + dmac_wait_idle(DMAC_CHANNEL3);//wait to finish recv + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(Maix_i2s_wait_record_obj, Maix_i2s_wait_record); + +STATIC mp_obj_t Maix_i2s_play(void*self_, mp_obj_t audio_obj) +{ + Maix_i2s_obj_t* self = (Maix_i2s_obj_t*)self_; + Maix_audio_obj_t *audio_p = MP_OBJ_TO_PTR(audio_obj); + i2s_send_data_dma(self->i2s_num, audio_p->audio.buf, audio_p->audio.points, DMAC_CHANNEL4); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(Maix_i2s_play_obj,Maix_i2s_play); + +STATIC mp_obj_t Maix_i2s_deinit(void*self_) +{ + Maix_i2s_obj_t* self = (Maix_i2s_obj_t*)self_; + m_del(uint32_t,self->buf,self->points_num); + m_del_obj(Maix_i2s_obj_t,self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(Maix_i2s_deinit_obj,Maix_i2s_deinit); + +// STATIC MP_DEFINE_CONST_FUN_OBJ_KW(Maix_i2s_set_dma_divede_16_obj,1,); +// STATIC MP_DEFINE_CONST_FUN_OBJ_KW(Maix_i2s_set_dma_divede_16_obj,1,); + + +STATIC const mp_rom_map_elem_t Maix_i2s_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___deinit__), MP_ROM_PTR(&Maix_i2s_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&Maix_i2s_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_channel_config), MP_ROM_PTR(&Maix_i2s_channel_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_sample_rate), MP_ROM_PTR(&Maix_i2s_set_sample_rate_obj) }, + { MP_ROM_QSTR(MP_QSTR_record), MP_ROM_PTR(&Maix_i2s_record_obj) }, + { MP_ROM_QSTR(MP_QSTR_wait_record), MP_ROM_PTR(&Maix_i2s_wait_record_obj) }, + { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&Maix_i2s_play_obj) }, + //advance interface , some user don't use it + // { MP_ROM_QSTR(MP_QSTR_set_dma_divede_16), MP_ROM_PTR(&Maix_i2s_set_dma_divede_16_obj) }, + // { MP_ROM_QSTR(MP_QSTR_set_dma_divede_16), MP_ROM_PTR(&Maix_i2s_get_dma_divede_16_obj) }, + { MP_ROM_QSTR(MP_QSTR_DEVICE_0), MP_ROM_INT(I2S_DEVICE_0) }, + { MP_ROM_QSTR(MP_QSTR_DEVICE_1), MP_ROM_INT(I2S_DEVICE_1) }, + { MP_ROM_QSTR(MP_QSTR_DEVICE_2), MP_ROM_INT(I2S_DEVICE_2) }, + + { MP_ROM_QSTR(MP_QSTR_CHANNEL_0), MP_ROM_INT(I2S_CHANNEL_0) }, + { MP_ROM_QSTR(MP_QSTR_CHANNEL_1), MP_ROM_INT(I2S_CHANNEL_1) }, + { MP_ROM_QSTR(MP_QSTR_CHANNEL_2), MP_ROM_INT(I2S_CHANNEL_2) }, + { MP_ROM_QSTR(MP_QSTR_CHANNEL_3), MP_ROM_INT(I2S_CHANNEL_3) }, + + { MP_ROM_QSTR(MP_QSTR_IGNORE_WORD_LENGTH), MP_ROM_INT(IGNORE_WORD_LENGTH) }, + { MP_ROM_QSTR(MP_QSTR_RESOLUTION_12_BIT), MP_ROM_INT(RESOLUTION_12_BIT) }, + { MP_ROM_QSTR(MP_QSTR_RESOLUTION_16_BIT), MP_ROM_INT(RESOLUTION_16_BIT) }, + { MP_ROM_QSTR(MP_QSTR_RESOLUTION_20_BIT), MP_ROM_INT(RESOLUTION_20_BIT) }, + { MP_ROM_QSTR(MP_QSTR_RESOLUTION_24_BIT), MP_ROM_INT(RESOLUTION_24_BIT) }, + { MP_ROM_QSTR(MP_QSTR_RESOLUTION_32_BIT), MP_ROM_INT(RESOLUTION_32_BIT) }, + + { MP_ROM_QSTR(MP_QSTR_SCLK_CYCLES_16), MP_ROM_INT(SCLK_CYCLES_16) }, + { MP_ROM_QSTR(MP_QSTR_SCLK_CYCLES_24), MP_ROM_INT(SCLK_CYCLES_24) }, + { MP_ROM_QSTR(MP_QSTR_SCLK_CYCLES_32), MP_ROM_INT(SCLK_CYCLES_32) }, + + { MP_ROM_QSTR(MP_QSTR_TRANSMITTER), MP_ROM_INT(I2S_TRANSMITTER) }, + { MP_ROM_QSTR(MP_QSTR_RECEIVER), MP_ROM_INT(I2S_RECEIVER) }, + + { MP_ROM_QSTR(MP_QSTR_STANDARD_MODE), MP_ROM_INT(STANDARD_MODE) }, + { MP_ROM_QSTR(MP_QSTR_RIGHT_JUSTIFYING_MODE), MP_ROM_INT(RIGHT_JUSTIFYING_MODE) }, + { MP_ROM_QSTR(MP_QSTR_LEFT_JUSTIFYING_MODE), MP_ROM_INT(LEFT_JUSTIFYING_MODE) }, + +}; + +STATIC MP_DEFINE_CONST_DICT(Maix_i2s_dict, Maix_i2s_locals_dict_table); + +const mp_obj_type_t Maix_i2s_type = { + { &mp_type_type }, + .name = MP_QSTR_I2S, + .print = Maix_i2s_print, + .make_new = Maix_i2s_make_new, + .locals_dict = (mp_obj_dict_t*)&Maix_i2s_dict, +}; diff --git a/src/声源定位代码/mic-code/Maix/Maix_kpu.c b/src/声源定位代码/mic-code/Maix/Maix_kpu.c new file mode 100644 index 0000000..dad3aae --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/Maix_kpu.c @@ -0,0 +1,1840 @@ +/* +* Copyright 2019 Sipeed Co.,Ltd. + +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include +#include +#include +#include +#include + +#include "sipeed_yolo2.h" +#include "sipeed_conf.h" +#include "sipeed_kpu.h" + +#include "w25qxx.h" + +#include +#include "mpconfigboard.h" + +#include "imlib.h" +#include "py_assert.h" +#include "py_helper.h" +#include "extmod/vfs.h" +#include "vfs_wrapper.h" +#include "py_image.h" +#include "syscalls.h" +#include "printf.h" +#include "sleep.h" +#include "sipeed_mem.h" +#include "Maix_kpu.h" + +/////////////////////////////////////////////////////////////////////////////// + + +typedef struct{ + uint16_t index; + uint16_t wi; + uint16_t hi; + uint16_t wo; + uint16_t ho; + uint16_t chi; + uint16_t cho; + uint16_t dw; + uint16_t kernel_type; + uint16_t pool_type; + uint32_t type; + uint32_t para_size; +} __attribute__((aligned(8))) py_kpu_netinfo_list_data_t; + +static int check_img_format(image_t* arg_img, uint16_t w, uint16_t h, uint16_t ch, int kmodel_type) +{ + if (arg_img->pix_ai == NULL) + { + mp_printf(&mp_plat_print, "[MAIXPY]kpu: pix_ai is NULL!\r\n"); + return -1; + } + if(abs(kmodel_type)==3 || abs(kmodel_type)==4){ + if(arg_img->w != w || arg_img->h != h) + { + mp_printf(&mp_plat_print, "[MAIXPY]kpu: img w=%d,h=%d, but model w=%d,h=%d\r\n",\ + arg_img->w, arg_img->h, w, h); + return -1; + } + if(arg_img->bpp == IMAGE_BPP_GRAYSCALE && ch != 1) + { + mp_printf(&mp_plat_print, "[MAIXPY]kpu: grayscale img, but model channel=%d\r\n", ch); + return -1; + } + if(arg_img->bpp == IMAGE_BPP_RGB565 && ch != 3) + { + mp_printf(&mp_plat_print, "[MAIXPY]kpu: RGB img, but model channel=%d\r\n", ch); + return -1; + } + } + if(arg_img->bpp != IMAGE_BPP_GRAYSCALE && arg_img->bpp != IMAGE_BPP_RGB565) + { + mp_printf(&mp_plat_print, "[MAIXPY]kpu: img bpp not support yet!\r\n"); + return -1; + } + //here is right image format + return 0; +} + + +static int py_kpu_class_yolo2_print_to_buf(mp_obj_t self_in, char *buf); + +static void py_kpu_net_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_kpu_net_obj_t *self = self_in; + + const char *path = NULL; + if(MP_OBJ_IS_STR(self->model_path)) + { + path = mp_obj_str_get_str(self->model_path); + } + + uint32_t addr = 0; + if(MP_OBJ_IS_INT(self->model_addr)) + { + addr = mp_obj_get_int(self->model_addr); + } + + char net_args[512]; + + if(py_kpu_class_yolo2_print_to_buf(self->net_args, net_args) != 0) + { + sprintf(net_args,"\"(null)\""); + } + + mp_printf(print, + "{\"model_addr\": %d, \"model_size\": %d, \"model_path\": \"%s\", \"net_args\": %s}", + addr, + mp_obj_get_int(self->model_size), + path, + net_args + ); +} + +STATIC mp_obj_t py_kpu_deinit(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +mp_obj_t py_kpu_net_del(mp_obj_t self_in) +{ + mp_printf(&mp_plat_print, "kpu_net __del__\r\n"); + py_kpu_deinit(1, &self_in, NULL); + return mp_const_none; +} +mp_obj_t py_kpu_net_model_addr(mp_obj_t self_in) { return ((py_kpu_net_obj_t *)self_in)->model_addr; } +mp_obj_t py_kpu_net_model_size(mp_obj_t self_in) { return ((py_kpu_net_obj_t *)self_in)->model_size; } +mp_obj_t py_kpu_net_model_path(mp_obj_t self_in) { return ((py_kpu_net_obj_t *)self_in)->model_path; } +mp_obj_t py_kpu_net_arg(mp_obj_t self_in) { return ((py_kpu_net_obj_t *)self_in)->net_args; } + +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_net_del_obj, py_kpu_net_del); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_net_model_addr_obj, py_kpu_net_model_addr); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_net_model_size_obj, py_kpu_net_model_size); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_net_model_path_obj, py_kpu_net_model_path); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_net_arg_obj, py_kpu_net_arg); + +static const mp_rom_map_elem_t py_kpu_net_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&py_kpu_net_del_obj) }, + { MP_ROM_QSTR(MP_QSTR_model_addr), MP_ROM_PTR(&py_kpu_net_model_addr_obj) }, + { MP_ROM_QSTR(MP_QSTR_model_size), MP_ROM_PTR(&py_kpu_net_model_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_model_path), MP_ROM_PTR(&py_kpu_net_model_path_obj) }, + { MP_ROM_QSTR(MP_QSTR_net_arg), MP_ROM_PTR(&py_kpu_net_arg_obj) } +}; + +static MP_DEFINE_CONST_DICT(py_kpu_net_dict, py_kpu_net_dict_table); + +const mp_obj_type_t py_kpu_net_obj_type = { + { &mp_type_type }, + .name = MP_QSTR_kpu_net, + .print = py_kpu_net_obj_print, + .locals_dict = (mp_obj_t) &py_kpu_net_dict +}; + +typedef struct { + uint32_t magic_number; + uint32_t layer_number; + uint32_t layer_cfg_addr_offset; + uint32_t eight_bit_mode; + float scale; + float bias; + } model_config_t; + + +typedef struct { + uint32_t reg_addr_offset; + uint32_t act_addr_offset; + uint32_t bn_addr_offset; + uint32_t bn_len; + uint32_t weights_addr_offset; + uint32_t weights_len; + } layer_config_t; + +static char* get_kpu_err_str(int err) +{ + switch(err) + { + case SIPEED_KPU_ERR_NONE: + return "ERR_NONE"; + case SIPEED_KPU_ERR_PARAM: + return "ERR_PARAM: please check param, load address or kmodel file name"; + case SIPEED_KPU_ERR_KMODEL_VERSION: + #if CONFIG_MICROPYTHON_KMODEL_V4_SUPPORT + return "ERR_KMODEL_VERSION: only support kmodel V3/V4 now"; + #else + return "ERR_KMODEL_VERSION: only support kmodel V3"; + #endif + case SIPEED_KPU_ERR_KMODEL_FORMAT: + return "ERR_KMODEL_FORMAT: layer_header.body_size <=0"; + case SIPEED_KPU_ERR_DECRYPT: + return "ERR_DECRYPT: check smodel match with your board"; + case SIPEED_KPU_ERR_READ_FILE: + return "ERR_READ_FILE: read file failed"; + case SIPEED_KPU_ERR_NO_MEM: + return "ERR_NO_MEM: memory not enough"; + case SIPEED_KPU_ERR_GET_CONV_LAYER: + return "ERR_GET_CONV_LAYER: first layer should be conv layer"; + case SIPEED_KPU_ERR_RUN_MODEL: + return "ERR_RUN_MODEL: maybe model dirty"; + case SIPEED_KPU_ERR_MODELS_FULL: + return "ERR_MODELS_FULL: we only support load 5 models in the same time"; + case SIPEED_KPU_ERR_PERMITION: + return "ERR_PERMITION"; + case SIPEED_KPU_ERR_IDX_OUTRANGE: + return "SIPEED_KPU_ERR_IDX_OUTRANGE"; + case SIPEED_KPU_ERR_SIZE_NOTMATCH: + return "SIPEED_KPU_ERR_SIZE_NOTMATCH"; + case SIPEED_KPU_ERR_OUTPUTS_NODONE: + return "ERR_OUTPUTS_SET: need kpu.set_outputs() to set shape"; + case SIPEED_KPU_ERR_NOT_IMPLEMENT: + return "ERR_NOT_IMPLEMENT"; + case SIPEED_KPU_ERR_UNKNOWN: + default: + return "ERR_UNKNOWN"; + } +} + +int model_init(kpu_task_t *task, const char *path) +{ + uint32_t layer_cfg_addr; + model_config_t model_cfg; + layer_config_t layer_cfg; + kpu_layer_argument_t *layer_arg_ptr; + void *ptr; + mp_obj_t file; + int ferr; + + file = vfs_internal_open(path,"rb", &ferr); + if(file == MP_OBJ_NULL || ferr != 0) + mp_raise_OSError(ferr); + + memset(task, 0, sizeof(kpu_task_t)); + //model_read(addr, (uint8_t *)&model_cfg, sizeof(model_config_t)); + vfs_internal_read(file, (uint8_t *)&model_cfg, sizeof(model_config_t), &ferr); + if( ferr != 0) + mp_raise_OSError(ferr); + + if (model_cfg.magic_number != 0x12345678) + return -1; + layer_arg_ptr = (kpu_layer_argument_t *)malloc(12 * 8 * model_cfg.layer_number); + if (layer_arg_ptr == NULL) + return -2; + + memset(layer_arg_ptr, 0, 12 * 8 * model_cfg.layer_number); + task->layers = layer_arg_ptr; + task->layers_length = model_cfg.layer_number; + task->eight_bit_mode = model_cfg.eight_bit_mode; + task->output_scale = model_cfg.scale; + task->output_bias = model_cfg.bias; + + layer_cfg_addr = model_cfg.layer_cfg_addr_offset; + for (uint32_t i = 0; i < model_cfg.layer_number; i++) + { + // read layer config + //model_read(layer_cfg_addr, (uint8_t *)&layer_cfg, sizeof(layer_config_t)); + vfs_internal_seek(file, layer_cfg_addr, VFS_SEEK_SET, &ferr); + if(ferr != 0) + mp_raise_OSError(ferr); + + vfs_internal_read(file, (uint8_t *)&layer_cfg, sizeof(layer_config_t), &ferr); + if( ferr != 0) + mp_raise_OSError(ferr); + + // read reg arg + //model_read(addr + layer_cfg.reg_addr_offset, (uint8_t *)layer_arg_ptr, sizeof(kpu_layer_argument_t)); + vfs_internal_seek(file, layer_cfg.reg_addr_offset, VFS_SEEK_SET, &ferr); + if(ferr != 0) + mp_raise_OSError(ferr); + + vfs_internal_read(file, (uint8_t *)layer_arg_ptr, sizeof(kpu_layer_argument_t), &ferr); + if( ferr != 0) + mp_raise_OSError(ferr); + + + + // read act arg + ptr = malloc(sizeof(kpu_activate_table_t)); + if (ptr == NULL) + return -2; + //model_read(addr + layer_cfg.act_addr_offset, (uint8_t *)ptr, sizeof(kpu_activate_table_t)); + + vfs_internal_seek(file, layer_cfg.act_addr_offset, VFS_SEEK_SET, &ferr); + if(ferr != 0) + mp_raise_OSError(ferr); + + vfs_internal_read(file, (uint8_t *)ptr, sizeof(kpu_activate_table_t), &ferr); + if( ferr != 0) + mp_raise_OSError(ferr); + + + layer_arg_ptr->kernel_calc_type_cfg.data.active_addr = (uint32_t)ptr; + // read bn arg + ptr = malloc(layer_cfg.bn_len); + if (ptr == NULL) + return -2; + //model_read(addr + layer_cfg.bn_addr_offset, (uint8_t *)ptr, layer_cfg.bn_len); + vfs_internal_seek(file, layer_cfg.bn_addr_offset, VFS_SEEK_SET, &ferr); + if(ferr != 0) + mp_raise_OSError(ferr); + + vfs_internal_read(file, (uint8_t *)ptr, layer_cfg.bn_len, &ferr); + if( ferr != 0) + mp_raise_OSError(ferr); + + layer_arg_ptr->kernel_pool_type_cfg.data.bwsx_base_addr = (uint32_t)ptr; + // read weights arg + ptr = malloc(layer_cfg.weights_len); + if (ptr == NULL) + return -2; + //model_read(addr + layer_cfg.weights_addr_offset, (uint8_t *)ptr, layer_cfg.weights_len); + vfs_internal_seek(file, layer_cfg.weights_addr_offset, VFS_SEEK_SET, &ferr); + if(ferr != 0) + mp_raise_OSError(ferr); + + vfs_internal_read(file, (uint8_t *)ptr, layer_cfg.weights_len, &ferr); + if( ferr != 0) + mp_raise_OSError(ferr); + + layer_arg_ptr->kernel_load_cfg.data.para_start_addr = (uint32_t)ptr; + // next layer + layer_cfg_addr += sizeof(layer_config_t); + layer_arg_ptr++; + } + vfs_internal_close(file, &ferr); + if( ferr != 0) + mp_raise_OSError(ferr); + return 0; +} + +int model_deinit(kpu_task_t *task) +{ + for (uint32_t i = 0; i < task->layers_length; i++) + { + free((void*)task->layers[i].kernel_calc_type_cfg.data.active_addr); + free((void*)task->layers[i].kernel_pool_type_cfg.data.bwsx_base_addr); + free((void*)task->layers[i].kernel_load_cfg.data.para_start_addr); + } + free(task->layers); + return 0; +} + +STATIC mp_obj_t py_kpu_class_load(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + int err = 0; + uint32_t model_size; + py_kpu_net_obj_t *o = m_new_obj_with_finaliser(py_kpu_net_obj_t); + o->base.type = &py_kpu_net_obj_type; + + if(mp_obj_get_type(pos_args[0]) == &mp_type_int) + { //load from flash address + mp_int_t model_addr = mp_obj_get_int(pos_args[0]); + + if(model_addr <= 0)//TODO: address of code end + { + m_del(py_kpu_net_obj_t, o,sizeof(py_kpu_net_obj_t)); + mp_raise_ValueError("[MAIXPY]kpu: model_addr must > 0 "); + return mp_const_false; + } + + o->model_addr = mp_obj_new_int(model_addr); + o->model_path = mp_const_none; + sipeed_kpu_err_t ret = sipeed_kpu_model_load(&o->kmodel_ctx, model_addr, NULL, &model_size); + if(ret != SIPEED_KPU_ERR_NONE) + { + err = ret; //load error + goto error; + } + } + else if(mp_obj_get_type(pos_args[0]) == &mp_type_str) + { + const char *path = mp_obj_str_get_str(pos_args[0]); + + o->model_path = mp_obj_new_str(path,strlen(path)); + o->model_addr = mp_const_none; + + // if(NULL != strstr(path,".bin")) + // { + // err=model_init(kpu_task,path); + // if( err != 0 ) + // { + // model_deinit(kpu_task); + // goto error; + // } + + // } + // else + if( (NULL != strstr(path,".kmodel")) || (NULL != strstr(path,".smodel")) || (NULL != strstr(path,".emodel")) ) + { + int ret = sipeed_kpu_model_load(&o->kmodel_ctx, 0, path, &model_size); + if(ret != SIPEED_KPU_ERR_NONE) + { + err = ret; + goto error; + } + } + else + { + m_del(py_kpu_net_obj_t, o,sizeof(py_kpu_net_obj_t)); + mp_raise_ValueError("[MAIXPY]kpu: model format don't match, only supply .kmodel "); + return mp_const_false; + } + + } + else + { + m_del(py_kpu_net_obj_t, o,sizeof(py_kpu_net_obj_t)); + mp_raise_TypeError("[MAIXPY]kpu: only accept int or string"); + return mp_const_false; + } + o->net_args = mp_const_none; + o->net_deinit = mp_const_none; + o->model_size = mp_obj_new_int(model_size); + o->max_layers = mp_obj_new_int(sipeed_kpu_model_get_layer_num(o->kmodel_ctx)); + + return MP_OBJ_FROM_PTR(o); + +error: +{ + char* err_msg = get_kpu_err_str(err); + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "[MAIXPY]kpu: load error:%d, %s", err, err_msg)); +} +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_kpu_class_load_obj, 1, py_kpu_class_load); + + + + +STATIC mp_obj_t py_kpu_class_load_flash(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + int err = 0; + uint32_t model_size; + py_kpu_net_obj_t *o = m_new_obj_with_finaliser(py_kpu_net_obj_t); + o->base.type = &py_kpu_net_obj_type; + + if((mp_obj_get_type(pos_args[0]) == &mp_type_int) && \ + (mp_obj_get_type(pos_args[1]) == &mp_type_int) && \ + (mp_obj_get_type(pos_args[2]) == &mp_type_int) && \ + (mp_obj_get_type(pos_args[3]) == &mp_type_int)) + { + mp_int_t model_addr = mp_obj_get_int(pos_args[0]); + mp_int_t is_dual_buf = mp_obj_get_int(pos_args[1]); + mp_int_t batch_size = mp_obj_get_int(pos_args[2]); + mp_int_t spi_speed = mp_obj_get_int(pos_args[3]); + + if(model_addr <= 0)//TODO: address of code end + { + m_del(py_kpu_net_obj_t, o,sizeof(py_kpu_net_obj_t)); + mp_raise_ValueError("[MAIXPY]kpu: model_addr must > 0 "); + return mp_const_false; + } + + o->model_addr = mp_obj_new_int(model_addr); + o->model_path = mp_const_none; + sipeed_kpu_err_t ret = sipeed_kpu_model_load_flash(&o->kmodel_ctx, model_addr, is_dual_buf, batch_size, spi_speed, &model_size); + if(ret != SIPEED_KPU_ERR_NONE) + { + err = ret; //load error + goto error; + } + o->net_args = mp_const_none; + o->net_deinit = mp_const_none; + o->model_size = mp_obj_new_int(model_size); + o->max_layers = mp_obj_new_int(sipeed_kpu_model_get_layer_num(o->kmodel_ctx)); + return MP_OBJ_FROM_PTR(o); + } else { + mp_raise_ValueError("[MAIXPY]kpu: load_flash arg format error: task=kpu.load_flash(flash_addr, is_dual_buf, batch_size, spi_speed)"); + return mp_const_false; + } +error: +{ + char* err_msg = get_kpu_err_str(err); + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "[MAIXPY]kpu: load_flash error:%d, %s", err, err_msg)); +} +return mp_const_none; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_kpu_class_load_flash_obj, 4, py_kpu_class_load_flash); + + +//Kmodel V4 need set output shape manually +//set_outputs(int idx, int w, int h, int ch) +STATIC mp_obj_t py_kpu_class_set_outputs(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + if((mp_obj_get_type(pos_args[0]) == &py_kpu_net_obj_type) && \ + (mp_obj_get_type(pos_args[1]) == &mp_type_int) && \ + (mp_obj_get_type(pos_args[2]) == &mp_type_int) && \ + (mp_obj_get_type(pos_args[3]) == &mp_type_int) && \ + (mp_obj_get_type(pos_args[4]) == &mp_type_int)) + { + py_kpu_net_obj_t *kpu_net = MP_OBJ_TO_PTR(pos_args[0]); + uint16_t idx = mp_obj_get_int(pos_args[1]); + uint16_t w = mp_obj_get_int(pos_args[2]); + uint16_t h = mp_obj_get_int(pos_args[3]); + uint16_t ch = mp_obj_get_int(pos_args[4]); + int res = sipeed_kpu_set_outputs_shape(kpu_net->kmodel_ctx, idx, w, h, ch); + mp_obj_t ret=mp_const_false; + + switch(res) + { + case SIPEED_KPU_ERR_IDX_OUTRANGE: + mp_printf(&mp_plat_print, \ + "[MAIXPY]kpu: set_outputs arg value error:kmodel output idx %d out of range\r\n", idx); + break; + case SIPEED_KPU_ERR_SIZE_NOTMATCH: + mp_printf(&mp_plat_print, \ + "[MAIXPY]kpu: set_outputs arg value error: w,c,ch size not match output size\r\n"); + break; + case SIPEED_KPU_ERR_NONE: + ret=mp_const_true; + break; + case SIPEED_KPU_ERR_PARAM: + default: + mp_printf(&mp_plat_print, \ + "[MAIXPY]kpu: set_outputs ctx error, maybe init err:%d\r\n", res); + break; + + } + return ret; + } else { + mp_raise_ValueError("[MAIXPY]kpu: set_outputs arg format error: set_outputs(task, int idx, int w, int h, int ch)"); + return mp_const_false; + } + +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_kpu_class_set_outputs_obj, 4, py_kpu_class_set_outputs); + +/////////////////////////////////////////////////////////////////////////////// + +typedef struct py_kpu_class_yolo_args_obj { + mp_obj_base_t base; + + mp_obj_t threshold, nms_value, anchor_number, anchor, rl_args; +} __attribute__((aligned(8))) py_kpu_class_yolo_args_obj_t; + +typedef struct py_kpu_class_region_layer_arg +{ + float threshold; + float nms_value; + int anchor_number; + float *anchor; +}__attribute__((aligned(8))) py_kpu_class_yolo_region_layer_arg_t; + +static void py_kpu_class_yolo2_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_kpu_class_yolo_args_obj_t *yolo_args = self_in; + + py_kpu_class_yolo_region_layer_arg_t *rl_arg = yolo_args->rl_args; + + char msg[300]; + + uint8_t num = rl_arg->anchor_number; + + if(num>0) + { + sprintf(msg,"%f",rl_arg->anchor[0]); + for(uint16_t i = 1; i < num * 2; i++) + snprintf(msg, sizeof(msg), "%s, %f",msg, rl_arg->anchor[i]); + } + + mp_printf(print, + "{\"threshold\":%f, \"nms_value\":%f, \"anchor_number\":%d, \"anchor\":\"(%s)\"}", + rl_arg->threshold, + rl_arg->nms_value, + rl_arg->anchor_number, + msg); +} + +mp_obj_t py_kpu_calss_yolo2_anchor(mp_obj_t self_in); + +mp_obj_t py_kpu_calss_yolo2_threshold(mp_obj_t self_in) { return ((py_kpu_class_yolo_args_obj_t *)self_in)->threshold; } +mp_obj_t py_kpu_calss_yolo2_nms_value(mp_obj_t self_in) { return ((py_kpu_class_yolo_args_obj_t *)self_in)->nms_value; } +mp_obj_t py_kpu_calss_yolo2_anchor_number(mp_obj_t self_in) { return ((py_kpu_class_yolo_args_obj_t *)self_in)->anchor_number; } + +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_calss_yolo2_anchor_obj, py_kpu_calss_yolo2_anchor); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_calss_yolo2_threshold_obj, py_kpu_calss_yolo2_threshold); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_calss_yolo2_nms_value_obj, py_kpu_calss_yolo2_nms_value); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_calss_yolo2_anchor_number_obj, py_kpu_calss_yolo2_anchor_number); + +static const mp_rom_map_elem_t py_kpu_class_yolo2_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_anchor), MP_ROM_PTR(&py_kpu_calss_yolo2_anchor_obj) }, + { MP_ROM_QSTR(MP_QSTR_threshold), MP_ROM_PTR(&py_kpu_calss_yolo2_threshold_obj) }, + { MP_ROM_QSTR(MP_QSTR_nms_value), MP_ROM_PTR(&py_kpu_calss_yolo2_nms_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_anchor_number), MP_ROM_PTR(&py_kpu_calss_yolo2_anchor_number_obj) } +}; + +static MP_DEFINE_CONST_DICT(py_kpu_class_yolo2_dict, py_kpu_class_yolo2_dict_table); + +static const mp_obj_type_t py_kpu_class_yolo_args_obj_type = { + { &mp_type_type }, + .name = MP_QSTR_kpu_yolo2, + .print = py_kpu_class_yolo2_print, + // .subscr = py_kpu_calss_yolo2_subscr, + .locals_dict = (mp_obj_t) &py_kpu_class_yolo2_dict +}; + +static int py_kpu_class_yolo2_print_to_buf(mp_obj_t self_in, char *buf) +{ + if(buf == NULL) + return -1; + + if(mp_obj_get_type(self_in) == &py_kpu_class_yolo_args_obj_type) + { + py_kpu_class_yolo_args_obj_t *yolo_args = self_in; + + py_kpu_class_yolo_region_layer_arg_t *rl_arg = yolo_args->rl_args; + + char msg[300]; + + uint8_t num = rl_arg->anchor_number; + + if(num>0) + { + sprintf(msg,"%f",rl_arg->anchor[0]); + for(uint16_t i = 1; i < num * 2; i++) + snprintf(msg, sizeof(msg), "%s, %f",msg, rl_arg->anchor[i]); + } + + sprintf(buf, + "{\"threshold\":%f, \"nms_value\":%f, \"anchor_number\":%d, \"anchor\":\"(%s)\"}", + rl_arg->threshold, + rl_arg->nms_value, + rl_arg->anchor_number, + msg); + return 0; + } + return -1; +} + +mp_obj_t py_kpu_calss_yolo2_anchor(mp_obj_t self_in) +{ + if(mp_obj_get_type(self_in) == &py_kpu_class_yolo_args_obj_type) + { + py_kpu_class_yolo_args_obj_t *yolo_args = MP_OBJ_TO_PTR(self_in); + + py_kpu_class_yolo_region_layer_arg_t *rl_arg = yolo_args->rl_args; + + mp_obj_t *tuple, *tmp; + + tmp = (mp_obj_t *)malloc(rl_arg->anchor_number * 2 * sizeof(mp_obj_t)); + + for (uint8_t index = 0; index < rl_arg->anchor_number * 2; index++) + tmp[index] = mp_obj_new_float(rl_arg->anchor[index]); + + tuple = mp_obj_new_tuple(rl_arg->anchor_number * 2, tmp); + + free(tmp); + return tuple; + } + else + { + mp_raise_TypeError("[MAIXPY]kpu: object type error"); + return mp_const_false; + } +} + +mp_obj_t py_kpu_calss_yolo2_deinit(mp_obj_t self_in) +{ + if(mp_obj_get_type(self_in) == &py_kpu_class_yolo_args_obj_type) + { + py_kpu_class_yolo_args_obj_t *yolo_args = MP_OBJ_TO_PTR(self_in); + + py_kpu_class_yolo_region_layer_arg_t *rl_arg = yolo_args->rl_args; + + if(rl_arg->anchor) + { + free(rl_arg->anchor); + rl_arg->anchor = NULL; + } + + if(rl_arg) + { + free(rl_arg); + rl_arg->anchor = NULL; + } + return mp_const_true; + } + else + { + mp_raise_TypeError("[MAIXPY]kpu: object type error"); + return mp_const_false; + } +} + +STATIC mp_obj_t py_kpu_class_init_yolo2(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + enum { ARG_kpu_net, ARG_threshold, ARG_nms_value, ARG_anchor_number, ARG_anchor, ARG_dma}; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_kpu_net, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_threshold, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_nms_value, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_anchor_number, MP_ARG_INT, {.u_int = 0x0} }, + { MP_QSTR_anchor, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_dma, MP_ARG_INT, {.u_int = -1} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(mp_obj_get_type(args[ARG_kpu_net].u_obj) == &py_kpu_net_obj_type) + { + float threshold, nms_value, *anchor = NULL; + int anchor_number; + + sipeed_kpu_use_dma(args[ARG_dma].u_int); + threshold = mp_obj_get_float(args[ARG_threshold].u_obj); + if(!(threshold >= 0.0 && threshold <= 1.0)) + { + mp_raise_ValueError("[MAIXPY]kpu: threshold only support 0 to 1"); + return mp_const_false; + } + + nms_value = mp_obj_get_float(args[ARG_nms_value].u_obj); + if(!(nms_value >= 0.0 && nms_value <= 1.0)) + { + mp_raise_ValueError("[MAIXPY]kpu: nms_value only support 0 to 1"); + return mp_const_false; + } + + anchor_number = args[ARG_anchor_number].u_int; + + if(anchor_number > 0) + { + //need free + anchor = (float*)malloc(anchor_number * 2 * sizeof(float)); + + mp_obj_t *items; + mp_obj_get_array_fixed_n(args[ARG_anchor].u_obj, args[ARG_anchor_number].u_int*2, &items); + + for(uint8_t index = 0; index < args[ARG_anchor_number].u_int * 2; index++) + anchor[index] = mp_obj_get_float(items[index]); + } + else + { + mp_raise_ValueError("[MAIXPY]kpu: anchor_number should > 0"); + return mp_const_false; + } + + py_kpu_class_yolo_args_obj_t *yolo_args = m_new_obj(py_kpu_class_yolo_args_obj_t); + + yolo_args->base.type = &py_kpu_class_yolo_args_obj_type; + + yolo_args->threshold = mp_obj_new_float(threshold); + yolo_args->nms_value = mp_obj_new_float(nms_value); + yolo_args->anchor_number = mp_obj_new_int(anchor_number); + + mp_obj_t *tuple, *tmp; + + tmp = (mp_obj_t *)malloc(anchor_number * 2 * sizeof(mp_obj_t)); + + for (uint8_t index = 0; index < anchor_number * 2; index++) + tmp[index] = mp_obj_new_float(anchor[index]); + + tuple = mp_obj_new_tuple(anchor_number * 2, tmp); + + free(tmp); + + yolo_args->anchor = tuple; + + //need free + py_kpu_class_yolo_region_layer_arg_t *rl_arg = malloc(sizeof(py_kpu_class_yolo_region_layer_arg_t)); + + rl_arg->threshold = threshold; + rl_arg->nms_value = nms_value; + rl_arg->anchor_number = anchor_number; + rl_arg->anchor = anchor; + + yolo_args->rl_args = MP_OBJ_FROM_PTR(rl_arg); + + py_kpu_net_obj_t *kpu_net = MP_OBJ_TO_PTR(args[ARG_kpu_net].u_obj); + + kpu_net->net_args = MP_OBJ_FROM_PTR(yolo_args); + + kpu_net->net_deinit = MP_OBJ_FROM_PTR(py_kpu_calss_yolo2_deinit); + + return mp_const_true; + } + else + { + mp_raise_TypeError("[MAIXPY]kpu: kpu_net type error"); + return mp_const_false; + } +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_kpu_class_init_yolo2_obj, 5, py_kpu_class_init_yolo2); + +/////////////////////////////////////////////////////////////////////////////// + +typedef struct py_kpu_class_yolo2_find_obj { + mp_obj_base_t base; + + mp_obj_t x, y, w, h, classid, index, value, objnum; +} __attribute__((aligned(8))) py_kpu_class_yolo2_find_obj_t; + +typedef struct py_kpu_class_list_link_data { + rectangle_t rect; + int classid; + float value; + int index; + int objnum; +} __attribute__((aligned(8))) py_kpu_class_yolo2__list_link_data_t; + +static void py_kpu_class_yolo2_find_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_kpu_class_yolo2_find_obj_t *self = self_in; + mp_printf(print, + "{\"x\":%d, \"y\":%d, \"w\":%d, \"h\":%d, \"value\":%f, \"classid\":%d, \"index\":%d, \"objnum\":%d}", + mp_obj_get_int(self->x), + mp_obj_get_int(self->y), + mp_obj_get_int(self->w), + mp_obj_get_int(self->h), + (double)mp_obj_get_float(self->value), + mp_obj_get_int(self->classid), + mp_obj_get_int(self->index), + mp_obj_get_int(self->objnum)); +} + +mp_obj_t py_kpu_class_yolo2_find_rect(mp_obj_t self_in) +{ + return mp_obj_new_tuple(4, (mp_obj_t[]){((py_kpu_class_yolo2_find_obj_t *)self_in)->x, + ((py_kpu_class_yolo2_find_obj_t *)self_in)->y, + ((py_kpu_class_yolo2_find_obj_t *)self_in)->w, + ((py_kpu_class_yolo2_find_obj_t *)self_in)->h}); +} + +mp_obj_t py_kpu_class_yolo2_find_x(mp_obj_t self_in) { return ((py_kpu_class_yolo2_find_obj_t *)self_in)->x; } +mp_obj_t py_kpu_class_yolo2_find_y(mp_obj_t self_in) { return ((py_kpu_class_yolo2_find_obj_t *)self_in)->y; } +mp_obj_t py_kpu_class_yolo2_find_w(mp_obj_t self_in) { return ((py_kpu_class_yolo2_find_obj_t *)self_in)->w; } +mp_obj_t py_kpu_class_yolo2_find_h(mp_obj_t self_in) { return ((py_kpu_class_yolo2_find_obj_t *)self_in)->h; } +mp_obj_t py_kpu_class_yolo2_find_classid(mp_obj_t self_in) { return ((py_kpu_class_yolo2_find_obj_t *)self_in)->classid; } +mp_obj_t py_kpu_class_yolo2_find_index(mp_obj_t self_in) { return ((py_kpu_class_yolo2_find_obj_t *)self_in)->index; } +mp_obj_t py_kpu_class_yolo2_find_value(mp_obj_t self_in) { return ((py_kpu_class_yolo2_find_obj_t *)self_in)->value; } +mp_obj_t py_kpu_class_yolo2_find_objnum(mp_obj_t self_in) { return ((py_kpu_class_yolo2_find_obj_t *)self_in)->objnum; } + +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_yolo2_find_rect_obj, py_kpu_class_yolo2_find_rect); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_yolo2_find_x_obj, py_kpu_class_yolo2_find_x); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_yolo2_find_y_obj, py_kpu_class_yolo2_find_y); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_yolo2_find_w_obj, py_kpu_class_yolo2_find_w); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_yolo2_find_h_obj, py_kpu_class_yolo2_find_h); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_yolo2_find_classid_obj, py_kpu_class_yolo2_find_classid); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_yolo2_find_index_obj, py_kpu_class_yolo2_find_index); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_yolo2_find_value_obj, py_kpu_class_yolo2_find_value); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_yolo2_find_objnum_obj, py_kpu_class_yolo2_find_objnum); + +static const mp_rom_map_elem_t py_kpu_class_yolo2_find_type_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&py_kpu_class_yolo2_find_rect_obj) }, + { MP_ROM_QSTR(MP_QSTR_x), MP_ROM_PTR(&py_kpu_class_yolo2_find_x_obj) }, + { MP_ROM_QSTR(MP_QSTR_y), MP_ROM_PTR(&py_kpu_class_yolo2_find_y_obj) }, + { MP_ROM_QSTR(MP_QSTR_w), MP_ROM_PTR(&py_kpu_class_yolo2_find_w_obj) }, + { MP_ROM_QSTR(MP_QSTR_h), MP_ROM_PTR(&py_kpu_class_yolo2_find_h_obj) }, + { MP_ROM_QSTR(MP_QSTR_classid), MP_ROM_PTR(&py_kpu_class_yolo2_find_classid_obj) }, + { MP_ROM_QSTR(MP_QSTR_index), MP_ROM_PTR(&py_kpu_class_yolo2_find_index_obj) }, + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&py_kpu_class_yolo2_find_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_objnum), MP_ROM_PTR(&py_kpu_class_yolo2_find_objnum_obj) } +}; + +static MP_DEFINE_CONST_DICT(py_kpu_class_yolo2_find_type_locals_dict, py_kpu_class_yolo2_find_type_locals_dict_table); + +static const mp_obj_type_t py_kpu_class_yolo2_find_type = { + { &mp_type_type }, + .name = MP_QSTR_kpu_yolo2_find, + .print = py_kpu_class_yolo2_find_print, + // .subscr = py_kpu_class_subscr, + .locals_dict = (mp_obj_t) &py_kpu_class_yolo2_find_type_locals_dict +}; + +volatile static uint8_t g_ai_done_flag = 0; + +static void ai_done(void *ctx) +{ + g_ai_done_flag = 1; +} + +STATIC mp_obj_t py_kpu_class_run_yolo2(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + if(mp_obj_get_type(pos_args[0]) == &py_kpu_net_obj_type) + { + py_kpu_net_obj_t *kpu_net = MP_OBJ_TO_PTR(pos_args[0]); + image_t *arg_img = py_image_cobj(pos_args[1]); + //PY_ASSERT_TRUE_MSG(IM_IS_MUTABLE(arg_img), "Image format is not supported."); + uint16_t w0=0;uint16_t h0=0;uint16_t ch0=0; + int kmodel_type=sipeed_kpu_model_get_type(kpu_net->kmodel_ctx); + if(abs(kmodel_type)==3 || abs(kmodel_type)==4){ + if(sipeed_kpu_model_get_input_shape(kpu_net->kmodel_ctx, &w0, &h0, &ch0) != SIPEED_KPU_ERR_NONE) + { + mp_raise_ValueError("[MAIXPY]kpu: first layer not conv layer!\r\n"); + return mp_const_none; + } + } + if(check_img_format(arg_img, w0, h0, ch0, kmodel_type)) + { + mp_raise_ValueError("[MAIXPY]kpu: check img format err!\r\n"); + return mp_const_none; + } + /*****************************region prepare*************************************************/ + py_kpu_class_yolo_args_obj_t *net_args = MP_OBJ_TO_PTR(kpu_net->net_args); + py_kpu_class_yolo_region_layer_arg_t *rl_arg = net_args->rl_args; + region_layer_t kpu_detect_rl; + kpu_detect_rl.anchor_number = rl_arg->anchor_number; + kpu_detect_rl.anchor = rl_arg->anchor; + kpu_detect_rl.threshold = rl_arg->threshold; + kpu_detect_rl.nms_value = rl_arg->nms_value; + if(region_layer_init(&kpu_detect_rl, kpu_net->kmodel_ctx)) + { + mp_raise_ValueError("[MAIXPY]kpu: region_layer_init err!\r\n"); + return mp_const_none; + } + /*************************************************************************************/ + g_ai_done_flag = 0; + sipeed_kpu_err_t ret = sipeed_kpu_model_run(kpu_net->kmodel_ctx, arg_img->pix_ai, K210_DMA_CH_KPU, ai_done, NULL); + if(ret != SIPEED_KPU_ERR_NONE) + { + char* msg = get_kpu_err_str(ret); + mp_raise_msg(&mp_type_OSError, msg); + } + while (!g_ai_done_flag) + ; + g_ai_done_flag = 0; + /****************************start region layer***************************************/ + static obj_info_t mpy_kpu_detect_info; + region_layer_run(&kpu_detect_rl, &mpy_kpu_detect_info); + uint8_t obj_num = 0; + obj_num = mpy_kpu_detect_info.obj_number; + + if (obj_num > 0) + { + list_t out; + list_init(&out, sizeof(py_kpu_class_yolo2__list_link_data_t)); + + for (uint8_t index = 0; index < obj_num; index++) + { + py_kpu_class_yolo2__list_link_data_t lnk_data; + lnk_data.rect.x = mpy_kpu_detect_info.obj[index].x1; + lnk_data.rect.y = mpy_kpu_detect_info.obj[index].y1; + lnk_data.rect.w = mpy_kpu_detect_info.obj[index].x2 - mpy_kpu_detect_info.obj[index].x1; + lnk_data.rect.h = mpy_kpu_detect_info.obj[index].y2 - mpy_kpu_detect_info.obj[index].y1; + lnk_data.classid = mpy_kpu_detect_info.obj[index].classid; + lnk_data.value = mpy_kpu_detect_info.obj[index].prob; + + lnk_data.index = index; + lnk_data.objnum = obj_num; + list_push_back(&out, &lnk_data); + } + + mp_obj_list_t *objects_list = mp_obj_new_list(list_size(&out), NULL); + + for (size_t i = 0; list_size(&out); i++) + { + py_kpu_class_yolo2__list_link_data_t lnk_data; + list_pop_front(&out, &lnk_data); + + py_kpu_class_yolo2_find_obj_t *o = m_new_obj(py_kpu_class_yolo2_find_obj_t); + + o->base.type = &py_kpu_class_yolo2_find_type; + + o->x = mp_obj_new_int(lnk_data.rect.x); + o->y = mp_obj_new_int(lnk_data.rect.y); + o->w = mp_obj_new_int(lnk_data.rect.w); + o->h = mp_obj_new_int(lnk_data.rect.h); + o->classid = mp_obj_new_int(lnk_data.classid); + o->index = mp_obj_new_int(lnk_data.index); + o->value = mp_obj_new_float(lnk_data.value); + o->objnum = mp_obj_new_int(lnk_data.objnum); + + objects_list->items[i] = o; + } + region_layer_deinit(&kpu_detect_rl); + return objects_list; + } + else + { + region_layer_deinit(&kpu_detect_rl); + + return mp_const_none; + } + } + else + { + mp_raise_TypeError("[MAIXPY]kpu: kpu_net type error"); + return mp_const_false; + } + +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_kpu_class_run_yolo2_obj, 2, py_kpu_class_run_yolo2); + +/////////////////////////////////////////////////////////////////////////////// + +typedef void (*call_net_arg_deinit)(mp_obj_t o); + +void call_deinit(call_net_arg_deinit call_back, mp_obj_t o) +{ + call_back(o); +} + +STATIC mp_obj_t py_kpu_deinit(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + if(mp_obj_get_type(pos_args[0]) == &py_kpu_net_obj_type) + { + py_kpu_net_obj_t *kpu_net = MP_OBJ_TO_PTR(pos_args[0]); + + if(kpu_net->kmodel_ctx) + sipeed_kpu_model_destroy(&kpu_net->kmodel_ctx); + if(kpu_net->net_deinit != mp_const_none && MP_OBJ_TO_PTR(kpu_net->net_deinit)) + { + call_deinit(MP_OBJ_TO_PTR(kpu_net->net_deinit),kpu_net->net_args); + kpu_net->net_deinit = mp_const_none; + m_del_obj(py_kpu_class_yolo_args_obj_t, kpu_net->net_args); + kpu_net->net_args = mp_const_none; + } + return mp_const_true; + } + else + { + mp_raise_TypeError("[MAIXPY]kpu: kpu_net type error"); + return mp_const_false; + } + +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_kpu_deinit_obj, 1, py_kpu_deinit); +/////////////////////////////////////////////////////////////////////////////// +/*forward + 单纯的网络前向运�? + 输入参数�? + net结构体的obj(必须) + 是否使用softmax(可选) + stride(可选,默认1�? + 计算到第几层(可选):默认为计算完,可选计算到第n�? + 计算到第n层,即修改第n层的send_data_out�?,使能dma输出,方法为�? + 修改原自动生成的kpu_task_init,设置layers_length为n + + 输出�? + 特征图obj + 即n个通道的m*n的图片,0~255灰度,可以使用color map映射为伪彩色 + m*n即为最后一个layer的输出尺�? + last_layer->image_size.data.o_row_wid, o_col_high + 新建一个类型,直接存储特征图数组, + 对外提供转化某通道特征图到Image对象的方法, + 并且提供deinit方向释放特征图空间�? +*/ + + + +typedef struct fmap +{ + uint8_t* data; + uint32_t size; + uint16_t index; + uint16_t w; + uint16_t h; + uint16_t ch; + uint16_t typecode; +} __attribute__((aligned(8)))fmap_t; + +typedef struct py_kpu_fmap_obj +{ + mp_obj_base_t base; + fmap_t fmap; +} __attribute__((aligned(8))) py_kpu_fmap_obj_t; + +static void py_kpu_fmap_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_kpu_fmap_obj_t *fmap_obj = MP_OBJ_TO_PTR(self_in); + fmap_t* fmap = &(fmap_obj->fmap); + + mp_printf(print, + "{\"fmap\": \"data\"=0x%x, \"size\"=%d, \"index\": %d, \"w\": %d, \"h\": %d, \"ch\": %d, \"typecode\": %c}", + fmap->data, fmap->size, fmap->index, fmap->w, fmap->h, fmap->ch, fmap->typecode); + + return; +} + +static mp_int_t py_fmap_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) +{ + py_kpu_fmap_obj_t *self = self_in; + //if(flags == MP_BUFFER_READ) + bufinfo->buf = self->fmap.data; + bufinfo->len = self->fmap.size; + bufinfo->typecode = (char)(self->fmap.typecode); + return 0; +} + +static mp_obj_t py_fmap_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + py_kpu_fmap_obj_t *self = self_in; + if (value == MP_OBJ_NULL) { // delete + } else if (value == MP_OBJ_SENTINEL) { // load + switch ((char)(self->fmap.typecode)) { + case 'B': { + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(self->fmap.size, index, &slice)) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + for (mp_uint_t i = 0; i < result->len; i++) { + uint8_t p = self->fmap.data[slice.start + i]; + result->items[i] = mp_obj_new_int(p); + } + return result; + } + mp_uint_t i = mp_get_index(self->base.type, self->fmap.size, index, false); + uint8_t p = self->fmap.data[i]; + return mp_obj_new_int(p); + } + break; + case 'f': { + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(self->fmap.size/sizeof(float), index, &slice)) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + for (mp_uint_t i = 0; i < result->len; i++) { + float p = ((float*)(self->fmap.data))[slice.start + i]; + result->items[i] = mp_obj_new_float(p); + } + return result; + } + mp_uint_t i = mp_get_index(self->base.type, self->fmap.size/sizeof(float), index, false); + float p = ((float*)(self->fmap.data))[i]; + return mp_obj_new_float(p); + } + break; + default: { + mp_printf(&mp_plat_print, "typecode don't support read!\r\n"); + return MP_OBJ_NULL; + } + break; + } + } else { // store + switch ((char)(self->fmap.typecode)) { + case 'B': { + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(self->fmap.size, index, &slice)) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "only slices with step=1 (aka None) are supported")); + } + if (MP_OBJ_IS_TYPE(value, &mp_type_list)) { + mp_uint_t value_l_len; + mp_obj_t *value_l; + mp_obj_get_array(value, &value_l_len, &value_l); + PY_ASSERT_TRUE_MSG(value_l_len == (slice.stop - slice.start), "cannot grow or shrink image"); + for (mp_uint_t i = 0; i < (slice.stop - slice.start); i++) { + uint8_t p = mp_obj_get_int(value_l[i]); + self->fmap.data[slice.start + i] = p; + } + } else { + uint8_t p = mp_obj_get_int(value); + for (mp_uint_t i = 0; i < (slice.stop - slice.start); i++) { + self->fmap.data[slice.start + i] = p; + } + } + return mp_const_none; + } + mp_uint_t i = mp_get_index(self->base.type, self->fmap.size, index, false); + uint8_t p = mp_obj_get_int(value); + self->fmap.data[i] = p; + return mp_const_none; + } + break; + case 'f': { + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(self->fmap.size/sizeof(float), index, &slice)) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "only slices with step=1 (aka None) are supported")); + } + if (MP_OBJ_IS_TYPE(value, &mp_type_list)) { + mp_uint_t value_l_len; + mp_obj_t *value_l; + mp_obj_get_array(value, &value_l_len, &value_l); + PY_ASSERT_TRUE_MSG(value_l_len == (slice.stop - slice.start), "cannot grow or shrink image"); + for (mp_uint_t i = 0; i < (slice.stop - slice.start); i++) { + float p = mp_obj_get_float(value_l[i]); + ((float*)(self->fmap.data))[slice.start + i] = p; + } + } else { + float p = mp_obj_get_float(value); + for (mp_uint_t i = 0; i < (slice.stop - slice.start); i++) { + ((float*)(self->fmap.data))[slice.start + i] = p; + } + } + return mp_const_none; + } + mp_uint_t i = mp_get_index(self->base.type, self->fmap.size/sizeof(float), index, false); + float p = mp_obj_get_float(value); + ((float*)(self->fmap.data))[i] = p; + return mp_const_none; + } + break; + default: { + mp_printf(&mp_plat_print, "typecode don't support write!\r\n"); + return MP_OBJ_NULL; + } + break; + + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_kpu_fmap_size(mp_obj_t self_in) { return mp_obj_new_int(((py_kpu_fmap_obj_t *)self_in)->fmap.size); } +mp_obj_t py_kpu_fmap_index(mp_obj_t self_in) { return mp_obj_new_int(((py_kpu_fmap_obj_t *)self_in)->fmap.index); } +mp_obj_t py_kpu_fmap_w(mp_obj_t self_in) { return mp_obj_new_int(((py_kpu_fmap_obj_t *)self_in)->fmap.w); } +mp_obj_t py_kpu_fmap_h(mp_obj_t self_in) { return mp_obj_new_int(((py_kpu_fmap_obj_t *)self_in)->fmap.h); } +mp_obj_t py_kpu_fmap_ch(mp_obj_t self_in) { return mp_obj_new_int(((py_kpu_fmap_obj_t *)self_in)->fmap.ch); } +mp_obj_t py_kpu_fmap_typecode(mp_obj_t self_in) { return mp_obj_new_str((const char*)((py_kpu_fmap_obj_t *)self_in)->fmap.typecode,1); } + +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_fmap_size_obj, py_kpu_fmap_size); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_fmap_index_obj, py_kpu_fmap_index); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_fmap_w_obj, py_kpu_fmap_w); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_fmap_h_obj, py_kpu_fmap_h); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_fmap_ch_obj, py_kpu_fmap_ch); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_fmap_typecode_obj, py_kpu_fmap_typecode); + + + +static const mp_rom_map_elem_t py_kpu_fmap_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&py_kpu_fmap_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_index), MP_ROM_PTR(&py_kpu_fmap_index_obj) }, + { MP_ROM_QSTR(MP_QSTR_w), MP_ROM_PTR(&py_kpu_fmap_w_obj) }, + { MP_ROM_QSTR(MP_QSTR_h), MP_ROM_PTR(&py_kpu_fmap_h_obj) }, + { MP_ROM_QSTR(MP_QSTR_ch), MP_ROM_PTR(&py_kpu_fmap_ch_obj) }, + { MP_ROM_QSTR(MP_QSTR_typecode), MP_ROM_PTR(&py_kpu_fmap_typecode_obj) } +}; + +static MP_DEFINE_CONST_DICT(py_kpu_fmap_dict, py_kpu_fmap_dict_table); + + +static const mp_obj_type_t py_kpu_fmap_obj_type = { + { &mp_type_type }, + .name = MP_QSTR_kpu_fmap, + .print = py_kpu_fmap_print, + .buffer_p = { .get_buffer = py_fmap_get_buffer }, + .subscr = py_fmap_subscr, + .locals_dict = (mp_obj_t) &py_kpu_fmap_dict +}; + + +static int get_typecode_size(char type) +{ + int len; + switch(type) + { + case 'B': + len = 1; + case 'f': + len = 4; + default: + len = 1; + } + return len; +} + +STATIC mp_obj_t py_kpu_set_layers(mp_obj_t kpu_net_obj, mp_obj_t len_obj) +{ + py_kpu_net_obj_t *kpu_net = MP_OBJ_TO_PTR(kpu_net_obj); + int layers_length = mp_obj_get_int(len_obj); //how many layers you want calculate, set <=0 to calculate all layers + int max_length = mp_obj_get_int(kpu_net->max_layers); + // sipeed_kpu_err_t ret; + + if(layers_length > 0) + { //set layer count + if(layers_length <= max_length) + { + //mp_printf(&mp_plat_print, "set layers_length to %d\r\n", layers_length); + /* ret = */sipeed_kpu_model_set_output(kpu_net->kmodel_ctx, 0, layers_length); + } + else + { + mp_printf(&mp_plat_print, "err: set layers_length to %d, but max %d\r\n", layers_length, max_length); + return mp_const_false; + } + } else //calculate all layers + { + //mp_printf(&mp_plat_print, "set layers_length to %d\r\n", max_length); + sipeed_kpu_model_set_output(kpu_net->kmodel_ctx, 0, max_length); + } + return mp_const_true; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_kpu_set_layers_obj, py_kpu_set_layers); + +STATIC mp_obj_t py_kpu_forward(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + enum { ARG_kpu_net, ARG_img, ARG_out_index, ARG_dma}; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_kpu_net, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_img, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_out_index, MP_ARG_INT, {.u_int = 0x0} }, + { MP_QSTR_dma, MP_ARG_INT, {.u_int = -1} }, + }; //type + char char_temp[30]; + bool need_free_data_in = false; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(mp_obj_get_type(args[ARG_kpu_net].u_obj) == &py_kpu_net_obj_type) + { + sipeed_kpu_err_t ret; + py_kpu_net_obj_t *kpu_net = MP_OBJ_TO_PTR(args[ARG_kpu_net].u_obj); + sipeed_kpu_use_dma(args[ARG_dma].u_int); + + int kmodel_type=sipeed_kpu_model_get_type(kpu_net->kmodel_ctx); + uint8_t* data_in; + if(py_image_obj_is_image(args[ARG_img].u_obj)) + { + uint16_t w0=0, h0=0, ch0=0; + image_t *arg_img = py_image_cobj(args[ARG_img].u_obj); + data_in = arg_img->pix_ai; + // check V3 model input shape + if(abs(kmodel_type)==3 || abs(kmodel_type)==4){ + if(sipeed_kpu_model_get_input_shape(kpu_net->kmodel_ctx, &w0, &h0, &ch0) != SIPEED_KPU_ERR_NONE) + { + mp_raise_ValueError("[MAIXPY]kpu: first layer not conv layer!\r\n"); + return mp_const_none; + } + + } + if(check_img_format(arg_img, w0, h0, ch0, kmodel_type)) + { + mp_raise_ValueError("[MAIXPY]kpu: check img format err!\r\n"); + return mp_const_none; + } + } + else if(MP_OBJ_IS_TYPE(args[ARG_img].u_obj, &mp_type_list)) + { + mp_uint_t arg_list_len; + mp_obj_t *arg_list_obj; + mp_obj_get_array(args[ARG_img].u_obj, &arg_list_len, &arg_list_obj); + if (!arg_list_len) { + mp_raise_ValueError("[MAIXPY]kpu: check input list format err!\r\n"); + return mp_const_none; + } + + data_in = malloc(arg_list_len*sizeof(float)); + if(data_in == NULL) { + mp_raise_ValueError("[MAIXPY]kpu: alloc list mem err!\r\n"); + return mp_const_none; + } + need_free_data_in = true; + float* float_in = (float*)data_in; //理论上malloc申请的地址肯定是4字节对齐的 + for(mp_uint_t i = 0; i < arg_list_len; i++) { + float_in[i] = mp_obj_get_float(arg_list_obj[i]); + } + } + else + { + mp_raise_ValueError("input not support"); + } + int out_index = args[ARG_out_index].u_int; //which output you want, defaultly index 0 + /*************************************************************************************/ + g_ai_done_flag = 0; + ret = sipeed_kpu_model_run(kpu_net->kmodel_ctx, data_in, K210_DMA_CH_KPU, ai_done, NULL); + if(ret != SIPEED_KPU_ERR_NONE) + { + char* msg = get_kpu_err_str(ret); + mp_raise_msg(&mp_type_OSError, msg); + } + + while (!g_ai_done_flag){ + }; + g_ai_done_flag = 0; + if(need_free_data_in) { + free(data_in); + } + + /*************************************************************************************/ + if(abs(kmodel_type)==3) { + uint8_t* features; + size_t count; + kpu_model_layer_type_t layer_type; + ret = sipeed_kpu_model_get_layer_type(kpu_net->kmodel_ctx,sipeed_kpu_model_get_layer_num(kpu_net->kmodel_ctx)-1, &layer_type); + if(ret != SIPEED_KPU_ERR_NONE) + { + mp_raise_msg(&mp_type_OSError, "sipeed_kpu_model_get_layer_num err"); + } + ret = sipeed_kpu_get_output(kpu_net->kmodel_ctx, out_index, &features, &count); + if(ret != SIPEED_KPU_ERR_NONE) + { + mp_raise_msg(&mp_type_OSError, "sipeed_kpu_get_output err"); + } + py_kpu_fmap_obj_t *o = m_new_obj(py_kpu_fmap_obj_t); + o->base.type = &py_kpu_fmap_obj_type; + fmap_t* fmap = &(o->fmap); + fmap->data = features; + fmap->size = (uint32_t)count;// + 1; + fmap->index = sipeed_kpu_model_get_layer_num(kpu_net->kmodel_ctx)-1; + if(layer_type == KL_K210_CONV) + { //conv layer + + kpu_layer_argument_t* layer = sipeed_kpu_model_get_conv_layer(kpu_net->kmodel_ctx, sipeed_kpu_model_get_layer_num(kpu_net->kmodel_ctx)-1); + if(!layer) + { + snprintf(char_temp, sizeof(char_temp), "%d", SIPEED_KPU_ERR_GET_CONV_LAYER); + mp_raise_msg(&mp_type_OSError, char_temp); + } + fmap->w = layer->image_size.data.o_row_wid+1; + fmap->h = layer->image_size.data.o_col_high+1; + fmap->ch = layer->image_channel_num.data.o_ch_num+1; + fmap->typecode = 'B'; + } else + { //other layer, get original output + + fmap->h = 1; + fmap->ch = 1; + fmap->typecode = (uint16_t)sipeed_kpu_model_getdtype_from_type(layer_type); + fmap->w = (uint16_t)(count/get_typecode_size(fmap->typecode)); //TODO: auto cal w,h,c + } + return MP_OBJ_FROM_PTR(o); + //kpu_single_task_deinit + } else if(abs(kmodel_type)==4) { + uint8_t* features; + size_t count; + + ret = sipeed_kpu_get_output(kpu_net->kmodel_ctx, out_index, &features, &count); + if(ret != SIPEED_KPU_ERR_NONE) + { + mp_raise_msg(&mp_type_OSError, "sipeed_kpu_get_output err"); + } + py_kpu_fmap_obj_t *o = m_new_obj(py_kpu_fmap_obj_t); + o->base.type = &py_kpu_fmap_obj_type; + fmap_t* fmap = &(o->fmap); + fmap->data = features; + fmap->size = (uint32_t)count;// + 1; + fmap->index = sipeed_kpu_model_get_layer_num(kpu_net->kmodel_ctx)-1; + + ret = sipeed_kpu_get_outputs_shape(kpu_net->kmodel_ctx, out_index, &(fmap->w), &(fmap->h), &(fmap->ch)); + if(ret != SIPEED_KPU_ERR_NONE) + { + mp_raise_msg(&mp_type_OSError, "sipeed_kpu_get_outputs_shape err"); + } + fmap->typecode = (uint16_t)('f'); //默认float + return MP_OBJ_FROM_PTR(o); + //kpu_single_task_deinit + } + } + + return mp_const_none; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_kpu_forward_obj, 1, py_kpu_forward); + + +STATIC mp_obj_t py_kpu_get_output(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + if((mp_obj_get_type(pos_args[0]) != &py_kpu_net_obj_type) || \ + (mp_obj_get_type(pos_args[1]) != &mp_type_int)) + { + mp_raise_ValueError("[MAIXPY]kpu: get_output arg format error: get_output(task, int idx)"); + return mp_const_false; + } + + py_kpu_net_obj_t *kpu_net = MP_OBJ_TO_PTR(pos_args[0]); + uint16_t out_index = mp_obj_get_int(pos_args[1]); + sipeed_kpu_err_t ret; + char char_temp[30]; + int kmodel_type=sipeed_kpu_model_get_type(kpu_net->kmodel_ctx); + + if(abs(kmodel_type)==3) { + uint8_t* features; + size_t count; + kpu_model_layer_type_t layer_type; + ret = sipeed_kpu_model_get_layer_type(kpu_net->kmodel_ctx,sipeed_kpu_model_get_layer_num(kpu_net->kmodel_ctx)-1, &layer_type); + if(ret != SIPEED_KPU_ERR_NONE) + { + snprintf(char_temp, sizeof(char_temp), "%d", ret); + mp_raise_msg(&mp_type_OSError, char_temp); + } + ret = sipeed_kpu_get_output(kpu_net->kmodel_ctx, out_index, &features, &count); + if(ret != SIPEED_KPU_ERR_NONE) + { + snprintf(char_temp, sizeof(char_temp), "%d", ret); + mp_raise_msg(&mp_type_OSError, char_temp); + } + py_kpu_fmap_obj_t *o = m_new_obj(py_kpu_fmap_obj_t); + o->base.type = &py_kpu_fmap_obj_type; + fmap_t* fmap = &(o->fmap); + fmap->data = features; + fmap->size = (uint32_t)count;// + 1; + fmap->index = sipeed_kpu_model_get_layer_num(kpu_net->kmodel_ctx)-1; + if(layer_type == KL_K210_CONV) + { //conv layer + kpu_layer_argument_t* layer = sipeed_kpu_model_get_conv_layer(kpu_net->kmodel_ctx, sipeed_kpu_model_get_layer_num(kpu_net->kmodel_ctx)-1); + if(!layer) + { + snprintf(char_temp, sizeof(char_temp), "%d", SIPEED_KPU_ERR_GET_CONV_LAYER); + mp_raise_msg(&mp_type_OSError, char_temp); + } + fmap->w = layer->image_size.data.o_row_wid+1; + fmap->h = layer->image_size.data.o_col_high+1; + fmap->ch = layer->image_channel_num.data.o_ch_num+1; + fmap->typecode = 'B'; + } else + { //other layer, get original output + fmap->h = 1; + fmap->ch = 1; + fmap->typecode = (uint16_t)sipeed_kpu_model_getdtype_from_type(layer_type); + fmap->w = (uint16_t)(count/get_typecode_size(fmap->typecode)); //TODO: auto cal w,h,c + } + return MP_OBJ_FROM_PTR(o); + //kpu_single_task_deinit + } else if(abs(kmodel_type)==4) { + uint8_t* features; + size_t count; + + ret = sipeed_kpu_get_output(kpu_net->kmodel_ctx, out_index, &features, &count); + if(ret != SIPEED_KPU_ERR_NONE) + { + mp_raise_msg(&mp_type_OSError, "get_output err"); + } + py_kpu_fmap_obj_t *o = m_new_obj(py_kpu_fmap_obj_t); + o->base.type = &py_kpu_fmap_obj_type; + fmap_t* fmap = &(o->fmap); + fmap->data = features; + fmap->size = (uint32_t)count;// + 1; + fmap->index = sipeed_kpu_model_get_layer_num(kpu_net->kmodel_ctx)-1; + + ret = sipeed_kpu_get_outputs_shape(kpu_net->kmodel_ctx, out_index, &(fmap->w), &(fmap->h), &(fmap->ch)); + if(ret != SIPEED_KPU_ERR_NONE) + { + mp_raise_msg(&mp_type_OSError, "get_outputs_shape err"); + } + fmap->typecode = (uint16_t)('f'); //默认float + return MP_OBJ_FROM_PTR(o); + //kpu_single_task_deinit + } + + return mp_const_none; +} + + + + +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_kpu_get_output_obj, 2, py_kpu_get_output); + +//gen ch channel fmap, in Image_t format +/*typedef struct image { + int w; + int h; + int bpp; + union { + uint8_t *pixels; + uint8_t *data; + }; + uint8_t *pix_ai; //for MAIX AI speed up +} __attribute__((aligned(8)))image_t; +*/ + +STATIC mp_obj_t py_kpu_fmap(mp_obj_t fmap_obj, mp_obj_t ch_obj) +{ + int ch = mp_obj_get_int(ch_obj); + fmap_t* fmap = &(((py_kpu_fmap_obj_t*)fmap_obj)->fmap); + if(ch<0 || ch>= (fmap->ch)) + { + char str_ret[40]; + sprintf(str_ret,"[MAIXPY]kpu: ch err,input 0~%d\r\n", fmap->ch); + mp_raise_ValueError(str_ret); + return mp_const_none; + } + if(fmap->typecode != 'B') + { + char str_ret[50]; + snprintf(str_ret, sizeof(str_ret), "[MAIXPY]kpu: can't convet float fmap yet\r\n"); + mp_raise_ValueError(str_ret); + return mp_const_none; + } + mp_obj_t image = py_image(fmap->w, fmap->h, IMAGE_BPP_GRAYSCALE, fmap->data + ((fmap->w)*(fmap->h))*ch); + return image; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_kpu_fmap_obj, py_kpu_fmap); + +STATIC mp_obj_t py_kpu_fmap_free(mp_obj_t fmap_obj) +{ + //fmap_t* fmap = &(((py_kpu_fmap_obj_t*)fmap_obj)->fmap); + //free(fmap->data); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_fmap_free_obj, py_kpu_fmap_free); + + + + +typedef struct py_kpu_class_netinfo_find_obj { + mp_obj_base_t base; + + mp_obj_t index,type,wi,hi,wo,ho,chi,cho,dw,kernel_type,pool_type,para_size; +} __attribute__((aligned(8))) py_kpu_class_netinfo_find_obj_t; + +static void py_kpu_class_netinfo_find_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_kpu_class_netinfo_find_obj_t *self = self_in; + mp_printf(print, + "{\"index\":%d, \"type\":%s, \"wi\":%d, \"hi\":%d, \"wo\":%d, \"ho\":%d, \"chi\":%d, \"cho\":%d, \"dw\":%d, \"kernel_type\":%d, \"pool_type\":%d, \"para_size\":%d}", + mp_obj_get_int(self->index), + sipeed_kpu_model_getname_from_type(mp_obj_get_int(self->type)), + mp_obj_get_int(self->wi), + mp_obj_get_int(self->hi), + mp_obj_get_int(self->wo), + mp_obj_get_int(self->ho), + mp_obj_get_int(self->chi), + mp_obj_get_int(self->cho), + mp_obj_get_int(self->dw), + mp_obj_get_int(self->kernel_type), + mp_obj_get_int(self->pool_type), + mp_obj_get_int(self->para_size)); +} + +mp_obj_t py_kpu_class_netinfo_find_index (mp_obj_t self_in) { return ((py_kpu_class_netinfo_find_obj_t *)self_in)->index; } +mp_obj_t py_kpu_class_netinfo_find__type (mp_obj_t self_in) { return ((py_kpu_class_netinfo_find_obj_t *)self_in)->type; } +mp_obj_t py_kpu_class_netinfo_find_wi(mp_obj_t self_in) { return ((py_kpu_class_netinfo_find_obj_t *)self_in)->wi; } +mp_obj_t py_kpu_class_netinfo_find_hi(mp_obj_t self_in) { return ((py_kpu_class_netinfo_find_obj_t *)self_in)->hi; } +mp_obj_t py_kpu_class_netinfo_find_wo(mp_obj_t self_in) { return ((py_kpu_class_netinfo_find_obj_t *)self_in)->wo; } +mp_obj_t py_kpu_class_netinfo_find_ho(mp_obj_t self_in) { return ((py_kpu_class_netinfo_find_obj_t *)self_in)->ho; } +mp_obj_t py_kpu_class_netinfo_find_chi(mp_obj_t self_in) { return ((py_kpu_class_netinfo_find_obj_t *)self_in)->chi; } +mp_obj_t py_kpu_class_netinfo_find_cho(mp_obj_t self_in) { return ((py_kpu_class_netinfo_find_obj_t *)self_in)->cho; } +mp_obj_t py_kpu_class_netinfo_find_dw(mp_obj_t self_in) { return ((py_kpu_class_netinfo_find_obj_t *)self_in)->dw; } +mp_obj_t py_kpu_class_netinfo_find_kernel_type(mp_obj_t self_in) { return ((py_kpu_class_netinfo_find_obj_t *)self_in)->kernel_type; } +mp_obj_t py_kpu_class_netinfo_find_pool_type(mp_obj_t self_in) { return ((py_kpu_class_netinfo_find_obj_t *)self_in)->pool_type; } +mp_obj_t py_kpu_class_netinfo_find_para_size(mp_obj_t self_in) { return ((py_kpu_class_netinfo_find_obj_t *)self_in)->para_size; } + +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_netinfo_find_index_obj, py_kpu_class_netinfo_find_index); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_netinfo_find__type_obj, py_kpu_class_netinfo_find__type); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_netinfo_find_wi_obj, py_kpu_class_netinfo_find_wi); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_netinfo_find_hi_obj, py_kpu_class_netinfo_find_hi); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_netinfo_find_wo_obj, py_kpu_class_netinfo_find_wo); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_netinfo_find_ho_obj, py_kpu_class_netinfo_find_ho); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_netinfo_find_chi_obj, py_kpu_class_netinfo_find_chi); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_netinfo_find_cho_obj, py_kpu_class_netinfo_find_cho); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_netinfo_find_dw_obj, py_kpu_class_netinfo_find_dw); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_netinfo_find_kernel_type_obj, py_kpu_class_netinfo_find_kernel_type); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_netinfo_find_pool_type_obj, py_kpu_class_netinfo_find_pool_type); +static MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_class_netinfo_find_para_size_obj, py_kpu_class_netinfo_find_para_size); + +static const mp_rom_map_elem_t py_kpu_class_netinfo_find_type_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_index), MP_ROM_PTR(&py_kpu_class_netinfo_find_index_obj) }, + { MP_ROM_QSTR(MP_QSTR_type), MP_ROM_PTR(&py_kpu_class_netinfo_find__type_obj) }, + { MP_ROM_QSTR(MP_QSTR_wi), MP_ROM_PTR(&py_kpu_class_netinfo_find_wi_obj) }, + { MP_ROM_QSTR(MP_QSTR_hi), MP_ROM_PTR(&py_kpu_class_netinfo_find_hi_obj) }, + { MP_ROM_QSTR(MP_QSTR_wo), MP_ROM_PTR(&py_kpu_class_netinfo_find_wo_obj) }, + { MP_ROM_QSTR(MP_QSTR_ho), MP_ROM_PTR(&py_kpu_class_netinfo_find_ho_obj) }, + { MP_ROM_QSTR(MP_QSTR_chi), MP_ROM_PTR(&py_kpu_class_netinfo_find_chi_obj) }, + { MP_ROM_QSTR(MP_QSTR_cho), MP_ROM_PTR(&py_kpu_class_netinfo_find_cho_obj) }, + { MP_ROM_QSTR(MP_QSTR_dw), MP_ROM_PTR(&py_kpu_class_netinfo_find_dw_obj) }, + { MP_ROM_QSTR(MP_QSTR_kernel_type), MP_ROM_PTR(&py_kpu_class_netinfo_find_kernel_type_obj) }, + { MP_ROM_QSTR(MP_QSTR_pool_type), MP_ROM_PTR(&py_kpu_class_netinfo_find_pool_type_obj) }, + { MP_ROM_QSTR(MP_QSTR_para_size), MP_ROM_PTR(&py_kpu_class_netinfo_find_para_size_obj) }, +}; + +static MP_DEFINE_CONST_DICT(py_kpu_class_netinfo_find_type_locals_dict, py_kpu_class_netinfo_find_type_locals_dict_table); + +static const mp_obj_type_t py_kpu_class_netinfo_find_type = { + { &mp_type_type }, + .name = MP_QSTR_kpu_netinfo_find, + .print = py_kpu_class_netinfo_find_print, + // .subscr = py_kpu_class_subscr, + .locals_dict = (mp_obj_t) &py_kpu_class_netinfo_find_type_locals_dict +}; + + +STATIC mp_obj_t py_kpu_netinfo(mp_obj_t py_kpu_net_obj) +{ + py_kpu_net_obj_t *kpu_net = MP_OBJ_TO_PTR(py_kpu_net_obj); + + int len = sipeed_kpu_model_get_layer_num(kpu_net->kmodel_ctx); + if(len <= 0) + { + mp_raise_ValueError("get layer num error!"); + } + kpu_layer_argument_t *layer; + list_t out; + list_init(&out, sizeof(py_kpu_netinfo_list_data_t)); + + sipeed_kpu_model_print_layer_info(kpu_net->kmodel_ctx); + for (uint8_t index = 0; index < len; index++) + { + py_kpu_netinfo_list_data_t data; + sipeed_kpu_err_t ret; + data.index = index; + ret = sipeed_kpu_model_get_layer_type(kpu_net->kmodel_ctx, index, (kpu_model_layer_type_t*)&data.type); + if(ret != SIPEED_KPU_ERR_NONE) + mp_raise_OSError(ret); + layer = sipeed_kpu_model_get_conv_layer(kpu_net->kmodel_ctx, index); + if(layer != NULL) //conv layer + { + data.wi = layer->image_size.data.i_row_wid+1; + data.hi = layer->image_size.data.i_col_high+1; + data.wo = layer->image_size.data.o_row_wid+1; + data.ho = layer->image_size.data.o_col_high+1; + data.chi = layer->image_channel_num.data.i_ch_num+1; + data.cho = layer->image_channel_num.data.o_ch_num+1; + data.dw = layer->interrupt_enabe.data.depth_wise_layer; + data.kernel_type = layer->kernel_pool_type_cfg.data.kernel_type; + data.pool_type = layer->kernel_pool_type_cfg.data.pool_type; + data.para_size = layer->kernel_load_cfg.data.para_size; + } else{ + data.wi = 0; + data.hi = 0; + data.wo = 0; + data.ho = 0; + data.chi = 0; + data.cho = 0; + data.dw = 0; + data.kernel_type = 0; + data.pool_type = 0; + data.para_size = sipeed_kpu_model_get_layer_size(kpu_net->kmodel_ctx, index); + } + list_push_back(&out, &data); + } + + mp_obj_list_t *objects_list = mp_obj_new_list(list_size(&out), NULL); + + for (size_t i = 0; list_size(&out); i++) + { + py_kpu_netinfo_list_data_t lnk_data; + list_pop_front(&out, &lnk_data); + + py_kpu_class_netinfo_find_obj_t *o = m_new_obj(py_kpu_class_netinfo_find_obj_t); + + o->base.type = &py_kpu_class_netinfo_find_type; + + o->index = mp_obj_new_int(lnk_data.index); + o->type = mp_obj_new_int(lnk_data.type); + o->wi = mp_obj_new_int(lnk_data.wi); + o->hi = mp_obj_new_int(lnk_data.hi); + o->wo = mp_obj_new_int(lnk_data.wo); + o->ho = mp_obj_new_int(lnk_data.ho); + o->chi = mp_obj_new_int(lnk_data.chi); + o->cho= mp_obj_new_int(lnk_data.cho); + o->dw = mp_obj_new_int(lnk_data.dw); + o->kernel_type = mp_obj_new_int(lnk_data.kernel_type); + o->pool_type = mp_obj_new_int(lnk_data.pool_type); + o->para_size = mp_obj_new_int(lnk_data.para_size); + + objects_list->items[i] = o; + } + + return objects_list; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kpu_netinfo_obj, py_kpu_netinfo); + + +STATIC mp_obj_t py_kpu_memtest(void) +{ + int mem0,mem1,mem; + char* ptr; + mem0=0; + mem1=0x800000; + while(mem1-mem0>0x400){ + for(mem=(mem0+mem1)/2; memFACE_FEATURE_DIM_MAX) + { + mp_raise_ValueError("feature dim <=200\r\n"); + } + for(int i=0; imodel = m_new(kpu_model_info_t, 1); + self->kpu_model = model; + self->model->kmodel_ctx = model->kmodel_ctx; + self->model->max_layers = model->max_layers; + self->model->model_addr = model->model_addr; + if(model->model_path == mp_const_none) + self->model->model_path = NULL; + else + self->model->model_path = mp_obj_str_get_str(model->model_path); + self->model->model_size = model->model_size; + int ret = maix_kpu_classifier_init(&self->obj, self->model, (int)class_num, (int)sample_num, false, 0, feature_length); + if(ret < 0) + mp_raise_OSError(-ret); +} + +STATIC int add_class_img(maix_kpu_classifier_t* self, image_t* img, int idx){ + int ret = maix_kpu_classifier_add_class_img(self->obj, img, idx); + if(ret < 0) + mp_raise_OSError(-ret); + return ret; +} + +STATIC int rm_class_img(maix_kpu_classifier_t* self){ + int ret = maix_kpu_classifier_rm_class_img(self->obj); + if(ret < 0) + mp_raise_OSError(-ret); + return ret; +} + +STATIC int add_sample_img(maix_kpu_classifier_t* self, image_t* img){ + int ret = maix_kpu_classifier_add_sample_img(self->obj, img); + if(ret < 0) + mp_raise_OSError(-ret); + return ret; +} + +STATIC int rm_sample_img(maix_kpu_classifier_t* self){ + int ret = maix_kpu_classifier_rm_sample_img(self->obj); + if(ret < 0) + mp_raise_OSError(-ret); + return ret; +} + +STATIC void clear_obj(maix_kpu_classifier_t* self){ + int ret = maix_kpu_classifier_del(&self->obj); + if(ret < 0) + mp_raise_OSError(-ret); +} + +STATIC void train(maix_kpu_classifier_t* self){ + int ret = maix_kpu_classifier_train(self->obj); + if(ret < 0) + mp_raise_OSError(-ret); +} + +STATIC int predict(maix_kpu_classifier_t* self, image_t* img, float* min_distance){ + int ret = maix_kpu_classifier_predict(self->obj, img, min_distance, NULL, NULL, NULL, NULL); + if(ret < 0) + mp_raise_OSError(-ret); + return ret; +} + + +STATIC void save_trained_model(maix_kpu_classifier_t* self, const char* path){ + int ret = maix_kpu_classifier_save(self->obj, path); + if(ret < 0) + mp_raise_OSError(-ret); +} + +STATIC void load_trained_model(maix_kpu_classifier_t* self, const char* path, py_kpu_net_obj_t* model, int* class_num, int* sample_num, int feature_length){ + self->model = m_new(kpu_model_info_t, 1); + self->kpu_model = model; + self->model->kmodel_ctx = model->kmodel_ctx; + self->model->max_layers = model->max_layers; + self->model->model_addr = model->model_addr; + if(model->model_path == mp_const_none) + self->model->model_path = NULL; + else + self->model->model_path = mp_obj_str_get_str(model->model_path); + self->model->model_size = model->model_size; + int ret = maix_kpu_classifier_load(&self->obj, path, self->model, class_num, sample_num, feature_length); + if(ret < 0) + mp_raise_OSError(-ret); +} + +mp_obj_t maix_kpu_classifier_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + maix_kpu_classifier_t* self = m_new_obj_with_finaliser(maix_kpu_classifier_t); + self->base.type = &Maix_kpu_classifier_type; + self->obj = NULL; + if(n_args<3) + { + mp_raise_ValueError("model, class num, sample num"); + } + if(mp_obj_get_type(args[0]) != &py_kpu_net_obj_type){ + mp_raise_ValueError("model"); + } + int feature_length = 0; + if(n_kw > 0) + { + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + feature_length = py_helper_keyword_int(n_args, args, 3, &kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fea_len), 0); + } + sipeed_kpu_use_dma(1); + init_obj(self, (py_kpu_net_obj_t*)args[0], mp_obj_get_int(args[1]), mp_obj_get_int(args[2]), feature_length); + return (mp_obj_t)self; +} + +mp_obj_t classifier_add_class_img(size_t n_args, const mp_obj_t *args){ + if(mp_obj_get_type(args[0]) != &Maix_kpu_classifier_type){ + mp_raise_ValueError("must be obj"); + } + maix_kpu_classifier_t* self = (maix_kpu_classifier_t*)args[0]; + image_t* img = py_image_cobj(args[1]); + int idx = -1; + if(n_args > 2) + idx = mp_obj_get_int(args[2]); + int ret_index = add_class_img(self, img, idx); + return mp_obj_new_int(ret_index); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(classifier_add_class_img_obj, 2, 3, classifier_add_class_img); + + +mp_obj_t classifier_rm_class_img(mp_obj_t self_in){ + if(mp_obj_get_type(self_in) != &Maix_kpu_classifier_type){ + mp_raise_ValueError("must be obj"); + } + maix_kpu_classifier_t* self = (maix_kpu_classifier_t*)self_in; + int ret_index = rm_class_img(self); + return mp_obj_new_int(ret_index); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(classifier_rm_class_img_obj, classifier_rm_class_img); + + +mp_obj_t classifier_add_sample_img(mp_obj_t self_in, mp_obj_t img_in){ + if(mp_obj_get_type(self_in) != &Maix_kpu_classifier_type){ + mp_raise_ValueError("must be obj"); + } + maix_kpu_classifier_t* self = (maix_kpu_classifier_t*)self_in; + image_t* img = py_image_cobj(img_in); + int ret = add_sample_img(self, img); + return mp_obj_new_int(ret); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_2(classifier_add_sample_img_obj, classifier_add_sample_img); + +mp_obj_t classifier_rm_sample_img(mp_obj_t self_in){ + if(mp_obj_get_type(self_in) != &Maix_kpu_classifier_type){ + mp_raise_ValueError("must be obj"); + } + maix_kpu_classifier_t* self = (maix_kpu_classifier_t*)self_in; + int ret_index = rm_sample_img(self); + return mp_obj_new_int(ret_index); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(classifier_rm_sample_img_obj, classifier_rm_sample_img); + +mp_obj_t classifier_del(mp_obj_t self_in){ + if(mp_obj_get_type(self_in) != &Maix_kpu_classifier_type){ + mp_raise_ValueError("must be obj"); + } + mp_printf(&mp_plat_print, "classifier __del__\r\n"); + maix_kpu_classifier_t* self = (maix_kpu_classifier_t*)self_in; + clear_obj(self); + return mp_const_none; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(classifier_del_obj, classifier_del); + +mp_obj_t classifier_train(mp_obj_t self_in){ + if(mp_obj_get_type(self_in) != &Maix_kpu_classifier_type){ + mp_raise_ValueError("must be obj"); + } + maix_kpu_classifier_t* self = (maix_kpu_classifier_t*)self_in; + train(self); + return mp_const_none; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(classifier_train_obj, classifier_train); + +mp_obj_t classifier_predict(mp_obj_t self_in, mp_obj_t img_in){ + if(mp_obj_get_type(self_in) != &Maix_kpu_classifier_type){ + mp_raise_ValueError("must be obj"); + } + maix_kpu_classifier_t* self = (maix_kpu_classifier_t*)self_in; + image_t* img = py_image_cobj(img_in); + float min_distance; + int ret_index = predict(self, img, &min_distance); + mp_obj_t t[2]; + t[0] = mp_obj_new_int(ret_index); + t[1] = mp_obj_new_float(min_distance); + return mp_obj_new_tuple(2,t); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_2(classifier_predict_obj, classifier_predict); + + +mp_obj_t classifier_save(mp_obj_t self_in, mp_obj_t path_in){ + if(mp_obj_get_type(self_in) != &Maix_kpu_classifier_type){ + mp_raise_ValueError("must be obj"); + } + maix_kpu_classifier_t* self = (maix_kpu_classifier_t*)self_in; + const char* path = mp_obj_str_get_str(path_in); + save_trained_model(self, path); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(classifier_save_obj, classifier_save); + +/** + * @param model_in kpu model object + * @param path_in saved classifier model path + * @param class_num init object classnum, if not set, the same as saved model's + * @param sample_num ... + */ +mp_obj_t classifier_load(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args){ + mp_obj_t model_in = args[0]; + mp_obj_t path_in = args[1]; + if(mp_obj_get_type(model_in) == &Maix_kpu_classifier_type){ + mp_raise_ValueError("must be class"); + } + if(mp_obj_get_type(model_in) != &py_kpu_net_obj_type){ + mp_raise_ValueError("must be model"); + } + if(mp_obj_get_type(path_in) != &mp_type_str){ + mp_raise_ValueError("path err"); + } + maix_kpu_classifier_t* self = m_new_obj_with_finaliser(maix_kpu_classifier_t); + self->base.type = &Maix_kpu_classifier_type; + self->obj = NULL; + int class_num = 0, sample_num = 0; + if(n_args > 2) + { + class_num = mp_obj_get_int(args[2]); + } + if(n_args > 3) + { + sample_num = mp_obj_get_int(args[3]); + } + int feature_length = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fea_len), 0); + load_trained_model(self, mp_obj_str_get_str(path_in), (py_kpu_net_obj_t*)model_in, &class_num, &sample_num, feature_length); + mp_obj_t* items[3] = {(mp_obj_t)self, mp_obj_new_int(class_num), mp_obj_new_int(sample_num)}; + return mp_obj_new_tuple(3, items); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(classifier_load_obj, 2, classifier_load); + + +STATIC const mp_map_elem_t locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_classifier) }, + { MP_ROM_QSTR(MP_QSTR_add_class_img), (mp_obj_t)(&classifier_add_class_img_obj) }, + { MP_ROM_QSTR(MP_QSTR_add_sample_img), (mp_obj_t)(&classifier_add_sample_img_obj) }, + { MP_ROM_QSTR(MP_QSTR_train), (mp_obj_t)(&classifier_train_obj) }, + { MP_ROM_QSTR(MP_QSTR_predict), (mp_obj_t)(&classifier_predict_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), (mp_obj_t)(&classifier_del_obj) }, + { MP_ROM_QSTR(MP_QSTR_rm_class_img), (mp_obj_t)(&classifier_rm_class_img_obj) }, + { MP_ROM_QSTR(MP_QSTR_rm_sample_img), (mp_obj_t)(&classifier_rm_sample_img_obj) }, + { MP_ROM_QSTR(MP_QSTR_save), (mp_obj_t)(&classifier_save_obj) }, + { MP_ROM_QSTR(MP_QSTR_load), (mp_obj_t)(&classifier_load_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(locals_dict, locals_dict_table); + +const mp_obj_type_t Maix_kpu_classifier_type = { + .base = { &mp_type_type }, + .name = MP_QSTR_classifier, + .make_new = maix_kpu_classifier_make_new, + .locals_dict = (mp_obj_dict_t*)&locals_dict +}; + diff --git a/src/声源定位代码/mic-code/Maix/Maix_mic_array.c b/src/声源定位代码/mic-code/Maix/Maix_mic_array.c new file mode 100644 index 0000000..12ac21b --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/Maix_mic_array.c @@ -0,0 +1,286 @@ +#include +#include + +#include "py/obj.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "py/objarray.h" +#include "py/binary.h" +#include "py_assert.h" +#include "mperrno.h" +#include "mphalport.h" +#include "modMaix.h" +#include "imlib.h" + +#include "sleep.h" +#include "lcd.h" +#include "sysctl.h" +#include "fpioa.h" +#include "lib_mic.h" +#include "sipeed_sk9822.h" +#include "py_image.h" + +#define PLL2_OUTPUT_FREQ 45158400UL + +STATIC uint16_t colormap_parula[64] = { + 0x3935, 0x4156, 0x4178, 0x4199, 0x41ba, 0x41db, 0x421c, 0x423d, + 0x4a7e, 0x429e, 0x42df, 0x42ff, 0x431f, 0x435f, 0x3b7f, 0x3bbf, + 0x33ff, 0x2c1f, 0x2c3e, 0x2c7e, 0x2c9d, 0x24bd, 0x24dd, 0x251c, + 0x1d3c, 0x1d5c, 0x1d7b, 0x159a, 0x05ba, 0x05d9, 0x05d8, 0x0df7, + 0x1e16, 0x2615, 0x2e34, 0x3634, 0x3652, 0x3e51, 0x4e70, 0x566f, + 0x666d, 0x766c, 0x866b, 0x8e49, 0x9e48, 0xae27, 0xbe26, 0xc605, + 0xd5e4, 0xdde5, 0xe5c5, 0xf5c6, 0xfdc7, 0xfde7, 0xfe27, 0xfe46, + 0xfe86, 0xfea5, 0xf6e5, 0xf704, 0xf744, 0xf764, 0xffa3, 0xffc2}; + +STATIC uint16_t colormap_parula_rect[64][14 * 14] __attribute__((aligned(128))); + +STATIC int init_colormap_parula_rect() +{ + for (uint32_t i = 0; i < 64; i++) + { + for (uint32_t j = 0; j < 14 * 14; j++) + { + colormap_parula_rect[i][j] = colormap_parula[i]; + } + } + return 0; +} +STATIC uint8_t lib_init_flag = 0; + +STATIC volatile uint8_t mic_done = 0; +STATIC uint8_t thermal_map_data[256]; + +STATIC void lib_mic_cb(void) +{ + mic_done = 1; +} + +STATIC mp_obj_t Maix_mic_array_init(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + enum { + ARG_i2s_d0, + ARG_i2s_d1, + ARG_i2s_d2, + ARG_i2s_d3, + ARG_i2s_ws, + ARG_i2s_sclk, + + ARG_sk9822_dat, + ARG_sk9822_clk, + }; + + // sysctl_pll_set_freq(SYSCTL_PLL2, PLL2_OUTPUT_FREQ); //如果使用i2s,必须设置PLL2 + static const mp_arg_t allowed_args[]={ + {MP_QSTR_i2s_d0, MP_ARG_INT, {.u_int = 23}}, + {MP_QSTR_i2s_d1, MP_ARG_INT, {.u_int = 22}}, + {MP_QSTR_i2s_d2, MP_ARG_INT, {.u_int = 21}}, + {MP_QSTR_i2s_d3, MP_ARG_INT, {.u_int = 20}}, + {MP_QSTR_i2s_ws, MP_ARG_INT, {.u_int = 19}}, + {MP_QSTR_i2s_sclk, MP_ARG_INT, {.u_int = 18}}, + + {MP_QSTR_sk9822_dat, MP_ARG_INT, {.u_int = 24}}, + {MP_QSTR_sk9822_clk, MP_ARG_INT, {.u_int = 25}}, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + //evil code + fpioa_set_function(args[ARG_i2s_d0].u_int, FUNC_I2S0_IN_D0); + fpioa_set_function(args[ARG_i2s_d1].u_int, FUNC_I2S0_IN_D1); + fpioa_set_function(args[ARG_i2s_d2].u_int, FUNC_I2S0_IN_D2); + fpioa_set_function(args[ARG_i2s_d3].u_int, FUNC_I2S0_IN_D3); + fpioa_set_function(args[ARG_i2s_ws].u_int, FUNC_I2S0_WS); + fpioa_set_function(args[ARG_i2s_sclk].u_int, FUNC_I2S0_SCLK); + //TODO: optimize Soft SPI + fpioa_set_function(args[ARG_sk9822_dat].u_int, FUNC_GPIOHS0 + SK9822_DAT_GPIONUM); + fpioa_set_function(args[ARG_sk9822_clk].u_int, FUNC_GPIOHS0 + SK9822_CLK_GPIONUM); + + // init_colormap_parula_rect(); + sipeed_init_mic_array_led(); + + int ret = lib_mic_init(DMAC_CHANNEL4, lib_mic_cb, thermal_map_data); + if(ret != 0) + { + char tmp[64]; + sprintf(tmp,"lib_mic init error with %d",ret); + mp_raise_ValueError((const char*)tmp); + return mp_const_false; + } + lib_init_flag = 1; + // sysctl_enable_irq(); + return mp_const_true; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(Maix_mic_array_init_obj, 0, Maix_mic_array_init); + +STATIC mp_obj_t Maix_mic_array_deinit(void) +{ + if(lib_init_flag) + { + lib_mic_deinit(); + lib_init_flag = 0; + } + return mp_const_true; +} + +MP_DEFINE_CONST_FUN_OBJ_0(Maix_mic_array_deinit_obj, Maix_mic_array_deinit); + +STATIC mp_obj_t Maix_mic_array_get_map(void) +{ + image_t out; + + out.w = 16; + out.h = 16; + out.bpp = IMAGE_BPP_GRAYSCALE; + out.data = xalloc(256); + mic_done = 0; + + volatile uint8_t retry = 100; + + while(mic_done == 0) + { + retry--; + msleep(1); + } + + if(mic_done == 0 && retry == 0) + { + xfree(out.data); + mp_raise_OSError(MP_ETIMEDOUT); + return mp_const_false; + } + + memcpy(out.data, thermal_map_data, 256); + + return py_image_from_struct(&out); +} + +MP_DEFINE_CONST_FUN_OBJ_0(Maix_mic_array_get_map_obj, Maix_mic_array_get_map); + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +STATIC uint8_t voice_strength_len[12] = {14, 20, 14, 14, 20, 14, 14, 20, 14, 14, 20, 14}; + +//voice strength, to calc direction +STATIC uint8_t voice_strength[12][32] = { + {197, 198, 199, 213, 214, 215, 228, 229, 230, 231, 244, 245, 246, 247}, //14 + {178, 179, 192, 193, 194, 195, 196, 208, 209, 210, 211, 212, 224, 225, 226, 227, 240, 241, 242, 243}, //20 + {128, 129, 130, 131, 144, 145, 146, 147, 160, 161, 162, 163, 176, 177}, + {64, 65, 80, 81, 82, 83, 96, 97, 98, 99, 112, 113, 114, 115}, + {0, 1, 2, 3, 16, 17, 18, 19, 32, 33, 34, 35, 36, 48, 49, 50, 51, 52, 66, 67}, + {4, 5, 6, 7, 20, 21, 22, 23, 37, 38, 39, 53, 54, 55}, + {8, 9, 10, 11, 24, 25, 26, 27, 40, 41, 42, 56, 57, 58}, + {12, 13, 14, 15, 28, 29, 30, 31, 43, 44, 45, 46, 47, 59, 60, 61, 62, 63, 76, 77}, + {78, 79, 92, 93, 94, 95, 108, 109, 110, 111, 124, 125, 126, 127}, + {140, 141, 142, 143, 156, 157, 158, 159, 173, 172, 174, 175, 190, 191}, + {188, 189, 203, 204, 205, 206, 207, 219, 220, 221, 222, 223, 236, 237, 238, 239, 252, 253, 254, 255}, + {200, 201, 202, 216, 217, 218, 232, 233, 234, 235, 248, 249, 250, 251}, +}; + +STATIC void calc_voice_strength(uint8_t *voice_data, uint8_t *led_brightness) +{ + uint32_t tmp_sum[12] = {0}; + uint8_t i, index, tmp; + + for (index = 0; index < 12; index++) + { + tmp_sum[index] = 0; + for (i = 0; i < voice_strength_len[index]; i++) + { + tmp_sum[index] += voice_data[voice_strength[index][i]]; + } + tmp = (uint8_t)tmp_sum[index] / voice_strength_len[index]; + led_brightness[index] = tmp > 15 ? 15 : tmp; + } +} + +STATIC mp_obj_t Maix_mic_array_get_dir(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + uint8_t led_brightness[12]={0}; + + image_t *arg_img = py_image_cobj(pos_args[0]); + PY_ASSERT_TRUE_MSG(IM_IS_MUTABLE(arg_img), "Image format is not supported."); + + if(arg_img->w!=16 || arg_img->h!=16 || arg_img->bpp!=IMAGE_BPP_GRAYSCALE) + { + mp_raise_ValueError("image type error, only support 16*16 grayscale image"); + return mp_const_false; + } + + calc_voice_strength(arg_img->data, led_brightness); + + mp_obj_t *tuple, *tmp; + + tmp = (mp_obj_t *)malloc(12 * sizeof(mp_obj_t)); + + for (uint8_t index = 0; index < 12; index++) + tmp[index] = mp_obj_new_int(led_brightness[index]); + + tuple = mp_obj_new_tuple(12, tmp); + + free(tmp); + + return tuple; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(Maix_mic_array_get_dir_obj, 1, Maix_mic_array_get_dir); + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +STATIC mp_obj_t Maix_mic_array_set_led(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + int index, brightness[12] = {0}, led_color[12] = {0}, color[3] = {0}; + + mp_obj_t *items; + mp_obj_get_array_fixed_n(pos_args[0], 12, &items); + + for(index= 0; index < 12; index++) + brightness[index] = mp_obj_get_int(items[index]); + + mp_obj_get_array_fixed_n(pos_args[1], 3, &items); + for(index = 0; index < 3; index++) + color[index] = mp_obj_get_int(items[index]); + + //rgb + uint32_t set_color = (color[2] << 16) | (color[1] << 8) | (color[0]); + + for (index = 0; index < 12; index++) + { + led_color[index] = (brightness[index] / 2) > 1 ? (((0xe0 | (brightness[index] * 2)) << 24) | set_color) : 0xe0000000; + } + +//FIXME close irq? +sysctl_disable_irq(); + sk9822_start_frame(); + for (index = 0; index < 12; index++) + { + sk9822_send_data(led_color[index]); + } + sk9822_stop_frame(); + +sysctl_enable_irq(); + + return mp_const_true; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(Maix_mic_array_set_led_obj, 2, Maix_mic_array_set_led); + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +STATIC const mp_rom_map_elem_t Maix_mic_array_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&Maix_mic_array_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&Maix_mic_array_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_dir), MP_ROM_PTR(&Maix_mic_array_get_dir_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_led), MP_ROM_PTR(&Maix_mic_array_set_led_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_map), MP_ROM_PTR(&Maix_mic_array_get_map_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(Maix_mic_array_dict, Maix_mic_array_locals_dict_table); + +const mp_obj_type_t Maix_mic_array_type = { + { &mp_type_type }, + .name = MP_QSTR_MIC_ARRAY, + .locals_dict = (mp_obj_dict_t*)&Maix_mic_array_dict, +}; diff --git a/src/声源定位代码/mic-code/Maix/Maix_utils.c b/src/声源定位代码/mic-code/Maix/Maix_utils.c new file mode 100644 index 0000000..bc0cf2e --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/Maix_utils.c @@ -0,0 +1,86 @@ +#include "py/obj.h" +#include "py/runtime.h" +#include "py/mperrno.h" +#include "mpconfigboard.h" +#include "stdint.h" +#include "stdbool.h" +#include "stdlib.h" +#include "sipeed_mem.h" +#include "w25qxx.h" + +STATIC mp_obj_t py_gc_heap_size(size_t n_args, const mp_obj_t *args) { + config_data_t config; + load_config_from_spiffs(&config); + if(n_args == 0) + return mp_obj_new_int(config.gc_heap_size); + else if(n_args != 1) + mp_raise_OSError(MP_EINVAL); + config.gc_heap_size = mp_obj_get_int(args[0]); + if( !save_config_to_spiffs(&config) ) + mp_raise_OSError(MP_EIO); + return mp_const_none; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_gc_heap_size_obj, 0, 1, py_gc_heap_size); + +// sys_free(): return the number of bytes of available sys heap RAM +STATIC mp_obj_t py_heap_free(void) { + return MP_OBJ_NEW_SMALL_INT(get_free_heap_size2()); +} +MP_DEFINE_CONST_FUN_OBJ_0(py_heap_free_obj, py_heap_free); + +// STATIC mp_obj_t py_malloc(mp_obj_t arg) { +// void malloc_stats(void); +// malloc_stats(); +// void* p = malloc(mp_obj_get_int(arg)); +// return mp_obj_new_int((mp_int_t)p); +// } + +// STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_malloc_obj, py_malloc); + +// STATIC mp_obj_t py_free(mp_obj_t arg) { +// free(mp_obj_get_int(arg)); +// return mp_const_none; +// } + +// STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_free_obj, py_free); + + +// STATIC mp_obj_t py_flash_write(mp_obj_t addr, mp_obj_t data_in) { +// mp_buffer_info_t bufinfo; +// mp_get_buffer_raise(data_in, &bufinfo, MP_BUFFER_READ); +// w25qxx_status_t status = w25qxx_write_data_dma(mp_obj_get_int(addr), bufinfo.buf, (uint32_t)bufinfo.len); +// return mp_obj_new_int(status); // (status != W25QXX_OK) +// } + +// STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_flash_write_obj, py_flash_write); + +STATIC mp_obj_t py_flash_read(mp_obj_t addr, mp_obj_t len_in) { + size_t length = mp_obj_get_int(len_in); + byte* data = m_new(byte, length); + w25qxx_status_t status = w25qxx_read_data_dma(mp_obj_get_int(addr), data, (uint32_t)length, W25QXX_QUAD_FAST); + if(status != W25QXX_OK) + { + mp_raise_OSError(MP_EIO); + } + return mp_obj_new_bytes(data, length); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_flash_read_obj, py_flash_read); + +static const mp_map_elem_t locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_utils) }, + { MP_ROM_QSTR(MP_QSTR_gc_heap_size), (mp_obj_t)(&py_gc_heap_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_heap_free), (mp_obj_t)(&py_heap_free_obj) }, + // { MP_ROM_QSTR(MP_QSTR_malloc), (mp_obj_t)(&py_malloc_obj) }, + // { MP_ROM_QSTR(MP_QSTR_free), (mp_obj_t)(&py_free_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_read), (mp_obj_t)(&py_flash_read_obj) }, + // { MP_ROM_QSTR(MP_QSTR_flash_write), (mp_obj_t)(&py_flash_write_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(locals_dict, locals_dict_table); + +const mp_obj_type_t Maix_utils_type = { + .base = { &mp_type_type }, + .name = MP_QSTR_utils, + .locals_dict = (mp_obj_dict_t*)&locals_dict +}; \ No newline at end of file diff --git a/src/声源定位代码/mic-code/Maix/include/Maix_config.h b/src/声源定位代码/mic-code/Maix/include/Maix_config.h new file mode 100644 index 0000000..65cfa1a --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/include/Maix_config.h @@ -0,0 +1,5 @@ +#include "obj.h" + +mp_map_elem_t *dict_iter_next(mp_obj_dict_t *dict, size_t *cur); +mp_obj_t maix_config_init(); +mp_obj_t maix_config_get_value(mp_obj_t key, mp_obj_t def_value); diff --git a/src/声源定位代码/mic-code/Maix/include/Maix_i2s.h b/src/声源定位代码/mic-code/Maix/include/Maix_i2s.h new file mode 100644 index 0000000..26db124 --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/include/Maix_i2s.h @@ -0,0 +1,23 @@ +#ifndef MICROPY_MAIX_I2S_H +#define MICROPY_MAIX_I2S_H + +#include "py/obj.h" +#include "i2s.h" +typedef struct _i2s_channle_t{ + i2s_word_length_t resolution; + i2s_word_select_cycles_t cycles; + i2s_work_mode_t align_mode; + i2s_transmit_t mode; +}i2s_channle_t; + +typedef struct _Maix_i2s_obj_t { + mp_obj_base_t base; + i2s_device_number_t i2s_num; + i2s_channle_t channel[4]; + uint32_t sample_rate; + uint32_t points_num; + uint32_t* buf; + i2s_word_select_cycles_t cycles; + uint32_t chn_mask; +} Maix_i2s_obj_t; +#endif \ No newline at end of file diff --git a/src/声源定位代码/mic-code/Maix/include/Maix_kpu.h b/src/声源定位代码/mic-code/Maix/include/Maix_kpu.h new file mode 100644 index 0000000..5805414 --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/include/Maix_kpu.h @@ -0,0 +1,25 @@ +#ifndef __MAIX_KPU_H +#define __MAIX_KPU_H + +#include "py/obj.h" + +typedef struct py_kpu_net_obj +{ + mp_obj_base_t base; + + void* kmodel_ctx; //sipeed_model_ctx_t + mp_obj_t model_size; + mp_obj_t model_addr; + mp_obj_t model_path; + mp_obj_t max_layers; + mp_obj_t net_args; // for yolo2 + mp_obj_t net_deinit; // for yolo2 +} __attribute__((aligned(8))) py_kpu_net_obj_t; + +extern const mp_obj_type_t py_kpu_net_obj_type; + +extern const mp_obj_type_t Maix_kpu_classifier_type; + + +#endif + diff --git a/src/声源定位代码/mic-code/Maix/include/modMaix.h b/src/声源定位代码/mic-code/Maix/include/modMaix.h new file mode 100644 index 0000000..be282db --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/include/modMaix.h @@ -0,0 +1,31 @@ +/* +* Copyright 2019 Sipeed Co.,Ltd. + +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#ifndef MICROPY_INCLUDED_MAIX_MAIX_H +#define MICROPY_INCLUDED_MAIX_MAIX_H + +#include "py/obj.h" +#include "i2s.h" + +extern const mp_obj_type_t Maix_fpioa_type; +extern const mp_obj_type_t Maix_gpio_type; +extern const mp_obj_type_t Maix_i2s_type; +extern const mp_obj_type_t Maix_audio_type; +extern const mp_obj_type_t Maix_fft_type; +extern const mp_obj_type_t Maix_mic_array_type; +extern const mp_obj_type_t cpufreq_type; +extern const mp_obj_type_t Maix_utils_type; +extern const mp_obj_type_t Maix_config_type; +#endif // MICROPY_INCLUDED_MAIX_MAIX_H \ No newline at end of file diff --git a/src/声源定位代码/mic-code/Maix/include/py_cpufreq.h b/src/声源定位代码/mic-code/Maix/include/py_cpufreq.h new file mode 100644 index 0000000..12291a0 --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/include/py_cpufreq.h @@ -0,0 +1,11 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * CPU Frequency module. + * + */ +#ifndef __PY_CPUFREQ_H__ +#define __PY_CPUFREQ_H__ +#endif // __PY_CPUFREQ_H__ diff --git a/src/声源定位代码/mic-code/Maix/modMaix.c b/src/声源定位代码/mic-code/Maix/modMaix.c new file mode 100644 index 0000000..c70666e --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/modMaix.c @@ -0,0 +1,46 @@ +/* +* Copyright 2019 Sipeed Co.,Ltd. + +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include +#include "py/runtime.h" + +#include "modMaix.h" + +STATIC const mp_rom_map_elem_t maix_module_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_machine) }, + { MP_ROM_QSTR(MP_QSTR_FPIOA), MP_ROM_PTR(&Maix_fpioa_type) }, + { MP_ROM_QSTR(MP_QSTR_GPIO), MP_ROM_PTR(&Maix_gpio_type) }, + { MP_ROM_QSTR(MP_QSTR_I2S), MP_ROM_PTR(&Maix_i2s_type) }, + { MP_ROM_QSTR(MP_QSTR_Audio), MP_ROM_PTR(&Maix_audio_type) }, + { MP_ROM_QSTR(MP_QSTR_FFT), MP_ROM_PTR(&Maix_fft_type) }, +#if CONFIG_MAIXPY_MIC_ARRAY_ENABLE + { MP_ROM_QSTR(MP_QSTR_MIC_ARRAY), MP_ROM_PTR(&Maix_mic_array_type) }, +#endif + { MP_ROM_QSTR(MP_QSTR_freq), MP_ROM_PTR(&cpufreq_type) }, + { MP_ROM_QSTR(MP_QSTR_utils), MP_ROM_PTR(&Maix_utils_type) }, + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&Maix_config_type) }, +}; + +STATIC MP_DEFINE_CONST_DICT ( + maix_module_globals, + maix_module_globals_table +); + +const mp_obj_module_t maix_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&maix_module_globals, +}; + diff --git a/src/声源定位代码/mic-code/Maix/py_cpufreq.c b/src/声源定位代码/mic-code/Maix/py_cpufreq.c new file mode 100644 index 0000000..a2d1b7e --- /dev/null +++ b/src/声源定位代码/mic-code/Maix/py_cpufreq.c @@ -0,0 +1,136 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * CPU frequency scaling module. + * + */ + +#include +#include +#include +#include +#include "sysctl.h" +#include "py_cpufreq.h" +#include "py_helper.h" +#include "mpconfigboard.h" +#include "vfs_spiffs.h" +#include "sipeed_sys.h" + + +#define ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0])) +// static const uint32_t kpufreq_freqs[] = {100, 200, 400}; +//static const uint32_t cpufreq_pllq[] = {5, 6, 7, 8, 9}; + +// static const uint32_t cpufreq_latency[] = { // Flash latency (see table 11) +// FLASH_LATENCY_3, FLASH_LATENCY_4, FLASH_LATENCY_5, FLASH_LATENCY_7, FLASH_LATENCY_7 +// }; + +uint32_t cpufreq_get_cpuclk() +{ + uint32_t cpuclk = sysctl_clock_get_freq(SYSCTL_CLOCK_CPU); + + return cpuclk; +} + +mp_obj_t py_cpufreq_get_current_frequencies() +{ + mp_obj_t tuple[2] = { + mp_obj_new_int(cpufreq_get_cpuclk() / (1000000)), + mp_obj_new_int(sysctl_clock_get_freq(SYSCTL_CLOCK_AI) / (1000000)), + }; + return mp_obj_new_tuple(2, tuple); +} + +// mp_obj_t py_kpufreq_get_supported_frequencies() +// { + +// mp_obj_t freq_list = mp_obj_new_list(0, NULL); +// for (int i=0; i FREQ_KPU_MAX || + freq_kpu < FREQ_KPU_MIN) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Unsupported KPU frequency!")); + } + + // Frequency is Not supported. + if ( config.freq_cpu > FREQ_CPU_MAX || + config.freq_cpu < FREQ_CPU_MIN ) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Unsupported CPU frequency!")); + } + + // Return if frequency hasn't changed. + if (( 20 > abs(config.freq_cpu - cpufreq_get_cpuclk())) && ( 20 > abs(freq_kpu - sysctl_clock_get_freq(SYSCTL_CLOCK_AI) ))) { + mp_printf(&mp_plat_print, "No change\r\n"); + return mp_const_none; + } + if(!save_config_to_spiffs(&config)) + mp_printf(&mp_plat_print, "save config fail"); + mp_printf(&mp_plat_print, "\r\nreboot now\r\n"); + mp_hal_delay_ms(50); + sipeed_sys_reset(); + + return mp_const_true; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_cpufreq_set_freq_obj,0, py_cpufreq_set_frequency); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_cpufreq_get_current_freq_obj, py_cpufreq_get_current_frequencies); +// STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_kpufreq_get_supported_frequencies_obj, py_kpufreq_get_supported_frequencies); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_kpufreq_get_kpu_obj, py_kpufreq_get_kpu); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_kpufreq_get_cpu_obj, py_kpufreq_get_cpu); + +static const mp_map_elem_t locals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_freq) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set), (mp_obj_t)&py_cpufreq_set_freq_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get), (mp_obj_t)&py_cpufreq_get_current_freq_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_kpu), (mp_obj_t)&py_kpufreq_get_kpu_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_cpu), (mp_obj_t)&py_kpufreq_get_cpu_obj }, +}; +STATIC MP_DEFINE_CONST_DICT(locals_dict, locals_dict_table); + +const mp_obj_type_t cpufreq_type = { + .base = { &mp_type_type }, + .name = MP_QSTR_freq, + .locals_dict = (mp_obj_t)&locals_dict, +};