// metadata.mbt
//
// FLAC 流自描述头:`fLaC` 魔数 + 元数据块链。
// STREAMINFO 是必且唯一的第一个块;其余块(PADDING/SEEKTABLE/VORBIS_COMMENT/…)
// 在 M1 只记录偏移与长度,内容解析留给后续增量。
// 所有输入校验都返回结构化错误——文件是外部不可信输入。
///|
/// STREAMINFO 的解析结果。
pub struct StreamInfo {
/// 最小块长(样本数)。
min_block_size : Int32
/// 最大块长(样本数)。
max_block_size : Int32
/// 最小帧长(字节),0 表示未知。
min_frame_size : Int32
/// 最大帧长(字节),0 表示未知。
max_frame_size : Int32
/// 采样率(Hz),1..655350。
sample_rate : Int32
/// 声道数,1..8。
channels : Int32
/// 位深,4..32。
bits_per_sample : Int32
/// 总样本数,0 表示未知。
total_samples : Int64
/// MD5 原始签名(16 字节)。
md5 : Bytes
}
///|
/// STREAMINFO 之外的元数据块(只记录位置,不拷贝数据)。
pub struct MetadataBlock {
/// 块类型码(1=PADDING 2=APPLICATION 3=SEEKTABLE 4=VORBIS_COMMENT 5=CUESHEET 6=PICTURE)。
block_type : Int32
/// 相对文件起始的字节偏移。
offset : Int32
/// 载荷字节长度。
length : Int32
}
///|
/// 元数据解析结果。
pub struct ParsedMetadata {
/// 流的 STREAMINFO。
stream_info : StreamInfo
/// 其余元数据块(不含 STREAMINFO)。
blocks : Array[MetadataBlock]
/// 第一个音频帧的字节偏移。
frame_offset : Int32
}
///|
/// 断言 `[offset, offset + length)` 在数据范围内,否则抛 Truncated。
fn require_range(
data : Bytes,
offset : Int32,
length : Int32,
) -> Unit raise FlacError {
if offset < 0 ||
length < 0 ||
offset > data.length() ||
data.length() - offset < length {
raise Truncated(offset~, need=length, actual=data.length())
}
}
///|
/// 读 1..3 字节大端序无符号整数(帧长/块长字段都在 24 位内,不会溢出 Int)。
fn read_be(
data : Bytes,
offset : Int32,
length : Int32,
) -> Int32 raise FlacError {
require_range(data, offset, length)
let mut v = 0
for i in 0.. StreamInfo raise FlacError {
require_range(data, offset, 34)
let min_block_size = read_be(data, offset, 2)
let max_block_size = read_be(data, offset + 2, 2)
let min_frame_size = read_be(data, offset + 4, 3)
let max_frame_size = read_be(data, offset + 7, 3)
// 字节 10..18:20 位采样率 | 3 位声道数-1 | 5 位位深-1 | 36 位总样本数
let b0 = data.unsafe_get(offset + 10).to_int()
let b1 = data.unsafe_get(offset + 11).to_int()
let b2 = data.unsafe_get(offset + 12).to_int()
let b3 = data.unsafe_get(offset + 13).to_int()
let sample_rate = (b0 << 12) | (b1 << 4) | (b2 >> 4)
let channels = ((b2 >> 1) & 0x7) + 1
let bits_per_sample = (((b2 & 0x1) << 4) | (b3 >> 4)) + 1
let mut total = Int64::from_int(b3 & 0x0F) << 32
for i in 14..<18 {
total = total |
(Int64::from_int(data.unsafe_get(offset + i).to_int()) << ((17 - i) * 8))
}
if sample_rate < 1 || sample_rate > 655350 {
raise BadMetadata(reason="采样率非法")
}
if bits_per_sample < 4 || bits_per_sample > 32 {
raise BadMetadata(reason="位深非法(合法范围 4..32)")
}
if channels < 1 || channels > 8 {
raise BadMetadata(reason="声道数非法")
}
if max_block_size < 16 ||
min_block_size < 1 ||
min_block_size > max_block_size {
raise BadMetadata(reason="块长范围非法")
}
let md5w = @buffer.Buffer()
for i in 18..<34 {
md5w.write_byte(data.unsafe_get(offset + i))
}
{
min_block_size,
max_block_size,
min_frame_size,
max_frame_size,
sample_rate,
channels,
bits_per_sample,
total_samples: total,
md5: md5w.to_bytes(),
}
}
///|
/// 解析 `fLaC` 魔数与全部元数据块,返回 STREAMINFO、其余块的位置表与
/// 第一个音频帧的偏移。
pub fn parse_metadata(data : Bytes) -> ParsedMetadata raise FlacError {
require_range(data, 0, 4)
if data.unsafe_get(0).to_int() != 0x66 ||
data.unsafe_get(1).to_int() != 0x4C ||
data.unsafe_get(2).to_int() != 0x61 ||
data.unsafe_get(3).to_int() != 0x43 {
raise NotFlac
}
let mut info : StreamInfo? = None
let blocks = []
let mut p = 4
let mut frame_offset = -1
while frame_offset < 0 {
require_range(data, p, 4)
let first = data.unsafe_get(p).to_int()
let last = (first >> 7) & 1
let block_type = first & 0x7F
// 24 位块长——不能按 32 位大端整数读,last 标志会把 Int 顶溢出
let length = (data.unsafe_get(p + 1).to_int() << 16) |
(data.unsafe_get(p + 2).to_int() << 8) |
data.unsafe_get(p + 3).to_int()
require_range(data, p + 4, length)
if block_type == 0 {
if info is Some(_) {
raise BadMetadata(reason="STREAMINFO 出现多次")
}
if length != 34 {
raise BadMetadata(reason="STREAMINFO 长度必须为 34")
}
info = Some(parse_streaminfo(data, p + 4))
} else if block_type == 127 {
// 127 是禁用值(避免与帧同步码混淆);7..126 为保留值,真实文件
// (xiph 官方测试文件含 type 126)在用,按 libFLAC 的做法跳过继续
raise BadMetadata(reason="元数据块类型 127 为禁用值")
} else {
blocks.push({ block_type, offset: p + 4, length, })
}
p = p + 4 + length
if last != 0 {
frame_offset = p
}
}
match info {
Some(stream_info) => { stream_info, blocks, frame_offset, }
None => raise BadMetadata(reason="缺少 STREAMINFO")
}
}