☰
使用阿里云回调函数实现远程设备单向喊话
2026/9/26 8:27:52 网站建设 项目流程

前言

在物联网项目中,经常会遇到这样的需求:用户在网页上录制一段语音,然后让远端的嵌入式设备立即播放,实现远程单向喊话。

最直接的做法是浏览器录音后,将音频直接发送给设备。但这种方式要求浏览器和设备保持稳定连接,音频稍大时也容易受到网络波动影响。

本文采用另一种实现方式:

1. 浏览器将录音上传到阿里云 OSS;

2. 上传成功后,由 OSS 服务器回调指定设备;

3. 设备收到 OSS 文件名后下载音频;

4. 将音频转换为 WAV 并播放;

5. 播放完成后删除设备上的临时文件。

这种方案将“文件传输”和“设备通知”分开,浏览器不需要直接把音频传输给嵌入式设备。

一、整体架构

完整的数据链路如下:

浏览器录音

↓

浏览器直接上传音频到阿里云 OSS

↓

OSS 保存文件成功

↓

阿里云 OSS 服务器调用 Callback URL

↓

FRP 将公网请求转发到内网设备

↓

设备收到 OSS 对象名

↓

设备从 OSS 下载音频

↓

转换为 WAV → 播放 → 删除本地临时文件

这里最关键的一点是:

Callback 请求不是浏览器发出的,而是阿里云 OSS 服务器发出的。

因此,即使网页使用 HTTPS,而设备 Callback 暂时使用 HTTP,也不会触发浏览器的混合内容限制。浏览器只负责向 OSS 上传文件。

二、什么是 OSS Callback

OSS Callback 是阿里云 OSS 提供的上传回调机制。

客户端上传文件时,可以同时告诉 OSS:

文件上传成功后回调哪个 URL;

回调请求使用什么 Content-Type;

回调正文中需要包含哪些 OSS 系统变量。

例如:

callback: { url: "http://<公网IP>:6221/wave", body: "audio_name=${object}", contentType: "application/x-www-form-urlencoded", }

其中 `${object}` 是 OSS 的系统变量。上传成功后,OSS 会将它替换为实际对象名。

假设上传的对象名是:

audio/wave/21/1758761234567_xxx.webm

设备实际收到的请求正文就是:

audio_name=audio/wave/21/1758761234567_xxx.web

三、前端录音与 OSS 上传

1. 使用 MediaRecorder 录音

现代浏览器可以通过 `MediaRecorder` API 录制麦克风音频:

const stream = await navigator.mediaDevices.getUserMedia({ audio: { channelCount: 1, sampleRate: 16000, echoCancellation: true, noiseSuppression: true, }, }); const recorder = new MediaRecorder(stream, { mimeType: "audio/webm;codecs=opus", audioBitsPerSecond: 48000, });

录音结束后,将多个音频分片合并为一个 Blob:

const audioBlob = new Blob(chunks, { type: recorder.mimeType || "audio/webm", });

2. 生成 OSS 对象名

为了区分设备和录音,可以将设备编号放进 OSS 对象路径中:

const objectName = `audio/wave/${deviceId}/${Date.now()}_${crypto.randomUUID()}.webm`;

3. 根据设备生成 Callback 地址

如果每台设备在 FRP 上对应一个独立的公网端口,可以制定简单的端口规则:

公网端口 = 6200 + 设备编号

例如:

设备编号公网端口Callback URL
6214192.168.10.1http://8.137.101.41:6214
6215192.168.10.1http://8.137.101.41:6215
function callbackUrlForDevice(deviceId) { const callbackPort = 6200 + Number.parseInt(deviceId, 10); return `http://<公网IP>:${callbackPort}/wave`; }

4. 上传时配置 OSS Callback

使用阿里云 OSS JavaScript SDK 上传:

const client = new OSS({ region: "oss-cn-beijing", endpoint: "oss-cn-beijing.aliyuncs.com", accessKeyId: "<AccessKeyId>", accessKeySecret: "<AccessKeySecret>", bucket: "<Bucket名称>", secure: true, }); await client.put(objectName, audioBlob, { headers: { "Content-Type": audioBlob.type || "audio/webm", }, callback: { url: callbackUrlForDevice(deviceId), body: "audio_name=${object}", contentType: "application/x-www-form-urlencoded", }, });

执行这段代码后,浏览器不需要再调用设备接口。文件上传完成后,OSS 会自动发送 Callback。

四、FRP 将 Callback 转发到内网设备

嵌入式设备通常位于局域网,没有公网 IP,因此可以使用frp做TCP端口映射。

设备端示例配置:

serverAddr = "<FRP服务器公网IP>" serverPort = 7000 [[proxies]] name = "wave-device-21" type = "tcp" localIP = "192.168.10.102" localPort = 8765 remotePort = 6221

此时请求会被转发到设备:

五、Python 接收 Callback

设备端可以使用 FastAPI 提供接口。

示例代码:

from urllib.parse import parse_qs from fastapi import FastAPI, HTTPException, Request app = FastAPI() @app.post("/wave") async def wave(request: Request): raw = await request.body() text = raw.decode("utf-8", errors="replace") values = parse_qs(text, keep_blank_values=True) audio_name = values.get("audio_name", [""])[0] if not audio_name: raise HTTPException(status_code=400, detail="缺少 audio_name") # 将下载和播放任务加入队列 audio_jobs.put(audio_name) return { "status": "queued", "audio_name": audio_name, }

Callback 接口必须及时返回成功响应,不建议在请求中同步等待下载、转换和播放全部完成。更合理的方式是:

接收请求 → 参数校验 → 加入播放队列 → 立即返回 HTTP 200

后台线程再依次处理播放任务。

六、设备下载和播放音频

1. 从 OSS 下载

Python 使用 `oss2` 下载对象:

import oss2 auth = oss2.Auth("<AccessKeyId>", "<AccessKeySecret>") bucket = oss2.Bucket( auth, "https://oss-cn-beijing.aliyuncs.com", "<Bucket名称>", ) bucket.get_object_to_file(audio_name, local_file)

2. 转换为 WAV

浏览器通常录制为 WebM/Opus,而嵌入式设备上的 `aplay` 更适合播放 WAV,因此可以使用 `pydub + ffmpeg` 转换:

from pydub import AudioSegment audio = AudioSegment.from_file(local_file, format="webm") audio = audio.set_frame_rate(16000) audio = audio.set_channels(2) audio = audio.set_sample_width(2) audio.export(wav_file, format="wav", codec="pcm_s16le") ``` ### 3. 播放并清理 ```python import subprocess subprocess.run( ["aplay", "-Dhw:1", wav_file], check=True, )

七、安全建议

为了方便演示,很多示例会把 OSS AccessKey 直接写在前端代码中,但这不适合生产环境。

因为前端 JavaScript 会下载到用户浏览器,任何人都能在开发者工具中看到长期 AccessKey。一旦泄露,攻击者可能读取、上传甚至删除 Bucket 中的数据。

生产环境建议:

1. 创建权限最小化的 RAM 用户;

2. 后端签发 STS 临时凭证;

3. 前端只使用短期 STS Token 上传;

4. 限制只能操作指定的 OSS 前缀;

5. 定期轮换 AccessKey;

6. 对 `/wave` 增加 OSS Callback 签名校验,防止伪造播放请求;

7. 限制音频大小、格式和播放时长;

8. 为 OSS 音频目录配置生命周期自动清理规则。

八、总结

本文实现了一套基于阿里云 OSS Callback 的远程单向喊话方案:

浏览器负责录音和上传

OSS 负责保存文件并通知设备

FRP 负责公网到内网的请求转发

设备负责下载、转换和播放

相比浏览器直接向设备传输音频,这种方式降低了设备接口的传输压力,也不需要维持长连接。通过设备编号与 FRP 公网端口的映射,还可以将同一套前端扩展到多台设备。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询