为了确保您的开发体验,建议您使用Apifox浏览器插件或客户端进行调试。
二进制帧,非 JSON 事件: 与 /ws的文本 JSON 事件({"type","player","data"})不同,本端点只发送 Binary 类型消息,每条消息恰为一帧完整的音频帧,不发送文本消息。全部字段为小端序,字段间无对齐填充。
version = 1):| 偏移 | 长度 | 类型 | 字段 | 说明 |
|---|---|---|---|---|
| 0 | 4 | u32 | magic | 恒为 0x56544157(线上字节 57 41 54 56) |
| 4 | 4 | u32 | payloadLen | 本字段之后的字节数,= 帧长 − 8;消费者可直接用消息长度 |
| 8 | 4 | u32 | version | 线格式版本,当前为 1 |
| 12 | 4 | u32 | headerLen | 定长头总长,即 bins 的起始偏移,恒为 4 的倍数;当前为 48 |
| 16 | 4 | u32 | flags | 标志位,见下 |
| 20 | 4 | u32 | epoch | 捕获段序号,见下 |
| 24 | 8 | f64 | ts | 时间戳(毫秒),见下 |
| 32 | 4 | f32 | rms | 均方根,线性值 |
| 36 | 4 | f32 | peakL | 左声道(声道 0)sample peak,线性值 |
| 40 | 4 | f32 | peakR | 右声道(声道 1)sample peak,线性值 |
| 44 | 4 | u32 | playerLen | player 的字节数 |
headerLen | 4 × N | f32 × N | bins | 幅度谱;N = (帧长 − headerLen − playerLen) / 4 |
headerLen + 4 × N | playerLen | UTF-8 | player | 播放器标识名;空闲帧为空串 |
flags:bit0 为 1 表示该路已启用音量归一化,见「音量归一化」。其余位保留,服务端置 0,消费者应忽略。epoch:捕获子进程每成功启动一路捕获即分配一个新值(进程内从 1 起递增)。同一捕获子进程内,同一 (player, epoch) 的帧来自同一次连续捕获,其 ts 单调递增。捕获重新开始(播放器重启、订阅或播放状态变化使该路被停止后再次启动、捕获子进程重启)时取新值;捕获子进程重启后从 1 重新计数,可能与重启前的值相同,此时以 ts 的跳变识别断点(重启期间无帧;看门狗判定子进程无响应而重启时不退避,间隔可能短于 1 秒)。不得假定 epoch 单调。空闲帧恒为 0。ts:由 Windows QueryPerformanceCounter 换算的毫秒值(含小数部分),开机起算、系统级,与墙钟无关,仅用于帧间相对时间。跨 player 切换处不保证单调。rms:两声道取均值后、本帧窗口内最后至多 1024 个采样的均方根。peakL / peakR:本帧窗口内全部采样在两声道取均值之前,声道 0 / 声道 1 的绝对值最大值(不做过采样)。与 rms、bins 只用最后至多 1024 个采样不同,峰值覆盖整个窗口,不漏瞬态。峰值不截断:采样值超出 [-1, 1] 时照实输出大于 1 的值。bins:N = 512,第 i 个元素对应频率 i × 46.875 Hz(采样率 48000 Hz、1024 点 FFT、周期 Hann 窗),为线性幅度(非 dB)。未开启音量归一化且采样值在 [-1, 1] 内时,rms、peakL、peakR 与 bins 均不超过 1。服务端把采样中的 NaN 与 ±Inf 按 0 计入,这四项不会因此出现 NaN 或无穷大。playerLen 之后,同时增大 headerLen(保持 4 的倍数),version 不变。消费者读取自己认识的字段,再从 headerLen 处读取 bins 与 player,不受新增字段影响;因此 headerLen 须从帧内读取,不得写死为 48。version。消费者遇到不认识的 version,整帧丢弃。payloadLen ≥ 40),空闲帧恰为此长度。接收方(含服务端内部的解码器)按以下顺序判定,任一成立即为坏帧、整帧丢弃:version ≠ 1;headerLen < 48,或不是 4 的倍数,或大于帧长;headerLen + playerLen 大于帧长;headerLen − playerLen 不是 4 的倍数。| 形态 | player | epoch | flags | rms / bins | peakL / peakR | 出现条件 |
|---|---|---|---|---|---|---|
| 音频帧 | 播放器名 | ≥ 1 | 该路启用归一化时 bit0 为 1,否则为 0 | 按本帧窗口计算的值,N = 512 | 本帧窗口的实测峰值 | 目标进程在本帧窗口内有非静音音频数据 |
| 静音帧 | 播放器名 | ≥ 1 | 同音频帧 | 全为 0,N = 512 | 0 | 本帧窗口内无非静音数据:暂停、静音段;按 WASAPI 机制推断,播放器经 ASIO 或 WASAPI 独占模式输出(绕过系统混音)时亦然,未实测 |
| 空闲帧 | "" | 0 | 0 | rms 为 0,N = 0 | 0 | 仅根端点:无活跃播放器时每 100 毫秒一帧 |
/ws 的 player_switch 由同一次变更触发)后,后续帧的 player 变为新播放器名,epoch 为该播放器自身捕获段的值。/ws 收到 player_clear 的状态)时,根端点持续推送空闲帧;仅订阅本端点的消费者据此即可知道已清空,无需订阅 /ws。prior-player-expire 超时被清除时仍是活跃播放器:根端点继续转发它的帧(通常为静音帧),不推送空闲帧。/audio-ws 与 /{player}/audio-ws 均无连接时,服务端不扫描进程、不运行捕获子进程(例外:服务启动时无条件枚举一次进程,以清理上次运行遗留的捕获子进程)。有连接时,服务端捕获进程在运行的下列播放器:有 /{player}/audio-ws 连接的播放器(不论播放状态);根端点有连接时的当前活跃播放器(含暂停中)以及所有状态为 playing 的播放器,后者使活跃播放器切换后新播放器的帧无需等待捕获启动。连接与断开约 在 1.5 秒内反映到捕获上。config.yml 的 audio-normalize(全局开关,默认 true)与 <player>-audio-normalize(按播放器覆盖,不设则跟随全局)控制;参数 audio-normalize-target(目标响度,dBFS,默认 -14,范围 [-60, 0])与 audio-normalize-max-gain(增益上限,dB,默认 30,范围 [0, 45])全局生效。对某播放器开启后,其音频帧的 rms、peakL、peakR 与全部 bins 乘以同一个随响度平滑变化的增益,值可能大于 1,服务端不钳制,显示时由消费者自行截断;此时 peakL / peakR 大于 1 不代表真实削波,需要判断真实削波的消费者应依据 flags bit0 区分。静音帧与空闲帧的数值不受影响。开启归一化的播放器,其每一帧(含静音帧)的 flags bit0 均为 1;空闲帧的 flags 恒为 0。各播放器开关不一致时,根端点在活跃播放器切换前后的数值尺度不同,消费者可据 flags bit0 的变化得知。/{player}/audio-ws,在下列情况下可能短时或持续收不到帧,服务端不发送任何通知,故判活不应仅凭是否有帧:magic 重新同步magic 与 version,服务端发出的帧均已通过上述校验;需要处理非服务端来源数据的消费者,应补全其余判定。Float32Array 视图直接读取 rms、peakL、peakR 与 bins,前提是 buf 为从偏移 0 开始的独立 ArrayBuffer:浏览器中设 binaryType = 'arraybuffer' 即满足。在 Node 中收到的若是 Buffer(如 ws 库默认的 nodebuffer),须先复制成独立 ArrayBuffer(b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength))再解析:直接传入 Buffer 时 DataView 构造抛出 TypeError;直接传入其 .buffer 只有在 byteOffset 为 0 且该 ArrayBuffer 的 byteLength 恰等于消息长度时才正确,否则帧被丢弃或解错。Float32Array 按宿主字节序读取,常见平台(x86、ARM)均为小端,与线格式一致。