// flac.mbt
//
// 顶层解码编排:元数据 → 帧定位(同步扫描 + 帧头校验)→ 子帧解码 →
// 声道去相关 → 输出。声道去相关公式(left/right/side、mid/side 的
// `mid' = (mid<<1)|(side&1)` 还原)经 ffmpeg 手造帧与真实文件双向验证;
// side 声道位宽恒为 bps+1(bps=32 时 33 位,故子帧域用 Int64)。
// 帧迭代逻辑集中在 `decode_next_block`,整文件 `decode` 与流式
// `FlacStream` 共用同一条路径。
///|
/// 整文件解码结果(planar:每声道一个样本数组,样本为 bps 位宽的有符号值)。
pub struct FlacFile {
/// 流的 STREAMINFO 等元数据。
info : StreamInfo
/// 每声道样本(声道数 = `info.channels`)。
channels : Array[Array[Int32]]
}
///|
/// 一个完整块的解码产物(已按声道分配去相关,长度 = 帧的 block_size)。
priv struct Block {
block_size : Int32
channels : Int32
samples : Array[Array[Int32]]
}
///|
/// 尝试在读取器当前位置解析帧头:任何帧头级失败(同步码、保留位、
/// 变长帧号、CRC-8、截断)都折叠为 None,交给上层决定是否继续扫描;
/// 帧头之后的子帧/CRC-16 错误则直接上抛,不静默跳过。
fn try_parse_header(br : BitReader, info : StreamInfo) -> FrameHeader? {
Some(parse_frame_header(br, info)) catch {
_ => None
}
}
///|
/// 解码帧体:各声道子帧 + 零填充到字节边界 + CRC-16 校验。
/// 成功后读取器停在下一帧起点(字节边界)。
fn decode_frame_body(
br : BitReader,
h : FrameHeader,
temp : Array[Array[Int64]],
) -> Unit raise FlacError {
let side_idx = if h.channel_assignment == 8 || h.channel_assignment == 10 {
1
} else if h.channel_assignment == 9 {
0
} else {
-1
}
for c in 0..> 3
let expect = br.read_bits64(16).to_int()
br.check()
let actual = crc16_range(br.data, h.start_byte, crc_pos)
if expect != actual {
raise BadCrc(kind="CRC-16", expected=actual, actual=expect)
}
}
///|
/// 把子帧域(Int64)的声道矩阵按声道分配去相关成最终 Int 样本。
fn decorrelate(
h : FrameHeader,
temp : Array[Array[Int64]],
) -> Array[Array[Int32]] {
let out : Array[Array[Int32]] = []
for _ in 0..> 1).to_int())
out[1].push(((mid2 - side) >> 1).to_int())
}
}
out
}
///|
/// 定位并解码下一个完整块。
/// 帧头级失败向后扫描同步码重同步(容忍尾部杂字节);扫描到尽头返回 None。
/// 子帧或 CRC-16 失败直接上抛,不跳帧。
fn decode_next_block(
br : BitReader,
info : StreamInfo,
) -> Block? raise FlacError {
let mut header : FrameHeader? = None
let mut scan_done = false
while header is None && !scan_done {
let cur = br.pos >> 3
br.truncated = false
header = try_parse_header(br, info)
if header is None {
let next = scan_sync(br.data, cur + 1)
if next < 0 {
scan_done = true
} else {
br.seek(next * 8)
}
}
}
match header {
None => None
Some(h) => {
let temp : Array[Array[Int64]] = []
for _ in 0.. String {
let hexs : Array[String] = [
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f",
]
let mut out = ""
for i in 0..> 4] + hexs[v & 0xF]
}
out
}
///|
/// 若 STREAMINFO 写了 MD5(非全零)则校验:交织小端、每样本符号扩展到
/// ceil(bps/8) 字节后紧凑打包(RFC 9639 §8.4,含 4/12/20 位等非 8 倍数
/// 位深;8/16/24/32 位的打包方式此前已与 ffmpeg 写入值交叉验证)。
fn verify_md5(
info : StreamInfo,
channels : Array[Array[Int32]],
) -> Unit raise FlacError {
let mut has_md5 = false
for i in 0.. 32 {
return
}
// RFC 9639 §8.4:位深非整数字节时,每个样本符号扩展到下一个完整
// 字节数(ceil(bps/8)),小端存储、跨声道交织。Int32 的补码表示
// 已天然完成符号扩展,取低 nbytes 字节即可——8/16/24/32 位时
// ceil(bps/8)==bps/8,与既有验证过的行为完全一致。
let nbytes = (bps + 7) / 8
let nch = channels.length()
if nch == 0 {
return
}
let frames = channels[0].length()
let ctx = @crypto.MD5::new()
let buf = @buffer.Buffer()
for f in 0..> (8 * i)) & 0xFF).to_byte())
}
}
if buf.length() >= 65536 {
ctx.update(buf.to_bytes())
buf.reset()
}
}
if buf.length() > 0 {
ctx.update(buf.to_bytes())
}
let fa = ctx.finalize()
let dbuf = @buffer.Buffer()
for i in 0.. FlacFile raise FlacError {
let meta = parse_metadata(data)
let info = meta.stream_info
let br = BitReader::new(data)
br.seek(meta.frame_offset * 8)
let out : Array[Array[Int32]] = []
for _ in 0.. 0 && decoded >= total {
break
}
match decode_next_block(br, info) {
None => eof = true
Some(b) => {
// total_samples 已知时截断到总长
let take64 = if total > 0 &&
decoded + Int64::from_int(b.block_size) > total {
total - decoded
} else {
Int64::from_int(b.block_size)
}
let take = take64.to_int()
for c in 0.. 0 && decoded < total {
raise Truncated(
offset=data.length(),
need=(total - decoded).to_int(),
actual=decoded.to_int(),
)
}
verify_md5(info, out)
{ info, channels: out, }
}