《倒带大挑战》的技术实现

《倒带大挑战》是一款基于音频处理技术的趣味交互应用,详情请参考 倒放的灵感。多人在线部分我们之前有你画我猜的经验,这里不做赘述。本文将深入探讨声音的录制、上传,存储和倒放实现。

上传和存储

这里有两方面的考虑,一是实时性,关系到应用体验,二是存储和传输成本,关系到服务器成本。
经过多方考虑,我选择客户端那临时鉴权,然后上传到阿里云oss。而下载通过 cdn 加速。这样既保证了实时性,又降低了服务器成本。文件保留时间为暂定 7 天。所以存储成本也相对可控。这种方案理论上速度也可以,但还需要实际论证。

格式方面,打算使用WebM (Opus 编码)格式。5 秒的语音,在使用 Opus 编码(16kbps - 24kbps)的情况下,体积可以压缩到 10KB - 30KB 左右,且音质对这一项目绰绰有余。H5 使用 MediaRecorder API 时,指定 mimeType: ‘audio/webm;codecs=opus’。

另外,如果用户量极大,服务器申请 STS Token 的接口可能会被打爆。可能需要:
前端缓存 Token: 只要 Token 没过期(比如 15 分钟内),前端就不需要重复请求。
PostObject 策略: 使用 PostObject 预签名(V4 签名)。服务器预先生成一个包含“有效期、文件大小限制、目标路径”的表单签名发给前端,前端直接 POST 到 OSS。这种方式服务器压力最小,且不需要 STS 接口。

在录制结束时,可以写一个简单的算法剔除首尾的静音部分(Silence Trimming),这样不仅能进一步缩小体积,还能让“合集大放送”环节衔接得更紧凑,更有鬼畜感!

倒放的实现技术

在 H5 环境下实现音频倒放,核心工具是 Web Audio API。它的处理逻辑非常直观:音频在计算机眼中就是一串数字(采样点),倒放本质上就是把这个数组反转。
以下是实现倒放的技术全流程:

  1. 核心原理:AudioBuffer 处理
    Web Audio API 允许我们将音频文件解码为 AudioBuffer。这是一个存储在内存中的非压缩音频对象,包含了音频的原始采样数据。
    倒放核心代码:
1
2
3
4
5
6
7
8
9
10
11
12
// 假设你已经有了一个 AudioBuffer 对象
function reverseAudio(audioBuffer) {
const numberOfChannels = audioBuffer.numberOfChannels;

for (let i = 0; i < numberOfChannels; i++) {
// 获取当前声道的采样数据(Float32Array)
const channelData = audioBuffer.getChannelData(i);
// 直接反转数组
Array.prototype.reverse.call(channelData);
}
return audioBuffer;
}
  1. 完整流程实现
    步骤 A:录制音频
    使用 MediaRecorder API 获取用户的语音。
1
2
3
4
5
6
7
8
9
10
let chunks = [];
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const mediaRecorder = new MediaRecorder(stream);

mediaRecorder.ondataavailable = (e) => chunks.push(e.data);
mediaRecorder.onstop = async () => {
const blob = new Blob(chunks, { type: 'audio/webm;codecs=opus' });
// 接下来进行倒放处理
processAudio(blob);
};

步骤 B:解码与反转
录制得到的是一个压缩后的 Blob,我们需要先将其解码回 AudioBuffer 才能操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function processAudio(blob) {
const arrayBuffer = await blob.arrayBuffer();
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();

// 1. 解码
const decodedBuffer = await audioCtx.decodeAudioData(arrayBuffer);

// 2. 倒放(调用上面的 reverseAudio 函数)
const reversedBuffer = reverseAudio(decodedBuffer);

// 3. 播放预览
playBuffer(reversedBuffer, audioCtx);
}

function playBuffer(buffer, context) {
const source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.start();
}

另外,一个重要的策略是上传原始录音,客户端即时倒放。所有的“倒放”效果全靠 Web Audio API 在每个玩家的本地浏览器里实时计算。

性能优化

  1. 避免重复解码
    在客户端,同一个音频文件可能会被播放多次(例如玩家反复听某个倒放片段)。为了避免每次播放都重新解码(decodeAudioData),应该将解码后的 AudioBuffer 缓存起来。当需要再次播放时,直接使用缓存的 Buffer,这样可以极大减少 CPU 消耗并提升响应速度。

  2. 音量标准化(Normalization):
    有些玩家录音声音小。处理 AudioBuffer 时,可以找到最大振幅,按比例把整个数组的数值放大到 1.0 附近。这样复盘合集大放送时,每个人的声音音量才统一。

  3. 采样率统一:
    为了确保所有人的音频在合集播放时不卡顿,建议在 decodeAudioData 后检查采样率。如果不同,虽然 AudioContext 会自动重采样,但统一使用 24000Hz 会更稳。

最后要考虑的是 iOS 的交互限制。iOS 上的 Safari 浏览器对音频播放有严格的限制,要求音频必须由用户的手势触发才能播放。所以,所有声音都由用户点击按钮播放。UI 设计上也要考虑好这一点。


《倒带大挑战》的技术实现
https://erdianzhang.cn/2026/02/24/《倒带大挑战》的技术实现/
作者
兔特科技
发布于
2026年2月24日
许可协议