//! \file OggStream.cs //! \date Sat Apr 08 01:43:58 2017 //! \brief libogg partial implementation. // // Copyright (C) 2017 by morkt // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // using System; using System.IO; using System.Text; using GameRes.Utility; namespace GameRes.Formats.Vorbis { internal sealed class OggBitStream : IDisposable { LsbBitStream m_input; public OggBitStream (OggPacket input) { // certainly an overhead to create a new stream for every packet, but it's so convenient var buf = new MemoryStream (input.Packet); m_input = new LsbBitStream (buf); } /// Read bits from a stream. /// -1 if there was not enough bits in a stream public int ReadBits (int count) { if (count <= 24) return m_input.GetBits (count); else if (count > 32) throw new ArgumentOutOfRangeException ("count", "Attempted to read more than 32 bits from OggBitStream."); int lo = m_input.GetBits (24); return m_input.GetBits (count - 24) << 24 | lo; } /// Read 8-bit integer from bitstream. /// -1 if there was not enough bits in a stream public int ReadByte () { return ReadBits (8); } /// Read 8-bit integer from bitstream. /// Thrown if there's not enough bits in a stream. public byte ReadUInt8 () { int b = ReadBits (8); if (-1 == b) throw new EndOfStreamException(); return (byte)b; } /// Read 32-bit integer from bitstream. /// Thrown if there's not enough bits in a stream. public int ReadInt32 () { int lo = ReadBits (16); int hi = ReadBits (16); if (-1 == lo || -1 == hi) throw new EndOfStreamException(); return hi << 16 | lo; } /// Attempt to read bytes from stream. /// Thrown if there's not enough bytes in a bitstream. public byte[] ReadBytes (int count) { var buf = new byte[count]; for (int i = 0; i < count; ++i) buf[i] = ReadUInt8(); return buf; } bool m_disposed = false; public void Dispose () { if (!m_disposed) { m_input.Dispose(); m_disposed = true; } } } // struct ogg_packet // https://xiph.org/ogg/doc/libogg/ogg_packet.html internal class OggPacket { public byte[] Packet; public bool BoS; public bool EoS; public long GranulePos; public long PacketNo; public void SetPacket (long packet_no, byte[] packet) { PacketNo = packet_no; Packet = packet; } } // struct ogg_stream_state // https://xiph.org/ogg/doc/libogg/ogg_stream_state.html internal class OggStreamState { byte[] BodyData; // bytes from packet bodies int BodyStorage; // storage elements allocated int BodyFill; // elements stored; fill mark int BodyReturned; // elements of fill returned int[] LacingVals; // The values that will go to the segment table granulepos values for headers. long[] GranuleVals; // Not compact this way, but it is simple coupled to the lacing fifo. int LacingStorage; int LacingFill; byte[] Header; // working space for header encode int HeaderFill; bool EoS; // set when we have buffered the last packet in the logical bitstream bool BoS; // set after we've written the initial page of a logical bitstream int SerialNo; int PageNo; long PacketNo; // sequence number for decode; the framing knows where there's a hole in the data, // but we need coupling so that the codec (which is in a seperate abstraction // layer) also knows about the gap long GranulePos; // https://xiph.org/ogg/doc/libogg/ogg_stream_init.html public OggStreamState (int serial_no) { BodyStorage = 0x4000; LacingStorage = 0x400; BodyData = new byte[BodyStorage]; LacingVals = new int[LacingStorage]; GranuleVals = new long[LacingStorage]; Header = new byte[282]; SerialNo = serial_no; } public void Clear () { BodyStorage = 0; BodyFill = 0; BodyReturned = 0; LacingStorage = 0; LacingFill = 0; HeaderFill = 0; EoS = false; BoS = false; SerialNo = 0; PageNo = 0; PacketNo = 0; GranulePos = 0; } public bool PacketIn (OggPacket op) { int bytes = op.Packet.Length; int lacing_vals = bytes / 255 + 1; if (BodyReturned > 0) { // advance packet data according to the body_returned pointer. // We had to keep it around to return a pointer into the buffer last call. BodyFill -= BodyReturned; if (BodyFill > 0) Buffer.BlockCopy (BodyData, BodyReturned, BodyData, 0, BodyFill); BodyReturned = 0; } // make sure we have the buffer storage if(!BodyExpand (bytes) || !LacingExpand (lacing_vals)) return false; // Copy in the submitted packet. Buffer.BlockCopy (op.Packet, 0, BodyData, BodyFill, op.Packet.Length); BodyFill += op.Packet.Length; // Store lacing vals for this packet int i; for (i = 0; i < lacing_vals-1; ++i) { LacingVals[LacingFill + i] = 0xFF; GranuleVals[LacingFill + i] = GranulePos; } LacingVals[LacingFill + i] = bytes % 0xFF; GranulePos = GranuleVals[LacingFill+i] = GranulePos; // flag the first segment as the beginning of the packet LacingVals[LacingFill] |= 0x100; LacingFill += lacing_vals; PacketNo++; EoS = op.EoS; return true; } public void Write (Stream output) { var page = new OggPage(); while (PageOut (page)) { output.Write (page.Header, 0, page.HeaderLength); output.Write (page.Body, page.BodyStart, page.BodyLength); } } public void Flush (Stream output) { var page = new OggPage(); while (Flush (page, true, 0x1000)) { output.Write (page.Header, 0, page.HeaderLength); output.Write (page.Body, page.BodyStart, page.BodyLength); } } public bool PageOut (OggPage page) { bool force = EoS && (LacingFill > 0) || (LacingFill > 0 && !BoS); return Flush (page, force, 0x1000); } bool BodyExpand (int needed) { if (BodyStorage - needed <= BodyFill) { if (BodyStorage > int.MaxValue - needed) { Clear(); return false; } int body_storage = BodyStorage + needed; if (body_storage < int.MaxValue - 1024) body_storage += 1024; Array.Resize (ref BodyData, body_storage); BodyStorage = body_storage; } return true; } bool LacingExpand (int needed) { if (LacingStorage - needed <= LacingFill) { if (LacingStorage > int.MaxValue - needed) { Clear(); return false; } int lacing_storage = LacingStorage + needed; if (lacing_storage < int.MaxValue - 32) lacing_storage += 32; Array.Resize (ref LacingVals, lacing_storage); Array.Resize (ref GranuleVals, lacing_storage); LacingStorage = lacing_storage; } return true; } bool Flush (OggPage og, bool force, int fill) { int maxvals = Math.Min (LacingFill, 0xFF); if (0 == maxvals) return false; // construct a page // decide how many segments to include int vals = 0; int acc = 0; long granule_pos = -1; // If this is the initial header case, the first page must only include // the initial header packet if (!BoS) // 'initial header page' case { granule_pos = 0; for (vals = 0; vals < maxvals; vals++) { if ((LacingVals[vals] & 0xFF) < 0xFF) { vals++; break; } } } else { int packets_done = 0; int packet_just_done = 0; for (vals = 0; vals < maxvals; vals++) { if (acc > fill && packet_just_done >= 4) { force = true; break; } acc += LacingVals[vals] & 0xFF; if ((LacingVals[vals] & 0xFF) < 0xFF) { granule_pos = GranuleVals[vals]; packet_just_done = ++packets_done; } else packet_just_done = 0; } if (0xFF == vals) force = true; } if (!force) return false; // construct the header in temp storage Encoding.ASCII.GetBytes ("OggS", 0, 4, Header, 0); // stream structure version Header[4] = 0; // continued packet flag? Header[5] = 0; if ((LacingVals[0] & 0x100) == 0) Header[5] |= 1; // first page flag? if (!BoS) Header[5] |= 2; // last page flag? if (EoS && LacingFill == vals) Header[5] |= 4; BoS = true; // 64 bits of PCM position LittleEndian.Pack (granule_pos, Header, 6); // 32 bits of stream serial number LittleEndian.Pack (SerialNo, Header, 14); // 32 bits of page counter (we have both counter and page header because this // val can roll over) if (-1 == PageNo) PageNo = 0; LittleEndian.Pack (PageNo, Header, 18); ++PageNo; int bytes = 0; // segment table Header[26] = (byte)vals; for (int i = 0; i < vals; ++i) bytes += Header[i+27] = (byte)LacingVals[i]; // set pointers in the ogg_page struct og.Header = Header; og.HeaderLength = HeaderFill = vals + 27; og.Body = BodyData; og.BodyStart = BodyReturned; og.BodyLength = bytes; // advance the lacing data and set the body_returned pointer LacingFill -= vals; Array.Copy (LacingVals, vals, LacingVals, 0, LacingFill); Array.Copy (GranuleVals, vals, GranuleVals, 0, LacingFill); BodyReturned += bytes; // calculate the checksum og.SetChecksum(); return true; } } // struct ogg_page // https://xiph.org/ogg/doc/libogg/ogg_page.html internal class OggPage { public byte[] Header; public int HeaderLength; public byte[] Body; public int BodyStart; public int BodyLength; public void SetChecksum () { Header[22] = Header[23] = Header[24] = Header[25] = 0; uint crc = Crc32Normal.UpdateCrc (0, Header, 0, HeaderLength); crc = Crc32Normal.UpdateCrc (crc, Body, BodyStart, BodyLength); LittleEndian.Pack (crc, Header, 22); } } }