///|
/// Ogg FLAC muxing. The audio frames and STREAMINFO are produced by the
/// native encoder; this module only adds the Ogg mapping and page framing.
/// The output is one logical stream and is validated by OggDemux on the
/// reverse path.
const OGG_FLAC_SERIAL : UInt32 = 0x4d464c43U
///|
const OGG_MAX_PAGE_DATA : Int32 = 65025
///|
const OGG_MAX_PACKET_CHUNK : Int32 = 65024
///|
fn ogg_write_le32(out : Array[Byte], value : UInt32) -> Unit {
for i = 0; i < 4; i = i + 1 {
out.push((value >> (i * 8)).to_byte())
}
}
///|
fn ogg_write_le64(out : Array[Byte], value : UInt64) -> Unit {
for i = 0; i < 8; i = i + 1 {
out.push((value >> (i * 8)).to_byte())
}
}
///|
/// Build one Ogg page for a packet chunk. The lacing table is derived from
/// the chunk length; callers must keep a non-final chunk at a multiple of 255
/// bytes so the packet is marked as continuing without a terminator.
fn ogg_make_page(
packet : Bytes,
flags : Byte,
granule : UInt64,
serial : UInt32,
sequence : UInt32,
) -> Bytes raise FlacError {
let lacing : Array[Byte] = []
let mut remaining = packet.length()
while remaining >= 255 {
lacing.push(255)
remaining -= 255
}
lacing.push(remaining.to_byte())
if lacing.length() > 255 {
raise Limit("Ogg page requires more than 255 lacing values")
}
let page : Array[Byte] = [79, 103, 103, 83, 0, flags]
ogg_write_le64(page, granule)
ogg_write_le32(page, serial)
ogg_write_le32(page, sequence)
for _i = 0; _i < 4; _i = _i + 1 {
page.push(0)
}
page.push(lacing.length().to_byte())
for value in lacing {
page.push(value)
}
for byte in packet {
page.push(byte)
}
let crc = ogg_crc(page)
for i = 0; i < 4; i = i + 1 {
page[22 + i] = (crc >> (i * 8)).to_byte()
}
Bytes::from_array(page)
}
///|
/// Split a packet into pages. A packet larger than one page is represented by
/// full 255-lacing pages followed by a terminating page. Intermediate pages
/// carry a continuation flag and, for audio, an unknown granule.
fn ogg_packet_pages(
packet : Bytes,
serial : UInt32,
first_sequence : UInt32,
granule : UInt64,
eos : Bool,
) -> (Array[Bytes], UInt32) raise FlacError {
let pages : Array[Bytes] = []
let mut offset = 0
let mut sequence = first_sequence
let mut first = true
while packet.length() - offset > OGG_MAX_PACKET_CHUNK {
let remaining = packet.length() - offset
let take = if remaining == OGG_MAX_PAGE_DATA {
64770
} else {
OGG_MAX_PAGE_DATA
}
let chunk = packet[offset:offset + take].to_owned()
let flags = if first { 0 } else { 1 }
pages.push(
ogg_make_page(
chunk,
flags.to_byte(),
0xffffffffffffffffUL,
serial,
sequence,
),
)
offset += take
sequence += 1U
first = false
}
let tail = packet[offset:].to_owned()
let flags = if first { if eos { 4 } else { 0 } } else if eos { 5 } else { 1 }
pages.push(ogg_make_page(tail, flags.to_byte(), granule, serial, sequence))
(pages, sequence + 1U)
}
///|
fn ogg_metadata_block(block : Bytes, last : Bool) -> Bytes raise FlacError {
if block.length() < 4 {
raise Invalid(0L, "truncated native FLAC metadata block")
}
let out = block.to_array()
let flags = out[0].to_int()
out[0] = if last { (flags | 128).to_byte() } else { (flags & 127).to_byte() }
Bytes::from_array(out)
}
///|
fn ogg_first_packet(streaminfo : Bytes, header_count : Int32) -> Bytes {
let packet : Array[Byte] = [
127,
70,
76,
65,
67,
1,
0,
(header_count >> 8).to_byte(),
header_count.to_byte(),
]
// The Ogg mapping repeats the native marker before the STREAMINFO block.
packet.push(102)
packet.push(76)
packet.push(97)
packet.push(67)
for i = 0; i < streaminfo.length(); i = i + 1 {
let value = if i == 0 {
(streaminfo[i].to_int() & 127).to_byte()
} else {
streaminfo[i]
}
packet.push(value)
}
Bytes::from_array(packet)
}
///|
fn ogg_vorbis_comment(vendor : String) -> Bytes {
let text = @utf8.encode(vendor)
let payload_length = 4 + text.length() + 4
let block : Array[Byte] = [
132,
(payload_length >> 16).to_byte(),
(payload_length >> 8).to_byte(),
payload_length.to_byte(),
]
ogg_write_le32(block, text.length().to_uint())
for byte in text {
block.push(byte)
}
for _i = 0; _i < 4; _i = _i + 1 {
block.push(0)
}
Bytes::from_array(block)
}
///|
/// Read the native metadata blocks, retaining their raw four-byte headers.
/// The first block must be a 34-byte STREAMINFO. Returns the blocks and the
/// first audio-frame offset.
fn ogg_native_blocks(native : Bytes) -> (Array[Bytes], Int32) raise FlacError {
if native.length() < 42 || native[:4] != b"fLaC" {
raise Invalid(0L, "Ogg muxing requires a native FLAC stream")
}
let blocks : Array[Bytes] = []
let mut pos = 4
let mut first = true
while true {
if native.length() - pos < 4 {
raise Invalid(pos.to_int64(), "truncated FLAC metadata header")
}
let flags = native[pos].to_int()
let kind = flags & 127
let length = native[pos + 1].to_int() * 65536 +
native[pos + 2].to_int() * 256 +
native[pos + 3].to_int()
if native.length() - pos - 4 < length {
raise Invalid(pos.to_int64(), "truncated FLAC metadata payload")
}
if first && (kind != 0 || length != 34) {
raise Invalid(0L, "Ogg FLAC requires STREAMINFO first")
}
blocks.push(native[pos:pos + 4 + length].to_owned())
pos += 4 + length
first = false
if (flags & 128) != 0 {
break
}
}
(blocks, pos)
}
///|
/// Convert a complete native FLAC file into one Ogg FLAC logical stream.
/// The native stream is validated by build_index before its frames are
/// repackaged, so corrupt input cannot silently become a valid Ogg file.
pub fn ogg_from_flac(
native : Bytes,
serial~ : UInt32 = OGG_FLAC_SERIAL,
vendor~ : String = "MoonFLAC",
) -> Bytes raise FlacError {
let (blocks, first_frame) = ogg_native_blocks(native)
if first_frame >= native.length() {
raise Invalid(0L, "native FLAC contains no audio frames")
}
let metadata : Array[Bytes] = []
for i = 1; i < blocks.length(); i = i + 1 {
metadata.push(blocks[i])
}
if metadata.is_empty() {
metadata.push(ogg_vorbis_comment(vendor))
}
if metadata.length() > 65535 {
raise Limit("Ogg FLAC header packet count exceeds 16 bits")
}
let pages : Array[Bytes] = []
let first = ogg_first_packet(blocks[0], metadata.length())
pages.push(ogg_make_page(first, 2, 0UL, serial, 0U))
let mut sequence = 1U
for i = 0; i < metadata.length(); i = i + 1 {
let last = i == metadata.length() - 1
let block = ogg_metadata_block(metadata[i], last)
let (metadata_pages, next) = ogg_packet_pages(
block, serial, sequence, 0UL, false,
)
for page in metadata_pages {
pages.push(page)
}
sequence = next
}
let index = build_index(native)
if index.frames.is_empty() {
raise Invalid(0L, "native FLAC contains no indexed frames")
}
for i = 0; i < index.frames.length(); i = i + 1 {
let frame = index.frames[i]
let start = frame.offset.to_int()
let end = start + frame.bytes
if start < 0 || end > native.length() || frame.bytes <= 0 {
raise Invalid(frame.offset, "native FLAC frame location is out of bounds")
}
let packet = native[start:end].to_owned()
let eos = i == index.frames.length() - 1
let granule = (frame.sample + frame.samples.to_int64()).to_uint64()
let (frame_pages, next) = ogg_packet_pages(
packet, serial, sequence, granule, eos,
)
for page in frame_pages {
pages.push(page)
}
sequence = next
}
let out = @buffer.Buffer()
for page in pages {
out.write_bytes(page)
}
out.to_bytes()
}
///|
/// Encode interleaved integer PCM and wrap the result in Ogg FLAC. This is the
/// whole-buffer convenience API; native streaming remains available separately.
pub fn encode_ogg_pcm(
samples : Array[Int32],
config : EncoderConfig,
serial~ : UInt32 = OGG_FLAC_SERIAL,
vendor~ : String = "MoonFLAC",
) -> Bytes raise FlacError {
let native = encode_pcm(samples, config)
ogg_from_flac(native, serial~, vendor~)
}