Sign in

    moonopus

    纯 MoonBit 实现的 OGG/Opus 音频解码器,不依赖任何 C 库或系统编解码器

    opus
    ogg
    audio
    decoder
    codec
    wasm
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    13 hours ago
    Downloads
    2

    #moonopus

    纯 MoonBit 实现的 Opus 音频解码库:零 C 依赖、零 FFI,可编译到 wasm-gc、js 与 native。

    当前状态:进行中。 已完成 Ogg Opus 容器解封装(RFC 7845:页解析、跨页 packet 重组、OpusHead/OpusTags 解析与页序校验)、音频包结构解析 (RFC 6716 §3:TOC、code 0–3 帧打包、CBR/VBR、填充与畸形包分类)、 CELT 频域解码全链(RFC 6716 §4.3,见下)与帧合成模块(IMDCT、基音梳状 滤波),各模块均以双独立实现金标交叉验证。合成链路的端到端接线仍在 进行,尚未提供 PCM 输出;解码范围为 CELT-only(SILK 与 hybrid 模式不在范围内)。

    #功能

    • Ogg 容器解封装:页 CRC 校验、lacing 规则、跨页 packet 重组
    • RFC 7845 合规校验:BOS/EOS、页序号、continued 一致性、granule 规则、 链式流拒绝、OpusHead 各字段合法域
    • OpusHead / OpusTags 解析:声道、pre-skip、输入采样率、输出增益、 映射族与声道映射表、vendor 与 comments 元数据
    • RFC 6716 §3 音频包结构:TOC 配置表(32 配置的模式/带宽/帧时长)、 code 0–3 帧打包、单/双字节帧长编码、Opus 填充、按 [R1]–[R7] 对 畸形包分类拒绝
    • RFC 6716 §4.1 范围解码器(Opus 算术解码)
    • RFC 6716 §4.3 CELT 频域解码:帧头与 TF 调整(§4.3.1 / Table 56)、 能量包络(§4.3.2)、比特分配与 band boost(§4.3.3)、脉冲缓存 (§4.3.4.1)、PVQ 脉冲解码(§4.3.4.2)、展宽旋转(§4.3.4.3)、 递归分割(§4.3.4.4)、TF 变换(§4.3.4.5)、谱层量化解码主循环 (§4.3.4)、反塌缩(§4.3.5)、反归一化(§4.3.6)、帧级解码主流程
    • 帧合成模块:IMDCT 与重叠相加缓冲(§4.3.7)、基音梳状滤波(§4.3.7.1)

    #用法

    moon.pkg 中引入依赖:

    import { "LL728/moonopus", }

    在代码中打开一个 .opus 字节流:

    fn inspect(data : Bytes) {
    match @moonopus.open_opus(data) {
    Ok(stream) => println(
    "\{stream.channels()} ch, pre-skip \{stream.pre_skip()}, \{stream.packet_count()} audio packets",
    )
    Err(e) => println("not a valid Ogg Opus stream: \{e}")
    }
    }

    解析单个音频包的结构(RFC 6716 §3):

    fn inspect_packet(pkt : Bytes) {
    match @moonopus.parse_opus_packet(pkt) {
    Ok(p) => println(
    "config \{p.config()}: \{p.frame_count()} frame(s), \{p.duration_48k()} samples @48k",
    )
    Err(e) => println("malformed packet: \{e}")
    }
    }

    #开发

    moon test --target wasm-gc # 运行测试 moon test --target js python tools/gen_fixtures.py # 重新生成测试样例(测试页由独立实现构造)

    #许可证

    Apache-2.0。比特分配等表数据提取自 xiph/opus 参考实现(BSD-3-Clause,许可见 LICENSE-LIBOPUS),解码流程亦参考了该 实现与 RFC 6716。

    CeltAllocFlags

    pub struct CeltAllocFlags {
    offsets : Array[Int]
    alloc_trim : Int
    }

    分配相关的两个标志。

    CeltAllocation

    pub struct CeltAllocation {
    pulses : Array[Int]
    ebits : Array[Int]
    fine_priority : Array[Int]
    coded_bands : Int
    balance : Int
    }

    分配结果。字段含义与参考实现的同名形参一致。

    CeltDecoderState

    pub struct CeltDecoderState {
    old_e : Array[Double]
    old_log_e : Array[Double]
    old_log_e2 : Array[Double]
    rng : Int64
    }

    跨帧解码状态。初始值为全 0(对应参考实现的 memset 清零)。

    CeltDecoderState::new

    全零初始状态(rng = 0)。

    CeltFrameHeader

    pub struct CeltFrameHeader {
    silence : Bool
    postfilter_on : Bool
    postfilter_pitch : Int
    postfilter_gain : Double
    postfilter_tapset : Int
    is_transient : Bool
    intra : Bool
    }

    帧首四个符号的解码结果。

    CeltFrameResult

    pub struct CeltFrameResult {
    header : CeltFrameHeader
    spectrum : Array[Double]
    freq : Array[Double]
    collapse_masks : Array[Int]
    seed : Int64
    }

    一帧的频域解码结果。

    CeltPartitionLeaf

    pub struct CeltPartitionLeaf {
    n : Int
    blocks : Int
    lm : Int
    b : Int
    q : Int
    used : Int
    fill : Int
    pulses : Array[Int]
    }

    一个叶节点的解码控制量(前序排列于 CeltPartitionResult.leaves)。

    CeltPartitionResult

    pub struct CeltPartitionResult {
    splits : Array[CeltThetaSplit]
    leaves : Array[CeltPartitionLeaf]
    remaining_bits : Int
    }

    整棵树的解码结果:split 节点与叶都按熵消费顺序(前序)排列。

    CeltThetaSplit

    pub struct CeltThetaSplit {
    inv : Int
    imid : Int
    iside : Int
    delta : Int
    itheta : Int
    qalloc : Int
    qn : Int
    }

    split 节点解码结果:θ 换算的全部控制量。

    OggPage

    pub struct OggPage {
    data : Bytes
    offset : Int
    header_type : Int
    granule_position : Int64
    serial_number : UInt
    page_sequence : UInt
    checksum : UInt
    n_segments : Int
    body_len : Int
    }

    解析后的 OGG 页。

    data 保存原始字节序列,offset 是页在其中的起始位置。 segment table 位于 data[offset + 27 .. offset + 27 + n_segments), body 紧随其后,长度为 body_len。

    OggPage::parse

    fn OggPage::parse(data : Bytes, offset : Int) -> Result[OggPage, String]

    解析从 data[offset] 开始的一个 OGG 页。

    成功返回页结构;失败返回错误描述(magic 不符 / 版本不支持 / 数据截断 / CRC 校验失败)。

    OggPage::segment_len

    fn OggPage::segment_len(self : OggPage, i : Int) -> Int

    返回 segment table 中第 i 项的字节长度(0 <= i < n_segments)。

    OggPage::total_size

    fn OggPage::total_size(self : OggPage) -> Int

    返回整个页的总长度(页头 + segment table + body)。

    OpusBandwidth

    pub enum OpusBandwidth {
    Nb
    Mb
    Wb
    Swb
    Fb
    }

    五档音频带宽(RFC 6716 §3.1 Table 2):NB / MB / WB / SWB / FB。

    OpusHead

    type OpusHead

    解析后的 OpusHead 标识头(RFC 7845 §5.1)。

    OpusMode

    pub enum OpusMode {
    Silk
    Hybrid
    Celt
    }

    三种工作模式(RFC 6716 §3.1):SILK-only / Hybrid / CELT-only。

    OpusPacket

    pub struct OpusPacket {
    config : Int
    stereo : Bool
    mode : OpusMode
    bandwidth : OpusBandwidth
    frame_samples : Int
    frame_count : Int
    frame_lengths : Array[Int]
    is_vbr : Bool
    padding_bytes : Int
    }

    解析后的 Opus 音频包结构。

    OpusPacket::bandwidth

    fn OpusPacket::bandwidth(self : OpusPacket) -> OpusBandwidth

    音频带宽。

    OpusPacket::config

    fn OpusPacket::config(self : OpusPacket) -> Int

    配置号(0..31)。

    OpusPacket::duration_48k

    fn OpusPacket::duration_48k(self : OpusPacket) -> Int

    包内音频总时长(48 kHz 采样数)。

    OpusPacket::frame_count

    fn OpusPacket::frame_count(self : OpusPacket) -> Int

    包内帧数(code 0/1/2 为 1/2/2,code 3 为 M)。

    OpusPacket::frame_length

    fn OpusPacket::frame_length(self : OpusPacket, i : Int) -> Int?

    第 i 帧的压缩字节长;越界返回 None。

    OpusPacket::frame_samples

    fn OpusPacket::frame_samples(self : OpusPacket) -> Int

    每帧时长(48 kHz 采样数;2.5 ms=120 … 60 ms=2880)。

    OpusPacket::is_vbr

    fn OpusPacket::is_vbr(self : OpusPacket) -> Bool

    是否为变码率打包(code 2,或 code 3 的 v 位)。

    OpusPacket::mode

    fn OpusPacket::mode(self : OpusPacket) -> OpusMode

    工作模式。

    OpusPacket::padding_bytes

    fn OpusPacket::padding_bytes(self : OpusPacket) -> Int

    Opus 层尾部填充字节数(无填充时为 0)。

    OpusPacket::stereo

    fn OpusPacket::stereo(self : OpusPacket) -> Bool

    立体声标志(TOC 的 s 位)。

    OpusStream

    pub struct OpusStream {
    head : OpusHead
    tags : OpusTags
    audio : Array[Bytes]
    }

    一个已通过全部头部校验的 Ogg Opus 逻辑流。

    OpusStream::channel_mapping

    fn OpusStream::channel_mapping(self : OpusStream) -> Array[Byte]

    声道映射表(family 0 为空,语义等价于下标 0..C-1;255 表示纯静音声道)。

    OpusStream::channels

    fn OpusStream::channels(self : OpusStream) -> Int

    输出声道数(OpusHead channel count)。

    OpusStream::comments

    fn OpusStream::comments(self : OpusStream) -> Array[String]

    用户注释列表(OpusTags comments,UTF-8 解码,无效字节替换为 U+FFFD)。

    OpusStream::coupled_count

    fn OpusStream::coupled_count(self : OpusStream) -> Int

    其中配置为双声道输出的耦合流数 M(family 0 隐含为 C-1)。

    OpusStream::input_sample_rate

    fn OpusStream::input_sample_rate(self : OpusStream) -> UInt

    编码前原始输入采样率(仅元数据,不是播放采样率;0 表示未指定)。

    OpusStream::mapping_family

    fn OpusStream::mapping_family(self : OpusStream) -> Int

    声道映射族(0 / 1 / 255;保留值 2..254 按 255 语义解析)。

    OpusStream::output_gain

    fn OpusStream::output_gain(self : OpusStream) -> Int

    解码输出应施加的增益(Q7.8 dB,带符号)。

    OpusStream::packet

    fn OpusStream::packet(self : OpusStream, i : Int) -> Bytes?

    第 i 个音频数据包;越界返回 None。

    OpusStream::packet_count

    fn OpusStream::packet_count(self : OpusStream) -> Int

    音频数据包数量(OpusTags 之后的全部逻辑包,尚未做 TOC 解析)。

    OpusStream::pre_skip

    fn OpusStream::pre_skip(self : OpusStream) -> Int

    起播时应丢弃的 48 kHz 采样数(RFC 7845 §4.2 pre-skip)。

    OpusStream::stream_count

    fn OpusStream::stream_count(self : OpusStream) -> Int

    每包编码流数 N(OpusHead mapping table;family 0 隐含为 1)。

    OpusStream::vendor

    fn OpusStream::vendor(self : OpusStream) -> String

    vendor 字符串(OpusTags)。

    OpusTags

    type OpusTags

    解析后的 OpusTags 注释头(RFC 7845 §5.2)。

    PacketAssembler

    type PacketAssembler

    packet 重组器:跨页累积未完成的 packet。

    PacketAssembler::has_partial

    fn PacketAssembler::has_partial(self : PacketAssembler) -> Bool

    当前是否有跨页未完成的 packet(即上一个页以长度为 255 的 segment 结尾)。

    PacketAssembler::new

    新建一个空的 packet 重组器。

    PacketAssembler::push_page

    fn PacketAssembler::push_page(self : PacketAssembler, page : OggPage) -> Array[Bytes]

    处理一个页,返回该页内完成的所有 packet。

    跨页未完成的 packet 会累积在内部缓冲中,直到遇到长度 < 255 的 segment 才输出。 调用方应保证页按顺序传入,且页的 CONTINUED 标志与上一个页的未完成状态一致。

    RangeDecoder

    pub struct RangeDecoder {
    data : Bytes
    nbytes : Int
    offs : Int
    leftover : Int
    val : Int64
    rng : Int64
    raw_read : Int
    skipped : Int
    }

    范围解码器状态。创建后即可连续解码,所有 mut 字段透过 self 引用更新。

    RangeDecoder::dec_bits

    fn RangeDecoder::dec_bits(self : RangeDecoder, nbits : Int) -> Int

    ec_dec_bits():raw bits 从帧尾反向打包,LSB 起(§4.1.4)。

    与范围解码器的前向读取相互独立,允许重叠消费同一段数据。

    RangeDecoder::decode_bin_fs

    fn RangeDecoder::decode_bin_fs(self : RangeDecoder, ftb : Int) -> Int

    ec_decode_bin():返回 fs = ec_decode(1<<ftb),不更新状态(§4.1.3.1)。

    调用方自行决定用哪个 (fl, fh, ft) 调 update——Laplace 解码正是先取 fs 再自行构造目标区间(见 §4.3.2 的能量解码)。这与 decode_bit_logp(decode+update 的二元快捷路径)是两回事。

    RangeDecoder::decode_bit_logp

    fn RangeDecoder::decode_bit_logp(self : RangeDecoder, logp : Int) -> Int

    ec_dec_bit_logp():解一个二元符号,logp 是「1」的 log2 概率(§4.1.3.2)。

    RangeDecoder::decode_ctx

    fn RangeDecoder::decode_ctx(self : RangeDecoder, freqs : Array[Int]) -> Int

    解一个由频率数组描述的上下文,返回解出的符号 k(§4.1.2)。

    freqs[i] 是符号 i 的频率,其和即 ft;fl/fh 由累计和推出,符号由 比特流决定而非由调用方指定。fs 不落在任何符号区间时属实现错误。

    RangeDecoder::decode_icdf

    fn RangeDecoder::decode_icdf(self : RangeDecoder, icdf : Array[Int], ftb : Int) -> Int

    ec_dec_icdf():用逆累积分布表解符号(§4.1.3.3)。

    icdf[k] = (1<<ftb) - fh[k],表以 0 结尾(对应 fh = ft)。搜索首个 满足 fs < (1<<ftb) - icdf[k] 的 k。

    RangeDecoder::decode_uint

    fn RangeDecoder::decode_uint(self : RangeDecoder, ft : Int) -> Int

    ec_dec_uint():解 0..ft-1 的均匀整数(§4.1.5)。适用于 ft ≤ 2**31-1。

    ftb<=8 时直接解一个符号;否则高 8 位走范围码、余位走 raw bits。 结果可能超出 0..ft-1,此时按 §4.1.5 视为帧损坏(取模退回)。

    RangeDecoder::decode_uint64

    fn RangeDecoder::decode_uint64(self : RangeDecoder, ft : Int64) -> Int64

    ec_dec_uint() 的 64 位版本:ft 可达 2**32。

    为什么需要——PVQ 的码本按 32 bits 设计(RFC §4.3.4.4:codebook 上限 32 bits;cwrs.c 注释:K=128 或 whichever fits in 32 bits),而 V(N,K) 对 (96,5) 这类组合就已越过 Int32 上限。Int 版本据此转发,保证只有一份 实现。

    中间量 top_ft 与高 8 位的 hi 都不超过 257,可直接复用现有解码路径; 只有末尾拼接 raw bits 那一步需要 64 位。

    RangeDecoder::new

    fn RangeDecoder::new(data : Bytes) -> RangeDecoder

    创建解码器并完成初始化与首轮重归一化(RFC §4.1.1)。

    rng 置 128、val 置 127-(b0>>1),首字节的最低位存为 leftover 供后续 重归一化拼接;无输入时按 0 处理。

    RangeDecoder::skip_bits_to

    fn RangeDecoder::skip_bits_to(self : RangeDecoder, nbits : Int) -> Unit

    把位计数一次性推进到 nbits,不消费任何比特。

    对应静音帧的处理:参考实现令 nbits_total += nbits - ec_tell(),使 ec_tell() 之后返回帧长,于是帧首后续几个符号的门控(tell+16 之类)全部 落空、一个都不读。位置状态(rng/val/字节游标)保持不变。

    RangeDecoder::tell

    fn RangeDecoder::tell(self : RangeDecoder) -> Int

    ec_tell():已用位数的保守上界(§4.1.6.1)。初始化后为 1。

    RangeDecoder::tell_frac

    fn RangeDecoder::tell_frac(self : RangeDecoder) -> Int

    ec_tell_frac():1/8 位精度的已用位数(§4.1.6.2)。

    比特分配例程要求它与编码端 bit-exact 一致;满足 ec_tell() == ceil(ec_tell_frac() / 8)。

    CEL_BITRES

    let CEL_BITRES : Int

    位分辨率:范围解码器按 1/8 bit 计位(celt/entcode.h 的 BITRES)。

    CEL_COMB_MINPERIOD

    let CEL_COMB_MINPERIOD : Int

    最小基音周期(celt_celt.h 的 COMBFILTER_MINPERIOD)。

    CEL_DEEMPH_COEF

    let CEL_DEEMPH_COEF : Double

    去重 emphasis 系数(48 kHz):alpha_p = 0.8500061035。

    对应 §4.3.7.2 的 1/A(z) = 1/(1 - alpha_p·z⁻¹),也即编码端 A(z) = 1 - 0.85·z⁻¹(48 kHz 标准预加重)的逆。参考实现 modes.c 对 48 kHz 的 preemph[1..3] 均为 0/1/1,故只有一个有效系数。

    CEL_MAX_FINE_BITS

    let CEL_MAX_FINE_BITS : Int

    收尾阶段单带最多再补的细能量位数(quant_bands.c 的 MAX_FINE_BITS)。

    CEL_MAX_PSEUDO

    let CEL_MAX_PSEUDO : Int

    伪脉冲索引的最大值(rate.h 的 MAX_PSEUDO),也是条目 Kmax 的上限。

    CEL_NB_EBANDS

    let CEL_NB_EBANDS : Int

    标准 CELT 模式的带数(§4.3 说正常使用 21 个带)。

    CEL_OVERLAP

    let CEL_OVERLAP : Int

    模式 overlap:(shortMdctSize>>2)<<2,48 kHz 标准模式为 120。 也是镜像区与跨帧 pending(ov/2 = 60)的长度基准。

    CEL_SHORT_MDCT

    let CEL_SHORT_MDCT : Int

    2.5ms 短块的样本数(48 kHz),也是 eband5ms 的单位基准。

    FLAG_BOS

    let FLAG_BOS : Int

    FLAG_CONTINUED

    let FLAG_CONTINUED : Int

    页头类型标志位。

    FLAG_EOS

    let FLAG_EOS : Int

    cel_unquant_coarse_energy

    fn cel_unquant_coarse_energy(dec : RangeDecoder, old_e : Array[Double], start : Int, end : Int, intra : Bool, lm : Int) -> Unit

    §4.3.2.1 粗能量解码:逐带解 Laplace 增量(或位数不足时的降级路径), 再按 2-D 预测滤波器把能量状态推进一帧。

    old_e 进入时是上一帧的最终能量,逐带被本帧结果覆盖。带内还能不能 塞下一个 Laplace 符号,由解码器自身的载荷长度决定(参考实现取 ec_dec 的 storage × 8);lm 是帧长索引(0..3 对应 120/240/480/960)。

    预测式为 §4.3.2.1 的 (1-alpha·z_l^-1)(1-z_b^-1)/(1-beta·z_b^-1): 时间方向取上帧能量乘 coef(intra 时为 0),频率方向由 prev 累积本帧 已解出的量化误差并按 beta 衰减。浮点构建下 SHL32 是恒等的,故 q 就等于解出的整数 qi。

    cel_unquant_energy_finalise

    fn cel_unquant_energy_finalise(dec : RangeDecoder, old_e : Array[Double], start : Int, end : Int, fine_quant : Array[Int], fine_priority : Array[Int], bits_left : Int) -> Unit

    §4.3.2.2 收尾:把所有标志位解完后剩下的比特,按优先级 0→1 依次给 各带再补 1 位细能量;补不动的位就留着不用。

    bits_left 是本帧尚余的比特数(参考实现取 len*8 - ec_tell)。

    cel_unquant_fine_energy

    fn cel_unquant_fine_energy(dec : RangeDecoder, old_e : Array[Double], start : Int, end : Int, extra_quant : Array[Int]) -> Unit

    §4.3.2.2 细能量解码:把比特分配给出的 extra_quant[i] 位裸比特解释为 粗能量的修正量 (f+1/2)/2**B_i - 1/2。

    位数为 0 或剩余比特不够的带直接跳过(判据同参考实现)。QEXT 扩展 的 prev 缩放因子在此恒为 1,故不出现。

    celt_alloc_total

    fn celt_alloc_total(frame_bytes : Int, dec : RangeDecoder) -> Int

    本帧可用于分配的位数,单位 1/8 bit。

    frame_bytes 是本帧载荷字节数;<<BITRES 把比特换算成 1/8 bit, -1 是规范为分配结束预留的那 1 bit,再减去解码器已用位数 (ec_tell_frac 本身就是 1/8 bit 单位)。反抵消(anti-collapse)的预留 由帧解码阶段另行扣除。

    celt_anti_collapse

    fn celt_anti_collapse(x : Array[Double], collapse_masks : Array[Int], lm : Int, start : Int, end : Int, band_log_e : Array[Double], prev1_log_e : Array[Double], prev2_log_e : Array[Double], pulses : Array[Int], seed : Int64) -> Unit

    §4.3.5 反塌缩:就地修正 x 中 [start, end) 各带的塌缩短块。

    collapse_masks[i] 是带 i 的塌缩位图(bit k = 1<<LM 个短块序号, 0 表示塌缩);band_log_e/prev1_log_e/prev2_log_e 都按参考实现 的 2*nbEBands 布局取值(单声道只用前半,本函数的 max 分支会读到 声道 1 的占位槽);pulses[i] 是该带分配到的位数(1/8 bit)。 seed 按值传入,不改解码器状态(参考实现传 st->rng 的拷贝)。

    celt_anti_collapse_rsv

    fn celt_anti_collapse_rsv(is_transient : Bool, lm : Int, bits : Int) -> Int

    反塌缩预留位数(celt_decoder.c 的 anti_collapse_rsv):瞬态帧、 LM≥2 且可用预算不低于 (LM+2) 个 1/8-bit 单位时才预留 1 bit。

    celt_band_bins

    fn celt_band_bins(i : Int, m : Int) -> Int

    频带 i 的 MDCT bin 数。

    celt_bitexact_cos

    fn celt_bitexact_cos(x : Int) -> Int

    celt_bands.c 的 bitexact_cos:全平台位一致的余弦近似,输入是 0..16384 的 1/16384 弧度格点(0 与 16384 由调用方先行特判, 格点最小步长 64 保证中间量不越过 int16 断言域)。

    celt_bitexact_log2tan

    fn celt_bitexact_log2tan(isin : Int, icos : Int) -> Int

    celt_bands.c 的 bitexact_log2tan:log2(tan) 的定点近似, delta = FRAC_MUL16((N-1)<<7, log2tan(iside, imid)) 消费其结果。

    celt_bits2pulses

    fn celt_bits2pulses(band : Int, lm : Int, bits : Int) -> Int

    位数(1/8 bit)→ 伪脉冲索引(§4.3.4.1,rate.h 的 bits2pulses)。

    查表行 = LM+1(解码 lm∈0..3 落在表行 1..4,行 0 只参与条目去重)。 输入位数先减 1 与表内「位数−1」的存法对齐,再做固定 6 轮二分夹逼; 收尾 tie-break 在 lo/hi 间取距离更小的一端,距离相等取 lo(<=)。

    celt_comb_filter

    fn celt_comb_filter(buf : Array[Double], base : Int, t0 : Int, t1 : Int, n : Int, g0 : Double, g1 : Double, ts0 : Int, ts1 : Int, win : Array[Double]) -> Unit

    后滤波 comb 滤波,就地作用于 buf[base, base+n)。

    t0/g0/ts0 为旧参数、t1/g1/ts1 为新参数(G 已折算进 g), win 为预取的 MDCT 窗(长度即 overlap)。实现顺序与参考实现 一致:全零捷径 → 钳制 → 过渡段(滑动状态读 f 分支的四个历史 样本)→ 新增益零捷径 → 常数段。

    celt_compute_allocation

    fn celt_compute_allocation(dec : RangeDecoder, start : Int, end : Int, offsets : Array[Int], cap : Array[Int], alloc_trim : Int, total : Int, lm : Int) -> CeltAllocation

    比特分配(§4.3.3):恢复本帧每带的 PVQ 位数、细能量位与收尾优先级。

    offsets 是 dynalloc 的加成(单位 1/8 bit,长度 21),cap 由 celt_init_caps 给出,alloc_trim 是解出的 0..10(§4.3.3:5 表示不加 不减),total 用 celt_alloc_total 算(单位 1/8 bit)。范围解码器会被 推进——skip 决策要读符号,顺序与帧内其它符号一致。

    celt_compute_qn

    fn celt_compute_qn(n : Int, b : Int, offset : Int, pulse_cap : Int, stereo : Bool) -> Int

    compute_qn:split 增益的量化阶数(celt_bands.c 同名函数)。

    offset 与 pulse_cap 由调用方按 pulse_cap = logN[band] + LM<<BITRES、 offset = (pulse_cap>>1) - QTHETA[_TWOPHASE] 算好传入。 上限 8<<BITRES 保证立体声 itheta==16384 时 side 侧仍能编出脉冲; 结果为 1 或偶数。

    celt_compute_theta

    fn celt_compute_theta(dec : RangeDecoder, band : Int, lm : Int, n : Int, b : Int, blocks : Int, blocks0 : Int, stereo : Bool, intensity : Int, remaining_bits : Int, disable_inv : Bool, fill : Int) -> (CeltThetaSplit, Int, Int)

    compute_theta 的解码侧:解出 split 节点的 θ 与 mid/side 控制量。

    入参 lm 是本节点的 LM(分割后已减 1),blocks/blocks0 是 分割后/分割前的时间块数 B(PDF 选择看 B0),remaining_bits 只读 (qn==1 立体声 inv 判定的预算条件),扣减由调用方按返回的 qalloc 做。返回 (split, b 减 qalloc 后, fill 按 itheta 掩蔽后)。

    celt_decode_alloc_flags

    fn celt_decode_alloc_flags(dec : RangeDecoder, start : Int, end : Int, cap : Array[Int], frame_bytes : Int, lm : Int) -> CeltAllocFlags

    解 band boost 与 allocation trim。

    cap 由 celt_init_caps 给出,frame_bytes 是本帧载荷字节数(决定 预算与门控),lm 是帧长索引(决定每带的 boost 步长)。 范围解码器会被推进,顺序与帧内其它符号一致。

    celt_decode_anti_collapse_flag

    fn celt_decode_anti_collapse_flag(dec : RangeDecoder, rsv : Int) -> Int

    反塌缩标志位解码(celt_decoder.c:预留了才读,读 1 个 raw bit)。

    celt_decode_frame

    fn celt_decode_frame(dec : RangeDecoder, frame_bytes : Int, start : Int, end : Int, lm : Int, state : CeltDecoderState) -> CeltFrameResult

    解一帧的频域部分。dec 是按本帧载荷初始化的范围解码器, frame_bytes 是载荷字节数(≥2,与参考实现一致:len≤1 走 PLC, 不在本流程),start/end 是编码带窗(CELT-only 单声道为 0/21), lm 是帧长索引。state 跨帧就地更新。

    顺序与参考实现逐段对齐;返回值含全部可供金标比对的中间产物。

    celt_decode_frame_header

    fn celt_decode_frame_header(dec : RangeDecoder, frame_bytes : Int, start : Int, lm : Int) -> CeltFrameHeader

    解帧首四个符号。

    frame_bytes 是本帧载荷字节数(决定各项门控),start 是编码起始带 (单声道正常流为 0,Hybrid 模式为 17),lm 是帧长索引——2.5ms 帧 (LM=0)没有瞬态标志,因为已经是短块了。

    范围解码器会被推进,顺序与帧内其它符号一致。

    celt_decode_pulses

    fn celt_decode_pulses(dec : RangeDecoder, n : Int, k : Int) -> Array[Int]

    解一个 PVQ 脉冲向量:先按 V(N,K) 解出码字下标,再按 §4.3.4.2 的五步 迭代把下标还原成向量。

    n 是带的维数、k 是该带分到的脉冲数。返回长度 n 的向量,且 Σ|X| = k(脉冲数守恒,这是测试里的不变量)。

    k=0 时码字唯一(全零向量),不读比特——参考实现同样断言 k>0 才进此 函数,由外层负责 k=0 的情况。

    celt_decode_quant_partition

    fn celt_decode_quant_partition(dec : RangeDecoder, band : Int, lm : Int, n : Int, b : Int, blocks : Int, fill : Int, remaining_bits : Int) -> CeltPartitionResult

    quant_partition 的解码入口(控制与熵解码路径,谱写入关闭)。

    band 是原始带号(查脉冲缓存用,不随分割改变),lm 是本带 帧长索引,n 是带的 MDCT bin 数,b 是本带可用位数(1/8 bit), blocks 是时间块数 B,fill 是噪声填充位图,remaining_bits 是 进入本带时的全局剩余预算(1/8 bit)。

    celt_decode_spread

    fn celt_decode_spread(dec : RangeDecoder, frame_bytes : Int) -> Int

    解频谱扩展决策,返回 0..3(NONE/LIGHT/NORMAL/AGGRESSIVE)。

    frame_bytes 是本帧载荷字节数,位数不够就停在默认的 NORMAL——与编码 端一致:门控失败意味着两端都不写这个符号。

    celt_decode_tf

    fn celt_decode_tf(dec : RangeDecoder, start : Int, end : Int, is_transient : Bool, lm : Int, frame_bytes : Int) -> Array[Int]

    解每带 TF 调整,返回长度 21 的调整值数组(窗外项恒为 0)。

    frame_bytes 是本帧载荷字节数:tf_select 的预留位与逐带读取都由它把 关。范围解码器会被推进,顺序与帧内其它符号一致。

    celt_deemphasis

    fn celt_deemphasis(input : Array[Double], out : Array[Double], mem : Double) -> Double

    去重 emphasis(§4.3.7.2):一阶 IIR,y[n] = x[n] + alpha_p·y[n-1]。

    输入为 post-filter 后的时域样本,结果就地写入 out。mem 是上一帧 保留的递推状态,返回值是本帧结束后的状态(解码器跨帧保存)。与参考 实现 celt_deemphasis_c 的内层循环一致。

    celt_denormalise

    fn celt_denormalise(x : Array[Double], band_log_e : Array[Double], start : Int, end : Int, m : Int, silence : Bool) -> Array[Double]

    反归一化。x 是各带单位能量的形状(§4.3.4 的输出),band_log_e 是 §4.3.2 解出的能量状态,两者与返回值的长度都是 m * 120(带外部分为 未使用的占位 0)。

    celt_eband

    fn celt_eband(i : Int, m : Int) -> Int

    频带 i 的起始 bin(已乘缩放因子 M)。

    m 为帧长缩放(2.5ms→1、5ms→2、10ms→4、20ms→8)。

    celt_exp_rotation

    fn celt_exp_rotation(x : Array[Double], b : Int, k : Int, spread : Int) -> Unit

    对带向量原地施加展宽旋转。x 长度 n ≥ 2,b 是该带的时间块数, k 是脉冲数,spread ∈ 0..3(由 celt_decode_spread 的 icdf 解码保证)。

    调用方保证 k ≥ 1 且 n 是 b 的倍数——与参考实现相同:前者由 alg_unquant 的断言兜底,后者由带结构保证(参考实现直接整除截断)。

    celt_get_pulses

    fn celt_get_pulses(i : Int) -> Int

    伪脉冲索引 → 实际脉冲数(rate.h 的 get_pulses)。

    0..7 原样;8 起每 8 个索引一档,档内取低 3 位线性递增,档号减 1 作 左移量:8..15 → 8,9,...,15;16..23 → 16,18,...,30;末端 40 → 128。

    celt_imdct_frame

    fn celt_imdct_frame(freq : Array[Double], lm : Int, is_transient : Bool, pending : Array[Double]) -> (Array[Double], Array[Double])

    一帧频谱 -> 时域帧与新 pending(§4.3.7)。

    freq 长 120 << lm,瞬态帧按 freq[b + 2^lm·k] 交错取块; pending 是上一帧 raw 尾部 CEL_OVERLAP/2 个样本(首帧全 0)。 返回 (帧输出[N], 新 pending),均为新数组。内部缓冲布局与参考 实现的 out_syn 一致:[0, ov/2) 先承接上一帧尾,raw 段从 ov/2 起,镜像、取输出、截尾巴一气呵成。

    celt_init_caps

    fn celt_init_caps(lm : Int, channels : Int) -> Array[Int]

    §4.3.3 各带的最大分配上限 cap[](0 号声道到 nbEBands-1)。

    表按 caps[nbEBands*(2*LM + C - 1) + i] 取值——与 §4.3.3 描述的 「i = nbBands*(2*LM+stereo)」同式,再按 (值+64)*C*N>>2 换算成该带 最多可用的比特数(N 为该带的 MDCT bin 数,>>2 即规范的「除以 4」)。

    这一步是规范点名要与参考实现一致的换算,后续比特分配直接消费结果。

    celt_isqrt32

    fn celt_isqrt32(v : Int) -> Int

    mathops.c 的 isqrt32:二进制位搜索 floor(sqrt(v))——三角 PDF 的 累计频率反解靠它把 fm/f-tail 映射回 θ 档位。

    celt_lcg_rand

    fn celt_lcg_rand(seed : Int64) -> Int64

    伪随机数推进(celt_bands.c 的 celt_lcg_rand),uint32 环绕。 入参与返回值都按 u32 存在 Int64 里。

    celt_lm_of_frame_samples

    fn celt_lm_of_frame_samples(samples : Int) -> Int?

    帧长缩放 LM(§4.3 Table 55 的帧尺寸列):120×2^LM 样本。

    2.5/5/10/20 ms 对应 LM = 0/1/2/3。

    celt_max_bin

    fn celt_max_bin(m : Int) -> Int

    编码覆盖的最大 bin 数(band 21 的边界)。

    注意它小于 MDCT 长度 120*m:20ms 帧为 800 < 960,即 20–24 kHz 不参与编码(§4.3 的 band 20 终止于 20000 Hz)。

    celt_pulses2bits

    fn celt_pulses2bits(band : Int, lm : Int, pulses : Int) -> Int

    伪脉冲索引 → 位数(1/8 bit)(§4.3.4.1,rate.h 的 pulses2bits)。

    0 个脉冲恒为 0;其余查条目的第 q 槽再加 1(表内存的是「位数−1」)。

    celt_pvq_unindex

    fn celt_pvq_unindex(n : Int, k : Int, tab : Array[Int64], idx : Int64) -> Array[Int]

    §4.3.4.2 的五步迭代:把码字下标还原成脉冲向量。

    单独抽出来是因为它是纯函数——下标由比特流决定,不经解码器就没法指定 某个下标,而穷举所有下标正是验证脉冲数守恒的唯一办法。

    tab 是 celt_pvq_v_table(n, k) 的结果,idx 应落在 [0, V(n,k))。

    celt_pvq_v_table

    fn celt_pvq_v_table(n : Int, k : Int) -> Array[Int64]

    V(N,K):K 个脉冲分配到 N 维的码字数(§4.3.4.2)。

    按规范递归 V(N,K) = V(N-1,K) + V(N,K-1) + V(N-1,K-1),边界 V(N,0)=1、V(0,K)=0 (K>0),动态规划逐行填表。

    返回行主序的一维表:表[a*(k+1) + b] = V(a,b),其中 a ≤ n、b ≤ k。 值用 Int64——码本按 32 bits 设计(§4.3.4.4 与 cwrs.c 注释),(96,5) 这类组合就已越过 Int32 上限。

    celt_quant_all_bands

    fn celt_quant_all_bands(dec : RangeDecoder, x : Array[Double], start : Int, end : Int, lm : Int, short_blocks : Bool, pulses : Array[Int], tf_res : Array[Int], coded_bands : Int, total_bits : Int, balance : Int, spread : Int, seed : Int64, masks : Array[Int]) -> Int64

    单声道主循环。x 是帧频谱(长度 m*120,带区就地读写),pulses 是分配结果(1/8 bit),tf_res 是每带 TF 决策(-3..3),total_bits 是帧预算(1/8 bit),masks 写入每带 collapse mask(长度 ≥ end)。 返回推进后的 seed。

    celt_quant_band

    fn celt_quant_band(dec : RangeDecoder, x : Array[Double], lowband : Array[Double], band : Int, lm : Int, n : Int, b : Int, blocks : Int, tf_change : Int, fill : Int, gain : Double, spread : Int, seed : Int64, remaining : Int, lowband_out : Array[Double]) -> (Int, Int64, Int)

    带级解码。x 是本带频谱(长度 n,就地写入);lowband 是折叠源 (空数组 = 无折叠,q==0 走噪声路径;非空则长度 n,本函数会先对它 做前向 TF 变换,与参考实现同样修改入参);lowband_out 非空时在 末尾写入 ×√N0 的折叠预缩放。fill 是带级填充位图(先做 TF 位图 变换再下传)。返回 (collapse mask, seed, 剩余预算)。

    celt_renormalise_vector

    fn celt_renormalise_vector(x : Array[Double], off : Int, n : Int, gain : Double) -> Unit

    带区间重新归一化(celt_vq.c 的 renormalise_vector,浮点路径): x *= gain / sqrt(EPSILON + Σx²),就地修改 x[off..<off+n]。 EPSILON 是浮点构建的防零除下界(1e-15)。

    celt_tf_forward

    fn celt_tf_forward(x : Array[Double], n : Int, b : Int, tf_change : Int) -> Unit

    前向 TF 变换:作用于解码前的折叠源(编码端作用于待量化的向量)。

    三步顺序对应参考实现里 recombine 循环、时间分辨率循环、重排三段代码。 反向链按相反次序撤销,两条链互逆由测试保证。

    celt_tf_inverse

    fn celt_tf_inverse(x : Array[Double], n : Int, b : Int, tf_change : Int) -> Unit

    反向 TF 变换:解码后作用于量化域的已解码向量,还原到频序。

    celt_window

    fn celt_window(i : Int, overlap : Int) -> Double

    sine 窗:sin(π/2 · sin²(π/2 · (i+0.5)/overlap))。

    直接对应 libopus modes.c 的 window 生成式(float 构建)。用于 IMDCT 的 50% 重叠合成与 post-filter 的窗控。

    ogg_crc32

    fn ogg_crc32(data : Bytes) -> UInt

    计算一段完整字节序列的 OGG CRC-32,返回 0 到 0xFFFFFFFF 之间的无符号值。

    调用方可将其结果写入 OGG 页头的 22..26 字节(小端)做校验。

    ogg_crc32_update

    fn ogg_crc32_update(crc : UInt, data : Bytes, start : Int, len : Int) -> UInt

    增量计算:从已有的 crc 继续处理 data[start .. start + len) 的字节。

    用于 OGG 页校验——页内 22..26 字节是 CRC 字段本身,校验时需要把它当作 0 参与计算,因此把页头前 22 字节与 26 字节之后的部分分段累加。

    open_opus

    fn open_opus(data : Bytes) -> Result[OpusStream, String]

    打开一个 Ogg Opus 字节流。

    校验范围(RFC 7845 §4):
    • 首页必须带 BOS 且 granule 为 0,OpusHead 独占首页并完成于首页;
    • OpusTags 为第二个包,其完成页 granule 必须为 0;
    • 页序号必须从 0 连续;serial 全程一致;出现第二个 BOS 即为链式流 (本项目明确不支持,直接拒绝);
    • CONTINUED 标志与上一页未完成状态严格一致,否则该页首包不可解码;
    • EOS 页之后不得再有页;缺 EOS 的整页截断流按 §4 容忍, 但残缺页视为损坏并报错。

    当前范围:容器层解析,不含音频帧解码。

    parse_opus_packet

    fn parse_opus_packet(data : Bytes) -> Result[OpusPacket, String]

    解析一个 Opus 音频包的结构(RFC 6716 §3.1–§3.2)。

    畸形包按 §3.4 的 [R1]–[R7] 约束拒绝:
    • [R1] 至少 1 字节;
    • [R2] 隐式/显式帧长不超过 1275 字节;
    • [R3] code 1 包总长必须为奇数;
    • [R4] code 2 首帧长字段完整且不超过剩余字节;
    • [R5] code 3 至少 1 帧、总时长不超过 120 ms;
    • [R6] code 3 CBR:≥2 字节、填充开销 P ≤ N-2、R 是 M 的整数倍;
    • [R7] code 3 VBR:头部与显式帧长之和不超过包长。