// incremental.mbt
//
// 真正的增量流式解码:调用方按任意块大小持续喂入字节,本模块内部
// 维护一个未消费字节缓冲区,元数据凑齐即解析、每凑齐一帧即解码吐出,
// 不要求一次性拿到完整文件。与 `FlacStream`(一次性拿到完整 Bytes,
// 按帧惰性解码)不同,这里解决的是"边收边解"。
//
// 契约:FLAC 顶层结构(元数据块、帧)全部字节对齐、顺序排列,
// 因此 `next_frame_byte` 位置是确定的,不需要 `decode`/`FlacStream`
// 那套"扫描同步码重同步"的容错——增量场景下"当前缓冲区里扫不到
// 同步码"不代表"整条流没有同步码",只代表"还没传完"。这里把
// "数据不足"(可继续 feed)与"真正的格式错误"严格区分:数据不足
// 时 `next` 返回 None 等待更多输入;`finish()` 声明输入结束后,
// `total_samples` 已知且未解满才升级为 Truncated 错误。
///|
/// 增量 FLAC 解码器。
pub struct FlacFeeder {
/// 未消费字节(`buf[0]` 对应整个流的绝对偏移 `base_offset`)。
mut buf : Array[Byte]
/// `buf[0]` 在整条流中的绝对字节偏移。
mut base_offset : Int32
/// 已解析的 STREAMINFO(元数据凑齐前为 None)。
mut info : StreamInfo?
/// 元数据块(不含 STREAMINFO)。
mut blocks : Array[MetadataBlock]
/// 下一帧的绝对起始字节偏移(元数据凑齐后有效)。
mut next_frame_byte : Int32
/// 元数据是否已完整解析。
mut meta_done : Bool
/// `finish()` 是否已被调用(不会再有新输入)。
mut finished : Bool
/// 已交付给调用方的样本数。
mut decoded : Int64
/// 流是否已确定结束。
mut eof : Bool
}
///|
/// 创建空的增量解码器,等待第一次 `feed`。
pub fn FlacFeeder::new() -> FlacFeeder {
{
buf: [],
base_offset: 0,
info: None,
blocks: [],
next_frame_byte: 0,
meta_done: false,
finished: false,
decoded: 0,
eof: false,
}
}
///|
/// 追加一段输入字节。可在任意时刻调用任意次,块大小不限。
pub fn FlacFeeder::feed(self : FlacFeeder, chunk : Bytes) -> Unit {
for i in 0.. Bytes {
let w = @buffer.Buffer()
for i in 0.. Unit {
let drop = self.next_frame_byte - self.base_offset
if drop <= 0 {
return
}
let keep : Array[Byte] = []
for i in drop.. Bool {
self.eof
}
///|
/// 尝试解析元数据:数据不足返回 false(继续等 feed),解析成功返回
/// true,真正的格式错误直接上抛。
fn FlacFeeder::try_metadata(self : FlacFeeder) -> Bool raise FlacError {
if self.meta_done {
return true
}
let data = self.snapshot()
match
(Some(parse_metadata(data)) catch {
FlacError::Truncated(..) => None
e => raise e
}) {
None => false
Some(meta) => {
self.info = Some(meta.stream_info)
self.blocks = meta.blocks
self.next_frame_byte = meta.frame_offset
self.meta_done = true
true
}
}
}
///|
/// 取下一块样本(planar,`[声道][样本]`)。
///
/// - `Some(block)`:解出了一个完整块,继续调用 `next` 取下一块。
/// - `None`:当前无法再产出一个完整块。可能是"还需要更多数据"
/// (`is_done()` 为 false),也可能是"流已结束"(`is_done()` 为 true)。
/// - 抛 `Truncated`:仅在 `finish()` 已调用、`total_samples` 已知
/// 且未解满时发生(与整文件 `decode` 对截断文件的处理一致)。
pub fn FlacFeeder::next(
self : FlacFeeder,
) -> Array[Array[Int32]]? raise FlacError {
if self.eof {
return None
}
if !self.try_metadata() {
return None
}
let info = match self.info {
Some(i) => i
None => abort("元数据已解析但 info 缺失")
}
let total = info.total_samples
if total > 0 && self.decoded >= total {
self.eof = true
return None
}
// —— 解一帧:按 next_frame_byte 精确定位,不做扫描重同步 ——
let data = self.snapshot()
let br = BitReader::new(data)
br.seek((self.next_frame_byte - self.base_offset) * 8)
let h : FrameHeader = parse_frame_header(br, info) catch {
FlacError::Truncated(..) => {
// 帧头数据还没喂够:等更多输入。finish() 之后且 total_samples
// 已知时,这是真正的截断,升级为错误。
if self.finished && total > 0 {
self.raise_truncation(info)
}
return None
}
e => raise e
}
let temp : Array[Array[Int64]] = []
for _ in 0.. {
// 帧头解析成功但帧体没喂够:等更多输入,规则同上。
if self.finished && total > 0 {
self.raise_truncation(info)
}
return None
}
e => raise e
}
self.next_frame_byte = self.base_offset + (br.pos >> 3)
self.compact()
let samples = decorrelate(h, temp)
// total_samples 已知时截断到总长(本帧是最后一帧才会触发)
let take64 = if total > 0 &&
self.decoded + Int64::from_int(h.block_size) > total {
total - self.decoded
} else {
Int64::from_int(h.block_size)
}
let take = take64.to_int()
let out : Array[Array[Int32]] = []
for c in 0.. Unit raise FlacError {
let total = info.total_samples
raise Truncated(
offset=self.base_offset + self.buf.length(),
need=(total - self.decoded).to_int(),
actual=self.decoded.to_int(),
)
}
///|
/// 声明不会再有新输入。若 `total_samples` 已知且尝试解完缓冲区内
/// 剩余完整帧后仍未解满,抛 Truncated;`total_samples` 未知时,
/// 缓冲区尾部不足一帧的数据按整文件 `decode` 的口径静默忽略。
pub fn FlacFeeder::finish(self : FlacFeeder) -> Unit raise FlacError {
self.finished = true
let info = match self.info {
Some(i) => i
None =>
raise Truncated(
offset=self.buf.length(),
need=4,
actual=self.buf.length(),
)
}
// 先把缓冲区里还剩的完整帧尽量解完
while !self.eof {
let before = self.next_frame_byte
let _ = self.next()
if self.next_frame_byte == before {
break
}
}
let total = info.total_samples
if total > 0 && self.decoded < total {
self.raise_truncation(info)
}
self.eof = true
}