/Source/AC.AL/Format/Ogg/OggStream.cs
C# | 416 lines | 290 code | 62 blank | 64 comment | 61 complexity | 06feed3a9996a6f69dd30fb03f76bf53 MD5 | raw file
Possible License(s): LGPL-2.0
- using System;
- using System.IO;
- using csogg;
- using csvorbis;
-
- namespace AC.AL.Format.Ogg
- {
- public class OggStream : IBaseAudioStream
- {
- #region Constants
- /// <summary>
- ///The endianes of the computer.
- /// </summary>
- private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian;
- #endregion
-
- #region Private
- // temp vars
- private float[][][] tempPcm = new float[1][][];
- private int[] index;
-
- /// <summary>
- /// Flag if the stream reached the end.
- /// </summary>
- private bool eos;
-
- // sync and verify incoming physical bitstream
- private SyncState syncState;
-
- // take physical pages, weld into a logical stream of packets
- private StreamState streamState;
-
- // one Ogg bitstream page. Vorbis packets are inside
- private Page page;
-
- // one raw packet of data for decode
- private Packet packet;
-
- // struct that stores all the static vorbis bitstream settings
- private Info info;
-
- // struct that stores all the bitstream user comments
- private Comment comment;
-
- // central working state for the packet->PCM decoder
- private DspState dspState;
-
- // local working space for packet->PCM decode
- private Block block;
-
- /// Conversion buffer size
- private static int convsize = 4096 * 2;
-
- // Conversion buffer
- private static byte[] convbuffer = new byte[convsize];
-
- // where we are in the convbuffer
- private int convbufferOff;
-
- // bytes ready in convbuffer.
- private int convbufferSize;
-
- private Stream input;
- #endregion
-
- public int Channels
- {
- get { return info.channels; }
- }
-
- public int Samplerate
- {
- get { return info.rate; }
- }
-
- public OggStream(Stream setInput)
- {
- input = setInput;
- Rewind();
- }
-
- #region Rewind
- public void Rewind()
- {
- input.Position = 0;
- eos = false;
-
- syncState = new SyncState();
- streamState = new StreamState();
- page = new Page();
- packet = new Packet();
- info = new Info();
- comment = new Comment();
- dspState = new DspState();
- block = new Block(dspState);
- try
- {
- InitVorbis();
- index = new int[info.channels];
- }
- catch
- {
- eos = true;
- }
- }
- #endregion
-
- #region Read
- public int Read(byte[] buffer, int length)
- {
- if (eos)
- {
- return 0;
- }
-
- int offset = 0;
- int bytesRead = 0;
- while (eos == false &&
- length > 0)
- {
- // Fill convbuffer
- if (convbufferOff >= convbufferSize)
- {
- int result = GetNextPacket(packet);
- convbufferSize = result == -1 ? result : DecodePacket(packet);
- convbufferOff = 0;
- if (convbufferSize == -1)
- {
- eos = true;
- }
- }
-
- if (eos == false)
- {
- int bytesToCopy = Math.Min(length, convbufferSize - convbufferOff);
- Array.Copy(convbuffer, convbufferOff, buffer, offset, bytesToCopy);
- convbufferOff += bytesToCopy;
- bytesRead += bytesToCopy;
- length -= bytesToCopy;
- offset += bytesToCopy;
- }
- }
-
- return bytesRead;
- }
- #endregion
-
- #region InitVorbis
- 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;
- }
-
- throw new Exception("Input does not appear to be an Ogg bitstream.");
- }
-
- // 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)
- {
- // Need more data
- break;
- }
- // Don't complain about missing or corrupt data yet. We'll
- // catch it at the packet output phase
-
- if (result == 1)
- {
- // we can ignore any errors here
- streamState.pagein(page);
- // as they'll also become apparent
- // at packetout
- while (i < 2)
- {
- result = streamState.packetout(packet);
- if (result == 0)
- {
- break;
- }
-
- if (result == -1)
- {
- throw new Exception("Corrupt secondary header. Exiting.");
- }
-
- info.synthesis_headerin(comment, packet);
- i++;
- }
- }
- }
-
- // 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);
- }
-
- convsize = 4096 / info.channels;
- dspState.synthesis_init(info);
- block.init(dspState);
- }
- #endregion
-
- #region DecodePacket
- private int DecodePacket(Packet packet)
- {
- if (block.synthesis(packet) == 0)
- {
- // test for success!
- dspState.synthesis_blockin(block);
- }
-
- int convOff = 0;
- int samples;
- while ((samples = dspState.synthesis_pcmout(tempPcm, index)) > 0)
- {
- float[][] pcm = tempPcm[0];
- int bout = (samples < convsize ? samples : convsize);
-
- // convert floats to 16 bit signed ints (host order) and interleave
- for (int i = 0; i < 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)(IsLittleEndian ?
- val :
- unchecked((int)((uint)val >> 8)));
- convbuffer[ptr + 1] = (byte)(IsLittleEndian ?
- unchecked((int)((uint)val >> 8)) :
- val);
-
- ptr += Channels << 1;
- }
- }
-
- convOff += 2 * Channels * bout;
-
- // Tell vorbis how many samples we've consumed
- dspState.synthesis_read(bout);
- }
-
- return convOff;
- }
- #endregion
-
- #region GetNextPacket
- private int GetNextPacket(Packet packet)
- {
- // 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)
- {
- return -1;
- }
- else
- {
- streamState.pagein(page);
- }
- }
- else if (result1 == -1)
- {
- return -1;
- }
- else
- {
- fetchedPacket = true;
- }
- }
-
- return 0;
- }
- #endregion
-
- #region FetchData
- 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
- }
- }