/Multimedia/OpenTK/OggInputStream.cs
C# | 691 lines | 411 code | 82 blank | 198 comment | 68 complexity | ebe1bbbbf18f207bf76741a1146d351c MD5 | raw file
Possible License(s): Apache-2.0
- using System;
- using System.IO;
- using csogg;
- using csvorbis;
- using Delta.Utilities;
- using OpenTK.Audio.OpenAL;
-
- namespace Delta.Multimedia.OpenTK
- {
- /// <summary>
- /// Ogg input stream class used to decode an ogg vorbis input stream into
- /// usable pcm data for OpenAL.
- /// <para />
- /// As reference the original file can be found here:
- /// http://www.opentk.com/files/OggInputStream.cs
- /// </summary>
- internal class OggInputStream
- {
- #region Constants
- /// <summary>
- /// Default conversion buffer size is 8192 bytes!
- /// </summary>
- private const int DefaultConvsize = 4096 * 2;
-
- /// <summary>
- /// Initialize conversion buffer with 8192 bytes by default.
- /// The buffer will contain all channels at once, so the size of
- /// the convbuffer per channel can be calculated by DefaultConvsize / channels.
- /// See convsizePerChannel variable and the "InitVorbis" method.
- /// </summary>
- private static byte[] convbuffer = new byte[DefaultConvsize];
- #endregion
-
- #region Format (Public)
- /// <summary>
- /// Gets the format of the ogg file. Is either MONO16 or STEREO16.
- /// </summary>
- public ALFormat Format
- {
- get
- {
- return info.channels > 1
- ? ALFormat.Stereo16
- : ALFormat.Mono16;
- }
- }
- #endregion
-
- #region SampleRate (Public)
- public int SampleRate
- {
- get
- {
- return info.rate;
- }
- }
- #endregion
-
- #region Private
-
- #region convsizePerChannel (Private)
- /// <summary>
- /// The size of the convbuffer per channel. Currently set as
- /// the Default, but will be changed in the "InitVorbis" method to
- /// DefaultConvsize / channels.
- /// </summary>
- private static int convsizePerChannel = DefaultConvsize;
- #endregion
-
- #region _pcm (Private)
- /// <summary>
- /// temp vars
- /// </summary>
- private readonly float[][][] _pcm = new float[1][][];
- #endregion
-
- #region _index (Private)
- /// <summary>
- /// _index
- /// </summary>
- private readonly int[] _index;
- #endregion
-
- #region eos (Private)
- /// <summary>
- /// end of stream
- /// </summary>
- private bool eos;
- #endregion
-
- #region syncState (Private)
- /// <summary>
- /// sync and verify incoming physical bitstream
- /// </summary>
- private readonly SyncState syncState = new SyncState();
- #endregion
-
- #region streamState (Private)
- /// <summary>
- /// take physical pages, weld into a logical stream of packets
- /// </summary>
- private readonly StreamState streamState = new StreamState();
- #endregion
-
- #region page (Private)
- /// <summary>
- /// one Ogg bitstream page. Vorbis packets are inside
- /// </summary>
- private readonly Page page = new Page();
- #endregion
-
- #region packet (Private)
- /// <summary>
- /// one raw packet of data for decode
- /// </summary>
- private readonly Packet packet = new Packet();
- #endregion
-
- #region info (Private)
- /// <summary>
- /// struct that stores all the static vorbis bitstream settings
- /// </summary>
- private readonly Info info = new Info();
- #endregion
-
- #region comment (Private)
- /// <summary>
- /// struct that stores all the bitstream user comments
- /// </summary>
- private readonly Comment comment = new Comment();
- #endregion
-
- #region dspState (Private)
- /// <summary>
- /// central working state for the packet->PCM decoder
- /// </summary>
- private readonly DspState dspState = new DspState();
- #endregion
-
- #region block (Private)
- /// <summary>
- /// local working space for packet->PCM decode
- /// </summary>
- private readonly Block block;
- #endregion
-
- #region convbufferOff (Private)
- /// <summary>
- /// where we are in the convbuffer
- /// </summary>
- private int convbufferOff;
- #endregion
-
- #region convbufferSize (Private)
- /// <summary>
- /// bytes ready in convbuffer.
- /// </summary>
- private int convbufferSize;
- #endregion
-
- #region readDummy (Private)
- /// <summary>
- /// a dummy used by read() to read 1 byte.
- /// </summary>
- private readonly byte[] readDummy = new byte[1];
- #endregion
-
- #region input (Private)
- /// <summary>
- /// Input
- /// </summary>
- private readonly Stream input;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Creates an OggInputStream that decompressed the specified ogg file.
- /// </summary>
- /// <param name="setInput">Input file stream.</param>
- public OggInputStream(Stream setInput)
- {
- input = setInput;
- block = new Block(dspState);
- try
- {
- InitVorbis();
- _index = new int[info.channels];
- }
- catch (Exception ex)
- {
- Log.Warning("Failed to initialize ogg input stream: " + ex);
- eos = true;
- }
- }
- #endregion
-
- #region Read (Public)
- /// <summary>
- /// Reads the next byte of data from this input stream. The value byte is
- /// returned as an int in the range 0 to 255. If no byte is available
- /// because the end of the stream has been reached, the value -1 is
- /// returned. This method blocks until input data is available, the end
- /// of the stream is detected, or an exception is thrown.
- /// </summary>
- /// <returns>the next byte of data, or -1 if the end of the stream is
- /// reached.</returns>
- public int Read()
- {
- int retVal = Read(readDummy, 0, 1);
- return
- retVal == -1
- ? -1
- : readDummy[0];
- }
-
- /// <summary>
- /// Reads up to len bytes of data from the input stream into an
- /// array of bytes.
- /// </summary>
- /// <param name="b">the buffer into which the data is read.</param>
- /// <param name="off">the start offset of the data.</param>
- /// <param name="len">the maximum number of bytes read.</param>
- /// <returns>
- /// The total number of bytes read into the buffer, or -1 if there is no
- /// more data because the end of the stream has been reached.
- /// </returns>
- public int Read(byte[] b, int off, int len)
- {
- if (eos)
- {
- return 0;
- }
-
- int bytesRead = 0;
- while (!eos &&
- (len > 0))
- {
- FillConvbuffer();
-
- if (!eos)
- {
- int bytesToCopy = Math.Min(len, convbufferSize - convbufferOff);
- Array.Copy(convbuffer, convbufferOff, b, off, bytesToCopy);
- convbufferOff += bytesToCopy;
- bytesRead += bytesToCopy;
- len -= bytesToCopy;
- off += bytesToCopy;
- } // if
- } // while
-
- return bytesRead;
- }
-
- /// <summary>
- /// Reads up to len bytes of data from the input stream into a ByteBuffer.
- /// </summary>
- /// <param name="stream"></param>
- /// <param name="off">the start offset of the data.</param>
- /// <param name="len">the maximum number of bytes read.</param>
- /// <returns>the total number of bytes read into the buffer, or -1 if
- /// there is no more data because the end of the stream has been reached.
- /// </returns>
- public int Read(MemoryStream stream, int off, int len)
- {
- if (eos)
- {
- return 0;
- }
-
- stream.Seek(off, SeekOrigin.Begin);
- int bytesRead = 0;
- while (!eos &&
- (len > 0))
- {
- FillConvbuffer();
-
- if (!eos)
- {
- int bytesToCopy = Math.Min(len, convbufferSize - convbufferOff);
- stream.Write(convbuffer, convbufferOff, bytesToCopy);
- convbufferOff += bytesToCopy;
- bytesRead += bytesToCopy;
- len -= bytesToCopy;
- }
- } // while
-
- return bytesRead;
- }
- #endregion
-
- #region Skip (Public)
- /// <summary>
- /// Skips over and discards n bytes of data from the input stream.
- /// The skip method may, for a variety of reasons, end up skipping over
- /// some smaller number of bytes, possibly 0. The actual number of bytes
- /// skipped is returned.
- /// </summary>
- /// <param name="n">the number of bytes to be skipped.</param>
- /// <returns>the actual number of bytes skipped.</returns>
- public long Skip(long n)
- {
- int bytesRead = 0;
- while (bytesRead < n)
- {
- int res = Read();
- if (res == -1)
- {
- break;
- }
-
- bytesRead++;
- } // while
-
- return bytesRead;
- }
- #endregion
-
- #region ToString (Public)
- /// <summary>
- /// Gets information on the ogg.
- /// </summary>
- /// <returns>Returns a string representing this ogg input stream.</returns>
- public override string ToString()
- {
- return
- "Version=" + info.version + "\n" +
- "Channels=" + info.channels + "\n" +
- "Rate (hz)=" + info.rate;
- }
- #endregion
-
- #region Methods (Private)
-
- #region FillConvbuffer
- /// <summary>
- /// Helper function. Decodes a packet to the convbuffer if it is empty.
- /// Updates convbufferSize, convbufferOff, and eos.
- /// </summary>
- private void FillConvbuffer()
- {
- if (convbufferOff >= convbufferSize)
- {
- convbufferSize = LazyDecodePacket();
- convbufferOff = 0;
- if (convbufferSize == -1)
- {
- eos = true;
- }
- }
- }
- #endregion
-
- #region InitVorbis
- /// <summary>
- /// Initalizes the vorbis stream. Reads the stream until info
- /// and comment are read.
- /// </summary>
- private void InitVorbis()
- {
- // Now we can read pages
- syncState.init();
-
- // grab some data at the head of the stream. We want the first page
- // (which is guaranteed to be small and only contain the Vorbis
- // stream initial header) We need the first page to get the stream
- // serialno.
-
- // submit a 4k block to libvorbis' Ogg layer
- int index = syncState.buffer(4096);
- byte[] buffer = syncState.data;
- int bytes = input.Read(buffer, 0, buffer.Length);
- syncState.wrote(bytes);
- // Get the first page.
- if (syncState.pageout(page) != 1)
- {
- // have we simply run out of data? If so, we're done.
- if (bytes < 4096)
- {
- return; //break;
- }
-
- // error case. Must not be Vorbis data
- throw new Exception("Input does not appear to be an Ogg bitstream.");
- } // if
-
- // Get the serial number and set up the rest of decode.
- // serialno first; use it to set up a logical stream
- streamState.init(page.serialno());
-
- // extract the initial header from the first page and verify that the
- // Ogg bitstream is in fact Vorbis data
-
- // I handle the initial header first instead of just having the code
- // read all three Vorbis headers at once because reading the initial
- // header is an easy way to identify a Vorbis bitstream and it's
- // useful to see that functionality seperated out.
-
- info.init();
- comment.init();
- if (streamState.pagein(page) < 0)
- {
- // error; stream version mismatch perhaps
- throw new Exception("Error reading first page of Ogg bitstream data.");
- }
-
- if (streamState.packetout(packet) != 1)
- {
- // no page? must not be vorbis
- throw new Exception("Error reading initial header packet.");
- }
-
- if (info.synthesis_headerin(comment, packet) < 0)
- {
- // error case; not a vorbis header
- throw new Exception(
- "This Ogg bitstream does not contain Vorbis audio data.");
- }
-
- // At this point, we're sure we're Vorbis. We've set up the logical
- // (Ogg) bitstream decoder. Get the comment and codebook headers and
- // set up the Vorbis decoder
-
- // The next two packets in order are the comment and codebook headers.
- // They're likely large and may span multiple pages. Thus we read
- // and submit data until we get our two packets, watching that no
- // pages are missing. If a page is missing, error out; losing a
- // header page is the only place where missing data is fatal.
- int i = 0;
- while (i < 2)
- {
- while (i < 2)
- {
- int result = syncState.pageout(page);
- if (result == 0)
- {
- break; // Need more data
- }
-
- // Don't complain about missing or corrupt data yet. We'll
- // catch it at the packet output phase
- if (result == 1)
- {
- streamState.pagein(page);
-
- // We can ignore any errors here as they'll also become apparent
- // at packetout.
- while (i < 2)
- {
- result = streamState.packetout(packet);
- if (result == 0)
- {
- break;
- }
-
- if (result == -1)
- {
- // Uh oh; data at some point was corrupted or missing!
- // We can't tolerate that in a header. Die.
- throw new Exception("Corrupt secondary header. Exiting.");
- }
-
- info.synthesis_headerin(comment, packet);
- i++;
- } // while
- } // if
- } // while
-
- // no harm in not checking before adding more
- index = syncState.buffer(4096);
- buffer = syncState.data;
- bytes = input.Read(buffer, index, 4096);
-
- // NOTE: This is a bugfix. read will return -1 which will mess up
- // syncState.
- if (bytes < 0)
- {
- bytes = 0;
- }
-
- if (bytes == 0 &&
- i < 2)
- {
- throw new Exception(
- "End of file before finding all Vorbis headers!");
- }
-
- syncState.wrote(bytes);
- } // while
-
- convsizePerChannel = DefaultConvsize / info.channels;
-
- // OK, got and parsed all three headers. Initialize the Vorbis
- // packet->PCM decoder. central decode state
- dspState.synthesis_init(info);
-
- // local state for most of the decode so multiple block decodes can
- // proceed in parallel. We could init multiple vorbis_block structures
- // for vd here.
- block.init(dspState);
- }
- #endregion
-
- #region DecodePacket
- /// <summary>
- /// Decodes a packet.
- /// </summary>
- /// <returns>Packet length to write into convbufferSize</returns>
- private int DecodePacket()
- {
- // check the endianes of the computer.
- bool bigEndian = !BitConverter.IsLittleEndian;
-
- if (block.synthesis(packet) == 0)
- {
- // test for success!
- dspState.synthesis_blockin(block);
- }
-
- // **pcm is a multichannel float vector. In stereo, for
- // example, pcm[0] is left, and pcm[1] is right. samples is
- // the size of each channel. Convert the float values
- // (-1.<=range<=1.) to whatever PCM format and write it out
- int convOff = 0;
- int samples;
- while ((samples = dspState.synthesis_pcmout(_pcm, _index)) > 0)
- {
- float[][] pcm = _pcm[0];
- int bout =
- samples < convsizePerChannel
- ? samples
- : convsizePerChannel;
-
- // convert floats to 16 bit signed ints (host order) and interleave
- for (int i = 0; i < info.channels; i++)
- {
- int ptr = (i << 1) + convOff;
-
- int mono = _index[i];
-
- for (int j = 0; j < bout; j++)
- {
- int val = (int)(pcm[i][mono + j] * 32767);
-
- // might as well guard against clipping
- val = Math.Max(-32768, Math.Min(32767, val));
- val |=
- val < 0
- ? 0x8000
- : 0;
-
- convbuffer[ptr + 0] =
- (byte)
- (bigEndian
- ? unchecked((int)((uint)val >> 8))
- : val);
- convbuffer[ptr + 1] =
- (byte)
- (bigEndian
- ? val
- : unchecked((int)((uint)val >> 8)));
-
- ptr += (info.channels) << 1;
- } // for
- } // for
-
- convOff += 2 * info.channels * bout;
-
- // Tell orbis how many samples were consumed
- dspState.synthesis_read(bout);
- } // while
-
- return convOff;
- }
- #endregion
-
- #region LazyDecodePacket
- /// <summary>
- /// Decodes the next packet.
- /// </summary>
- /// <returns>Bytes read into convbuffer of -1 if end of file</returns>
- private int LazyDecodePacket()
- {
- int result = GetNextPacket();
- if (result == -1)
- {
- return -1;
- }
-
- // We have a packet. Decode it.
- return DecodePacket();
- }
- #endregion
-
- #region GetNextPacket
- /// <summary>
- /// Get the next packet in the music file.
- /// </summary>
- /// <returns>
- /// Returns -1 if getting the next packet failed, otherwise 0.
- /// </returns>
- private int GetNextPacket()
- {
- // get next packet.
- bool fetchedPacket = false;
- while (eos == false &&
- fetchedPacket == false)
- {
- int result1 = streamState.packetout(packet);
- if (result1 == 0)
- {
- // no more packets in page. Fetch new page.
- int result2 = 0;
- while (eos == false &&
- result2 == 0)
- {
- result2 = syncState.pageout(page);
- if (result2 == 0)
- {
- FetchData();
- }
- }
-
- // return if we have reaced end of file.
- if ((result2 == 0) && (page.eos() != 0))
- {
- return -1;
- }
-
- if (result2 == 0)
- {
- // need more data fetching page..
- FetchData();
- }
- else if (result2 == -1)
- {
- //throw new Exception("syncState.pageout(page) result == -1");
- Log.Test("syncState.pageout(page) result == -1");
- return -1;
- }
- else
- {
- int result3 = streamState.pagein(page);
- }
- } // if
- else if (result1 == -1)
- {
- //throw new Exception("streamState.packetout(packet) result == -1");
- Log.Test("streamState.packetout(packet) result == -1");
- return -1;
- }
- else
- {
- fetchedPacket = true;
- }
- } // while
-
- return 0;
- }
- #endregion
-
- #region FetchData
- /// <summary>
- /// Copies data from input stream to syncState.
- /// </summary>
- private void FetchData()
- {
- if (eos == false)
- {
- // copy 4096 bytes from compressed stream to syncState.
- int index = syncState.buffer(4096);
- if (index < 0)
- {
- eos = true;
- return;
- }
- int bytes = input.Read(syncState.data, index, 4096);
- syncState.wrote(bytes);
- if (bytes == 0)
- {
- eos = true;
- }
- }
- }
- #endregion
-
- #endregion
- }
- }
-