// vorbis_stream.mbt
//
// 高层解码编排:把 OGG 页流解析成 Vorbis 头并解码音频,产出 PCM。
// 对上层(CLI / WASM demo)暴露一个与 bit/floor/residue 无关的简洁接口。
///|
/// 从 OGG 页流解码 Vorbis 音频的状态机。
/// 依次消费 identification / comment / setup 三个 header packet,之后的 packet 视为音频帧。
/// 内部状态一律私有:`push_page` 逐步推进的状态机有先后依赖(header 未就绪就读不了
/// PCM),字段放开写会让外部绕过这个次序。读取统一走下面的访问器。
pub struct VorbisStream {
priv assembler : PacketAssembler
priv mut header_state : Int
priv mut info : VorbisInfo?
priv mut comment : VorbisComment?
// setup 不在流上留存:解析完即交给 Decoder,此后不再需要。
priv mut decoder : Decoder?
priv pcm : Array[Array[Float]]
// 已见到的最大 granule position,即流中合法的 PCM 帧总数。-1 表示未见到。
priv mut last_granule : Int64
}
///|
/// 新建空流,初始等待 identification header。
pub fn VorbisStream::new() -> VorbisStream {
{
assembler: PacketAssembler::new(),
header_state: 0,
info: None,
comment: None,
decoder: None,
pcm: [],
last_granule: -1L,
}
}
///|
/// 喂入一个 OGG 页,按当前状态解析 header 或解码音频 packet。
pub fn VorbisStream::push_page(
self : VorbisStream,
page : OggPage,
) -> Result[Unit, String] {
// granule position 记录该页结束时流中的合法 PCM 帧总数;-1 表示该页没有
// 结束任何 packet。取最大值而非「最后见到的一个」,避免非单调的异常流把
// 已经解出的样本裁掉。
if page.granule_position >= 0L && page.granule_position > self.last_granule {
self.last_granule = page.granule_position
}
let packets = self.assembler.push_page(page)
for packet in packets {
match self.header_state {
0 =>
match VorbisInfo::parse(packet) {
Ok(i) => {
self.info = Some(i)
self.header_state = 1
}
Err(e) => return Err(e)
}
1 =>
match VorbisComment::parse(packet) {
Ok(c) => {
self.comment = Some(c)
self.header_state = 2
}
Err(e) => return Err(e)
}
2 =>
match self.info {
Some(i) =>
match VorbisSetup::parse(packet, i.channels) {
Ok(s) => {
self.decoder = Some(Decoder::new(i, s))
self.header_state = 3
}
Err(e) => return Err(e)
}
None => return Err("setup header before identification")
}
_ =>
match self.decoder {
Some(d) =>
match d.decode_packet(BitReader::new(packet)) {
Ok(out) =>
for ch in 0.. return Err(e)
}
None => return Err("audio packet before setup")
}
}
}
Ok(())
}
///|
/// 三个 header 是否已全部解析完成。
pub fn VorbisStream::is_ready(self : VorbisStream) -> Bool {
self.header_state >= 3
}
///|
/// comment 头里的元数据(TITLE / ARTIST 等);comment 头尚未解析时为 `None`。
///
/// 播放器可在 `is_ready()` 为真后读它来显示曲目信息。
/// `decode_ogg` 返回的流一定已解析过 comment 头,除非文件在该头之前就结束了。
pub fn VorbisStream::metadata(self : VorbisStream) -> VorbisComment? {
self.comment
}
///|
/// 采样率(header 解析完成后可用,否则返回 0)。
pub fn VorbisStream::sample_rate(self : VorbisStream) -> Int {
match self.info {
Some(i) => i.sample_rate
None => 0
}
}
///|
/// 声道数。
pub fn VorbisStream::channels(self : VorbisStream) -> Int {
match self.info {
Some(i) => i.channels
None => 0
}
}
///|
/// 累计解码出的 PCM 样本(每声道一个 Float 数组,约 [-1,1])。
pub fn VorbisStream::pcm(self : VorbisStream) -> Array[Array[Float]] {
self.pcm
}
///|
/// 已见到的最大 granule position,即合法的 PCM 帧总数;尚未见到时返回 -1。
pub fn VorbisStream::granule_position(self : VorbisStream) -> Int64 {
self.last_granule
}
///|
/// 把 PCM 裁剪到 granule position 指定的长度。
///
/// Vorbis 按块编码,最后一个 packet 往往解出超过原始长度的样本(编码器补的
/// 尾巴)。granule position 才是权威的帧数,多出来的部分必须丢掉。
/// 增量喂页的调用方应在流结束后自行调用一次;`decode_ogg` 已经代为调用。
pub fn VorbisStream::trim_to_granule(self : VorbisStream) -> Unit {
if self.last_granule < 0L {
return
}
for ch in 0.. Int64::from_int(cur) {
cur
} else {
self.last_granule.to_int()
}
if limit < cur {
let trimmed : Array[Float] = []
for i in 0.. Result[VorbisStream, String] {
let stream = VorbisStream::new()
let mut offset = 0
while offset < data.length() {
match OggPage::parse(data, offset) {
Ok(page) => {
match stream.push_page(page) {
Ok(_) => ()
Err(e) => return Err(e)
}
offset += page.total_size()
}
Err(e) => return Err(e)
}
}
stream.trim_to_granule()
Ok(stream)
}