// opus_stream.mbt
//
// Ogg Opus 流的打开入口:按 RFC 7845 §4 的页序与连续性规则逐页组装逻辑
// 包,解析两个头包并收集音频数据包。
// 输入为外部不可信字节流:任何越界、顺序或字段非法都返回 Err。
///|
/// 一个已通过全部头部校验的 Ogg Opus 逻辑流。
pub struct OpusStream {
head : OpusHead
tags : OpusTags
audio : Array[Bytes]
}
///|
/// 打开一个 Ogg Opus 字节流。
///
/// 校验范围(RFC 7845 §4):
/// * 首页必须带 BOS 且 granule 为 0,OpusHead 独占首页并完成于首页;
/// * OpusTags 为第二个包,其完成页 granule 必须为 0;
/// * 页序号必须从 0 连续;serial 全程一致;出现第二个 BOS 即为链式流
/// (本项目明确不支持,直接拒绝);
/// * CONTINUED 标志与上一页未完成状态严格一致,否则该页首包不可解码;
/// * EOS 页之后不得再有页;缺 EOS 的整页截断流按 §4 容忍,
/// 但残缺页视为损坏并报错。
///
/// 当前范围:容器层解析,不含音频帧解码。
pub fn open_opus(data : Bytes) -> Result[OpusStream, String] {
let assembler = PacketAssembler::new()
let mut head : OpusHead? = None
let mut tags : OpusTags? = None
let mut head_done = false
let mut tags_done = false
let audio : Array[Bytes] = []
let mut offset = 0
let mut page_index = 0
let mut serial = 0U
let mut eos_seen = false
while offset < data.length() {
if eos_seen {
return Err("page after end of stream")
}
let page = match OggPage::parse(data, offset) {
Ok(p) => p
Err(e) => return Err(e)
}
let bos = (page.header_type & FLAG_BOS) != 0
if page_index == 0 {
if !bos {
return Err("first page must have BOS flag")
}
serial = page.serial_number
} else {
if bos {
return Err("chained Ogg Opus streams are not supported")
}
if page.serial_number != serial {
return Err("page serial number mismatch")
}
if page.page_sequence != page_index.reinterpret_as_uint() {
return Err("page sequence mismatch")
}
}
// §4:continued 标志必须与上一页的未完成状态严格一致,否则该页
// 首包不可解码——容器层直接拒绝而不是带病下传。
let cont = (page.header_type & FLAG_CONTINUED) != 0
let partial = assembler.has_partial()
if partial && !cont {
return Err("continued packet lost its continuation page")
}
if !partial && cont {
return Err("continued flag without a pending packet")
}
let size = page.total_size()
let done = assembler.push_page(page)
if page_index == 0 && (done.length() != 1 || assembler.has_partial()) {
return Err("OpusHead must be alone on the first page")
}
for pkt in done {
if !head_done {
// §4:ID 头页的 granule 必须为 0
if page.granule_position != 0L {
return Err("granule must be zero on ID header page")
}
let h = match parse_opus_head(pkt) {
Ok(v) => v
Err(e) => return Err(e)
}
head = Some(h)
head_done = true
} else if !tags_done {
// §4:注释头完成页的 granule 必须为 0
if page.granule_position != 0L {
return Err("granule must be zero on comment header page")
}
let t = match parse_opus_tags(pkt) {
Ok(v) => v
Err(e) => return Err(e)
}
tags = Some(t)
tags_done = true
} else {
audio.push(pkt)
}
}
if (page.header_type & FLAG_EOS) != 0 {
eos_seen = true
}
offset += size
page_index += 1
}
let h = match head {
Some(v) => v
None => return Err("missing OpusHead")
}
let t = match tags {
Some(v) => v
None => return Err("missing OpusTags")
}
Ok({ head: h, tags: t, audio, })
}
///|
/// 输出声道数(OpusHead channel count)。
pub fn OpusStream::channels(self : OpusStream) -> Int {
self.head.channels
}
///|
/// 起播时应丢弃的 48 kHz 采样数(RFC 7845 §4.2 pre-skip)。
pub fn OpusStream::pre_skip(self : OpusStream) -> Int {
self.head.pre_skip
}
///|
/// 编码前原始输入采样率(仅元数据,不是播放采样率;0 表示未指定)。
pub fn OpusStream::input_sample_rate(self : OpusStream) -> UInt {
self.head.input_sample_rate
}
///|
/// 解码输出应施加的增益(Q7.8 dB,带符号)。
pub fn OpusStream::output_gain(self : OpusStream) -> Int {
self.head.output_gain
}
///|
/// 声道映射族(0 / 1 / 255;保留值 2..254 按 255 语义解析)。
pub fn OpusStream::mapping_family(self : OpusStream) -> Int {
self.head.mapping_family
}
///|
/// 每包编码流数 N(OpusHead mapping table;family 0 隐含为 1)。
pub fn OpusStream::stream_count(self : OpusStream) -> Int {
self.head.stream_count
}
///|
/// 其中配置为双声道输出的耦合流数 M(family 0 隐含为 C-1)。
pub fn OpusStream::coupled_count(self : OpusStream) -> Int {
self.head.coupled_count
}
///|
/// 声道映射表(family 0 为空,语义等价于下标 0..C-1;255 表示纯静音声道)。
pub fn OpusStream::channel_mapping(self : OpusStream) -> Array[Byte] {
self.head.channel_mapping
}
///|
/// vendor 字符串(OpusTags)。
pub fn OpusStream::vendor(self : OpusStream) -> String {
self.tags.vendor
}
///|
/// 用户注释列表(OpusTags comments,UTF-8 解码,无效字节替换为 U+FFFD)。
pub fn OpusStream::comments(self : OpusStream) -> Array[String] {
self.tags.comments
}
///|
/// 音频数据包数量(OpusTags 之后的全部逻辑包,尚未做 TOC 解析)。
pub fn OpusStream::packet_count(self : OpusStream) -> Int {
self.audio.length()
}
///|
/// 第 `i` 个音频数据包;越界返回 `None`。
pub fn OpusStream::packet(self : OpusStream, i : Int) -> Bytes? {
if i < 0 || i >= self.audio.length() {
None
} else {
Some(self.audio[i])
}
}