// opus_decode.mbt
//
// Ogg Opus 字节流 → PCM 的端到端驱动(CELT-only 全频带单声道范围):
// open_opus 取包序列 → §3 解析取每帧载荷 → celt_decode_frame_audio
// 逐帧解码 → 拼接并按 OpusHead 的 pre-skip 截掉编码器前瞻。
//
// 拒绝条件(与实现能力一致,错误信息直说):
// - 仅 CELT-only(config 16..31)且全频带(config 28..31):后者保证
// 走 48 kHz 标准模式 end=21 的已验证路径,其它带宽 end<21 分支
// 暂无金标覆盖,不接;
// - 单声道:立体声/多声道映射不在范围;
// - 帧载荷 ≤1 字节走丢包隐藏(参考实现同式 len≤1 → PLC),本流程
// 无 PLC,遇到 DTX/空帧直接 Err;
// - 无丢包、无乱序:包序即页序,逐包顺序解码。
///|
/// 解码整个 .opus(Ogg Opus)字节流为 48 kHz 单声道 PCM。
///
/// 跨帧状态(频域 CeltDecoderState + 时域 CeltSynthState)在流内延续,
/// 每条流独立初始化。返回的样本已按 pre-skip 截头,尚未按 granulepos
/// 截尾(与逐包全量解码一致)。
pub fn decode_opus_pcm(data : Bytes) -> Result[Array[Double], String] {
let stream = match open_opus(data) {
Ok(s) => s
Err(e) => return Err("not a valid Ogg Opus stream: \{e}")
}
if stream.channels() != 1 {
return Err(
"本解码器为单声道 CELT-only,此流 \{stream.channels()} 声道",
)
}
let celt = CeltDecoderState::new()
let synth = CeltSynthState::new()
let pcm : Array[Double] = []
for i in 0.. b
None => return Err("packet \{i}: 载荷缺失")
}
let p = match parse_opus_packet(pkt) {
Ok(p) => p
Err(e) => return Err("packet \{i}: \{e}")
}
match p.mode() {
OpusMode::Celt => ()
_ =>
return Err(
"packet \{i}: config \{p.config()} 非 CELT-only,超出范围",
)
}
match p.bandwidth() {
OpusBandwidth::Fb => ()
_ =>
return Err(
"packet \{i}: config \{p.config()} 非全频带,超出范围",
)
}
if p.stereo() {
return Err("packet \{i}: TOC 标记立体声,超出单声道范围")
}
let lm = match celt_lm_of_frame_samples(p.frame_samples()) {
Some(v) => v
None => return Err("packet \{i}: 非标准 CELT 帧时长")
}
for f in 0.. b
None => return Err("packet \{i} frame \{f}: 载荷缺失")
}
if fb.length() <= 1 {
return Err(
"packet \{i} frame \{f}: 空帧/DTX 走 PLC,超出无损范围",
)
}
let dec = RangeDecoder::new(fb)
let res = celt_decode_frame_audio(
dec,
fb.length(),
0,
21,
lm,
celt,
synth,
)
for s in res.pcm {
pcm.push(s)
}
}
}
let skip = stream.pre_skip()
if skip > pcm.length() {
return Err("pre-skip \{skip} 超过解码总长 \{pcm.length()}")
}
// 有效输出 = 末页 granule − pre-skip(RFC 7845 §4.3:granule 即
// 48 kHz 总样本位置);granule 超出实解长度说明容器与载荷不自洽,拒绝
let end = (stream.granule() - skip.to_int64()).to_int()
if end < 0 {
return Err("granule \{stream.granule()} 小于 pre-skip \{skip}")
}
if end > pcm.length() - skip {
return Err("granule \{stream.granule()} 超出实解总长 \{pcm.length()}")
}
let out = Array::make(end, 0.0)
// celt_sig 域换算(参考实现 arch.h:CELT_SIG_SCALE = 32768.f,
// SIG2RES(a) = a/32768、RES2FLOAT(a) = a):RFC 的综合公式(IMDCT、
// comb、deemphasis)在参考实现里工作于 celt_sig 域,即「浮点音频 ×
// 32768」;opus_decode_float 在 API 边界除以 32768 得到 [-1,1] 的
// 浮点 PCM。此处同口径换算,状态(hist/pending/deemph_mem)仍留在
// celt_sig 域跨帧延续,只对最终输出换算。
for i in 0..