PageRenderTime 59ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/Source/AC.AL/Format/Ogg/OggStream.cs

http://acal.codeplex.com
C# | 416 lines | 290 code | 62 blank | 64 comment | 61 complexity | 06feed3a9996a6f69dd30fb03f76bf53 MD5 | raw file
Possible License(s): LGPL-2.0
  1. using System;
  2. using System.IO;
  3. using csogg;
  4. using csvorbis;
  5. namespace AC.AL.Format.Ogg
  6. {
  7. public class OggStream : IBaseAudioStream
  8. {
  9. #region Constants
  10. /// <summary>
  11. ///The endianes of the computer.
  12. /// </summary>
  13. private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian;
  14. #endregion
  15. #region Private
  16. // temp vars
  17. private float[][][] tempPcm = new float[1][][];
  18. private int[] index;
  19. /// <summary>
  20. /// Flag if the stream reached the end.
  21. /// </summary>
  22. private bool eos;
  23. // sync and verify incoming physical bitstream
  24. private SyncState syncState;
  25. // take physical pages, weld into a logical stream of packets
  26. private StreamState streamState;
  27. // one Ogg bitstream page. Vorbis packets are inside
  28. private Page page;
  29. // one raw packet of data for decode
  30. private Packet packet;
  31. // struct that stores all the static vorbis bitstream settings
  32. private Info info;
  33. // struct that stores all the bitstream user comments
  34. private Comment comment;
  35. // central working state for the packet->PCM decoder
  36. private DspState dspState;
  37. // local working space for packet->PCM decode
  38. private Block block;
  39. /// Conversion buffer size
  40. private static int convsize = 4096 * 2;
  41. // Conversion buffer
  42. private static byte[] convbuffer = new byte[convsize];
  43. // where we are in the convbuffer
  44. private int convbufferOff;
  45. // bytes ready in convbuffer.
  46. private int convbufferSize;
  47. private Stream input;
  48. #endregion
  49. public int Channels
  50. {
  51. get { return info.channels; }
  52. }
  53. public int Samplerate
  54. {
  55. get { return info.rate; }
  56. }
  57. public OggStream(Stream setInput)
  58. {
  59. input = setInput;
  60. Rewind();
  61. }
  62. #region Rewind
  63. public void Rewind()
  64. {
  65. input.Position = 0;
  66. eos = false;
  67. syncState = new SyncState();
  68. streamState = new StreamState();
  69. page = new Page();
  70. packet = new Packet();
  71. info = new Info();
  72. comment = new Comment();
  73. dspState = new DspState();
  74. block = new Block(dspState);
  75. try
  76. {
  77. InitVorbis();
  78. index = new int[info.channels];
  79. }
  80. catch
  81. {
  82. eos = true;
  83. }
  84. }
  85. #endregion
  86. #region Read
  87. public int Read(byte[] buffer, int length)
  88. {
  89. if (eos)
  90. {
  91. return 0;
  92. }
  93. int offset = 0;
  94. int bytesRead = 0;
  95. while (eos == false &&
  96. length > 0)
  97. {
  98. // Fill convbuffer
  99. if (convbufferOff >= convbufferSize)
  100. {
  101. int result = GetNextPacket(packet);
  102. convbufferSize = result == -1 ? result : DecodePacket(packet);
  103. convbufferOff = 0;
  104. if (convbufferSize == -1)
  105. {
  106. eos = true;
  107. }
  108. }
  109. if (eos == false)
  110. {
  111. int bytesToCopy = Math.Min(length, convbufferSize - convbufferOff);
  112. Array.Copy(convbuffer, convbufferOff, buffer, offset, bytesToCopy);
  113. convbufferOff += bytesToCopy;
  114. bytesRead += bytesToCopy;
  115. length -= bytesToCopy;
  116. offset += bytesToCopy;
  117. }
  118. }
  119. return bytesRead;
  120. }
  121. #endregion
  122. #region InitVorbis
  123. private void InitVorbis()
  124. {
  125. // Now we can read pages
  126. syncState.init();
  127. // grab some data at the head of the stream. We want the first page
  128. // (which is guaranteed to be small and only contain the Vorbis
  129. // stream initial header) We need the first page to get the stream
  130. // serialno.
  131. // submit a 4k block to libvorbis' Ogg layer
  132. int index = syncState.buffer(4096);
  133. byte[] buffer = syncState.data;
  134. int bytes = input.Read(buffer, 0, buffer.Length);
  135. syncState.wrote(bytes);
  136. // Get the first page.
  137. if (syncState.pageout(page) != 1)
  138. {
  139. // have we simply run out of data? If so, we're done.
  140. if (bytes < 4096)
  141. {
  142. return;
  143. }
  144. throw new Exception("Input does not appear to be an Ogg bitstream.");
  145. }
  146. // Get the serial number and set up the rest of decode.
  147. // serialno first; use it to set up a logical stream
  148. streamState.init(page.serialno());
  149. // extract the initial header from the first page and verify that the
  150. // Ogg bitstream is in fact Vorbis data
  151. // I handle the initial header first instead of just having the code
  152. // read all three Vorbis headers at once because reading the initial
  153. // header is an easy way to identify a Vorbis bitstream and it's
  154. // useful to see that functionality seperated out.
  155. info.init();
  156. comment.init();
  157. if (streamState.pagein(page) < 0)
  158. {
  159. // error; stream version mismatch perhaps
  160. throw new Exception("Error reading first page of Ogg bitstream data.");
  161. }
  162. if (streamState.packetout(packet) != 1)
  163. {
  164. // no page? must not be vorbis
  165. throw new Exception("Error reading initial header packet.");
  166. }
  167. if (info.synthesis_headerin(comment, packet) < 0)
  168. {
  169. // error case; not a vorbis header
  170. throw new Exception(
  171. "This Ogg bitstream does not contain Vorbis audio data.");
  172. }
  173. // At this point, we're sure we're Vorbis. We've set up the logical
  174. // (Ogg) bitstream decoder. Get the comment and codebook headers and
  175. // set up the Vorbis decoder
  176. // The next two packets in order are the comment and codebook headers.
  177. // They're likely large and may span multiple pages. Thus we read
  178. // and submit data until we get our two packets, watching that no
  179. // pages are missing. If a page is missing, error out; losing a
  180. // header page is the only place where missing data is fatal.
  181. int i = 0;
  182. while (i < 2)
  183. {
  184. while (i < 2)
  185. {
  186. int result = syncState.pageout(page);
  187. if (result == 0)
  188. {
  189. // Need more data
  190. break;
  191. }
  192. // Don't complain about missing or corrupt data yet. We'll
  193. // catch it at the packet output phase
  194. if (result == 1)
  195. {
  196. // we can ignore any errors here
  197. streamState.pagein(page);
  198. // as they'll also become apparent
  199. // at packetout
  200. while (i < 2)
  201. {
  202. result = streamState.packetout(packet);
  203. if (result == 0)
  204. {
  205. break;
  206. }
  207. if (result == -1)
  208. {
  209. throw new Exception("Corrupt secondary header. Exiting.");
  210. }
  211. info.synthesis_headerin(comment, packet);
  212. i++;
  213. }
  214. }
  215. }
  216. // no harm in not checking before adding more
  217. index = syncState.buffer(4096);
  218. buffer = syncState.data;
  219. bytes = input.Read(buffer, index, 4096);
  220. // NOTE: This is a bugfix. read will return -1 which will mess up syncState.
  221. if (bytes < 0)
  222. {
  223. bytes = 0;
  224. }
  225. if (bytes == 0 &&
  226. i < 2)
  227. {
  228. throw new Exception("End of file before finding all Vorbis headers!");
  229. }
  230. syncState.wrote(bytes);
  231. }
  232. convsize = 4096 / info.channels;
  233. dspState.synthesis_init(info);
  234. block.init(dspState);
  235. }
  236. #endregion
  237. #region DecodePacket
  238. private int DecodePacket(Packet packet)
  239. {
  240. if (block.synthesis(packet) == 0)
  241. {
  242. // test for success!
  243. dspState.synthesis_blockin(block);
  244. }
  245. int convOff = 0;
  246. int samples;
  247. while ((samples = dspState.synthesis_pcmout(tempPcm, index)) > 0)
  248. {
  249. float[][] pcm = tempPcm[0];
  250. int bout = (samples < convsize ? samples : convsize);
  251. // convert floats to 16 bit signed ints (host order) and interleave
  252. for (int i = 0; i < Channels; i++)
  253. {
  254. int ptr = (i << 1) + convOff;
  255. int mono = index[i];
  256. for (int j = 0; j < bout; j++)
  257. {
  258. int val = (int)(pcm[i][mono + j] * 32767);
  259. // might as well guard against clipping
  260. val = Math.Max(-32768, Math.Min(32767, val));
  261. val |= (val < 0 ? 0x8000 : 0);
  262. convbuffer[ptr + 0] = (byte)(IsLittleEndian ?
  263. val :
  264. unchecked((int)((uint)val >> 8)));
  265. convbuffer[ptr + 1] = (byte)(IsLittleEndian ?
  266. unchecked((int)((uint)val >> 8)) :
  267. val);
  268. ptr += Channels << 1;
  269. }
  270. }
  271. convOff += 2 * Channels * bout;
  272. // Tell vorbis how many samples we've consumed
  273. dspState.synthesis_read(bout);
  274. }
  275. return convOff;
  276. }
  277. #endregion
  278. #region GetNextPacket
  279. private int GetNextPacket(Packet packet)
  280. {
  281. // get next packet.
  282. bool fetchedPacket = false;
  283. while (eos == false &&
  284. fetchedPacket == false)
  285. {
  286. int result1 = streamState.packetout(packet);
  287. if (result1 == 0)
  288. {
  289. // no more packets in page. Fetch new page.
  290. int result2 = 0;
  291. while (eos == false &&
  292. result2 == 0)
  293. {
  294. result2 = syncState.pageout(page);
  295. if (result2 == 0)
  296. {
  297. FetchData();
  298. }
  299. }
  300. // return if we have reaced end of file.
  301. if (result2 == 0 &&
  302. page.eos() != 0)
  303. {
  304. return -1;
  305. }
  306. if (result2 == 0)
  307. {
  308. // need more data fetching page..
  309. FetchData();
  310. }
  311. else if (result2 == -1)
  312. {
  313. return -1;
  314. }
  315. else
  316. {
  317. streamState.pagein(page);
  318. }
  319. }
  320. else if (result1 == -1)
  321. {
  322. return -1;
  323. }
  324. else
  325. {
  326. fetchedPacket = true;
  327. }
  328. }
  329. return 0;
  330. }
  331. #endregion
  332. #region FetchData
  333. private void FetchData()
  334. {
  335. if (eos == false)
  336. {
  337. // copy 4096 bytes from compressed stream to syncState.
  338. int index = syncState.buffer(4096);
  339. if (index < 0)
  340. {
  341. eos = true;
  342. return;
  343. }
  344. int bytes = input.Read(syncState.data, index, 4096);
  345. syncState.wrote(bytes);
  346. if (bytes == 0)
  347. {
  348. eos = true;
  349. }
  350. }
  351. }
  352. #endregion
  353. }
  354. }