Skip to content

Instantly share code, notes, and snippets.

@UCIS
Created June 17, 2020 18:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save UCIS/c52757fef62239d502806f53b6700a58 to your computer and use it in GitHub Desktop.
Save UCIS/c52757fef62239d502806f53b6700a58 to your computer and use it in GitHub Desktop.
Opus in Ogg packetizer
class OggPacketizer : IPacketizer {
static UInt32[] checksumTable;
static UInt32 serial_counter = 1;
uint pageIndex = 0;
uint serial;
UInt64 position = 0;
static OggPacketizer() {
initChecksumTable();
}
public OggPacketizer() {
serial = serial_counter++;
}
public Byte[] BuildHeader(Byte channels, UInt32 originalSampleRate) {
Byte[] id_header = new Byte[19];
setUint32(id_header, 0, 1937076303); // Magic Signature 'Opus'
setUint32(id_header, 4, 1684104520); // Magic Signature 'Head'
setUint8(id_header, 8, 1); // Version
setUint8(id_header, 9, channels); // Channel count
setUint16(id_header, 10, 0); // pre-skip, don't need to skip any value
setUint32(id_header, 12, originalSampleRate); // original sample rate, any valid sample e.g 8000
setUint16(id_header, 16, 0); // output gain
setUint8(id_header, 18, 0); // channel map 0 = one stream: mono or stereo
id_header = getPage(id_header, 2, 0, 0); // headerType of ID header is 2 i.e beginning of stream
Byte[] comment_header = new Byte[20];
setUint32(comment_header, 0, 1937076303); // Magic Signature 'Opus'
setUint32(comment_header, 4, 1936154964); // Magic Signature 'Tags'
setUint32(comment_header, 8, 4); // Vendor Length
setUint32(comment_header, 12, 1633837924); // Vendor name 'abcd'
setUint32(comment_header, 16, 0); // User Comment List Length
comment_header = getPage(comment_header, 0, 0, 0); // headerType of comment header is 0
return ArrayUtil.Merge(id_header, comment_header);
}
private Byte[] getPage(Byte[] segmentData, Byte headerType, UInt32 page_index, UInt64 position) {
/* ref: https://tools.ietf.org/id/draft-ietf-codec-oggopus-00.html */
var segmentTable = new Byte[1]; /* segment table stores segment length map. always providing one single segment */
segmentTable = new Byte[segmentData.Length / 255 + 1];
var page = new Byte[27 + segmentTable.Length + segmentData.Length];
//segmentTable[0] = checked((Byte)segmentData.Length);
for (int i = 0; i < segmentTable.Length - 1; i++) segmentTable[i] = 255;
segmentTable[segmentTable.Length - 1] = (Byte)(segmentData.Length % 255);
setUint32(page, 0, 1399285583); // page headers starts with 'OggS'
setUint8(page, 4, 0); // Version
setUint8(page, 5, headerType); // 1 = continuation, 2 = beginning of stream, 4 = end of stream
setUint32(page, 6, (UInt32)position); // granuale position -1 i.e single packet per page. storing into bytes.
setUint32(page, 10, (UInt32)(position >> 32));
setUint32(page, 14, serial); // Bitstream serial number
setUint32(page, 18, page_index); // Page sequence number
setUint8(page, 26, (Byte)segmentTable.Length); // Number of segments in page, giving always 1 segment
segmentTable.CopyTo(page, 27); // Segment Table inserting at 27th position since page header length is 27
segmentData.CopyTo(page, 27 + segmentTable.Length); // inserting at 28th since Segment Table(1) + header length(27)
setUint32(page, 22, getChecksum(page)); // Checksum - generating for page data and inserting at 22th position into 32 bits
return page;
}
public Byte[] GetDataPage(Byte[] packet, UInt32 samples) {
position += samples;
return getPage(packet, 0, pageIndex++, position);
}
private static UInt32 getChecksum(Byte[] data) {
UInt32 checksum = 0;
for (int i = 0; i < data.Length; i++) {
checksum = (checksum << 8) ^ checksumTable[((checksum >> 24) & 0xff) ^ data[i]];
}
return checksum >> 0;
}
private static void initChecksumTable() {
checksumTable = new UInt32[256];
for (var i = 0; i < 256; i++) {
UInt32 r = (UInt32)(i << 24);
for (var j = 0; j < 8; j++) {
r = ((r & 0x80000000) != 0) ? ((r << 1) ^ 0x04c11db7) : (r << 1);
}
checksumTable[i] = (r & 0xffffffff);
}
}
private static void setUint8(Byte[] buffer, int index, Byte value) {
buffer[index] = value;
}
private static void setUint16(Byte[] buffer, int index, UInt16 value) {
buffer[index++] = (Byte)(value & 0xFF);
buffer[index++] = (Byte)((value >> 8) & 0xFF);
}
private static void setUint32(Byte[] buffer, int index, UInt32 value) {
buffer[index++] = (Byte)(value & 0xFF);
buffer[index++] = (Byte)((value >> 8) & 0xFF);
buffer[index++] = (Byte)((value >> 16) & 0xFF);
buffer[index++] = (Byte)((value >> 24) & 0xFF);
}
private static void setUint32(Byte[] buffer, int index, Int32 value) {
setUint32(buffer, index, (UInt32)value);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment