diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml
new file mode 100644
index 0000000..043447c
--- /dev/null
+++ b/.github/workflows/docker-build.yml
@@ -0,0 +1,56 @@
+name: Build and Push Docker Image
+
+on:
+ push:
+ branches: [ main ]
+ tags:
+ - 'v*'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ docker:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Extract metadata
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: gmij/audio3a
+ tags: |
+ type=ref,event=branch
+ type=ref,event=pr
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=semver,pattern={{major}}
+ type=sha,prefix={{branch}}-
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./samples/Audio3A.WebApi/Dockerfile
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+
+ - name: Image digest
+ run: echo ${{ steps.docker_build.outputs.digest }}
diff --git a/docs/AUDIO_RECORDING.md b/docs/AUDIO_RECORDING.md
new file mode 100644
index 0000000..c224bea
--- /dev/null
+++ b/docs/AUDIO_RECORDING.md
@@ -0,0 +1,296 @@
+# 音频录制和下载功能说明
+
+## 概述
+
+在通话界面新增了音频录制和下载功能,用户可以录制整个通话过程,并分别下载原声和经过 3A 处理后的音频。
+
+## 功能特性
+
+### 录制功能
+- ✅ 实时录制通话音频
+- ✅ 同时录制原声和处理后音频
+- ✅ 可随时开始/停止录制
+- ✅ 录制状态可视化(脉冲动画)
+
+### 下载功能
+- ✅ 下载原声音频(麦克风直接输入)
+- ✅ 下载净化后音频(经过 3A 处理)
+- ✅ 自动生成带时间戳的文件名
+- ✅ WebM 格式(Opus 编解码器)
+
+## 使用指南
+
+### 1. 进入通话
+
+首先创建或加入一个房间,进入通话界面。
+
+### 2. 开始录制
+
+点击控制栏中的**录制按钮**(摄像机图标):
+- 按钮会变为红色
+- 显示脉冲动画表示正在录制
+- 此时会同时录制两路音频流
+
+### 3. 停止录制
+
+再次点击录制按钮停止录制:
+- 按钮恢复正常颜色
+- 动画停止
+- 录制的音频已保存在浏览器内存中
+
+### 4. 下载音频
+
+点击**下载按钮**(向下箭头图标),从下拉菜单选择:
+
+**选项 1:下载原声音频**
+- 文件名:`input-audio-YYYYMMDD-HHmmss.webm`
+- 内容:麦克风直接采集的原始音频
+- 包含:环境噪声、回声、音量波动等
+
+**选项 2:下载净化后音频**
+- 文件名:`processed-audio-YYYYMMDD-HHmmss.webm`
+- 内容:经过 3A 处理的音频
+- 效果:回声消除、噪声抑制、自动增益控制
+
+## 界面说明
+
+### 控制按钮布局
+
+通话界面底部控制栏(从左到右):
+
+1. **静音按钮** 🎤
+ - 功能:切换麦克风静音
+ - 状态:静音时显示红色
+
+2. **录制按钮** 📹
+ - 功能:开始/停止录制
+ - 状态:录制中显示红色脉冲动画
+
+3. **挂断按钮** 📞
+ - 功能:结束通话
+ - 样式:红色大按钮
+
+4. **下载按钮** ⬇️
+ - 功能:下载录制的音频
+ - 状态:未录制时禁用(灰色)
+
+5. **设置按钮** ⚙️
+ - 功能:预留(暂未实现)
+ - 状态:禁用
+
+### 视觉反馈
+
+**录制中**:
+- 录制按钮背景变红
+- 脉冲动画(淡入淡出效果)
+- 清晰提示正在录制状态
+
+**录制完成**:
+- 下载按钮从禁用变为可用
+- 可以重复录制(会覆盖之前的录音)
+
+## 技术实现
+
+### 架构
+
+```
+麦克风输入
+ ↓
+Web Audio API
+ ├─→ 原始音频流 → MediaRecorder → 原声录音
+ └─→ 3A 处理 → MediaStreamDestination → MediaRecorder → 净化后录音
+```
+
+### 使用的 API
+
+1. **MediaRecorder API**
+ - 用途:录制音频流
+ - 编码:audio/webm (Opus codec)
+ - 参数:每 100ms 收集一次数据
+
+2. **Web Audio API**
+ - 用途:创建音频处理图
+ - ScriptProcessor:处理音频数据
+ - MediaStreamDestination:导出处理后的音频流
+
+3. **JavaScript Interop**
+ - Blazor 与 JavaScript 通信
+ - 控制录制状态
+ - 触发下载操作
+
+### 文件格式
+
+- **格式**:WebM
+- **编解码器**:Opus
+- **采样率**:取决于浏览器和麦克风设置
+- **声道**:单声道(Mono)
+- **比特率**:自动调整
+
+## 浏览器兼容性
+
+### 支持的浏览器
+
+| 浏览器 | 版本要求 | MediaRecorder | Web Audio API |
+|--------|---------|---------------|---------------|
+| Chrome | 49+ | ✅ | ✅ |
+| Edge | 79+ | ✅ | ✅ |
+| Firefox | 25+ | ✅ | ✅ |
+| Safari | 14.1+ | ✅ | ✅ |
+| Opera | 36+ | ✅ | ✅ |
+
+### 已知限制
+
+1. **iOS Safari**
+ - 需要用户手势才能开始录制
+ - WebM 支持可能受限(Safari 14.1+)
+
+2. **移动浏览器**
+ - 可能需要额外的权限请求
+ - 某些设备可能不支持 Opus 编码
+
+3. **隐私模式**
+ - 某些浏览器在隐私模式下可能限制音频录制
+
+## 使用场景
+
+### 1. 会议记录
+- 录制完整会议内容
+- 对比 3A 处理效果
+- 事后回顾讨论内容
+
+### 2. 音频质量测试
+- 测试 3A 算法效果
+- 对比原声和处理后的差异
+- 优化音频处理参数
+
+### 3. 培训和演示
+- 录制演示内容
+- 提供清晰的音频材料
+- 展示 3A 处理能力
+
+### 4. 故障诊断
+- 记录音频问题
+- 分析噪声来源
+- 测试不同环境下的表现
+
+## 性能考虑
+
+### 内存使用
+
+- 录制时音频数据存储在浏览器内存
+- 长时间录制会占用较多内存
+- 建议录制时长不超过 30 分钟
+
+### 文件大小
+
+估算公式(Opus @ 48kHz):
+- 比特率:约 24-32 kbps(单声道)
+- 1 分钟:约 180-240 KB
+- 10 分钟:约 1.8-2.4 MB
+- 30 分钟:约 5.4-7.2 MB
+
+### 优化建议
+
+1. **定期下载**
+ - 长时间通话时分段录制
+ - 及时下载释放内存
+
+2. **监控内存**
+ - 注意浏览器内存警告
+ - 必要时停止录制
+
+3. **网络影响**
+ - 录制在本地进行,不占用网络
+ - 下载也是本地操作
+
+## 故障排查
+
+### 无法开始录制
+
+**症状**:点击录制按钮无反应
+
+**解决方法**:
+1. 检查麦克风权限是否已授予
+2. 确认已成功进入通话
+3. 查看浏览器控制台错误信息
+4. 尝试刷新页面重新进入
+
+### 下载按钮禁用
+
+**症状**:无法点击下载按钮
+
+**原因**:未进行过录制
+
+**解决方法**:
+1. 先点击录制按钮开始录制
+2. 等待一段时间
+3. 停止录制
+4. 下载按钮会变为可用
+
+### 下载的文件无法播放
+
+**症状**:下载的 WebM 文件播放失败
+
+**解决方法**:
+1. 使用支持 WebM 的播放器(VLC、Chrome)
+2. 转换为 MP3 格式(使用 FFmpeg):
+ ```bash
+ ffmpeg -i input-audio.webm -codec:a libmp3lame output.mp3
+ ```
+3. 检查文件大小(如果为 0 说明录制失败)
+
+### 音频质量问题
+
+**症状**:录制的音频质量差
+
+**解决方法**:
+1. 检查麦克风质量
+2. 确保良好的网络环境
+3. 启用 3A 处理功能
+4. 调整麦克风音量和位置
+
+## 隐私和安全
+
+### 数据存储
+
+- ✅ 所有录制在浏览器本地完成
+- ✅ 音频数据不会上传到服务器
+- ✅ 下载后立即从内存清除
+- ✅ 关闭页面会丢失未下载的录音
+
+### 权限要求
+
+- 麦克风访问权限
+- 文件下载权限(某些浏览器)
+
+### 最佳实践
+
+1. **告知参与者**
+ - 在开始录制前通知其他参与者
+ - 遵守隐私法规(如 GDPR)
+
+2. **安全存储**
+ - 下载后的文件妥善保管
+ - 考虑加密敏感内容
+
+3. **及时删除**
+ - 不需要的录音及时删除
+ - 避免长期保存敏感对话
+
+## 未来改进
+
+计划中的功能:
+
+- [ ] 支持更多音频格式(MP3、AAC)
+- [ ] 云端存储集成
+- [ ] 自动分段录制
+- [ ] 录音质量设置
+- [ ] 音频编辑功能
+- [ ] 转录服务集成
+
+## 参考资料
+
+- [MediaRecorder API](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder)
+- [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API)
+- [WebM 格式规范](https://www.webmproject.org/)
+- [Opus 编解码器](https://opus-codec.org/)
diff --git a/docs/DOCKER.md b/docs/DOCKER.md
new file mode 100644
index 0000000..cec77c6
--- /dev/null
+++ b/docs/DOCKER.md
@@ -0,0 +1,227 @@
+# Docker 部署指南
+
+本文档介绍如何使用 Docker 部署 Audio3A WebAPI 服务。
+
+## 快速开始
+
+### 使用预构建镜像
+
+从 Docker Hub 拉取并运行:
+
+```bash
+docker pull gmij/audio3a:latest
+docker run -d -p 8080:80 --name audio3a-api gmij/audio3a:latest
+```
+
+访问 `http://localhost:8080/swagger` 查看 API 文档。
+
+### 本地构建
+
+从源码构建镜像:
+
+```bash
+# 在项目根目录执行
+docker build -t audio3a-api -f samples/Audio3A.WebApi/Dockerfile .
+
+# 运行容器
+docker run -d -p 8080:80 --name audio3a-api audio3a-api
+```
+
+## 环境变量配置
+
+可以通过环境变量配置服务:
+
+```bash
+docker run -d -p 8080:80 \
+ -e ASPNETCORE_ENVIRONMENT=Production \
+ -e ASPNETCORE_URLS=http://+:80 \
+ --name audio3a-api \
+ gmij/audio3a:latest
+```
+
+### 常用环境变量
+
+| 变量名 | 默认值 | 说明 |
+|--------|--------|------|
+| `ASPNETCORE_ENVIRONMENT` | Production | 运行环境(Development/Production) |
+| `ASPNETCORE_URLS` | http://+:80 | 监听地址和端口 |
+
+## Docker Compose
+
+使用 Docker Compose 部署完整服务栈:
+
+```yaml
+version: '3.8'
+
+services:
+ audio3a-api:
+ image: gmij/audio3a:latest
+ ports:
+ - "8080:80"
+ environment:
+ - ASPNETCORE_ENVIRONMENT=Production
+ restart: unless-stopped
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost/swagger"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+```
+
+保存为 `docker-compose.yml` 后运行:
+
+```bash
+docker-compose up -d
+```
+
+## 持久化数据
+
+如果需要持久化数据,可以挂载卷:
+
+```bash
+docker run -d -p 8080:80 \
+ -v audio3a-data:/app/data \
+ --name audio3a-api \
+ gmij/audio3a:latest
+```
+
+## 健康检查
+
+检查容器状态:
+
+```bash
+# 查看容器日志
+docker logs audio3a-api
+
+# 检查容器状态
+docker ps | grep audio3a-api
+
+# 测试 API 可用性
+curl http://localhost:8080/api/rooms/stats
+```
+
+## 生产环境部署
+
+### 使用 HTTPS
+
+推荐使用反向代理(如 Nginx 或 Traefik)提供 HTTPS:
+
+```nginx
+server {
+ listen 443 ssl http2;
+ server_name api.audio3a.example.com;
+
+ ssl_certificate /path/to/cert.pem;
+ ssl_certificate_key /path/to/key.pem;
+
+ location / {
+ proxy_pass http://localhost:8080;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+}
+```
+
+### 资源限制
+
+限制容器资源使用:
+
+```bash
+docker run -d \
+ -p 8080:80 \
+ --memory="512m" \
+ --cpus="1.0" \
+ --name audio3a-api \
+ gmij/audio3a:latest
+```
+
+### 自动重启
+
+设置容器自动重启策略:
+
+```bash
+docker run -d \
+ -p 8080:80 \
+ --restart=unless-stopped \
+ --name audio3a-api \
+ gmij/audio3a:latest
+```
+
+## 故障排查
+
+### 容器无法启动
+
+```bash
+# 查看详细日志
+docker logs --tail 100 audio3a-api
+
+# 进入容器检查
+docker exec -it audio3a-api /bin/bash
+```
+
+### 端口冲突
+
+如果 8080 端口被占用,更改映射端口:
+
+```bash
+docker run -d -p 9090:80 --name audio3a-api gmij/audio3a:latest
+```
+
+### 性能问题
+
+监控容器资源使用:
+
+```bash
+docker stats audio3a-api
+```
+
+## 镜像标签说明
+
+| 标签 | 说明 |
+|------|------|
+| `latest` | main 分支最新版本 |
+| `v1.0.0` | 特定版本号 |
+| `main-abc1234` | 分支名+提交 SHA |
+
+## 清理
+
+停止并删除容器:
+
+```bash
+docker stop audio3a-api
+docker rm audio3a-api
+```
+
+删除镜像:
+
+```bash
+docker rmi gmij/audio3a:latest
+```
+
+清理未使用的镜像和卷:
+
+```bash
+docker system prune -a
+```
+
+## CI/CD 集成
+
+GitHub Actions 会自动构建和推送镜像到 Docker Hub。
+
+**触发条件**:
+- 推送到 main 分支
+- 创建版本标签(如 `v1.0.0`)
+- 手动触发 workflow
+
+**所需配置**:
+在 GitHub Repository Settings → Secrets 中添加:
+- `DOCKERHUB_USERNAME` - Docker Hub 用户名
+- `DOCKERHUB_TOKEN` - Docker Hub 访问令牌
+
+## 参考资料
+
+- [Docker 官方文档](https://docs.docker.com/)
+- [ASP.NET Core Docker 部署](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/)
+- [Docker Compose 文档](https://docs.docker.com/compose/)
diff --git a/docs/NEW_FEATURES.md b/docs/NEW_FEATURES.md
new file mode 100644
index 0000000..dad8a10
--- /dev/null
+++ b/docs/NEW_FEATURES.md
@@ -0,0 +1,74 @@
+# 新增功能说明
+
+## 概述
+
+本次更新实现了两个主要功能:
+
+1. **服务端房间管理集成** - 将 Web 前端从浏览器内存模拟切换到真实的 WebAPI 后端
+2. **实时波形可视化** - 在通话界面显示输入音频和 3A 处理后的波形图
+
+## 1. 服务端房间管理
+
+### 功能描述
+
+之前的 Web 应用使用 `MockApiService` 在浏览器内存中模拟房间管理功能,现在已经升级支持连接真实的 WebAPI 后端服务。
+
+### 配置方式
+
+#### appsettings.json
+
+```json
+{
+ "ApiBaseUrl": "https://localhost:7063",
+ "UseMockApi": false
+}
+```
+
+**配置项说明**:
+- `ApiBaseUrl`: WebAPI 后端地址
+- `UseMockApi`:
+ - `false` - 使用真实 API(连接服务端,默认)
+ - `true` - 使用 Mock API(浏览器内存,用于 GitHub Pages)
+
+## 2. 实时波形可视化
+
+### 功能描述
+
+在语音通话界面显示两个实时波形图:
+- **输入音频波形**(蓝色)- 显示从麦克风采集的原始音频
+- **3A 处理后波形**(绿色)- 显示经过回声消除、增益控制、噪声抑制后的音频
+
+### 使用效果
+
+1. **启动通话**:点击"开始通话"按钮
+2. **授权麦克风**:浏览器会请求麦克风权限
+3. **查看波形**:
+ - 上方显示输入音频的实时波形(蓝色)
+ - 下方显示 3A 处理后的波形(绿色)
+4. **静音功能**:点击静音按钮时,波形停止更新
+
+## 部署说明
+
+### 开发环境(本地测试)
+
+1. 修改 `samples/Audio3A.Web/wwwroot/appsettings.json`:
+ ```json
+ {
+ "ApiBaseUrl": "https://localhost:7063",
+ "UseMockApi": false
+ }
+ ```
+
+2. 启动服务:
+ ```bash
+ # 终端 1
+ cd samples/Audio3A.WebApi
+ dotnet run
+
+ # 终端 2
+ cd samples/Audio3A.Web
+ dotnet run
+ ```
+
+3. 访问 `https://localhost:5001`
+
diff --git a/samples/Audio3A.Web/Components/WaveformVisualizer.razor b/samples/Audio3A.Web/Components/WaveformVisualizer.razor
new file mode 100644
index 0000000..5ed04b8
--- /dev/null
+++ b/samples/Audio3A.Web/Components/WaveformVisualizer.razor
@@ -0,0 +1,121 @@
+@using Microsoft.JSInterop
+
+
+
+
+
+@code {
+ [Parameter]
+ public int Width { get; set; } = 800;
+
+ [Parameter]
+ public int Height { get; set; } = 120;
+
+ [Parameter]
+ public string? Label { get; set; }
+
+ [Parameter]
+ public string Color { get; set; } = "#52c41a";
+
+ [Parameter]
+ public string BackgroundColor { get; set; } = "rgba(0, 0, 0, 0.5)";
+
+ [Inject]
+ private IJSRuntime JSRuntime { get; set; } = null!;
+
+ private ElementReference _canvasRef;
+ private IJSObjectReference? _module;
+ private DotNetObjectReference? _objRef;
+
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+ if (firstRender)
+ {
+ try
+ {
+ _objRef = DotNetObjectReference.Create(this);
+ _module = await JSRuntime.InvokeAsync("import", "./js/waveform.js");
+ await _module.InvokeVoidAsync("initWaveform", _canvasRef, Width, Height, Color, BackgroundColor);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to initialize waveform: {ex.Message}");
+ }
+ }
+ }
+
+ ///
+ /// 更新波形数据
+ ///
+ public async Task UpdateWaveformAsync(float[] data)
+ {
+ if (_module != null)
+ {
+ try
+ {
+ await _module.InvokeVoidAsync("updateWaveform", _canvasRef, data);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to update waveform: {ex.Message}");
+ }
+ }
+ }
+
+ ///
+ /// 更新波形数据(byte array)
+ ///
+ public async Task UpdateWaveformAsync(byte[] data)
+ {
+ if (_module != null)
+ {
+ try
+ {
+ await _module.InvokeVoidAsync("updateWaveformFromBytes", _canvasRef, data);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to update waveform from bytes: {ex.Message}");
+ }
+ }
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (_module != null)
+ {
+ await _module.DisposeAsync();
+ }
+ _objRef?.Dispose();
+ }
+}
diff --git a/samples/Audio3A.Web/Pages/Call.razor b/samples/Audio3A.Web/Pages/Call.razor
index 60283c5..ba3e8e5 100644
--- a/samples/Audio3A.Web/Pages/Call.razor
+++ b/samples/Audio3A.Web/Pages/Call.razor
@@ -1,5 +1,6 @@
@page "/call/{RoomId}/{ParticipantId}"
-@inject MockApiService MockApi
+@using Audio3A.Web.Components
+@inject IApiService ApiService
@inject AudioCallService AudioService
@inject NavigationManager Navigation
@inject IMessageService Message
@@ -25,6 +26,26 @@
+
+
+
@@ -61,6 +82,14 @@
+
+
+
+
+
+
+
+
+
+
+