//! \file Vorbis.cs //! \date Fri Apr 07 21:07:30 2017 //! \brief partial libvorbis port. // // Only parts crucial for FSB5 decoding got implemented. // Parts that are not used in FSB5 decoder are left out completely or commented out. // // 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. // ----------------------------------------------------------------------------- // // libvorbis Copyright (c) 2002-2008 Xiph.org Foundation // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // // - Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // - Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // - Neither the name of the Xiph.org Foundation nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION // OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // using System; using System.Collections.Generic; using System.IO; using System.Text; namespace GameRes.Formats.Vorbis { // struct vorbis_info // https://xiph.org/vorbis/doc/libvorbis/vorbis_info.html class VorbisInfo { public int Version; public int Channels; public int Rate; public int BitrateUpper; public int BitrateNominal; public int BitrateLower; public CodecSetupInfo CodecSetup = new CodecSetupInfo(); const int TransormB = 1; const int WindowB = 1; const int TimeB = 1; const int FloorB = 2; const int ResB = 3; const int MapB = 1; Func[] FloorMethods; public VorbisInfo () { FloorMethods = new Func[] { UnpackFloor0, UnpackFloor1 }; } // https://xiph.org/vorbis/doc/libvorbis/vorbis_synthesis_headerin.html public void SynthesisHeaderin (VorbisComment vc, OggPacket op) { using (var input = new OggBitStream (op)) { int packtype = input.ReadUInt8(); var buf = input.ReadBytes (6); if (!buf.AsciiEqual ("vorbis")) throw new InvalidDataException ("Not an Ogg/Vorbis stream."); switch (packtype) { case 1: if (!op.BoS) throw InvalidHeader(); if (Rate != 0) throw InvalidHeader(); UnpackInfo (input); break; case 3: if (0 == Rate) throw InvalidHeader(); vc.UnpackComment (input); break; case 5: if (0 == Rate || null == vc.Vendor) throw InvalidHeader(); UnpackBooks (input); break; default: throw InvalidHeader(); } } } internal static int iLog (uint num) { int bits = 0; while (num != 0) { ++bits; num >>= 1; } return bits; } internal static int CountBits (uint num) { int bits = 0; if (num != 0) --num; while (num != 0) { ++bits; num >>= 1; } return bits; } /// /// Count number of bits set in . /// internal static int CountSetBits (uint num) { int bits = 0; while (num != 0) { bits += (int)num & 1; num >>= 1; } return bits; } // https://www.xiph.org/vorbis/doc/libvorbis/vorbis_packet_blocksize.html public int PacketBlockSize (OggPacket op) { using (var input = new OggBitStream (op)) { // Check the packet type if (input.ReadBits (1) != 0) throw new InvalidDataException ("Not an audio data packet."); int modebits = 0; for (int v = CodecSetup.Modes; v > 1; v >>= 1) { modebits++; } // read our mode and pre/post windowsize int mode = input.ReadBits (modebits); if (-1 == mode) throw new InvalidDataException ("Invalid Ogg/Vorbis packet."); return CodecSetup.BlockSizes[CodecSetup.ModeParam[mode].BlockFlag]; } } void UnpackInfo (OggBitStream input) { Version = input.ReadInt32(); if (Version != 0) throw new InvalidDataException ("Invalid Vorbis encoder version."); Channels = input.ReadUInt8(); Rate = input.ReadInt32(); BitrateUpper = input.ReadInt32(); BitrateNominal = input.ReadInt32(); BitrateLower = input.ReadInt32(); CodecSetup.BlockSizes[0] = 1 << input.ReadBits (4); CodecSetup.BlockSizes[1] = 1 << input.ReadBits (4); if (input.ReadBits (1) != 1) throw InvalidHeader(); } void UnpackBooks (OggBitStream input) { // codebooks CodecSetup.Books = input.ReadUInt8() + 1; if (CodecSetup.Books <= 0) throw InvalidHeader(); for (int i = 0; i < CodecSetup.Books; ++i) { var param = StaticBookUnpack (input); if (null == param) throw InvalidHeader(); CodecSetup.BookParam[i] = param; } // time backend settings; hooks are unused int times = input.ReadBits (6) + 1; if (times <= 0) throw InvalidHeader(); for (int i = 0; i < times; ++i) { int test = input.ReadBits (16); if (test < 0 || test >= TimeB) throw InvalidHeader(); } // floor backend settings CodecSetup.Floors = input.ReadBits (6) + 1; if (CodecSetup.Floors <= 0) throw InvalidHeader(); for (int i = 0; i < CodecSetup.Floors; i++) { int floor_type = input.ReadBits (16); if (floor_type < 0 || floor_type >= FloorB) throw InvalidHeader(); CodecSetup.FloorType[i] = floor_type; var param = FloorMethods[floor_type] (input); if (null == param) throw InvalidHeader(); CodecSetup.FloorParam[i] = param; } // residue backend settings CodecSetup.Residues = input.ReadBits (6) + 1; if (CodecSetup.Residues <= 0) throw InvalidHeader(); for (int i = 0; i < CodecSetup.Residues; ++i) { int residue_type = input.ReadBits (16); if (residue_type < 0 || residue_type >= ResB) throw InvalidHeader(); CodecSetup.ResidueType[i] = residue_type; var param = UnpackResidue (input); if (null == param) throw InvalidHeader(); CodecSetup.ResidueParam[i] = param; } // map backend settings CodecSetup.Maps = input.ReadBits (6) + 1; if (CodecSetup.Maps <= 0) throw InvalidHeader(); for (int i = 0; i < CodecSetup.Maps; ++i) { int map_type = input.ReadBits (16); if (map_type < 0 || map_type >= MapB) throw InvalidHeader(); CodecSetup.MapType[i] = map_type; var param = UnpackMapping (input); if (null == param) throw InvalidHeader(); CodecSetup.MapParam[i] = param; } // mode settings CodecSetup.Modes = input.ReadBits (6) + 1; if (CodecSetup.Modes <= 0) throw InvalidHeader(); for (int i = 0; i < CodecSetup.Modes; ++i) { CodecSetup.ModeParam[i].BlockFlag = input.ReadBits (1); CodecSetup.ModeParam[i].WindowType = input.ReadBits (16); CodecSetup.ModeParam[i].TransformType = input.ReadBits (16); CodecSetup.ModeParam[i].Mapping = input.ReadBits (8); if (CodecSetup.ModeParam[i].WindowType >= WindowB || CodecSetup.ModeParam[i].TransformType >= WindowB || CodecSetup.ModeParam[i].Mapping >= CodecSetup.Maps || CodecSetup.ModeParam[i].Mapping < 0) throw InvalidHeader(); } if (input.ReadBits (1) != 1) throw InvalidHeader(); } StaticCodebook StaticBookUnpack (OggBitStream input) { // make sure alignment is correct if (input.ReadBits (24) != 0x564342) return null; var s = new StaticCodebook(); // first the basic parameters s.dim = input.ReadBits (16); s.entries = input.ReadBits (24); if (-1 == s.entries) return null; if (iLog ((uint)s.dim) + iLog ((uint)s.entries) > 24) return null; // codeword ordering.... length ordered or unordered? switch (input.ReadBits (1)) { case 0: // allocated but unused entries? int unused = input.ReadBits (1); // unordered s.lengthlist = new byte[s.entries]; // allocated but unused entries? if (unused > 0) { // yes, unused entries for(int i = 0; i < s.entries; ++i) { if (input.ReadBits (1) > 0) { int num = input.ReadBits (5); if (-1 == num) return null; s.lengthlist[i] = (byte)(num+1); } else s.lengthlist[i] = 0; } } else { // all entries used; no tagging for (int i = 0; i < s.entries; ++i) { int num = input.ReadBits (5); if (-1 == num) return null; s.lengthlist[i] = (byte)(num+1); } } break; case 1: // ordered int length = input.ReadBits (5) + 1; if (0 == length) return null; s.lengthlist = new byte[s.entries]; for (int i = 0; i < s.entries; ) { int num = input.ReadBits (iLog ((uint)(s.entries-i))); if (-1 == num || length > 32 || num > s.entries-i || (num > 0 && ((num-1) >> (length-1)) > 1)) return null; for (int j = 0; j < num; ++j, ++i) s.lengthlist[i] = (byte)length; length++; } break; default: return null; } // Do we have a mapping to unpack? switch((s.maptype = input.ReadBits (4))) { case 0: // no mapping break; case 1: case 2: // implicitly populated value mapping // explicitly populated value mapping s.q_min = input.ReadInt32(); s.q_delta = input.ReadInt32(); s.q_quant = input.ReadBits (4) + 1; s.q_sequencep = input.ReadBits (1); if (-1 == s.q_sequencep) return null; int quantvals = 0; switch (s.maptype) { case 1: quantvals = s.dim == 0 ? 0 : s.Maptype1Quantvals(); break; case 2: quantvals = s.entries * s.dim; break; } // quantized values s.quantlist = new int[quantvals]; for (int i = 0; i < quantvals; ++i) s.quantlist[i] = input.ReadBits (s.q_quant); if (quantvals > 0 && s.quantlist[quantvals-1] == -1) return null; break; default: // EOF return null; } // all set return s; } VorbisInfoFloor UnpackFloor0 (OggBitStream input) { var info = new VorbisInfoFloor(); int order = input.ReadBits (8); int rate = input.ReadBits (16); int barkmap = input.ReadBits (16); int ampbits = input.ReadBits (6); int ampdB = input.ReadBits (8); int numbooks = input.ReadBits (4) + 1; if (order < 1 || rate < 1 || barkmap < 1 || numbooks < 1) return null; for (int j = 0; j < numbooks; ++j) { int books = input.ReadByte(); if (books < 0 || books >= CodecSetup.Books) return null; } return info; } VorbisInfoFloor UnpackFloor1 (OggBitStream input) { int max_class = -1; var info = new VorbisInfoFloor(); // read partitions int partitions = input.ReadBits (5); // only 0 to 31 legal var partition_class = new int[partitions]; for (int j = 0; j < partitions; ++j) { partition_class[j] = input.ReadBits (4); // only 0 to 15 legal if (partition_class[j] < 0) return null; if (max_class < partition_class[j]) max_class = partition_class[j]; } // read partition classes var class_dim = new int[max_class+1]; for (int j = 0; j < max_class+1; ++j) { class_dim[j] = input.ReadBits (3) + 1; // 1 to 8 int class_subs = input.ReadBits (2); // 0,1,2,3 bits if (class_subs < 0) return null; if (class_subs > 0) input.ReadBits (8); // class_book for (int k = 0; k < (1 << class_subs); ++k) { int class_subbook = input.ReadBits (8) - 1; // info.class_subbook[j][k] } } // read the post list int mult = input.ReadBits (2) + 1; // only 1,2,3,4 legal now int rangebits = input.ReadBits (4); if (rangebits < 0) return null; int count = 0; // var postlist = new int[VorbisInfoFloor.Posit + 2]; for (int j = 0, k = 0; j < partitions; ++j) { count += class_dim[partition_class[j]]; if (count > VorbisInfoFloor.Posit) return null; for (; k < count; ++k) { int t = input.ReadBits (rangebits); if (t < 0 || t >= (1 << rangebits)) return null; // postlist[k+2] = t; } } // postlist[0] = 0; // postlist[1] = 1< postlist[i]).ToArray(); for (int j = 1; j < count+2; j++) if(postlist[indices[j-1]] == postlist[indices[j]]) return null; */ return info; } object UnpackResidue (OggBitStream input) { var info = new VorbisInfoResidue(); info.begin = input.ReadBits (24); info.end = input.ReadBits (24); info.grouping = input.ReadBits (24) + 1; info.partitions = input.ReadBits (6) + 1; info.groupbook = input.ReadBits (8); // check for premature EOP if (info.groupbook < 0) return null; int acc = 0; for (int j = 0; j < info.partitions; ++j) { int cascade = input.ReadBits (3); int cflag = input.ReadBits (1); if (cflag < 0) return null; if (cflag > 0) { int c = input.ReadBits (5); if (c < 0) return null; cascade |= c << 3; } // info.secondstages[j] = cascade; acc += CountSetBits ((uint)cascade); } for (int j = 0; j < acc; ++j) { int book = input.ReadBits (8); if (book < 0) return null; if (book >= CodecSetup.Books) return null; // if (CodecSetup.book_param[book].maptype == 0) return null; // info.booklist[j] = book; } if (info.groupbook >= CodecSetup.Books) return null; /* int entries = CodecSetup.book_param[info.groupbook].entries; int dim = CodecSetup.book_param[info.groupbook].dim; int partvals = 1; if (dim < 1) return null; while (dim > 0) { partvals *= info.partitions; if (partvals > entries) return null; dim--; } info.partvals = partvals; */ return info; } VorbisInfoMapping UnpackMapping (OggBitStream input) { var info = new VorbisInfoMapping(); int b = input.ReadBits (1); if (b < 0) return null; if (b > 0) { info.submaps = input.ReadBits (4) + 1; if (info.submaps <= 0) return null; } else info.submaps = 1; b = input.ReadBits (1); if (b < 0) return null; if (b > 0) { info.coupling_steps = input.ReadBits (8) + 1; if (info.coupling_steps <= 0) return null; for (int i = 0; i < info.coupling_steps; ++i) { int bits = CountBits ((uint)Channels); int testM = input.ReadBits (bits); int testA = input.ReadBits (bits); if (testM < 0 || testA < 0 || testM == testA || testM >= Channels || testA >= Channels) return null; } } if (input.ReadBits (2) != 0) return null; if (info.submaps > 1) { for (int i = 0; i < Channels; ++i) { int chmuxlist = input.ReadBits (4); if (chmuxlist >= info.submaps || chmuxlist < 0) return null; } } for (int i = 0; i < info.submaps; ++i) { input.ReadByte(); // time submap unused int floorsubmap = input.ReadByte(); if (floorsubmap >= CodecSetup.Floors || floorsubmap < 0) return null; int residuesubmap = input.ReadByte(); if (residuesubmap >= CodecSetup.Residues || residuesubmap < 0) return null; } return info; } internal static InvalidDataException InvalidHeader () { return new InvalidDataException ("Invalid header in Ogg/Vorbis stream."); } } // struct vorbis_comment // https://xiph.org/vorbis/doc/libvorbis/vorbis_comment.html class VorbisComment { public List Comments; public byte[] Vendor; internal static readonly byte[] EncodeVendorString = Encoding.UTF8.GetBytes ("mørkt GARbro 20170407"); public VorbisComment () { Comments = new List(); } // https://xiph.org/vorbis/doc/libvorbis/vorbis_commentheader_out.html public void HeaderOut (OggPacket packet) { using (var buf = new MemoryStream()) using (var output = new BinaryWriter (buf)) { // preamble output.Write ((byte)3); output.Write ("vorbis".ToCharArray()); // vendor output.Write (EncodeVendorString.Length); output.Write (EncodeVendorString); // comments output.Write (Comments.Count); foreach (var comment in Comments) { if (comment != null && comment.Length > 0) { output.Write (comment.Length); output.Write (comment); } else { output.Write (0); } } output.Write ((byte)1); output.Flush(); packet.SetPacket (1, buf.ToArray()); packet.BoS = false; packet.EoS = false; packet.GranulePos = 0; } } internal void UnpackComment (OggBitStream input) { int vendor_len = input.ReadInt32(); if (vendor_len < 0) throw VorbisInfo.InvalidHeader(); var vendor = input.ReadBytes (vendor_len); int count = input.ReadInt32(); if (count < 0) throw VorbisInfo.InvalidHeader(); var comments = new List (count); for (int i = 0; i < count; ++i) { int len = input.ReadInt32(); if (len < 0) throw VorbisInfo.InvalidHeader(); var bytes = input.ReadBytes (len); comments.Add (bytes); } if (input.ReadBits (1) != 1) throw VorbisInfo.InvalidHeader(); this.Vendor = vendor; this.Comments = comments; } } // codec_setup_info class CodecSetupInfo { public int[] BlockSizes = new int[2]; public int Modes; public int Maps; public int Floors; public int Residues; public int Books; internal VorbisInfoMode[] ModeParam = new VorbisInfoMode[64]; internal int[] ResidueType = new int[64]; internal object[] ResidueParam = new object[64]; internal int[] FloorType = new int[64]; internal object[] FloorParam = new object[64]; internal int[] MapType = new int[64]; internal VorbisInfoMapping[] MapParam = new VorbisInfoMapping[64]; internal StaticCodebook[] BookParam = new StaticCodebook[256]; } // struct vorbis_info_mode struct VorbisInfoMode { public int BlockFlag; public int WindowType; public int TransformType; public int Mapping; } // struct static_codebook class StaticCodebook { public int dim; // codebook dimensions (elements per vector) public int entries; // codebook entries public byte[] lengthlist; // codeword lengths in bits // mapping *************************************************************** public int maptype; // 0=none // 1=implicitly populated values from map column // 2=listed arbitrary values // The below does a linear, single monotonic sequence mapping. public int q_min; // packed 32 bit float; quant value 0 maps to minval public int q_delta; // packed 32 bit float; val 1 - val 0 == delta public int q_quant; // bits: 0 < quant <= 16 public int q_sequencep; // bitflag public int[] quantlist; // map == 1: (int)(entries^(1/dim)) element column map // map == 2: list of dim*entries quantized entry vals internal int Maptype1Quantvals () { int vals = (int)Math.Floor (Math.Pow ((float)entries, 1.0f / dim)); // the above *should* be reliable, but we'll not assume that FP is // ever reliable when bitstream sync is at stake; verify via integer // means that vals really is the greatest value of dim for which // vals^b->bim <= b->entries. // treat the above as an initial guess for (;;) { int acc = 1; int acc1 = 1; for (int i = 0; i < dim; ++i) { acc *= vals; acc1 *= vals+1; } if (acc <= entries && acc1 > entries) break; if (acc > entries) vals--; else vals++; } return vals; } } class VorbisInfoMapping { public int submaps; public int coupling_steps; } class VorbisInfoFloor { public const int Posit = 63; } class VorbisInfoResidue { public int begin; public int end; public int grouping; public int partitions; public int groupbook; } }