ogg
import ogg from "ogg";
// the packets of the first logical stream of an Ogg file held in memory
var buf: [65536]u8; // for packets that span pages
var s: ogg.Stream = ogg.stream(0, buf[0..]);
var chosen: bool = false;
for (p in ogg.pages(data)) {
if (!chosen && p.bos()) {
s = ogg.stream(p.serial, buf[0..]);
chosen = true;
}
s.push(p) or { io.println("{err}"); return 1; };
while (true) {
let pk: ?ogg.Packet = s.next();
if (pk == null) { break; }
handle(pk!.data, pk!.granule); // a codec's packet; its page's granule position or -1
}
}ogg reads the Ogg container: the pages an .ogg or .opus file is
made of, and the packets of one logical stream put back together across
them. It knows nothing of codecs: vorbis reads its
packets through it. It is pure and sans-I/O (bytes in), and freestanding
(t0): nothing allocates. A packet lying within one page is a view of that
page; one spanning pages is gathered in the buffer you give the stream.
Pages
| Function | Does |
|---|---|
parse_page(data) -> Page | The page at the start of data, its CRC checked |
parse_header(data) -> Page | The same without the CRC check (a quick scan of granule positions) |
pages(data) | The good pages of a buffer in order, for for (p in ogg.pages(data)): garbage and damaged pages stepped over (skipped counts their bytes) |
sync(data, start) -> ?i64 | Where the next capture pattern "OggS" begins, at or after start |
crc(data), crc_update(crc, data) | Ogg’s CRC-32 (polynomial 0x04c11db7, not reflected, from 0) |
A Page has data (all of it), flags, granule (the codec’s
position after the last packet that ends on the page; -1 when none
does), serial, sequence, crc, segments and header_len; and
methods len(), body(), lacing(i), continued(), bos(), eos(),
packets() (how many end on it), first_packet() and check().
let p: ogg.Page = ogg.parse_page(data[at..]) or {
// Truncated: hand it more bytes; Corrupt: no page starts here; BadCrc: damaged
return 1;
};
if (p.bos() && p.first_packet().len >= 7 && p.first_packet()[0] == 1) { /* "\x01vorbis": Vorbis */ }
at = at + p.len();parse_page reads only the page’s own bytes: what follows it in data
is not looked at. Truncated means the data ends inside the page, so a
reader that receives a file in pieces waits for more.
Packets
| Function | Does |
|---|---|
stream(serial, buf) -> Stream | Packets of the stream serial; spanning packets gathered in buf |
s.push(page) | Take the next page (a page of another stream is ignored) |
s.next() -> ?Packet | The next whole packet, or null until the next page |
s.reset() | Forget what is held (after a seek) |
A Packet has data, granule (its page’s granule position when it is
the last packet ending on the page, else -1), bos, eos, and hole:
pages were lost before it (a gap in the page sequence numbers), so the
packet before it is missing and a decoder should start afresh.
var buf: [65536]u8;
var s: ogg.Stream = ogg.stream(serial, buf[0..]);
s.push(page) or {
// TooLarge: a spanning packet did not fit buf; it is dropped
};A packet stays valid until the next push. Take every packet of a page
before pushing the next: what is left is dropped. 64 KB of buf holds
the packets of every common stream (a Vorbis setup header is a few KB).
Errors
OggError is Truncated (the data ends inside a page), Corrupt (no
page starts there), BadCrc (the page’s checksum does not match) or
TooLarge (a packet larger than the stream’s buffer). Nothing traps on
any input except Page.lacing with an index outside 0..segments.
Last modified: