
前言在物联网项目中经常会遇到这样的需求用户在网页上录制一段语音然后让远端的嵌入式设备立即播放实现远程单向喊话。最直接的做法是浏览器录音后将音频直接发送给设备。但这种方式要求浏览器和设备保持稳定连接音频稍大时也容易受到网络波动影响。本文采用另一种实现方式1. 浏览器将录音上传到阿里云 OSS2. 上传成功后由 OSS 服务器回调指定设备3. 设备收到 OSS 文件名后下载音频4. 将音频转换为 WAV 并播放5. 播放完成后删除设备上的临时文件。这种方案将“文件传输”和“设备通知”分开浏览器不需要直接把音频传输给嵌入式设备。一、整体架构完整的数据链路如下浏览器录音↓浏览器直接上传音频到阿里云 OSS↓OSS 保存文件成功↓阿里云 OSS 服务器调用 Callback URL↓FRP 将公网请求转发到内网设备↓设备收到 OSS 对象名↓设备从 OSS 下载音频↓转换为 WAV → 播放 → 删除本地临时文件这里最关键的一点是Callback 请求不是浏览器发出的而是阿里云 OSS 服务器发出的。因此即使网页使用 HTTPS而设备 Callback 暂时使用 HTTP也不会触发浏览器的混合内容限制。浏览器只负责向 OSS 上传文件。二、什么是 OSS CallbackOSS 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_nameaudio/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;codecsopus, audioBitsPerSecond: 48000, });录音结束后将多个音频分片合并为一个 Blobconst 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 URL6214192.168.10.1http://8.137.101.41:62146215192.168.10.1http://8.137.101.41:6215function 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, errorsreplace) values parse_qs(text, keep_blank_valuesTrue) audio_name values.get(audio_name, [])[0] if not audio_name: raise HTTPException(status_code400, 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, formatwebm) audio audio.set_frame_rate(16000) audio audio.set_channels(2) audio audio.set_sample_width(2) audio.export(wav_file, formatwav, codecpcm_s16le) ### 3. 播放并清理 python import subprocess subprocess.run( [aplay, -Dhw:1, wav_file], checkTrue, )七、安全建议为了方便演示很多示例会把 OSS AccessKey 直接写在前端代码中但这不适合生产环境。因为前端 JavaScript 会下载到用户浏览器任何人都能在开发者工具中看到长期 AccessKey。一旦泄露攻击者可能读取、上传甚至删除 Bucket 中的数据。生产环境建议1. 创建权限最小化的 RAM 用户2. 后端签发 STS 临时凭证3. 前端只使用短期 STS Token 上传4. 限制只能操作指定的 OSS 前缀5. 定期轮换 AccessKey6. 对 /wave 增加 OSS Callback 签名校验防止伪造播放请求7. 限制音频大小、格式和播放时长8. 为 OSS 音频目录配置生命周期自动清理规则。八、总结本文实现了一套基于阿里云 OSS Callback 的远程单向喊话方案浏览器负责录音和上传OSS 负责保存文件并通知设备FRP 负责公网到内网的请求转发设备负责下载、转换和播放相比浏览器直接向设备传输音频这种方式降低了设备接口的传输压力也不需要维持长连接。通过设备编号与 FRP 公网端口的映射还可以将同一套前端扩展到多台设备。