From 869307b732b4025f9b85dc0a63115749dfe78f5d Mon Sep 17 00:00:00 2001 From: Peter Hultqvist Date: Sat, 2 Jun 2012 15:09:00 +0200 Subject: [PATCH 1/2] Extracted the Ogg Vorbis decoder into Decoder. Moved example code into Program. --- OggDecoder/DebugWriter.cs | 27 +++ OggDecoder/Decoder.cs | 352 +++++++++++++++++++++++++++++++++ OggDecoder/OggDecodeStream.cs | 358 +--------------------------------- OggDecoder/OggDecoder.cs | 54 ----- OggDecoder/OggDecoder.csproj | 31 +-- OggDecoder/OggPlayer.cs | 19 -- OggDecoder/Program.cs | 48 +++++ 7 files changed, 447 insertions(+), 442 deletions(-) create mode 100644 OggDecoder/DebugWriter.cs create mode 100644 OggDecoder/Decoder.cs delete mode 100644 OggDecoder/OggDecoder.cs delete mode 100644 OggDecoder/OggPlayer.cs create mode 100644 OggDecoder/Program.cs diff --git a/OggDecoder/DebugWriter.cs b/OggDecoder/DebugWriter.cs new file mode 100644 index 0000000..a6719bd --- /dev/null +++ b/OggDecoder/DebugWriter.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics; + +namespace OggDecoder +{ + class DebugWriter + { + [Conditional("DEBUG")] + public void WriteLine() + { + Debug.WriteLine(String.Empty); + } + + [Conditional("DEBUG")] + public void WriteLine(string s) + { + Debug.WriteLine(s); + } + + [Conditional("DEBUG")] + public void WriteLine(object o) + { + Debug.WriteLine(o.ToString()); + } + } +} + diff --git a/OggDecoder/Decoder.cs b/OggDecoder/Decoder.cs new file mode 100644 index 0000000..89bbc58 --- /dev/null +++ b/OggDecoder/Decoder.cs @@ -0,0 +1,352 @@ +using System; +using System.IO; +using System.Diagnostics; +using System.Text; + +using csogg; +using csvorbis; + +namespace OggDecoder +{ + public static class Decoder + { + private const int HEADER_SIZE = 36; + + /// + /// Decodes a stream of Ogg Vorbis data into wav file format. + /// + /// + /// A MemoryStream with the entire decoded stream. + /// + /// + /// Ogg Vorbis data to be decoded + /// + /// + /// Stream to write wav data. + /// If writeWavHeader is true then this stream must be seekable. + /// + /// + /// Write wav header in the beginning of the returned stream. + /// + public static void DecodeStream(Stream input, Stream output, bool writeWavHeader) + { + int convsize = 4096 * 2; + byte[] convbuffer = new byte[convsize]; // take 8k out of the data segment, not the stack + long start = output.Position; + + DebugWriter s_err = new DebugWriter(); + + if (writeWavHeader && !output.CanSeek) + throw new ArgumentException("To write wav header, the output stream must be seekable.", "output"); + + if (writeWavHeader) + output.Seek(HEADER_SIZE, SeekOrigin.Current); // reserve place for WAV header + + SyncState oy = new SyncState(); // sync and verify incoming physical bitstream + StreamState os = new StreamState(); // take physical pages, weld into a logical stream of packets + Page og = new Page(); // one Ogg bitstream page. Vorbis packets are inside + Packet op = new Packet(); // one raw packet of data for decode + + Info vi = new Info(); // struct that stores all the static vorbis bitstream settings + Comment vc = new Comment(); // struct that stores all the bitstream user comments + DspState vd = new DspState(); // central working state for the packet->PCM decoder + Block vb = new Block(vd); // local working space for packet->PCM decode + + int bytes = 0; + + // Decode setup + oy.init(); // Now we can read pages + + // we repeat if the bitstream is chained + while (true) + { + int eos = 0; + + // 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 = oy.buffer(4096); + bytes = input.Read(oy.data, index, 4096); + oy.wrote(bytes); + + // Get the first page. + if (oy.pageout(og) != 1) + { + // have we simply run out of data? If so, we're done. + if (bytes < 4096) + break; + + // error case. Must not be Vorbis data + s_err.WriteLine("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 + os.init(og.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. + + vi.init(); + vc.init(); + if (os.pagein(og) < 0) + { + // error; stream version mismatch perhaps + s_err.WriteLine("Error reading first page of Ogg bitstream data."); + } + + if (os.packetout(op) != 1) + { + // no page? must not be vorbis + s_err.WriteLine("Error reading initial header packet."); + } + + if (vi.synthesis_headerin(vc, op) < 0) + { + // error case; not a vorbis header + s_err.WriteLine("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 reead + // and submit data until we get our two pacakets, 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 = oy.pageout(og); + 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) + { + os.pagein(og); // we can ignore any errors here + // as they'll also become apparent + // at packetout + while (i < 2) + { + result = os.packetout(op); + 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. + s_err.WriteLine("Corrupt secondary header. Exiting."); + } + vi.synthesis_headerin(vc, op); + i++; + } + } + } + // no harm in not checking before adding more + index = oy.buffer(4096); + bytes = input.Read(oy.data, index, 4096); + if (bytes == 0 && i < 2) + { + s_err.WriteLine("End of file before finding all Vorbis headers!"); + } + oy.wrote(bytes); + } + + // Throw the comments plus a few lines about the bitstream we're + // decoding + { + byte[][] ptr = vc.user_comments; + for (int j = 0; j < vc.user_comments.Length; j++) + { + if (ptr [j] == null) + break; + s_err.WriteLine(vc.getComment(j)); + } + s_err.WriteLine("\nBitstream is " + vi.channels + " channel, " + vi.rate + "Hz"); + s_err.WriteLine("Encoded by: " + vc.getVendor() + "\n"); + } + + convsize = 4096 / vi.channels; + + // OK, got and parsed all three headers. Initialize the Vorbis + // packet->PCM decoder. + vd.synthesis_init(vi); // central decode state + vb.init(vd); // 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 + + float[][][] _pcm = new float[1][][]; + int[] _index = new int[vi.channels]; + // The rest is just a straight decode loop until end of stream + while (eos == 0) + { + while (eos == 0) + { + + int result = oy.pageout(og); + if (result == 0) + break; // need more data + if (result == -1) + { + // missing or corrupt data at this page position + s_err.WriteLine("Corrupt or missing data in bitstream; continuing..."); + } else + { + os.pagein(og); // can safely ignore errors at + // this point + while (true) + { + result = os.packetout(op); + + if (result == 0) + break; // need more data + if (result == -1) + { // missing or corrupt data at this page position + // no reason to complain; already complained above + } else + { + // we have a packet. Decode it + int samples; + if (vb.synthesis(op) == 0) + { // test for success! + vd.synthesis_blockin(vb); + } + + // **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 + + while ((samples = vd.synthesis_pcmout(_pcm, _index)) > 0) + { + float[][] pcm = _pcm [0]; + bool clipflag = false; + int bout = (samples < convsize ? samples : convsize); + + // convert floats to 16 bit signed ints (host order) and + // interleave + for (i = 0; i < vi.channels; i++) + { + int ptr = i * 2; + //int ptr=i; + int mono = _index [i]; + for (int j = 0; j < bout; j++) + { + int val = (int)(pcm [i] [mono + j] * 32767.0); + // short val=(short)(pcm[i][mono+j]*32767.); + // int val=(int)Math.round(pcm[i][mono+j]*32767.); + // might as well guard against clipping + if (val > 32767) + { + val = 32767; + clipflag = true; + } + if (val < -32768) + { + val = -32768; + clipflag = true; + } + if (val < 0) + val = val | 0x8000; + convbuffer [ptr] = (byte)(val); + convbuffer [ptr + 1] = (byte)((uint)val >> 8); + ptr += 2 * (vi.channels); + } + } + + if (clipflag) + { + //s_err.WriteLine("Clipping in frame "+vd.sequence); + } + + output.Write(convbuffer, 0, 2 * vi.channels * bout); + + vd.synthesis_read(bout); // tell libvorbis how + // many samples we + // actually consumed + } + } + } + if (og.eos() != 0) + eos = 1; + } + } + if (eos == 0) + { + index = oy.buffer(4096); + bytes = input.Read(oy.data, index, 4096); + oy.wrote(bytes); + if (bytes == 0) + eos = 1; + } + } + + // clean up this logical bitstream; before exit we see if we're + // followed by another [chained] + + os.clear(); + + // ogg_page and ogg_packet structs always point to storage in + // libvorbis. They're never freed or manipulated directly + + vb.clear(); + vd.clear(); + vi.clear(); // must be called last + } + + // OK, clean up the framer + oy.clear(); + s_err.WriteLine("Done."); + + if (writeWavHeader) + { + long end = output.Position; + int length = (int)(end - (start + HEADER_SIZE)); + + output.Seek(start, SeekOrigin.Begin); + WriteHeader(output, length, vi.rate, (ushort)16, (ushort)vi.channels); + output.Seek(end, SeekOrigin.Begin); + } + } + + static void WriteHeader(Stream stream, int length, int audioSampleRate, ushort audioBitsPerSample, ushort audioChannels) + { + BinaryWriter bw = new BinaryWriter(stream); + + bw.Write(new char[4] { 'R', 'I', 'F', 'F' }); + int fileSize = HEADER_SIZE + length; + bw.Write(fileSize); + bw.Write(new char[8] { 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ' }); + bw.Write((int)16); + bw.Write((short)1); + bw.Write((short)audioChannels); + bw.Write(audioSampleRate); + bw.Write((int)(audioSampleRate * ((audioBitsPerSample * audioChannels) / 8))); + bw.Write((short)((audioBitsPerSample * audioChannels) / 8)); + bw.Write((short)audioBitsPerSample); + + bw.Write(new char[4] { 'd', 'a', 't', 'a' }); + bw.Write(length); + } + } +} diff --git a/OggDecoder/OggDecodeStream.cs b/OggDecoder/OggDecodeStream.cs index b2c930a..e65a77c 100644 --- a/OggDecoder/OggDecodeStream.cs +++ b/OggDecoder/OggDecodeStream.cs @@ -10,372 +10,22 @@ namespace OggDecoder { public class OggDecodeStream : Stream { - class DebugWriter : TextWriter - { - public override Encoding Encoding - { - get { return Encoding.UTF8; } - } - - public override void WriteLine() - { - Debug.WriteLine(String.Empty); - } - - public override void WriteLine(string s) - { - Debug.WriteLine(s); - } - } - private Stream decodedStream; - private const int HEADER_SIZE = 36; public OggDecodeStream(Stream input, bool skipWavHeader) { if (input == null) throw new ArgumentNullException("input"); - decodedStream = DecodeStream(input, skipWavHeader); - } - public OggDecodeStream(Stream input):this(input, false) - { + decodedStream = new MemoryStream(); + Decoder.DecodeStream(input, decodedStream, !skipWavHeader); + decodedStream.Seek(0, SeekOrigin.Begin); } - Stream DecodeStream(Stream input, bool skipWavHeader) + public OggDecodeStream(Stream input) : this(input, false) { - int convsize=4096*2; - byte[] convbuffer=new byte[convsize]; // take 8k out of the data segment, not the stack - - TextWriter s_err = new DebugWriter(); - Stream output = new MemoryStream(); - - if(!skipWavHeader) - output.Seek(HEADER_SIZE, SeekOrigin.Begin); // reserve place for WAV header - - SyncState oy = new SyncState(); // sync and verify incoming physical bitstream - StreamState os = new StreamState(); // take physical pages, weld into a logical stream of packets - Page og = new Page(); // one Ogg bitstream page. Vorbis packets are inside - Packet op = new Packet(); // one raw packet of data for decode - - Info vi = new Info(); // struct that stores all the static vorbis bitstream settings - Comment vc = new Comment(); // struct that stores all the bitstream user comments - DspState vd = new DspState(); // central working state for the packet->PCM decoder - Block vb = new Block(vd); // local working space for packet->PCM decode - - byte[] buffer; - int bytes = 0; - - // Decode setup - - oy.init(); // Now we can read pages - - while (true) - { // we repeat if the bitstream is chained - int eos = 0; - - // 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 = oy.buffer(4096); - buffer = oy.data; - try - { - bytes = input.Read(buffer, index, 4096); - } - catch (Exception e) - { - s_err.WriteLine(e); - } - oy.wrote(bytes); - - // Get the first page. - if (oy.pageout(og) != 1) - { - // have we simply run out of data? If so, we're done. - if (bytes < 4096) break; - - // error case. Must not be Vorbis data - s_err.WriteLine("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 - os.init(og.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. - - vi.init(); - vc.init(); - if (os.pagein(og) < 0) - { - // error; stream version mismatch perhaps - s_err.WriteLine("Error reading first page of Ogg bitstream data."); - } - - if (os.packetout(op) != 1) - { - // no page? must not be vorbis - s_err.WriteLine("Error reading initial header packet."); - } - - if (vi.synthesis_headerin(vc, op) < 0) - { - // error case; not a vorbis header - s_err.WriteLine("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 reead - // and submit data until we get our two pacakets, 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 = oy.pageout(og); - 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) - { - os.pagein(og); // we can ignore any errors here - // as they'll also become apparent - // at packetout - while (i < 2) - { - result = os.packetout(op); - 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. - s_err.WriteLine("Corrupt secondary header. Exiting."); - } - vi.synthesis_headerin(vc, op); - i++; - } - } - } - // no harm in not checking before adding more - index = oy.buffer(4096); - buffer = oy.data; - try - { - bytes = input.Read(buffer, index, 4096); - } - catch (Exception e) - { - s_err.WriteLine(e); - } - if (bytes == 0 && i < 2) - { - s_err.WriteLine("End of file before finding all Vorbis headers!"); - } - oy.wrote(bytes); - } - - // Throw the comments plus a few lines about the bitstream we're - // decoding - { - byte[][] ptr = vc.user_comments; - for (int j = 0; j < vc.user_comments.Length; j++) - { - if (ptr[j] == null) break; - s_err.WriteLine(vc.getComment(j)); - } - s_err.WriteLine("\nBitstream is " + vi.channels + " channel, " + vi.rate + "Hz"); - s_err.WriteLine("Encoded by: " + vc.getVendor() + "\n"); - } - - convsize = 4096 / vi.channels; - - // OK, got and parsed all three headers. Initialize the Vorbis - // packet->PCM decoder. - vd.synthesis_init(vi); // central decode state - vb.init(vd); // 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 - - float[][][] _pcm = new float[1][][]; - int[] _index = new int[vi.channels]; - // The rest is just a straight decode loop until end of stream - while (eos == 0) - { - while (eos == 0) - { - - int result = oy.pageout(og); - if (result == 0) break; // need more data - if (result == -1) - { // missing or corrupt data at this page position - s_err.WriteLine("Corrupt or missing data in bitstream; continuing..."); - } - else - { - os.pagein(og); // can safely ignore errors at - // this point - while (true) - { - result = os.packetout(op); - - if (result == 0) break; // need more data - if (result == -1) - { // missing or corrupt data at this page position - // no reason to complain; already complained above - } - else - { - // we have a packet. Decode it - int samples; - if (vb.synthesis(op) == 0) - { // test for success! - vd.synthesis_blockin(vb); - } - - // **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 - - while ((samples = vd.synthesis_pcmout(_pcm, _index)) > 0) - { - float[][] pcm = _pcm[0]; - bool clipflag = false; - int bout = (samples < convsize ? samples : convsize); - - // convert floats to 16 bit signed ints (host order) and - // interleave - for (i = 0; i < vi.channels; i++) - { - int ptr = i * 2; - //int ptr=i; - int mono = _index[i]; - for (int j = 0; j < bout; j++) - { - int val = (int)(pcm[i][mono + j] * 32767.0); - // short val=(short)(pcm[i][mono+j]*32767.); - // int val=(int)Math.round(pcm[i][mono+j]*32767.); - // might as well guard against clipping - if (val > 32767) - { - val = 32767; - clipflag = true; - } - if (val < -32768) - { - val = -32768; - clipflag = true; - } - if (val < 0) val = val | 0x8000; - convbuffer[ptr] = (byte)(val); - convbuffer[ptr + 1] = (byte)((uint)val >> 8); - ptr += 2 * (vi.channels); - } - } - - if (clipflag) - { - //s_err.WriteLine("Clipping in frame "+vd.sequence); - } - - output.Write(convbuffer, 0, 2 * vi.channels * bout); - - vd.synthesis_read(bout); // tell libvorbis how - // many samples we - // actually consumed - } - } - } - if (og.eos() != 0) eos = 1; - } - } - if (eos == 0) - { - index = oy.buffer(4096); - buffer = oy.data; - try - { - bytes = input.Read(buffer, index, 4096); - } - catch (Exception e) - { - s_err.WriteLine(e); - } - oy.wrote(bytes); - if (bytes == 0) eos = 1; - } - } - - // clean up this logical bitstream; before exit we see if we're - // followed by another [chained] - - os.clear(); - - // ogg_page and ogg_packet structs always point to storage in - // libvorbis. They're never freed or manipulated directly - - vb.clear(); - vd.clear(); - vi.clear(); // must be called last - } - - // OK, clean up the framer - oy.clear(); - s_err.WriteLine("Done."); - - output.Seek(0, SeekOrigin.Begin); - if (!skipWavHeader) - { - WriteHeader(output, (int)(output.Length - HEADER_SIZE), vi.rate, (ushort)16, (ushort)vi.channels); - output.Seek(0, SeekOrigin.Begin); - } - return output; - } - - void WriteHeader(Stream stream, int length, int audioSampleRate, ushort audioBitsPerSample, ushort audioChannels) - { - BinaryWriter bw = new BinaryWriter(stream); - - bw.Write(new char[4] { 'R', 'I', 'F', 'F' }); - int fileSize = HEADER_SIZE + length; - bw.Write(fileSize); - bw.Write(new char[8] { 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ' }); - bw.Write((int)16); - bw.Write((short)1); - bw.Write((short)audioChannels); - bw.Write(audioSampleRate); - bw.Write((int)(audioSampleRate * ((audioBitsPerSample * audioChannels) / 8))); - bw.Write((short)((audioBitsPerSample * audioChannels) / 8)); - bw.Write((short)audioBitsPerSample); - - bw.Write(new char[4] { 'd', 'a', 't', 'a' }); - bw.Write(length); } - public override bool CanRead { get { return true; } diff --git a/OggDecoder/OggDecoder.cs b/OggDecoder/OggDecoder.cs deleted file mode 100644 index 491aec3..0000000 --- a/OggDecoder/OggDecoder.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.IO; -using csogg; -using csvorbis; - -namespace OggDecoder -{ - /// - /// Ogg Vorbis decoder test application. - /// - class Decoder - { - /// - /// The main entry point for the application. - /// - //[STAThread] - static void Main(string[] args) - { - TextWriter s_err = Console.Error; - FileStream input = null, output = null; - - if(args.Length == 2) - { - try - { - input = new FileStream(args[0], FileMode.Open, FileAccess.Read); - output = new FileStream(args[1], FileMode.OpenOrCreate); - } - catch(Exception e) - { - s_err.WriteLine(e); - } - } - else - { - return; - } - - OggDecodeStream decode = new OggDecodeStream(input, true); - - byte[] buffer = new byte[4096]; - int read; - while ((read = decode.Read(buffer, 0, buffer.Length)) > 0) - { - output.Write(buffer, 0, read); - } - - // Close some files - input.Close(); - output.Close(); - } - } -} - diff --git a/OggDecoder/OggDecoder.csproj b/OggDecoder/OggDecoder.csproj index 948b79f..83daa9b 100644 --- a/OggDecoder/OggDecoder.csproj +++ b/OggDecoder/OggDecoder.csproj @@ -10,14 +10,13 @@ App.ico OggDecoder - JScript Grid IE50 false Exe OggDecoder - OggDecoder.Decoder + OggDecoder.Program v2.0 @@ -41,9 +40,7 @@ bin\Debug\ - false 285212672 - false DEBUG;TRACE @@ -52,7 +49,6 @@ false false false - false 4 full prompt @@ -60,18 +56,14 @@ bin\Release\ - false 285212672 - false TRACE - false 4096 true false false - false 4 none prompt @@ -81,12 +73,10 @@ csogg {FA4ACE8B-CDEE-41A3-92D7-C73069B84B83} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} csvorbis {0087C0AF-E896-4C55-A999-5245560BCBE3} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} System @@ -96,12 +86,13 @@ Code - - Code - - + + Code + + + @@ -125,4 +116,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/OggDecoder/OggPlayer.cs b/OggDecoder/OggPlayer.cs deleted file mode 100644 index 534f81c..0000000 --- a/OggDecoder/OggPlayer.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Text; -using System.Media; -using System.IO; - -namespace OggDecoder -{ - class OggPlayer - { - static void Main(string[] args) - { - using (var file = new FileStream(args[0], FileMode.Open, FileAccess.Read)) - { - var player = new SoundPlayer(new OggDecodeStream(file)); - player.PlaySync(); - } - } - } -} diff --git a/OggDecoder/Program.cs b/OggDecoder/Program.cs new file mode 100644 index 0000000..49941fa --- /dev/null +++ b/OggDecoder/Program.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using csogg; +using csvorbis; +using System.Media; + +namespace OggDecoder +{ + /// + /// Ogg Vorbis decoder test application. + /// + class Program + { + static void Main(string[] args) + { + try + { + if (args.Length == 1) + Play(args [0]); + else if (args.Length == 2) + Decode(args [0], args [1]); + } catch (Exception e) + { + Console.Error.WriteLine(e); + } + } + + static void Play(string path) + { + using (var file = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + var player = new SoundPlayer(new OggDecodeStream(file)); + player.PlaySync(); + } + + } + + static void Decode(String inputPath, string outputPath) + { + using (FileStream input = new FileStream(inputPath, FileMode.Open, FileAccess.Read)) + using (FileStream output = new FileStream(outputPath, FileMode.OpenOrCreate)) + { + Decoder.DecodeStream(input, output, true); + } + } + } +} + From 2b68cf47cefa1033caea1390f6f687d642141539 Mon Sep 17 00:00:00 2001 From: Peter Hultqvist Date: Sat, 2 Jun 2012 15:39:36 +0200 Subject: [PATCH 2/2] Renamed project OggDecoder to OggTools to make a subclass OggDecoder not clash with the namespace. Set explicit "Microsoft Visual Studio" formatting(in MonoDevelop) to project files, it was specified as "Custom" but did not match the current format. --- .gitignore | 1 + OggDecoder/DebugWriter.cs | 27 -- OggDecoder/Decoder.cs | 352 ------------------ OggDecoder/OggDecodeStream.cs | 86 ----- OggDecoder/OggDecoder.csproj.user | 51 --- OggDecoder/Program.cs | 48 --- {OggDecoder => OggTools}/App.ico | Bin {OggDecoder => OggTools}/AssemblyInfo.cs | 153 ++++---- OggTools/DebugWriter.cs | 27 ++ {OggDecoder => OggTools}/Makefile | 0 OggTools/OggDecodeStream.cs | 86 +++++ OggTools/OggDecoder.cs | 352 ++++++++++++++++++ .../OggTools.csproj | 9 +- OggTools/Program.cs | 48 +++ csogg/csogg.csproj | 21 +- csvorbis.sln | 42 ++- csvorbis/csvorbis.csproj | 22 +- 17 files changed, 654 insertions(+), 671 deletions(-) delete mode 100644 OggDecoder/DebugWriter.cs delete mode 100644 OggDecoder/Decoder.cs delete mode 100644 OggDecoder/OggDecodeStream.cs delete mode 100644 OggDecoder/OggDecoder.csproj.user delete mode 100644 OggDecoder/Program.cs rename {OggDecoder => OggTools}/App.ico (100%) rename {OggDecoder => OggTools}/AssemblyInfo.cs (95%) create mode 100644 OggTools/DebugWriter.cs rename {OggDecoder => OggTools}/Makefile (100%) create mode 100644 OggTools/OggDecodeStream.cs create mode 100644 OggTools/OggDecoder.cs rename OggDecoder/OggDecoder.csproj => OggTools/OggTools.csproj (91%) create mode 100644 OggTools/Program.cs diff --git a/.gitignore b/.gitignore index 28e479b..253e96e 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ obj/ [Rr]elease*/ _ReSharper*/ [Tt]est[Rr]esult* +*.userprefs diff --git a/OggDecoder/DebugWriter.cs b/OggDecoder/DebugWriter.cs deleted file mode 100644 index a6719bd..0000000 --- a/OggDecoder/DebugWriter.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Diagnostics; - -namespace OggDecoder -{ - class DebugWriter - { - [Conditional("DEBUG")] - public void WriteLine() - { - Debug.WriteLine(String.Empty); - } - - [Conditional("DEBUG")] - public void WriteLine(string s) - { - Debug.WriteLine(s); - } - - [Conditional("DEBUG")] - public void WriteLine(object o) - { - Debug.WriteLine(o.ToString()); - } - } -} - diff --git a/OggDecoder/Decoder.cs b/OggDecoder/Decoder.cs deleted file mode 100644 index 89bbc58..0000000 --- a/OggDecoder/Decoder.cs +++ /dev/null @@ -1,352 +0,0 @@ -using System; -using System.IO; -using System.Diagnostics; -using System.Text; - -using csogg; -using csvorbis; - -namespace OggDecoder -{ - public static class Decoder - { - private const int HEADER_SIZE = 36; - - /// - /// Decodes a stream of Ogg Vorbis data into wav file format. - /// - /// - /// A MemoryStream with the entire decoded stream. - /// - /// - /// Ogg Vorbis data to be decoded - /// - /// - /// Stream to write wav data. - /// If writeWavHeader is true then this stream must be seekable. - /// - /// - /// Write wav header in the beginning of the returned stream. - /// - public static void DecodeStream(Stream input, Stream output, bool writeWavHeader) - { - int convsize = 4096 * 2; - byte[] convbuffer = new byte[convsize]; // take 8k out of the data segment, not the stack - long start = output.Position; - - DebugWriter s_err = new DebugWriter(); - - if (writeWavHeader && !output.CanSeek) - throw new ArgumentException("To write wav header, the output stream must be seekable.", "output"); - - if (writeWavHeader) - output.Seek(HEADER_SIZE, SeekOrigin.Current); // reserve place for WAV header - - SyncState oy = new SyncState(); // sync and verify incoming physical bitstream - StreamState os = new StreamState(); // take physical pages, weld into a logical stream of packets - Page og = new Page(); // one Ogg bitstream page. Vorbis packets are inside - Packet op = new Packet(); // one raw packet of data for decode - - Info vi = new Info(); // struct that stores all the static vorbis bitstream settings - Comment vc = new Comment(); // struct that stores all the bitstream user comments - DspState vd = new DspState(); // central working state for the packet->PCM decoder - Block vb = new Block(vd); // local working space for packet->PCM decode - - int bytes = 0; - - // Decode setup - oy.init(); // Now we can read pages - - // we repeat if the bitstream is chained - while (true) - { - int eos = 0; - - // 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 = oy.buffer(4096); - bytes = input.Read(oy.data, index, 4096); - oy.wrote(bytes); - - // Get the first page. - if (oy.pageout(og) != 1) - { - // have we simply run out of data? If so, we're done. - if (bytes < 4096) - break; - - // error case. Must not be Vorbis data - s_err.WriteLine("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 - os.init(og.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. - - vi.init(); - vc.init(); - if (os.pagein(og) < 0) - { - // error; stream version mismatch perhaps - s_err.WriteLine("Error reading first page of Ogg bitstream data."); - } - - if (os.packetout(op) != 1) - { - // no page? must not be vorbis - s_err.WriteLine("Error reading initial header packet."); - } - - if (vi.synthesis_headerin(vc, op) < 0) - { - // error case; not a vorbis header - s_err.WriteLine("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 reead - // and submit data until we get our two pacakets, 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 = oy.pageout(og); - 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) - { - os.pagein(og); // we can ignore any errors here - // as they'll also become apparent - // at packetout - while (i < 2) - { - result = os.packetout(op); - 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. - s_err.WriteLine("Corrupt secondary header. Exiting."); - } - vi.synthesis_headerin(vc, op); - i++; - } - } - } - // no harm in not checking before adding more - index = oy.buffer(4096); - bytes = input.Read(oy.data, index, 4096); - if (bytes == 0 && i < 2) - { - s_err.WriteLine("End of file before finding all Vorbis headers!"); - } - oy.wrote(bytes); - } - - // Throw the comments plus a few lines about the bitstream we're - // decoding - { - byte[][] ptr = vc.user_comments; - for (int j = 0; j < vc.user_comments.Length; j++) - { - if (ptr [j] == null) - break; - s_err.WriteLine(vc.getComment(j)); - } - s_err.WriteLine("\nBitstream is " + vi.channels + " channel, " + vi.rate + "Hz"); - s_err.WriteLine("Encoded by: " + vc.getVendor() + "\n"); - } - - convsize = 4096 / vi.channels; - - // OK, got and parsed all three headers. Initialize the Vorbis - // packet->PCM decoder. - vd.synthesis_init(vi); // central decode state - vb.init(vd); // 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 - - float[][][] _pcm = new float[1][][]; - int[] _index = new int[vi.channels]; - // The rest is just a straight decode loop until end of stream - while (eos == 0) - { - while (eos == 0) - { - - int result = oy.pageout(og); - if (result == 0) - break; // need more data - if (result == -1) - { - // missing or corrupt data at this page position - s_err.WriteLine("Corrupt or missing data in bitstream; continuing..."); - } else - { - os.pagein(og); // can safely ignore errors at - // this point - while (true) - { - result = os.packetout(op); - - if (result == 0) - break; // need more data - if (result == -1) - { // missing or corrupt data at this page position - // no reason to complain; already complained above - } else - { - // we have a packet. Decode it - int samples; - if (vb.synthesis(op) == 0) - { // test for success! - vd.synthesis_blockin(vb); - } - - // **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 - - while ((samples = vd.synthesis_pcmout(_pcm, _index)) > 0) - { - float[][] pcm = _pcm [0]; - bool clipflag = false; - int bout = (samples < convsize ? samples : convsize); - - // convert floats to 16 bit signed ints (host order) and - // interleave - for (i = 0; i < vi.channels; i++) - { - int ptr = i * 2; - //int ptr=i; - int mono = _index [i]; - for (int j = 0; j < bout; j++) - { - int val = (int)(pcm [i] [mono + j] * 32767.0); - // short val=(short)(pcm[i][mono+j]*32767.); - // int val=(int)Math.round(pcm[i][mono+j]*32767.); - // might as well guard against clipping - if (val > 32767) - { - val = 32767; - clipflag = true; - } - if (val < -32768) - { - val = -32768; - clipflag = true; - } - if (val < 0) - val = val | 0x8000; - convbuffer [ptr] = (byte)(val); - convbuffer [ptr + 1] = (byte)((uint)val >> 8); - ptr += 2 * (vi.channels); - } - } - - if (clipflag) - { - //s_err.WriteLine("Clipping in frame "+vd.sequence); - } - - output.Write(convbuffer, 0, 2 * vi.channels * bout); - - vd.synthesis_read(bout); // tell libvorbis how - // many samples we - // actually consumed - } - } - } - if (og.eos() != 0) - eos = 1; - } - } - if (eos == 0) - { - index = oy.buffer(4096); - bytes = input.Read(oy.data, index, 4096); - oy.wrote(bytes); - if (bytes == 0) - eos = 1; - } - } - - // clean up this logical bitstream; before exit we see if we're - // followed by another [chained] - - os.clear(); - - // ogg_page and ogg_packet structs always point to storage in - // libvorbis. They're never freed or manipulated directly - - vb.clear(); - vd.clear(); - vi.clear(); // must be called last - } - - // OK, clean up the framer - oy.clear(); - s_err.WriteLine("Done."); - - if (writeWavHeader) - { - long end = output.Position; - int length = (int)(end - (start + HEADER_SIZE)); - - output.Seek(start, SeekOrigin.Begin); - WriteHeader(output, length, vi.rate, (ushort)16, (ushort)vi.channels); - output.Seek(end, SeekOrigin.Begin); - } - } - - static void WriteHeader(Stream stream, int length, int audioSampleRate, ushort audioBitsPerSample, ushort audioChannels) - { - BinaryWriter bw = new BinaryWriter(stream); - - bw.Write(new char[4] { 'R', 'I', 'F', 'F' }); - int fileSize = HEADER_SIZE + length; - bw.Write(fileSize); - bw.Write(new char[8] { 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ' }); - bw.Write((int)16); - bw.Write((short)1); - bw.Write((short)audioChannels); - bw.Write(audioSampleRate); - bw.Write((int)(audioSampleRate * ((audioBitsPerSample * audioChannels) / 8))); - bw.Write((short)((audioBitsPerSample * audioChannels) / 8)); - bw.Write((short)audioBitsPerSample); - - bw.Write(new char[4] { 'd', 'a', 't', 'a' }); - bw.Write(length); - } - } -} diff --git a/OggDecoder/OggDecodeStream.cs b/OggDecoder/OggDecodeStream.cs deleted file mode 100644 index e65a77c..0000000 --- a/OggDecoder/OggDecodeStream.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.IO; -using System.Diagnostics; -using System.Text; - -using csogg; -using csvorbis; - -namespace OggDecoder -{ - public class OggDecodeStream : Stream - { - private Stream decodedStream; - - public OggDecodeStream(Stream input, bool skipWavHeader) - { - if (input == null) - throw new ArgumentNullException("input"); - - decodedStream = new MemoryStream(); - Decoder.DecodeStream(input, decodedStream, !skipWavHeader); - decodedStream.Seek(0, SeekOrigin.Begin); - } - - public OggDecodeStream(Stream input) : this(input, false) - { - } - - public override bool CanRead - { - get { return true; } - } - - public override bool CanSeek - { - get { return true; } - } - - public override bool CanWrite - { - get { return false; } - } - - public override void Flush() - { - throw new NotImplementedException(); - } - - public override long Length - { - get { return decodedStream.Length; } - } - - public override long Position - { - get - { - return decodedStream.Position; - } - set - { - decodedStream.Position = value; - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - return decodedStream.Read(buffer, offset, count); - } - - public override long Seek(long offset, SeekOrigin origin) - { - return Seek(offset, origin); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - } -} diff --git a/OggDecoder/OggDecoder.csproj.user b/OggDecoder/OggDecoder.csproj.user deleted file mode 100644 index 4a3636a..0000000 --- a/OggDecoder/OggDecoder.csproj.user +++ /dev/null @@ -1,51 +0,0 @@ - - - - Debug - AnyCPU - - - - 0 - ProjectFiles - 0 - - - - - - - en-US - false - - - false - false - false - false - false - - Project - - - - - - true - - - false - false - false - false - false - - Project - - - - - - true - - \ No newline at end of file diff --git a/OggDecoder/Program.cs b/OggDecoder/Program.cs deleted file mode 100644 index 49941fa..0000000 --- a/OggDecoder/Program.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.IO; -using csogg; -using csvorbis; -using System.Media; - -namespace OggDecoder -{ - /// - /// Ogg Vorbis decoder test application. - /// - class Program - { - static void Main(string[] args) - { - try - { - if (args.Length == 1) - Play(args [0]); - else if (args.Length == 2) - Decode(args [0], args [1]); - } catch (Exception e) - { - Console.Error.WriteLine(e); - } - } - - static void Play(string path) - { - using (var file = new FileStream(path, FileMode.Open, FileAccess.Read)) - { - var player = new SoundPlayer(new OggDecodeStream(file)); - player.PlaySync(); - } - - } - - static void Decode(String inputPath, string outputPath) - { - using (FileStream input = new FileStream(inputPath, FileMode.Open, FileAccess.Read)) - using (FileStream output = new FileStream(outputPath, FileMode.OpenOrCreate)) - { - Decoder.DecodeStream(input, output, true); - } - } - } -} - diff --git a/OggDecoder/App.ico b/OggTools/App.ico similarity index 100% rename from OggDecoder/App.ico rename to OggTools/App.ico diff --git a/OggDecoder/AssemblyInfo.cs b/OggTools/AssemblyInfo.cs similarity index 95% rename from OggDecoder/AssemblyInfo.cs rename to OggTools/AssemblyInfo.cs index 49a78fd..383da1f 100644 --- a/OggDecoder/AssemblyInfo.cs +++ b/OggTools/AssemblyInfo.cs @@ -1,78 +1,77 @@ -/* csvorbis - * Copyright (C) 2002 Mark Crichton - * - * Written by: Mark Crichton - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public License - * as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +/* csvorbis + * Copyright (C) 2002 Mark Crichton + * + * Written by: Mark Crichton + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ - -using System.Reflection; -using System.Runtime.CompilerServices; - -// -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -// -[assembly: AssemblyTitle("OggDecode")] -[assembly: AssemblyDescription("csVorbis Test Application")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("LGPL")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: - -[assembly: AssemblyVersion("1.0.*")] - -// -// In order to sign your assembly you must specify a key to use. Refer to the -// Microsoft .NET Framework documentation for more information on assembly signing. -// -// Use the attributes below to control which key is used for signing. -// -// Notes: -// (*) If no key is specified, the assembly is not signed. -// (*) KeyName refers to a key that has been installed in the Crypto Service -// Provider (CSP) on your machine. KeyFile refers to a file which contains -// a key. -// (*) If the KeyFile and the KeyName values are both specified, the -// following processing occurs: -// (1) If the KeyName can be found in the CSP, that key is used. -// (2) If the KeyName does not exist and the KeyFile does exist, the key -// in the KeyFile is installed into the CSP and used. -// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. -// When specifying the KeyFile, the location of the KeyFile should be -// relative to the project output directory which is -// %Project Directory%\obj\. For example, if your KeyFile is -// located in the project directory, you would specify the AssemblyKeyFile -// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] -// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework -// documentation for more information on this. -// -[assembly: AssemblyDelaySign(false)] -[assembly: AssemblyKeyFile("")] -[assembly: AssemblyKeyName("")] +using System.Reflection; +using System.Runtime.CompilerServices; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTitle("OggTools")] +[assembly: AssemblyDescription("csVorbis Test Application")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("LGPL")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.0.*")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] diff --git a/OggTools/DebugWriter.cs b/OggTools/DebugWriter.cs new file mode 100644 index 0000000..8242fba --- /dev/null +++ b/OggTools/DebugWriter.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics; + +namespace OggTools +{ + class DebugWriter + { + [Conditional("DEBUG")] + public void WriteLine() + { + Debug.WriteLine(String.Empty); + } + + [Conditional("DEBUG")] + public void WriteLine(string s) + { + Debug.WriteLine(s); + } + + [Conditional("DEBUG")] + public void WriteLine(object o) + { + Debug.WriteLine(o.ToString()); + } + } +} + diff --git a/OggDecoder/Makefile b/OggTools/Makefile similarity index 100% rename from OggDecoder/Makefile rename to OggTools/Makefile diff --git a/OggTools/OggDecodeStream.cs b/OggTools/OggDecodeStream.cs new file mode 100644 index 0000000..9a6c881 --- /dev/null +++ b/OggTools/OggDecodeStream.cs @@ -0,0 +1,86 @@ +using System; +using System.IO; +using System.Diagnostics; +using System.Text; + +using csogg; +using csvorbis; + +namespace OggTools +{ + public class OggDecodeStream : Stream + { + private Stream decodedStream; + + public OggDecodeStream(Stream input, bool skipWavHeader) + { + if (input == null) + throw new ArgumentNullException("input"); + + decodedStream = new MemoryStream(); + OggDecoder.DecodeStream(input, decodedStream, !skipWavHeader); + decodedStream.Seek(0, SeekOrigin.Begin); + } + + public OggDecodeStream(Stream input) : this(input, false) + { + } + + public override bool CanRead + { + get { return true; } + } + + public override bool CanSeek + { + get { return true; } + } + + public override bool CanWrite + { + get { return false; } + } + + public override void Flush() + { + throw new NotImplementedException(); + } + + public override long Length + { + get { return decodedStream.Length; } + } + + public override long Position + { + get + { + return decodedStream.Position; + } + set + { + decodedStream.Position = value; + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + return decodedStream.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return Seek(offset, origin); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + } +} diff --git a/OggTools/OggDecoder.cs b/OggTools/OggDecoder.cs new file mode 100644 index 0000000..62f7d7f --- /dev/null +++ b/OggTools/OggDecoder.cs @@ -0,0 +1,352 @@ +using System; +using System.IO; +using System.Diagnostics; +using System.Text; + +using csogg; +using csvorbis; + +namespace OggTools +{ + public static class OggDecoder + { + private const int HEADER_SIZE = 36; + + /// + /// Decodes a stream of Ogg Vorbis data into wav file format. + /// + /// + /// A MemoryStream with the entire decoded stream. + /// + /// + /// Ogg Vorbis data to be decoded + /// + /// + /// Stream to write wav data. + /// If writeWavHeader is true then this stream must be seekable. + /// + /// + /// Write wav header in the beginning of the returned stream. + /// + public static void DecodeStream(Stream input, Stream output, bool writeWavHeader) + { + int convsize = 4096 * 2; + byte[] convbuffer = new byte[convsize]; // take 8k out of the data segment, not the stack + long start = output.Position; + + DebugWriter s_err = new DebugWriter(); + + if (writeWavHeader && !output.CanSeek) + throw new ArgumentException("To write wav header, the output stream must be seekable.", "output"); + + if (writeWavHeader) + output.Seek(HEADER_SIZE, SeekOrigin.Current); // reserve place for WAV header + + SyncState oy = new SyncState(); // sync and verify incoming physical bitstream + StreamState os = new StreamState(); // take physical pages, weld into a logical stream of packets + Page og = new Page(); // one Ogg bitstream page. Vorbis packets are inside + Packet op = new Packet(); // one raw packet of data for decode + + Info vi = new Info(); // struct that stores all the static vorbis bitstream settings + Comment vc = new Comment(); // struct that stores all the bitstream user comments + DspState vd = new DspState(); // central working state for the packet->PCM decoder + Block vb = new Block(vd); // local working space for packet->PCM decode + + int bytes = 0; + + // Decode setup + oy.init(); // Now we can read pages + + // we repeat if the bitstream is chained + while (true) + { + int eos = 0; + + // 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 = oy.buffer(4096); + bytes = input.Read(oy.data, index, 4096); + oy.wrote(bytes); + + // Get the first page. + if (oy.pageout(og) != 1) + { + // have we simply run out of data? If so, we're done. + if (bytes < 4096) + break; + + // error case. Must not be Vorbis data + s_err.WriteLine("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 + os.init(og.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. + + vi.init(); + vc.init(); + if (os.pagein(og) < 0) + { + // error; stream version mismatch perhaps + s_err.WriteLine("Error reading first page of Ogg bitstream data."); + } + + if (os.packetout(op) != 1) + { + // no page? must not be vorbis + s_err.WriteLine("Error reading initial header packet."); + } + + if (vi.synthesis_headerin(vc, op) < 0) + { + // error case; not a vorbis header + s_err.WriteLine("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 reead + // and submit data until we get our two pacakets, 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 = oy.pageout(og); + 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) + { + os.pagein(og); // we can ignore any errors here + // as they'll also become apparent + // at packetout + while (i < 2) + { + result = os.packetout(op); + 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. + s_err.WriteLine("Corrupt secondary header. Exiting."); + } + vi.synthesis_headerin(vc, op); + i++; + } + } + } + // no harm in not checking before adding more + index = oy.buffer(4096); + bytes = input.Read(oy.data, index, 4096); + if (bytes == 0 && i < 2) + { + s_err.WriteLine("End of file before finding all Vorbis headers!"); + } + oy.wrote(bytes); + } + + // Throw the comments plus a few lines about the bitstream we're + // decoding + { + byte[][] ptr = vc.user_comments; + for (int j = 0; j < vc.user_comments.Length; j++) + { + if (ptr [j] == null) + break; + s_err.WriteLine(vc.getComment(j)); + } + s_err.WriteLine("\nBitstream is " + vi.channels + " channel, " + vi.rate + "Hz"); + s_err.WriteLine("Encoded by: " + vc.getVendor() + "\n"); + } + + convsize = 4096 / vi.channels; + + // OK, got and parsed all three headers. Initialize the Vorbis + // packet->PCM decoder. + vd.synthesis_init(vi); // central decode state + vb.init(vd); // 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 + + float[][][] _pcm = new float[1][][]; + int[] _index = new int[vi.channels]; + // The rest is just a straight decode loop until end of stream + while (eos == 0) + { + while (eos == 0) + { + + int result = oy.pageout(og); + if (result == 0) + break; // need more data + if (result == -1) + { + // missing or corrupt data at this page position + s_err.WriteLine("Corrupt or missing data in bitstream; continuing..."); + } else + { + os.pagein(og); // can safely ignore errors at + // this point + while (true) + { + result = os.packetout(op); + + if (result == 0) + break; // need more data + if (result == -1) + { // missing or corrupt data at this page position + // no reason to complain; already complained above + } else + { + // we have a packet. Decode it + int samples; + if (vb.synthesis(op) == 0) + { // test for success! + vd.synthesis_blockin(vb); + } + + // **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 + + while ((samples = vd.synthesis_pcmout(_pcm, _index)) > 0) + { + float[][] pcm = _pcm [0]; + bool clipflag = false; + int bout = (samples < convsize ? samples : convsize); + + // convert floats to 16 bit signed ints (host order) and + // interleave + for (i = 0; i < vi.channels; i++) + { + int ptr = i * 2; + //int ptr=i; + int mono = _index [i]; + for (int j = 0; j < bout; j++) + { + int val = (int)(pcm [i] [mono + j] * 32767.0); + // short val=(short)(pcm[i][mono+j]*32767.); + // int val=(int)Math.round(pcm[i][mono+j]*32767.); + // might as well guard against clipping + if (val > 32767) + { + val = 32767; + clipflag = true; + } + if (val < -32768) + { + val = -32768; + clipflag = true; + } + if (val < 0) + val = val | 0x8000; + convbuffer [ptr] = (byte)(val); + convbuffer [ptr + 1] = (byte)((uint)val >> 8); + ptr += 2 * (vi.channels); + } + } + + if (clipflag) + { + //s_err.WriteLine("Clipping in frame "+vd.sequence); + } + + output.Write(convbuffer, 0, 2 * vi.channels * bout); + + vd.synthesis_read(bout); // tell libvorbis how + // many samples we + // actually consumed + } + } + } + if (og.eos() != 0) + eos = 1; + } + } + if (eos == 0) + { + index = oy.buffer(4096); + bytes = input.Read(oy.data, index, 4096); + oy.wrote(bytes); + if (bytes == 0) + eos = 1; + } + } + + // clean up this logical bitstream; before exit we see if we're + // followed by another [chained] + + os.clear(); + + // ogg_page and ogg_packet structs always point to storage in + // libvorbis. They're never freed or manipulated directly + + vb.clear(); + vd.clear(); + vi.clear(); // must be called last + } + + // OK, clean up the framer + oy.clear(); + s_err.WriteLine("Done."); + + if (writeWavHeader) + { + long end = output.Position; + int length = (int)(end - (start + HEADER_SIZE)); + + output.Seek(start, SeekOrigin.Begin); + WriteHeader(output, length, vi.rate, (ushort)16, (ushort)vi.channels); + output.Seek(end, SeekOrigin.Begin); + } + } + + static void WriteHeader(Stream stream, int length, int audioSampleRate, ushort audioBitsPerSample, ushort audioChannels) + { + BinaryWriter bw = new BinaryWriter(stream); + + bw.Write(new char[4] { 'R', 'I', 'F', 'F' }); + int fileSize = HEADER_SIZE + length; + bw.Write(fileSize); + bw.Write(new char[8] { 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ' }); + bw.Write((int)16); + bw.Write((short)1); + bw.Write((short)audioChannels); + bw.Write(audioSampleRate); + bw.Write((int)(audioSampleRate * ((audioBitsPerSample * audioChannels) / 8))); + bw.Write((short)((audioBitsPerSample * audioChannels) / 8)); + bw.Write((short)audioBitsPerSample); + + bw.Write(new char[4] { 'd', 'a', 't', 'a' }); + bw.Write(length); + } + } +} diff --git a/OggDecoder/OggDecoder.csproj b/OggTools/OggTools.csproj similarity index 91% rename from OggDecoder/OggDecoder.csproj rename to OggTools/OggTools.csproj index 83daa9b..829aa74 100644 --- a/OggDecoder/OggDecoder.csproj +++ b/OggTools/OggTools.csproj @@ -15,8 +15,8 @@ IE50 false Exe - OggDecoder - OggDecoder.Program + OggTools + OggTools.Program v2.0 @@ -92,7 +92,7 @@ Code - + @@ -120,8 +120,9 @@ - + + diff --git a/OggTools/Program.cs b/OggTools/Program.cs new file mode 100644 index 0000000..ef396ed --- /dev/null +++ b/OggTools/Program.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using csogg; +using csvorbis; +using System.Media; + +namespace OggTools +{ + /// + /// Ogg Vorbis decoder test application. + /// + class Program + { + static void Main(string[] args) + { + try + { + if (args.Length == 1) + Play(args [0]); + else if (args.Length == 2) + Decode(args [0], args [1]); + } catch (Exception e) + { + Console.Error.WriteLine(e); + } + } + + static void Play(string path) + { + using (var file = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + var player = new SoundPlayer(new OggDecodeStream(file)); + player.PlaySync(); + } + + } + + static void Decode(String inputPath, string outputPath) + { + using (FileStream input = new FileStream(inputPath, FileMode.Open, FileAccess.Read)) + using (FileStream output = new FileStream(outputPath, FileMode.OpenOrCreate)) + { + OggDecoder.DecodeStream(input, output, true); + } + } + } +} + diff --git a/csogg/csogg.csproj b/csogg/csogg.csproj index 126d10a..237278a 100644 --- a/csogg/csogg.csproj +++ b/csogg/csogg.csproj @@ -7,17 +7,14 @@ {FA4ACE8B-CDEE-41A3-92D7-C73069B84B83} Debug AnyCPU - csogg - JScript Grid IE50 false Library csogg - v2.0 @@ -41,9 +38,7 @@ bin\Debug\ - false 285212672 - false DEBUG;TRACE @@ -52,7 +47,6 @@ false false false - false 4 full prompt @@ -60,18 +54,14 @@ bin\Release\ - false 285212672 - false TRACE - false 4096 true false false - false 4 none prompt @@ -130,4 +120,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/csvorbis.sln b/csvorbis.sln index a518c05..d6567e3 100644 --- a/csvorbis.sln +++ b/csvorbis.sln @@ -1,10 +1,11 @@ + Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csogg", "csogg\csogg.csproj", "{FA4ACE8B-CDEE-41A3-92D7-C73069B84B83}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csvorbis", "csvorbis\csvorbis.csproj", "{0087C0AF-E896-4C55-A999-5245560BCBE3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OggDecoder", "OggDecoder\OggDecoder.csproj", "{4CA9EB23-E111-4DAD-9932-F73E8E6C54C5}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OggTools", "OggTools\OggTools.csproj", "{4CA9EB23-E111-4DAD-9932-F73E8E6C54C5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -12,10 +13,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {FA4ACE8B-CDEE-41A3-92D7-C73069B84B83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FA4ACE8B-CDEE-41A3-92D7-C73069B84B83}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FA4ACE8B-CDEE-41A3-92D7-C73069B84B83}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FA4ACE8B-CDEE-41A3-92D7-C73069B84B83}.Release|Any CPU.Build.0 = Release|Any CPU {0087C0AF-E896-4C55-A999-5245560BCBE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0087C0AF-E896-4C55-A999-5245560BCBE3}.Debug|Any CPU.Build.0 = Debug|Any CPU {0087C0AF-E896-4C55-A999-5245560BCBE3}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -24,6 +21,41 @@ Global {4CA9EB23-E111-4DAD-9932-F73E8E6C54C5}.Debug|Any CPU.Build.0 = Debug|Any CPU {4CA9EB23-E111-4DAD-9932-F73E8E6C54C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {4CA9EB23-E111-4DAD-9932-F73E8E6C54C5}.Release|Any CPU.Build.0 = Release|Any CPU + {FA4ACE8B-CDEE-41A3-92D7-C73069B84B83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA4ACE8B-CDEE-41A3-92D7-C73069B84B83}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA4ACE8B-CDEE-41A3-92D7-C73069B84B83}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA4ACE8B-CDEE-41A3-92D7-C73069B84B83}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = OggTools\OggTools.csproj + Policies = $0 + $0.TextStylePolicy = $1 + $1.inheritsSet = VisualStudio + $1.inheritsScope = text/plain + $1.scope = text/x-csharp + $0.CSharpFormattingPolicy = $2 + $2.IndentSwitchBody = True + $2.AnonymousMethodBraceStyle = NextLine + $2.PropertyBraceStyle = NextLine + $2.PropertyGetBraceStyle = NextLine + $2.PropertySetBraceStyle = NextLine + $2.EventBraceStyle = NextLine + $2.EventAddBraceStyle = NextLine + $2.EventRemoveBraceStyle = NextLine + $2.StatementBraceStyle = NextLine + $2.ArrayInitializerBraceStyle = NextLine + $2.BeforeMethodDeclarationParentheses = False + $2.BeforeMethodCallParentheses = False + $2.BeforeConstructorDeclarationParentheses = False + $2.BeforeDelegateDeclarationParentheses = False + $2.NewParentheses = False + $2.inheritsSet = Mono + $2.inheritsScope = text/x-csharp + $2.scope = text/x-csharp + $0.TextStylePolicy = $3 + $3.inheritsSet = VisualStudio + $3.inheritsScope = text/plain + $3.scope = text/plain EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/csvorbis/csvorbis.csproj b/csvorbis/csvorbis.csproj index f3bdfc8..4546bb5 100644 --- a/csvorbis/csvorbis.csproj +++ b/csvorbis/csvorbis.csproj @@ -7,17 +7,14 @@ {0087C0AF-E896-4C55-A999-5245560BCBE3} Debug AnyCPU - csvorbis - JScript Grid IE50 false Library csvorbis - v2.0 @@ -41,9 +38,7 @@ bin\Debug\ - false 285212672 - false DEBUG;TRACE @@ -52,7 +47,6 @@ false false false - false 4 full prompt @@ -60,18 +54,14 @@ bin\Release\ - false 285212672 - false TRACE - false 4096 true false false - false 4 none prompt @@ -81,7 +71,6 @@ csogg {FA4ACE8B-CDEE-41A3-92D7-C73069B84B83} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} System @@ -210,4 +199,15 @@ + + + + + + + + + + + \ No newline at end of file