PageRenderTime 88ms CodeModel.GetById 31ms app.highlight 33ms RepoModel.GetById 17ms app.codeStats 0ms

/Multimedia/OpenTK/OggInputStream.cs

#
C# | 691 lines | 411 code | 82 blank | 198 comment | 68 complexity | ebe1bbbbf18f207bf76741a1146d351c MD5 | raw file
  1using System;
  2using System.IO;
  3using csogg;
  4using csvorbis;
  5using Delta.Utilities;
  6using OpenTK.Audio.OpenAL;
  7
  8namespace Delta.Multimedia.OpenTK
  9{
 10	/// <summary>
 11	/// Ogg input stream class used to decode an ogg vorbis input stream into
 12	/// usable pcm data for OpenAL.
 13	/// <para />
 14	/// As reference the original file can be found here:
 15	/// http://www.opentk.com/files/OggInputStream.cs
 16	/// </summary>
 17	internal class OggInputStream
 18	{
 19		#region Constants
 20		/// <summary>
 21		/// Default conversion buffer size is 8192 bytes!
 22		/// </summary>
 23		private const int DefaultConvsize = 4096 * 2;
 24
 25		/// <summary>
 26		/// Initialize conversion buffer with 8192 bytes by default.
 27		/// The buffer will contain all channels at once, so the size of
 28		/// the convbuffer per channel can be calculated by DefaultConvsize / channels.
 29		/// See convsizePerChannel variable and the "InitVorbis" method.
 30		/// </summary>
 31		private static byte[] convbuffer = new byte[DefaultConvsize];
 32		#endregion
 33
 34		#region Format (Public)
 35		/// <summary>
 36		/// Gets the format of the ogg file. Is either MONO16 or STEREO16.
 37		/// </summary>
 38		public ALFormat Format
 39		{
 40			get
 41			{
 42				return info.channels > 1
 43				       	? ALFormat.Stereo16
 44				       	: ALFormat.Mono16;
 45			}
 46		}
 47		#endregion
 48
 49		#region SampleRate (Public)
 50		public int SampleRate
 51		{
 52			get
 53			{
 54				return info.rate;
 55			}
 56		}
 57		#endregion
 58
 59		#region Private
 60
 61		#region convsizePerChannel (Private)
 62		/// <summary>
 63		/// The size of the convbuffer per channel. Currently set as
 64		/// the Default, but will be changed in the "InitVorbis" method to
 65		/// DefaultConvsize / channels.
 66		/// </summary>
 67		private static int convsizePerChannel = DefaultConvsize;
 68		#endregion
 69
 70		#region _pcm (Private)
 71		/// <summary>
 72		/// temp vars
 73		/// </summary>
 74		private readonly float[][][] _pcm = new float[1][][];
 75		#endregion
 76
 77		#region _index (Private)
 78		/// <summary>
 79		/// _index
 80		/// </summary>
 81		private readonly int[] _index;
 82		#endregion
 83
 84		#region eos (Private)
 85		/// <summary>
 86		/// end of stream
 87		/// </summary>
 88		private bool eos;
 89		#endregion
 90
 91		#region syncState (Private)
 92		/// <summary>
 93		/// sync and verify incoming physical bitstream
 94		/// </summary>
 95		private readonly SyncState syncState = new SyncState();
 96		#endregion
 97
 98		#region streamState (Private)
 99		/// <summary>
100		/// take physical pages, weld into a logical stream of packets
101		/// </summary>
102		private readonly StreamState streamState = new StreamState();
103		#endregion
104
105		#region page (Private)
106		/// <summary>
107		/// one Ogg bitstream page.  Vorbis packets are inside
108		/// </summary>
109		private readonly Page page = new Page();
110		#endregion
111
112		#region packet (Private)
113		/// <summary>
114		/// one raw packet of data for decode
115		/// </summary>
116		private readonly Packet packet = new Packet();
117		#endregion
118
119		#region info (Private)
120		/// <summary>
121		/// struct that stores all the static vorbis bitstream settings
122		/// </summary>
123		private readonly Info info = new Info();
124		#endregion
125
126		#region comment (Private)
127		/// <summary>
128		/// struct that stores all the bitstream user comments
129		/// </summary>
130		private readonly Comment comment = new Comment();
131		#endregion
132
133		#region dspState (Private)
134		/// <summary>
135		/// central working state for the packet->PCM decoder
136		/// </summary>
137		private readonly DspState dspState = new DspState();
138		#endregion
139
140		#region block (Private)
141		/// <summary>
142		/// local working space for packet->PCM decode
143		/// </summary>
144		private readonly Block block;
145		#endregion
146
147		#region convbufferOff (Private)
148		/// <summary>
149		/// where we are in the convbuffer
150		/// </summary>
151		private int convbufferOff;
152		#endregion
153
154		#region convbufferSize (Private)
155		/// <summary>
156		/// bytes ready in convbuffer.
157		/// </summary>
158		private int convbufferSize;
159		#endregion
160
161		#region readDummy (Private)
162		/// <summary>
163		/// a dummy used by read() to read 1 byte.
164		/// </summary>
165		private readonly byte[] readDummy = new byte[1];
166		#endregion
167
168		#region input (Private)
169		/// <summary>
170		/// Input
171		/// </summary>
172		private readonly Stream input;
173		#endregion
174
175		#endregion
176
177		#region Constructors
178		/// <summary>
179		/// Creates an OggInputStream that decompressed the specified ogg file.
180		/// </summary>
181		/// <param name="setInput">Input file stream.</param>
182		public OggInputStream(Stream setInput)
183		{
184			input = setInput;
185			block = new Block(dspState);
186			try
187			{
188				InitVorbis();
189				_index = new int[info.channels];
190			}
191			catch (Exception ex)
192			{
193				Log.Warning("Failed to initialize ogg input stream: " + ex);
194				eos = true;
195			}
196		}
197		#endregion
198
199		#region Read (Public)
200		/// <summary>
201		/// Reads the next byte of data from this input stream. The value byte is
202		/// returned as an int in the range 0 to 255. If no byte is available
203		/// because the end of the stream has been reached, the value -1 is
204		/// returned. This method blocks until input data is available, the end
205		/// of the stream is detected, or an exception is thrown.
206		/// </summary>
207		/// <returns>the next byte of data, or -1 if the end of the stream is
208		/// reached.</returns>
209		public int Read()
210		{
211			int retVal = Read(readDummy, 0, 1);
212			return
213				retVal == -1
214					? -1
215					: readDummy[0];
216		}
217
218		/// <summary>
219		/// Reads up to len bytes of data from the input stream into an
220		/// array of bytes.
221		/// </summary>
222		/// <param name="b">the buffer into which the data is read.</param>
223		/// <param name="off">the start offset of the data.</param>
224		/// <param name="len">the maximum number of bytes read.</param>
225		/// <returns>
226		/// The total number of bytes read into the buffer, or -1 if there is no
227		/// more data because the end of the stream has been reached.
228		/// </returns>
229		public int Read(byte[] b, int off, int len)
230		{
231			if (eos)
232			{
233				return 0;
234			}
235
236			int bytesRead = 0;
237			while (!eos &&
238			       (len > 0))
239			{
240				FillConvbuffer();
241
242				if (!eos)
243				{
244					int bytesToCopy = Math.Min(len, convbufferSize - convbufferOff);
245					Array.Copy(convbuffer, convbufferOff, b, off, bytesToCopy);
246					convbufferOff += bytesToCopy;
247					bytesRead += bytesToCopy;
248					len -= bytesToCopy;
249					off += bytesToCopy;
250				} // if
251			} // while
252
253			return bytesRead;
254		}
255
256		/// <summary>
257		/// Reads up to len bytes of data from the input stream into a ByteBuffer.
258		/// </summary>
259		/// <param name="stream"></param>
260		/// <param name="off">the start offset of the data.</param>
261		/// <param name="len">the maximum number of bytes read.</param>
262		/// <returns>the total number of bytes read into the buffer, or -1 if
263		/// there is no more data because the end of the stream has been reached.
264		/// </returns>
265		public int Read(MemoryStream stream, int off, int len)
266		{
267			if (eos)
268			{
269				return 0;
270			}
271
272			stream.Seek(off, SeekOrigin.Begin);
273			int bytesRead = 0;
274			while (!eos &&
275			       (len > 0))
276			{
277				FillConvbuffer();
278
279				if (!eos)
280				{
281					int bytesToCopy = Math.Min(len, convbufferSize - convbufferOff);
282					stream.Write(convbuffer, convbufferOff, bytesToCopy);
283					convbufferOff += bytesToCopy;
284					bytesRead += bytesToCopy;
285					len -= bytesToCopy;
286				}
287			} // while
288
289			return bytesRead;
290		}
291		#endregion
292
293		#region Skip (Public)
294		/// <summary>
295		/// Skips over and discards n bytes of data from the input stream.
296		/// The skip method may, for a variety of reasons, end up skipping over
297		/// some smaller number of bytes, possibly 0. The actual number of bytes
298		/// skipped is returned. 
299		/// </summary>
300		/// <param name="n">the number of bytes to be skipped.</param>
301		/// <returns>the actual number of bytes skipped.</returns>
302		public long Skip(long n)
303		{
304			int bytesRead = 0;
305			while (bytesRead < n)
306			{
307				int res = Read();
308				if (res == -1)
309				{
310					break;
311				}
312
313				bytesRead++;
314			} // while
315
316			return bytesRead;
317		}
318		#endregion
319
320		#region ToString (Public)
321		/// <summary>
322		/// Gets information on the ogg.
323		/// </summary>
324		/// <returns>Returns a string representing this ogg input stream.</returns>
325		public override string ToString()
326		{
327			return
328				"Version=" + info.version + "\n" +
329				"Channels=" + info.channels + "\n" +
330				"Rate (hz)=" + info.rate;
331		}
332		#endregion
333
334		#region Methods (Private)
335
336		#region FillConvbuffer
337		/// <summary>
338		/// Helper function. Decodes a packet to the convbuffer if it is empty.
339		/// Updates convbufferSize, convbufferOff, and eos.
340		/// </summary>
341		private void FillConvbuffer()
342		{
343			if (convbufferOff >= convbufferSize)
344			{
345				convbufferSize = LazyDecodePacket();
346				convbufferOff = 0;
347				if (convbufferSize == -1)
348				{
349					eos = true;
350				}
351			}
352		}
353		#endregion
354
355		#region InitVorbis
356		/// <summary>
357		/// Initalizes the vorbis stream. Reads the stream until info
358		/// and comment are read.
359		/// </summary>
360		private void InitVorbis()
361		{
362			// Now we can read pages
363			syncState.init();
364
365			// grab some data at the head of the stream.  We want the first page
366			// (which is guaranteed to be small and only contain the Vorbis
367			// stream initial header) We need the first page to get the stream
368			// serialno.
369
370			// submit a 4k block to libvorbis' Ogg layer
371			int index = syncState.buffer(4096);
372			byte[] buffer = syncState.data;
373			int bytes = input.Read(buffer, 0, buffer.Length);
374			syncState.wrote(bytes);
375			// Get the first page.
376			if (syncState.pageout(page) != 1)
377			{
378				// have we simply run out of data?  If so, we're done.
379				if (bytes < 4096)
380				{
381					return; //break;
382				}
383
384				// error case.  Must not be Vorbis data
385				throw new Exception("Input does not appear to be an Ogg bitstream.");
386			} // if
387
388			// Get the serial number and set up the rest of decode.
389			// serialno first; use it to set up a logical stream
390			streamState.init(page.serialno());
391
392			// extract the initial header from the first page and verify that the
393			// Ogg bitstream is in fact Vorbis data
394
395			// I handle the initial header first instead of just having the code
396			// read all three Vorbis headers at once because reading the initial
397			// header is an easy way to identify a Vorbis bitstream and it's
398			// useful to see that functionality seperated out.
399
400			info.init();
401			comment.init();
402			if (streamState.pagein(page) < 0)
403			{
404				// error; stream version mismatch perhaps
405				throw new Exception("Error reading first page of Ogg bitstream data.");
406			}
407
408			if (streamState.packetout(packet) != 1)
409			{
410				// no page? must not be vorbis
411				throw new Exception("Error reading initial header packet.");
412			}
413
414			if (info.synthesis_headerin(comment, packet) < 0)
415			{
416				// error case; not a vorbis header
417				throw new Exception(
418					"This Ogg bitstream does not contain Vorbis audio data.");
419			}
420
421			// At this point, we're sure we're Vorbis.  We've set up the logical
422			// (Ogg) bitstream decoder.  Get the comment and codebook headers and
423			// set up the Vorbis decoder
424
425			// The next two packets in order are the comment and codebook headers.
426			// They're likely large and may span multiple pages.  Thus we read
427			// and submit data until we get our two packets, watching that no
428			// pages are missing.  If a page is missing, error out; losing a
429			// header page is the only place where missing data is fatal. 
430			int i = 0;
431			while (i < 2)
432			{
433				while (i < 2)
434				{
435					int result = syncState.pageout(page);
436					if (result == 0)
437					{
438						break; // Need more data
439					}
440
441					// Don't complain about missing or corrupt data yet.  We'll
442					// catch it at the packet output phase
443					if (result == 1)
444					{
445						streamState.pagein(page);
446
447						// We can ignore any errors here as they'll also become apparent
448						// at packetout.
449						while (i < 2)
450						{
451							result = streamState.packetout(packet);
452							if (result == 0)
453							{
454								break;
455							}
456
457							if (result == -1)
458							{
459								// Uh oh; data at some point was corrupted or missing!
460								// We can't tolerate that in a header.  Die.
461								throw new Exception("Corrupt secondary header. Exiting.");
462							}
463
464							info.synthesis_headerin(comment, packet);
465							i++;
466						} // while
467					} // if
468				} // while
469
470				// no harm in not checking before adding more
471				index = syncState.buffer(4096);
472				buffer = syncState.data;
473				bytes = input.Read(buffer, index, 4096);
474
475				// NOTE: This is a bugfix. read will return -1 which will mess up
476				// syncState.
477				if (bytes < 0)
478				{
479					bytes = 0;
480				}
481
482				if (bytes == 0 &&
483					i < 2)
484				{
485					throw new Exception(
486						"End of file before finding all Vorbis headers!");
487				}
488
489				syncState.wrote(bytes);
490			} // while
491
492			convsizePerChannel = DefaultConvsize / info.channels;
493
494			// OK, got and parsed all three headers. Initialize the Vorbis
495			//  packet->PCM decoder. central decode state
496			dspState.synthesis_init(info);
497
498			// local state for most of the decode so multiple block decodes can
499			// proceed in parallel.  We could init multiple vorbis_block structures
500			// for vd here.
501			block.init(dspState);
502		}
503		#endregion
504
505		#region DecodePacket
506		/// <summary>
507		/// Decodes a packet.
508		/// </summary>
509		/// <returns>Packet length to write into convbufferSize</returns>
510		private int DecodePacket()
511		{
512			// check the endianes of the computer.
513			bool bigEndian = !BitConverter.IsLittleEndian;
514
515			if (block.synthesis(packet) == 0)
516			{
517				// test for success!
518				dspState.synthesis_blockin(block);
519			}
520
521			// **pcm is a multichannel float vector.  In stereo, for
522			// example, pcm[0] is left, and pcm[1] is right.  samples is
523			// the size of each channel. Convert the float values
524			// (-1.<=range<=1.) to whatever PCM format and write it out
525			int convOff = 0;
526			int samples;
527			while ((samples = dspState.synthesis_pcmout(_pcm, _index)) > 0)
528			{
529				float[][] pcm = _pcm[0];
530				int bout =
531					samples < convsizePerChannel
532						? samples
533						: convsizePerChannel;
534
535				// convert floats to 16 bit signed ints (host order) and interleave
536				for (int i = 0; i < info.channels; i++)
537				{
538					int ptr = (i << 1) + convOff;
539
540					int mono = _index[i];
541
542					for (int j = 0; j < bout; j++)
543					{
544						int val = (int)(pcm[i][mono + j] * 32767);
545
546						// might as well guard against clipping
547						val = Math.Max(-32768, Math.Min(32767, val));
548						val |=
549							val < 0
550								? 0x8000
551								: 0;
552
553						convbuffer[ptr + 0] =
554							(byte)
555							(bigEndian
556							 	? unchecked((int)((uint)val >> 8))
557							 	: val);
558						convbuffer[ptr + 1] =
559							(byte)
560							(bigEndian
561							 	? val
562							 	: unchecked((int)((uint)val >> 8)));
563
564						ptr += (info.channels) << 1;
565					} // for
566				} // for
567
568				convOff += 2 * info.channels * bout;
569
570				// Tell orbis how many samples were consumed
571				dspState.synthesis_read(bout);
572			} // while
573
574			return convOff;
575		}
576		#endregion
577
578		#region LazyDecodePacket
579		/// <summary>
580		/// Decodes the next packet.
581		/// </summary>
582		/// <returns>Bytes read into convbuffer of -1 if end of file</returns>
583		private int LazyDecodePacket()
584		{
585			int result = GetNextPacket();
586			if (result == -1)
587			{
588				return -1;
589			}
590
591			// We have a packet. Decode it.
592			return DecodePacket();
593		}
594		#endregion
595
596		#region GetNextPacket
597		/// <summary>
598		/// Get the next packet in the music file.
599		/// </summary>
600		/// <returns>
601		/// Returns -1 if getting the next packet failed, otherwise 0.
602		/// </returns>
603		private int GetNextPacket()
604		{
605			// get next packet.
606			bool fetchedPacket = false;
607			while (eos == false &&
608						 fetchedPacket == false)
609			{
610				int result1 = streamState.packetout(packet);
611				if (result1 == 0)
612				{
613					// no more packets in page. Fetch new page.
614					int result2 = 0;
615					while (eos == false &&
616					       result2 == 0)
617					{
618						result2 = syncState.pageout(page);
619						if (result2 == 0)
620						{
621							FetchData();
622						}
623					}
624
625					// return if we have reaced end of file.
626					if ((result2 == 0) && (page.eos() != 0))
627					{
628						return -1;
629					}
630
631					if (result2 == 0)
632					{
633						// need more data fetching page..
634						FetchData();
635					}
636					else if (result2 == -1)
637					{
638						//throw new Exception("syncState.pageout(page) result == -1");
639						Log.Test("syncState.pageout(page) result == -1");
640						return -1;
641					}
642					else
643					{
644						int result3 = streamState.pagein(page);
645					}
646				} // if
647				else if (result1 == -1)
648				{
649					//throw new Exception("streamState.packetout(packet) result == -1");
650					Log.Test("streamState.packetout(packet) result == -1");
651					return -1;
652				}
653				else
654				{
655					fetchedPacket = true;
656				}
657			} // while
658
659			return 0;
660		}
661		#endregion
662
663		#region FetchData
664		/// <summary>
665		/// Copies data from input stream to syncState.
666		/// </summary>
667		private void FetchData()
668		{
669			if (eos == false)
670			{
671				// copy 4096 bytes from compressed stream to syncState.
672				int index = syncState.buffer(4096);
673				if (index < 0)
674				{
675					eos = true;
676					return;
677				}
678				int bytes = input.Read(syncState.data, index, 4096);
679				syncState.wrote(bytes);
680				if (bytes == 0)
681				{
682					eos = true;
683				}
684			}
685		}
686		#endregion
687
688		#endregion
689	}
690}
691