PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/engine/src/main/java/org/terasology/audio/formats/OggReader.java

http://github.com/MovingBlocks/Terasology
Java | 537 lines | 279 code | 69 blank | 189 comment | 72 complexity | 2643197fdd782c4eae5ed3e063727f73 MD5 | raw file
Possible License(s): Apache-2.0
  1. /*
  2. * Copyright 2013 MovingBlocks
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.terasology.audio.formats;
  17. import com.jcraft.jogg.Packet;
  18. import com.jcraft.jogg.Page;
  19. import com.jcraft.jogg.StreamState;
  20. import com.jcraft.jogg.SyncState;
  21. import com.jcraft.jorbis.Block;
  22. import com.jcraft.jorbis.Comment;
  23. import com.jcraft.jorbis.DspState;
  24. import com.jcraft.jorbis.Info;
  25. import org.slf4j.Logger;
  26. import org.slf4j.LoggerFactory;
  27. import java.io.FilterInputStream;
  28. import java.io.IOException;
  29. import java.io.InputStream;
  30. import java.nio.ByteBuffer;
  31. import java.nio.ByteOrder;
  32. /**
  33. * Decompresses an Ogg file.
  34. * <br><br>
  35. * How to use:<br>
  36. * 1. Create OggInputStream passing in the input stream of the packed ogg file<br>
  37. * 2. Fetch format and sampling rate using getFormat() and getRate(). Use it to
  38. * initialize the sound player.<br>
  39. * 3. Read the PCM data using one of the read functions, and feed it to your player.
  40. * <br><br>
  41. * OggInputStream provides a read(ByteBuffer, int, int) that can be used to read
  42. * data directly into a native buffer.
  43. */
  44. public class OggReader extends FilterInputStream {
  45. private static final Logger logger = LoggerFactory.getLogger(OggReader.class);
  46. /**
  47. * The mono 16 bit format
  48. */
  49. private static final int FORMAT_MONO16 = 1;
  50. /**
  51. * The stereo 16 bit format
  52. */
  53. private static final int FORMAT_STEREO16 = 2;
  54. /// Conversion buffer size
  55. private static int convsize = 4096 * 2;
  56. // Conversion buffer
  57. private static byte[] convbuffer = new byte[convsize];
  58. // temp vars
  59. private float[][][] pcm = new float[1][][];
  60. private int[] index;
  61. // end of stream
  62. private boolean eos;
  63. // sync and verify incoming physical bitstream
  64. private SyncState syncState = new SyncState();
  65. // take physical pages, weld into a logical stream of packets
  66. private StreamState streamState = new StreamState();
  67. // one Ogg bitstream page. Vorbis packets are inside
  68. private Page page = new Page();
  69. // one raw packet of data for decode
  70. private Packet packet = new Packet();
  71. // struct that stores all the static vorbis bitstream settings
  72. private Info info = new Info();
  73. // struct that stores all the bitstream user comments
  74. private Comment comment = new Comment();
  75. // central working state for the packet->PCM decoder
  76. private DspState dspState = new DspState();
  77. // local working space for packet->PCM decode
  78. private Block block = new Block(dspState);
  79. // where we are in the convbuffer
  80. private int convbufferOff;
  81. // bytes ready in convbuffer.
  82. private int convbufferSize;
  83. // a dummy used by read() to read 1 byte.
  84. private byte[] readDummy = new byte[1];
  85. /**
  86. * Creates an OggInputStream that decompressed the specified ogg file.
  87. */
  88. public OggReader(InputStream input) {
  89. super(input);
  90. try {
  91. initVorbis();
  92. index = new int[info.channels];
  93. } catch (Exception e) {
  94. logger.error("Failed to read ogg file", e);
  95. eos = true;
  96. }
  97. }
  98. /**
  99. * Gets the format of the ogg file. Is either FORMAT_MONO16 or FORMAT_STEREO16
  100. */
  101. public int getChannels() {
  102. return info.channels;
  103. }
  104. /**
  105. * Gets the rate of the pcm audio.
  106. */
  107. public int getRate() {
  108. return info.rate;
  109. }
  110. /**
  111. * Reads the next byte of data from this input stream. The value byte is
  112. * returned as an int in the range 0 to 255. If no byte is available because
  113. * the end of the stream has been reached, the value -1 is returned. This
  114. * method blocks until input data is available, the end of the stream is
  115. * detected, or an exception is thrown.
  116. *
  117. * @return the next byte of data, or -1 if the end of the stream is reached.
  118. */
  119. @Override
  120. public int read() throws IOException {
  121. int retVal = read(readDummy, 0, 1);
  122. return (retVal == -1 ? -1 : readDummy[0]);
  123. }
  124. /**
  125. * Reads up to len bytes of data from the input stream into an array of bytes.
  126. *
  127. * @param b the buffer into which the data is read.
  128. * @param off the start offset of the data.
  129. * @param len the maximum number of bytes read.
  130. * @return the total number of bytes read into the buffer, or -1 if there is
  131. * no more data because the end of the stream has been reached.
  132. */
  133. @Override
  134. public int read(byte[] b, int off, int len) throws IOException {
  135. if (eos) {
  136. return -1;
  137. }
  138. int bytesRead = 0;
  139. int bytesRemaining = len;
  140. int offset = off;
  141. while (!eos && (bytesRemaining > 0)) {
  142. fillConvbuffer();
  143. if (!eos) {
  144. int bytesToCopy = Math.min(bytesRemaining, convbufferSize - convbufferOff);
  145. System.arraycopy(convbuffer, convbufferOff, b, offset, bytesToCopy);
  146. convbufferOff += bytesToCopy;
  147. bytesRead += bytesToCopy;
  148. bytesRemaining -= bytesToCopy;
  149. offset += bytesToCopy;
  150. }
  151. }
  152. return bytesRead;
  153. }
  154. /**
  155. * Reads up to len bytes of data from the input stream into a ByteBuffer.
  156. *
  157. * @param b the buffer into which the data is read.
  158. * @param off the start offset of the data.
  159. * @param len the maximum number of bytes read.
  160. * @return the total number of bytes read into the buffer, or -1 if there is
  161. * no more data because the end of the stream has been reached.
  162. */
  163. public int read(ByteBuffer b, int off, int len) throws IOException {
  164. if (eos) {
  165. return -1;
  166. }
  167. b.position(off);
  168. int bytesRead = 0;
  169. int bytesRemaining = len;
  170. while (!eos && (bytesRemaining > 0)) {
  171. fillConvbuffer();
  172. if (!eos) {
  173. int bytesToCopy = Math.min(bytesRemaining, convbufferSize - convbufferOff);
  174. b.put(convbuffer, convbufferOff, bytesToCopy);
  175. convbufferOff += bytesToCopy;
  176. bytesRead += bytesToCopy;
  177. bytesRemaining -= bytesToCopy;
  178. }
  179. }
  180. return bytesRead;
  181. }
  182. /**
  183. * Helper function. Decodes a packet to the convbuffer if it is empty.
  184. * Updates convbufferSize, convbufferOff, and eos.
  185. */
  186. private void fillConvbuffer() throws IOException {
  187. if (convbufferOff >= convbufferSize) {
  188. convbufferSize = lazyDecodePacket();
  189. convbufferOff = 0;
  190. if (convbufferSize == -1) {
  191. eos = true;
  192. }
  193. }
  194. }
  195. /**
  196. * Returns 0 after EOF is reached, otherwise always return 1.
  197. * <br><br>
  198. * Programs should not count on this method to return the actual number of
  199. * bytes that could be read without blocking.
  200. *
  201. * @return 1 before EOF and 0 after EOF is reached.
  202. */
  203. @Override
  204. public int available() throws IOException {
  205. return (eos ? 0 : 1);
  206. }
  207. /**
  208. * OggInputStream does not support mark and reset. This function does nothing.
  209. */
  210. @Override
  211. public synchronized void reset() throws IOException {
  212. }
  213. /**
  214. * OggInputStream does not support mark and reset.
  215. *
  216. * @return false.
  217. */
  218. @Override
  219. public boolean markSupported() {
  220. return false;
  221. }
  222. /**
  223. * Skips over and discards n bytes of data from the input stream. The skip
  224. * method may, for a variety of reasons, end up skipping over some smaller
  225. * number of bytes, possibly 0. The actual number of bytes skipped is returned.
  226. *
  227. * @param n the number of bytes to be skipped.
  228. * @return the actual number of bytes skipped.
  229. */
  230. @Override
  231. public long skip(long n) throws IOException {
  232. int bytesRead = 0;
  233. while (bytesRead < n) {
  234. int res = read();
  235. if (res == -1) {
  236. break;
  237. }
  238. bytesRead++;
  239. }
  240. return bytesRead;
  241. }
  242. /**
  243. * Initalizes the vorbis stream. Reads the stream until info and comment are read.
  244. */
  245. private void initVorbis() throws Exception {
  246. // Now we can read pages
  247. syncState.init();
  248. // grab some data at the head of the stream. We want the first page
  249. // (which is guaranteed to be small and only contain the Vorbis
  250. // stream initial header) We need the first page to get the stream
  251. // serialno.
  252. // submit a 4k block to libvorbis' Ogg layer
  253. int bufferIndex = syncState.buffer(4096);
  254. byte[] buffer = syncState.data;
  255. int bytes = in.read(buffer, bufferIndex, 4096);
  256. syncState.wrote(bytes);
  257. // Get the first page.
  258. if (syncState.pageout(page) != 1) {
  259. // have we simply run out of data? If so, we're done.
  260. if (bytes < 4096) {
  261. return; //break;
  262. }
  263. // error case. Must not be Vorbis data
  264. throw new Exception("Input does not appear to be an Ogg bitstream.");
  265. }
  266. // Get the serial number and set up the rest of decode.
  267. // serialno first; use it to set up a logical stream
  268. streamState.init(page.serialno());
  269. // extract the initial header from the first page and verify that the
  270. // Ogg bitstream is in fact Vorbis data
  271. // I handle the initial header first instead of just having the code
  272. // read all three Vorbis headers at once because reading the initial
  273. // header is an easy way to identify a Vorbis bitstream and it's
  274. // useful to see that functionality seperated out.
  275. info.init();
  276. comment.init();
  277. if (streamState.pagein(page) < 0) {
  278. // error; stream version mismatch perhaps
  279. throw new Exception("Error reading first page of Ogg bitstream data.");
  280. }
  281. if (streamState.packetout(packet) != 1) {
  282. // no page? must not be vorbis
  283. throw new Exception("Error reading initial header packet.");
  284. }
  285. if (info.synthesis_headerin(comment, packet) < 0) {
  286. // error case; not a vorbis header
  287. throw new Exception("This Ogg bitstream does not contain Vorbis audio data.");
  288. }
  289. // At this point, we're sure we're Vorbis. We've set up the logical
  290. // (Ogg) bitstream decoder. Get the comment and codebook headers and
  291. // set up the Vorbis decoder
  292. // The next two packets in order are the comment and codebook headers.
  293. // They're likely large and may span multiple pages. Thus we read
  294. // and submit data until we get our two packets, watching that no
  295. // pages are missing. If a page is missing, error out; losing a
  296. // header page is the only place where missing data is fatal.
  297. int i = 0;
  298. while (i < 2) {
  299. while (i < 2) {
  300. int result = syncState.pageout(page);
  301. if (result == 0) {
  302. break; // Need more data
  303. }
  304. // Don't complain about missing or corrupt data yet. We'll
  305. // catch it at the packet output phase
  306. if (result == 1) {
  307. streamState.pagein(page); // we can ignore any errors here
  308. // as they'll also become apparent
  309. // at packet out
  310. while (i < 2) {
  311. result = streamState.packetout(packet);
  312. if (result == 0) {
  313. break;
  314. }
  315. if (result == -1) {
  316. // Uh oh; data at some point was corrupted or missing!
  317. // We can't tolerate that in a header. Die.
  318. throw new Exception("Corrupt secondary header. Exiting.");
  319. }
  320. info.synthesis_headerin(comment, packet);
  321. i++;
  322. }
  323. }
  324. }
  325. // no harm in not checking before adding more
  326. bufferIndex = syncState.buffer(4096);
  327. buffer = syncState.data;
  328. bytes = in.read(buffer, bufferIndex, 4096);
  329. // NOTE: This is a bugfix. read will return -1 which will mess up syncState.
  330. if (bytes < 0) {
  331. bytes = 0;
  332. }
  333. if (bytes == 0 && i < 2) {
  334. throw new Exception("End of file before finding all Vorbis headers!");
  335. }
  336. syncState.wrote(bytes);
  337. }
  338. convsize = 4096 / info.channels;
  339. // OK, got and parsed all three headers. Initialize the Vorbis
  340. // packet->PCM decoder.
  341. dspState.synthesis_init(info); // central decode state
  342. block.init(dspState); // local state for most of the decode
  343. // so multiple block decodes can
  344. // proceed in parallel. We could init
  345. // multiple vorbis_block structures
  346. // for vd here
  347. }
  348. /**
  349. * Decodes a packet.
  350. */
  351. private int decodePacket() {
  352. // check the endianes of the computer.
  353. final boolean bigEndian = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
  354. if (block.synthesis(packet) == 0) {
  355. dspState.synthesis_blockin(block);
  356. }
  357. // **pcm is a multichannel float vector. In stereo, for
  358. // example, pcm[0] is left, and pcm[1] is right. samples is
  359. // the size of each channel. Convert the float values
  360. // (-1.<=range<=1.) to whatever PCM format and write it out
  361. int convOff = 0;
  362. int samples;
  363. while ((samples = dspState.synthesis_pcmout(pcm, index)) > 0) {
  364. float[][] localPcm = this.pcm[0];
  365. int bout = (samples < convsize ? samples : convsize);
  366. // convert floats to 16 bit signed ints (host order) and interleave
  367. for (int i = 0; i < info.channels; i++) {
  368. int ptr = (i << 1) + convOff;
  369. int mono = index[i];
  370. for (int j = 0; j < bout; j++) {
  371. int val = (int) (localPcm[i][mono + j] * 32767);
  372. // might as well guard against clipping
  373. val = Math.max(-32768, Math.min(32767, val));
  374. val |= (val < 0 ? 0x8000 : 0);
  375. convbuffer[ptr + 0] = (byte) (bigEndian ? val >>> 8 : val);
  376. convbuffer[ptr + 1] = (byte) (bigEndian ? val : val >>> 8);
  377. ptr += (info.channels) << 1;
  378. }
  379. }
  380. convOff += 2 * info.channels * bout;
  381. // Tell orbis how many samples were consumed
  382. dspState.synthesis_read(bout);
  383. }
  384. return convOff;
  385. }
  386. /**
  387. * Decodes the next packet.
  388. *
  389. * @return bytes read into convbuffer of -1 if end of file
  390. */
  391. private int lazyDecodePacket() throws IOException {
  392. int result = getNextPacket();
  393. if (result == -1) {
  394. return -1;
  395. }
  396. // we have a packet. Decode it
  397. return decodePacket();
  398. }
  399. /**
  400. * @return
  401. * @throws IOException
  402. */
  403. private int getNextPacket() throws IOException {
  404. // get next packet.
  405. boolean fetchedPacket = false;
  406. while (!eos && !fetchedPacket) {
  407. int result1 = streamState.packetout(packet);
  408. if (result1 == 0) {
  409. // no more packets in page. Fetch new page.
  410. int result2 = 0;
  411. while (!eos && result2 == 0) {
  412. result2 = syncState.pageout(page);
  413. if (result2 == 0) {
  414. fetchData();
  415. }
  416. }
  417. // return if we have reaced end of file.
  418. if ((result2 == 0) && (page.eos() != 0)) {
  419. return -1;
  420. }
  421. if (result2 == 0) {
  422. // need more data fetching page..
  423. fetchData();
  424. } else if (result2 == -1) {
  425. logger.warn("syncState.pageout(page) result == -1");
  426. return -1;
  427. } else {
  428. streamState.pagein(page);
  429. }
  430. } else if (result1 == -1) {
  431. logger.warn("streamState.packetout(packet) result == -1");
  432. return -1;
  433. } else {
  434. fetchedPacket = true;
  435. }
  436. }
  437. return 0;
  438. }
  439. /**
  440. * Copys data from input stream to syncState.
  441. */
  442. private void fetchData() throws IOException {
  443. if (!eos) {
  444. // copy 4096 bytes from compressed stream to syncState.
  445. int bufferIndex = syncState.buffer(4096);
  446. if (bufferIndex < 0) {
  447. eos = true;
  448. return;
  449. }
  450. int bytes = in.read(syncState.data, bufferIndex, 4096);
  451. syncState.wrote(bytes);
  452. if (bytes == 0) {
  453. eos = true;
  454. }
  455. }
  456. }
  457. /**
  458. * Gets information on the ogg.
  459. */
  460. @Override
  461. public String toString() {
  462. String s = "";
  463. s = s + "version " + info.version + "\n";
  464. s = s + "channels " + info.channels + "\n";
  465. s = s + "rate (hz) " + info.rate;
  466. return s;
  467. }
  468. }