///|
/// Create a new OGG demuxer from raw bytes.
fn new_ogg_demuxer(data : Bytes) -> OggDemuxer {
OggDemuxer::{ data, offset: 0, current_packet: [] }
}
///|
/// Read the next OGG page from the demuxer.
fn read_ogg_page(demuxer : OggDemuxer) -> OggPage raise AudioError {
let data = demuxer.data
let off = demuxer.offset
// Check sync pattern "OggS"
if off + 27 > data.length() {
raise AudioError::DecodeFailed("OGG: unexpected end of data")
}
if data[off].to_int() != 0x4F ||
data[off + 1].to_int() != 0x67 ||
data[off + 2].to_int() != 0x67 ||
data[off + 3].to_int() != 0x53 {
raise AudioError::DecodeFailed("OGG: invalid sync pattern")
}
let _version = read_u8(data, off + 4)
let header_type = read_u8(data, off + 5)
let granule_position = read_u64le(data, off + 6)
let serial_number = read_u32le(data, off + 14)
let page_sequence = read_u32le(data, off + 18)
let num_segments = read_u8(data, off + 26)
if off + 27 + num_segments > data.length() {
raise AudioError::DecodeFailed("OGG: truncated segment table")
}
// Read segment table
let segment_sizes : Array[Int] = []
let mut data_size = 0
for i in 0.. data.length() {
raise AudioError::DecodeFailed("OGG: truncated page data")
}
// Extract segments as individual byte arrays
let segments : Array[Bytes] = []
let mut seg_offset = off + header_size
for i in 0.. 0 {
let arr : Array[Byte] = []
for j in 0.. Bytes {
let arr : Array[Byte] = []
for part in parts {
for i in 0.. Bytes? raise AudioError {
let mut packet_parts : Array[Bytes] = demuxer.current_packet
demuxer.current_packet = []
while true {
if demuxer.offset >= demuxer.data.length() {
if packet_parts.length() > 0 {
let result = concat_bytes(packet_parts)
return Some(result)
}
return None
}
let page = read_ogg_page(demuxer)
for i, seg in page.segments {
packet_parts.push(seg)
let seg_size = seg.length()
if seg_size < 255 {
let packet = concat_bytes(packet_parts)
packet_parts = []
if i + 1 < page.segments.length() {
let remaining : Array[Bytes] = []
for j in (i + 1).. Array[Byte] {
let result : Array[Byte] = []
// Sync pattern "OggS"
result.push(b'\x4F')
result.push(b'\x67')
result.push(b'\x67')
result.push(b'\x53')
// Version
result.push(b'\x00')
// Header type
result.push(header_type.to_byte())
// Granule position (8 bytes, all zeros)
for _ in 0..<8 {
result.push(b'\x00')
}
// Serial number (4 bytes LE)
result.push((serial & 0xFF).to_byte())
result.push(((serial >> 8) & 0xFF).to_byte())
result.push(((serial >> 16) & 0xFF).to_byte())
result.push(((serial >> 24) & 0xFF).to_byte())
// Page sequence (4 bytes LE)
result.push((page_seq & 0xFF).to_byte())
result.push(((page_seq >> 8) & 0xFF).to_byte())
result.push(((page_seq >> 16) & 0xFF).to_byte())
result.push(((page_seq >> 24) & 0xFF).to_byte())
// CRC (4 bytes, zeros - we skip CRC validation)
for _ in 0..<4 {
result.push(b'\x00')
}
// Number of segments
let num_segments = segment_data.length()
result.push(num_segments.to_byte())
// Segment table
for seg in segment_data {
result.push(seg.length().to_byte())
}
// Segment data
for seg in segment_data {
for b in seg {
result.push(b)
}
}
result
}
///|
test "ogg demuxer single page single packet" {
let seg_data : Array[Byte] = [b'\x01', b'\x02', b'\x03']
let page = build_ogg_page(0x02, 1, 0, [seg_data]) // BOS flag
let bytes = Bytes::from_array(page)
let demuxer = new_ogg_demuxer(bytes)
let packet = next_packet(demuxer)
match packet {
Some(p) => {
assert_eq(p.length(), 3)
assert_eq(p[0].to_int(), 1)
assert_eq(p[1].to_int(), 2)
assert_eq(p[2].to_int(), 3)
}
None => assert_true(false)
}
}
///|
test "ogg demuxer multiple packets in one page" {
let seg1 : Array[Byte] = [b'\xAA', b'\xBB']
let seg2 : Array[Byte] = [b'\xCC']
let page = build_ogg_page(0x02, 1, 0, [seg1, seg2])
let bytes = Bytes::from_array(page)
let demuxer = new_ogg_demuxer(bytes)
let p1 = next_packet(demuxer)
match p1 {
Some(p) => assert_eq(p.length(), 2)
None => assert_true(false)
}
let p2 = next_packet(demuxer)
match p2 {
Some(p) => {
assert_eq(p.length(), 1)
assert_eq(p[0].to_int(), 0xCC)
}
None => assert_true(false)
}
}
///|
test "ogg demuxer returns None when exhausted" {
let seg : Array[Byte] = [b'\x01']
let page = build_ogg_page(0x02, 1, 0, [seg])
let bytes = Bytes::from_array(page)
let demuxer = new_ogg_demuxer(bytes)
next_packet(demuxer) |> ignore
let p = next_packet(demuxer)
assert_true(p is None)
}
///|
test "ogg demuxer error on invalid sync" {
let bytes = Bytes::from_array([b'\x00', b'\x00', b'\x00', b'\x00'])
let demuxer = new_ogg_demuxer(bytes)
let mut caught = false
try next_packet(demuxer) |> ignore catch {
AudioError::DecodeFailed(_) => caught = true
_ => ()
}
assert_true(caught)
}