// ogg_page.mbt
//
// OGG 页解析。一个 OGG 页由三部分组成:
// * 固定 27 字节页头(magic + version + header_type + granule + serial + seq + checksum + segments 计数)
// * segment table(页头最后一个字节指定条目数,每项是一个 segment 的字节长度)
// * body(所有 segment 字节的拼接)
//
// 本模块负责解析单个页并做 CRC 校验。把多个页的 segment 重组成逻辑 packet
// 的工作放在下一步(packet 重组)实现。
///|
/// OGG 页固定头长度(不含 segment table 和 body)。
const OGG_HEADER_SIZE : Int = 27
///|
/// 页头类型标志位。
pub const FLAG_CONTINUED : Int = 0x01
///|
pub const FLAG_BOS : Int = 0x02
///|
pub const FLAG_EOS : Int = 0x04
///|
/// 解析后的 OGG 页。
///
/// `data` 保存原始字节序列,`offset` 是页在其中的起始位置。
/// segment table 位于 `data[offset + 27 .. offset + 27 + n_segments)`,
/// body 紧随其后,长度为 `body_len`。
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
}
///|
/// 解析从 `data[offset]` 开始的一个 OGG 页。
///
/// 成功返回页结构;失败返回错误描述(magic 不符 / 版本不支持 / 数据截断 / CRC 校验失败)。
pub fn OggPage::parse(data : Bytes, offset : Int) -> Result[OggPage, String] {
// 页头长度不足
if data.length() - offset < OGG_HEADER_SIZE {
return Err("truncated Ogg page header")
}
// magic "OggS"
if data.unsafe_get(offset) != b'O' ||
data.unsafe_get(offset + 1) != b'g' ||
data.unsafe_get(offset + 2) != b'g' ||
data.unsafe_get(offset + 3) != b'S' {
return Err("not an Ogg page (bad magic)")
}
// version 必须为 0
if data.unsafe_get(offset + 4) != b'\x00' {
return Err("unsupported Ogg version")
}
let header_type = data.unsafe_get(offset + 5).to_int()
let granule_position = data
.unsafe_read_uint64_le(offset + 6)
.reinterpret_as_int64()
let serial_number = data.unsafe_read_uint32_le(offset + 14)
let page_sequence = data.unsafe_read_uint32_le(offset + 18)
let checksum = data.unsafe_read_uint32_le(offset + 22)
let n_segments = data.unsafe_get(offset + 26).to_int()
// segment table 长度足够
if data.length() - offset < OGG_HEADER_SIZE + n_segments {
return Err("truncated Ogg segment table")
}
// 计算 body 长度(所有 segment 长度之和)
let mut body_len = 0
for i in 0.. Int {
OGG_HEADER_SIZE + self.n_segments + self.body_len
}
///|
/// 返回 segment table 中第 `i` 项的字节长度(`0 <= i < n_segments`)。
pub fn OggPage::segment_len(self : OggPage, i : Int) -> Int {
self.data.unsafe_get(self.offset + OGG_HEADER_SIZE + i).to_int()
}