/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ /* JOrbis * Copyright (C) 2000 ymnk, JCraft,Inc. * * Written by: 2000 ymnk<ymnk@jcraft.com> * * Many thanks to * Monty <monty@xiph.org> and * The XIPHOPHORUS Company http://www.xiph.org/ . * JOrbis has been based on their awesome works, Vorbis codec. * * 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. */ package com.jcraft.jorbis; import com.jcraft.jogg.Packet; import com.jcraft.jogg.Page; import com.jcraft.jogg.StreamState; import com.jcraft.jogg.SyncState; import java.io.InputStream; public class VorbisFile { static final int CHUNKSIZE = 8500; static final int SEEK_SET = 0; static final int SEEK_CUR = 1; static final int SEEK_END = 2; static final int OV_FALSE = -1; static final int OV_EOF = -2; static final int OV_HOLE = -3; static final int OV_EREAD = -128; static final int OV_EFAULT = -129; static final int OV_EIMPL = -130; static final int OV_EINVAL = -131; static final int OV_ENOTVORBIS = -132; static final int OV_EBADHEADER = -133; static final int OV_EVERSION = -134; static final int OV_ENOTAUDIO = -135; static final int OV_EBADPACKET = -136; static final int OV_EBADLINK = -137; static final int OV_ENOSEEK = -138; InputStream datasource; boolean seekable = false; long offset; long end; SyncState oy = new SyncState(); int links; long[] offsets; long[] dataoffsets; int[] serialnos; long[] pcmlengths; Info[] vi; Comment[] vc; // Decoding working state local storage long pcm_offset; boolean decode_ready = false; int current_serialno; int current_link; float bittrack; float samptrack; StreamState os = new StreamState(); // take physical pages, weld into a logical // stream of packets DspState vd = new DspState(); // central working state for // the packet->PCM decoder Block vb = new Block(vd); // local working space for packet->PCM decode //ov_callbacks callbacks; public VorbisFile(String file) throws JOrbisException { super(); InputStream is; try { is = new SeekableInputStream(file); int ret = open(is, null, 0); if (ret == -1) { throw new JOrbisException("VorbisFile: open return -1"); } ((SeekableInputStream) is).seek(0); } catch (Exception e) { throw new JOrbisException("VorbisFile: " + e.toString()); } } public VorbisFile(InputStream is, byte[] initial, int ibytes) throws JOrbisException { super(); int ret = open(is, initial, ibytes); if (ret == -1) { } } private int get_data() { int index = oy.buffer(CHUNKSIZE); byte[] buffer = oy.data; int bytes; try { bytes = datasource.read(buffer, index, CHUNKSIZE); } catch (Exception e) { return OV_EREAD; } oy.wrote(bytes); if (bytes == -1) { bytes = 0; } return bytes; } private void seek_helper(long offst) { fseek(datasource, offst, SEEK_SET); this.offset = offst; oy.reset(); } private int get_next_page(Page page, long boundary) { if (boundary > 0) boundary += offset; while (true) { int more; if (boundary > 0 && offset >= boundary) return OV_FALSE; more = oy.pageseek(page); if (more < 0) { offset -= more; } else { if (more == 0) { if (boundary == 0) return OV_FALSE; int ret = get_data(); if (ret == 0) return OV_EOF; if (ret < 0) return OV_EREAD; } else { int ret = (int) offset; //!!! offset += more; return ret; } } } } private int get_prev_page(Page page) throws JOrbisException { long begin = offset; //!!! int ret; int offst = -1; while (offst == -1) { begin -= CHUNKSIZE; if (begin < 0) begin = 0; seek_helper(begin); while (offset < begin + CHUNKSIZE) { ret = get_next_page(page, begin + CHUNKSIZE - offset); if (ret == OV_EREAD) { return OV_EREAD; } if (ret < 0) { if (offst == -1) throw new JOrbisException(); break; } else { offst = ret; } } } seek_helper(offst); //!!! ret = get_next_page(page, CHUNKSIZE); if (ret < 0) { return OV_EFAULT; } return offst; } int bisect_forward_serialno(long begin, long searched, long end, int currentno, int m) { long endsearched = end; long next = end; Page page = new Page(); int ret; while (searched < endsearched) { long bisect; if (endsearched - searched < CHUNKSIZE) { bisect = searched; } else { bisect = (searched + endsearched) / 2; } seek_helper(bisect); ret = get_next_page(page, -1); if (ret == OV_EREAD) return OV_EREAD; if (ret < 0 || page.serialno() != currentno) { endsearched = bisect; if (ret >= 0) next = ret; } else { searched = ret + page.header_len + page.body_len; } } seek_helper(next); ret = get_next_page(page, -1); if (ret == OV_EREAD) return OV_EREAD; if (searched >= end || ret == -1) { links = m + 1; offsets = new long[m + 2]; offsets[m + 1] = searched; } else { ret = bisect_forward_serialno(next, offset, end, page.serialno(), m + 1); if (ret == OV_EREAD) return OV_EREAD; } offsets[m] = begin; return 0; } // uses the local ogg_stream storage in vf; this is important for // non-streaming input sources int fetch_headers(Info vi, Comment vc, int[] serialno, Page og_ptr) { Page og = new Page(); Packet op = new Packet(); int ret; if (og_ptr == null) { ret = get_next_page(og, CHUNKSIZE); if (ret == OV_EREAD) return OV_EREAD; if (ret < 0) return OV_ENOTVORBIS; og_ptr = og; } if (serialno != null) serialno[0] = og_ptr.serialno(); os.init(og_ptr.serialno()); // extract the initial header from the first page and verify that the // Ogg bitstream is in fact Vorbis data vi.init(); vc.init(); int i = 0; while (i < 3) { os.pagein(og_ptr); while (i < 3) { int result = os.packetout(op); if (result == 0) break; if (result == -1) { vi.clear(); vc.clear(); os.clear(); return -1; } if (vi.synthesis_headerin(vc, op) != 0) { vi.clear(); vc.clear(); os.clear(); return -1; } i++; } if (i < 3) if (get_next_page(og_ptr, 1) < 0) { vi.clear(); vc.clear(); os.clear(); return -1; } } return 0; } // last step of the OggVorbis_File initialization; get all the // vorbis_info structs and PCM positions. Only called by the seekable // initialization (local stream storage is hacked slightly; pay // attention to how that's done) void prefetch_all_headers(Info first_i, Comment first_c, int dataoffset) throws JOrbisException { Page og = new Page(); int ret; vi = new Info[links]; vc = new Comment[links]; dataoffsets = new long[links]; pcmlengths = new long[links]; serialnos = new int[links]; for (int i = 0; i < links; i++) { if (first_i != null && first_c != null && i == 0) { // we already grabbed the initial header earlier. This just // saves the waste of grabbing it again vi[i] = first_i; vc[i] = first_c; dataoffsets[i] = dataoffset; } else { // seek to the location of the initial header seek_helper(offsets[i]); //!!! vi[i] = new Info(); vc[i] = new Comment(); if (fetch_headers(vi[i], vc[i], null, null) == -1) { dataoffsets[i] = -1; } else { dataoffsets[i] = offset; os.clear(); } } // get the serial number and PCM length of this link. To do this, // get the last page of the stream { long end = offsets[i + 1]; //!!! seek_helper(end); while (true) { ret = get_prev_page(og); if (ret == -1) { // this should not be possible vi[i].clear(); vc[i].clear(); break; } if (og.granulepos() != -1) { serialnos[i] = og.serialno(); pcmlengths[i] = og.granulepos(); break; } } } } } private int make_decode_ready() { if (decode_ready) System.exit(1); vd.synthesis_init(vi[0]); vb.init(vd); decode_ready = true; return (0); } int open_seekable() throws JOrbisException { Info initial_i = new Info(); Comment initial_c = new Comment(); int serialno; long end; int ret; int dataoffset; Page og = new Page(); // is this even vorbis...? int[] foo = new int[1]; ret = fetch_headers(initial_i, initial_c, foo, null); serialno = foo[0]; dataoffset = (int) offset; //!! os.clear(); if (ret == -1) return (-1); if (ret < 0) return (ret); // we can seek, so set out learning all about this file seekable = true; fseek(datasource, 0, SEEK_END); offset = ftell(datasource); end = offset; // We get the offset for the last page of the physical bitstream. // Most OggVorbis files will contain a single logical bitstream end = get_prev_page(og); // moer than one logical bitstream? if (og.serialno() != serialno) { // Chained bitstream. Bisect-search each logical bitstream // section. Do so based on serial number only if (bisect_forward_serialno(0, 0, end + 1, serialno, 0) < 0) { clear(); return OV_EREAD; } } else { // Only one logical bitstream if (bisect_forward_serialno(0, end, end + 1, serialno, 0) < 0) { clear(); return OV_EREAD; } } prefetch_all_headers(initial_i, initial_c, dataoffset); return 0; } int open_nonseekable() { // we cannot seek. Set up a 'single' (current) logical bitstream entry links = 1; vi = new Info[links]; vi[0] = new Info(); // ?? vc = new Comment[links]; vc[0] = new Comment(); // ?? bug? // Try to fetch the headers, maintaining all the storage int[] foo = new int[1]; if (fetch_headers(vi[0], vc[0], foo, null) == -1) return (-1); current_serialno = foo[0]; make_decode_ready(); return 0; } // clear out the current logical bitstream decoder void decode_clear() { os.clear(); vd.clear(); vb.clear(); decode_ready = false; bittrack = 0.f; samptrack = 0.f; } // fetch and process a packet. Handles the case where we're at a // bitstream boundary and dumps the decoding machine. If the decoding // machine is unloaded, it loads it. It also keeps pcm_offset up to // date (seek and read both use this. seek uses a special hack with // readp). // // return: -1) hole in the data (lost packet) // 0) need more date (only if readp==0)/eof // 1) got a packet int process_packet(int readp) { Page og = new Page(); // handle one packet. Try to fetch it from current stream state // extract packets from page while (true) { // process a packet if we can. If the machine isn't loaded, // neither is a page if (decode_ready) { Packet op = new Packet(); int result = os.packetout(op); long granulepos; // if(result==-1)return(-1); // hole in the data. For now, swallow // and go. We'll need to add a real // error code in a bit. if (result > 0) { // got a packet. process it granulepos = op.granulepos; if (vb.synthesis(op) == 0) { // lazy check for lazy // header handling. The // header packets aren't // audio, so if/when we // submit them, // vorbis_synthesis will // reject them // suck in the synthesis data and track bitrate { int oldsamples = vd.synthesis_pcmout(null, null); vd.synthesis_blockin(vb); samptrack += vd.synthesis_pcmout(null, null) - oldsamples; bittrack += op.bytes * 8; } // update the pcm offset. if (granulepos != -1 && op.e_o_s == 0) { int link = (seekable ? current_link : 0); int samples; // this packet has a pcm_offset on it (the last packet // completed on a page carries the offset) After processing // (above), we know the pcm position of the *last* sample // ready to be returned. Find the offset of the *first* // // As an aside, this trick is inaccurate if we begin // reading anew right at the last page; the end-of-stream // granulepos declares the last frame in the stream, and the // last packet of the last page may be a partial frame. // So, we need a previous granulepos from an in-sequence page // to have a reference point. Thus the !op.e_o_s clause above samples = vd.synthesis_pcmout(null, null); granulepos -= samples; for (int i = 0; i < link; i++) { granulepos += pcmlengths[i]; } pcm_offset = granulepos; } return (1); } } } if (readp == 0) return (0); if (get_next_page(og, -1) < 0) return (0); // eof. leave unitialized // bitrate tracking; add the header's bytes here, the body bytes // are done by packet above bittrack += og.header_len * 8; // has our decoding just traversed a bitstream boundary? if (decode_ready) { if (current_serialno != og.serialno()) { decode_clear(); } } // Do we need to load a new machine before submitting the page? // This is different in the seekable and non-seekable cases. // // In the seekable case, we already have all the header // information loaded and cached; we just initialize the machine // with it and continue on our merry way. // // In the non-seekable (streaming) case, we'll only be at a // boundary if we just left the previous logical bitstream and // we're now nominally at the header of the next bitstream if (!decode_ready) { int i; if (seekable) { current_serialno = og.serialno(); // match the serialno to bitstream section. We use this rather than // offset positions to avoid problems near logical bitstream // boundaries for (i = 0; i < links; i++) { if (serialnos[i] == current_serialno) break; } if (i == links) return (-1); // sign of a bogus stream. error out, // leave machine uninitialized current_link = i; os.init(current_serialno); os.reset(); } else { // we're streaming // fetch the three header packets, build the info struct int foo[] = new int[1]; int ret = fetch_headers(vi[0], vc[0], foo, og); current_serialno = foo[0]; if (ret != 0) return ret; current_link++; i = 0; } make_decode_ready(); } os.pagein(og); } } // The helpers are over; it's all toplevel interface from here on out // clear out the OggVorbis_File struct int clear() { vb.clear(); vd.clear(); os.clear(); if (vi != null && links != 0) { for (int i = 0; i < links; i++) { vi[i].clear(); vc[i].clear(); } vi = null; vc = null; } if (dataoffsets != null) dataoffsets = null; if (pcmlengths != null) pcmlengths = null; if (serialnos != null) serialnos = null; if (offsets != null) offsets = null; oy.clear(); return (0); } static int fseek(InputStream fis, long off, int whence) { if (fis instanceof SeekableInputStream) { SeekableInputStream sis = (SeekableInputStream) fis; try { if (whence == SEEK_SET) { sis.seek(off); } else if (whence == SEEK_END) { sis.seek(sis.getLength() - off); } else { } } catch (Exception e) { } return 0; } try { if (whence == 0) { fis.reset(); } fis.skip(off); } catch (Exception e) { return -1; } return 0; } static long ftell(InputStream fis) { try { if (fis instanceof SeekableInputStream) { SeekableInputStream sis = (SeekableInputStream) fis; return (sis.tell()); } } catch (Exception e) { } return 0; } // inspects the OggVorbis file and finds/documents all the logical // bitstreams contained in it. Tries to be tolerant of logical // bitstream sections that are truncated/woogie. // // return: -1) error // 0) OK int open(InputStream is, byte[] initial, int ibytes) throws JOrbisException { return open_callbacks(is, initial, ibytes); } int open_callbacks(InputStream is, byte[] initial, int ibytes//, callbacks callbacks ) throws JOrbisException { int ret; datasource = is; oy.init(); // perhaps some data was previously read into a buffer for testing // against other stream types. Allow initialization from this // previously read data (as we may be reading from a non-seekable // stream) if (initial != null) { int index = oy.buffer(ibytes); System.arraycopy(initial, 0, oy.data, index, ibytes); oy.wrote(ibytes); } // can we seek? Stevens suggests the seek test was portable if (is instanceof SeekableInputStream) { ret = open_seekable(); } else { ret = open_nonseekable(); } if (ret != 0) { datasource = null; clear(); } return ret; } // How many logical bitstreams in this physical bitstream? public int streams() { return links; } // Is the FILE * associated with vf seekable? public boolean seekable() { return seekable; } // returns the bitrate for a given logical bitstream or the entire // physical bitstream. If the file is open for random access, it will // find the *actual* average bitrate. If the file is streaming, it // returns the nominal bitrate (if set) else the average of the // upper/lower bounds (if set) else -1 (unset). // // If you want the actual bitrate field settings, get them from the // vorbis_info structs public int bitrate(int i) { if (i >= links) return (-1); if (!seekable && i != 0) return (bitrate(0)); if (i < 0) { long bits = 0; for (int j = 0; j < links; j++) { bits += (offsets[j + 1] - dataoffsets[j]) * 8; } return ((int) Math.rint(bits / time_total(-1))); } else { if (seekable) { // return the actual bitrate return ((int) Math.rint((offsets[i + 1] - dataoffsets[i]) * 8 / time_total(i))); } else { // return nominal if set if (vi[i].bitrate_nominal > 0) { return vi[i].bitrate_nominal; } else { if (vi[i].bitrate_upper > 0) { if (vi[i].bitrate_lower > 0) { return (vi[i].bitrate_upper + vi[i].bitrate_lower) / 2; } else { return vi[i].bitrate_upper; } } return (-1); } } } } // returns the actual bitrate since last call. returns -1 if no // additional data to offer since last call (or at beginning of stream) public int bitrate_instant() { int _link = (seekable ? current_link : 0); if (samptrack == 0) return (-1); int ret = (int) (bittrack / samptrack * vi[_link].rate + .5); bittrack = 0.f; samptrack = 0.f; return (ret); } public int serialnumber(int i) { if (i >= links) return (-1); if (!seekable && i >= 0) return (serialnumber(-1)); if (i < 0) { return (current_serialno); } else { return (serialnos[i]); } } // returns: total raw (compressed) length of content if i==-1 // raw (compressed) length of that logical bitstream for i==0 to n // -1 if the stream is not seekable (we can't know the length) public long raw_total(int i) { if (!seekable || i >= links) return (-1); if (i < 0) { long acc = 0; // bug? for (int j = 0; j < links; j++) { acc += raw_total(j); } return (acc); } else { return (offsets[i + 1] - offsets[i]); } } // returns: total PCM length (samples) of content if i==-1 // PCM length (samples) of that logical bitstream for i==0 to n // -1 if the stream is not seekable (we can't know the length) public long pcm_total(int i) { if (!seekable || i >= links) return (-1); if (i < 0) { long acc = 0; for (int j = 0; j < links; j++) { acc += pcm_total(j); } return (acc); } else { return (pcmlengths[i]); } } // returns: total seconds of content if i==-1 // seconds in that logical bitstream for i==0 to n // -1 if the stream is not seekable (we can't know the length) public float time_total(int i) { if (!seekable || i >= links) return (-1); if (i < 0) { float acc = 0; for (int j = 0; j < links; j++) { acc += time_total(j); } return (acc); } else { return ((float) (pcmlengths[i]) / vi[i].rate); } } // seek to an offset relative to the *compressed* data. This also // immediately sucks in and decodes pages to update the PCM cursor. It // will cross a logical bitstream boundary, but only if it can't get // any packets out of the tail of the bitstream we seek to (so no // surprises). // // returns zero on success, nonzero on failure public int raw_seek(int pos) { if (!seekable) return (-1); // don't dump machine if we can't seek if (pos < 0 || pos > offsets[links]) { //goto seek_error; pcm_offset = -1; decode_clear(); return -1; } // clear out decoding machine state pcm_offset = -1; decode_clear(); // seek seek_helper(pos); // we need to make sure the pcm_offset is set. We use the // _fetch_packet helper to process one packet with readp set, then // call it until it returns '0' with readp not set (the last packet // from a page has the 'granulepos' field set, and that's how the // helper updates the offset switch (process_packet(1)) { case 0: // oh, eof. There are no packets remaining. Set the pcm offset to // the end of file pcm_offset = pcm_total(-1); return (0); case -1: // error! missing data or invalid bitstream structure //goto seek_error; pcm_offset = -1; decode_clear(); return -1; default: // all OK break; } while (true) { switch (process_packet(0)) { case 0: // the offset is set. If it's a bogus bitstream with no offset // information, it's not but that's not our fault. We still run // gracefully, we're just missing the offset return (0); case -1: // error! missing data or invalid bitstream structure //goto seek_error; pcm_offset = -1; decode_clear(); return -1; default: // continue processing packets break; } } // seek_error: // dump the machine so we're in a known state //pcm_offset=-1; //decode_clear(); //return -1; } // seek to a sample offset relative to the decompressed pcm stream // returns zero on success, nonzero on failure public int pcm_seek(long pos) { int link = -1; long total = pcm_total(-1); if (!seekable) return (-1); // don't dump machine if we can't seek if (pos < 0 || pos > total) { //goto seek_error; pcm_offset = -1; decode_clear(); return -1; } // which bitstream section does this pcm offset occur in? for (link = links - 1; link >= 0; link--) { total -= pcmlengths[link]; if (pos >= total) break; } // search within the logical bitstream for the page with the highest // pcm_pos preceeding (or equal to) pos. There is a danger here; // missing pages or incorrect frame number information in the // bitstream could make our task impossible. Account for that (it // would be an error condition) { long target = pos - total; long end = offsets[link + 1]; long begin = offsets[link]; int best = (int) begin; Page og = new Page(); while (begin < end) { long bisect; int ret; if (end - begin < CHUNKSIZE) { bisect = begin; } else { bisect = (end + begin) / 2; } seek_helper(bisect); ret = get_next_page(og, end - bisect); if (ret == -1) { end = bisect; } else { long granulepos = og.granulepos(); if (granulepos < target) { best = ret; // raw offset of packet with granulepos begin = offset; // raw offset of next packet } else { end = bisect; } } } // found our page. seek to it (call raw_seek). if (raw_seek(best) != 0) { //goto seek_error; pcm_offset = -1; decode_clear(); return -1; } } // verify result if (pcm_offset > pos) { //goto seek_error; pcm_offset = -1; decode_clear(); return -1; } if (pos > pcm_total(-1)) { //goto seek_error; pcm_offset = -1; decode_clear(); return -1; } // discard samples until we reach the desired position. Crossing a // logical bitstream boundary with abandon is OK. while (pcm_offset < pos) { int target = (int) (pos - pcm_offset); float[][][] _pcm = new float[1][][]; int[] _index = new int[getInfo(-1).channels]; int samples = vd.synthesis_pcmout(_pcm, _index); if (samples > target) samples = target; vd.synthesis_read(samples); pcm_offset += samples; if (samples < target) if (process_packet(1) == 0) { pcm_offset = pcm_total(-1); // eof } } return 0; // seek_error: // dump machine so we're in a known state //pcm_offset=-1; //decode_clear(); //return -1; } // seek to a playback time relative to the decompressed pcm stream // returns zero on success, nonzero on failure int time_seek(float seconds) { // translate time to PCM position and call pcm_seek int link = -1; long pcm_total = pcm_total(-1); float time_total = time_total(-1); if (!seekable) return (-1); // don't dump machine if we can't seek if (seconds < 0 || seconds > time_total) { //goto seek_error; pcm_offset = -1; decode_clear(); return -1; } // which bitstream section does this time offset occur in? for (link = links - 1; link >= 0; link--) { pcm_total -= pcmlengths[link]; time_total -= time_total(link); if (seconds >= time_total) break; } // enough information to convert time offset to pcm offset { long target = (long) (pcm_total + (seconds - time_total) * vi[link].rate); return (pcm_seek(target)); } //seek_error: // dump machine so we're in a known state //pcm_offset=-1; //decode_clear(); //return -1; } // tell the current stream offset cursor. Note that seek followed by // tell will likely not give the set offset due to caching public long raw_tell() { return (offset); } // return PCM offset (sample) of next PCM sample to be read public long pcm_tell() { return (pcm_offset); } // return time offset (seconds) of next PCM sample to be read public float time_tell() { // translate time to PCM position and call pcm_seek int link = -1; long pcm_total = 0; float time_total = 0.f; if (seekable) { pcm_total = pcm_total(-1); time_total = time_total(-1); // which bitstream section does this time offset occur in? for (link = links - 1; link >= 0; link--) { pcm_total -= pcmlengths[link]; time_total -= time_total(link); if (pcm_offset >= pcm_total) break; } } return ((float) time_total + (float) (pcm_offset - pcm_total) / vi[link].rate); } // link: -1) return the vorbis_info struct for the bitstream section // currently being decoded // 0-n) to request information for a specific bitstream section // // In the case of a non-seekable bitstream, any call returns the // current bitstream. NULL in the case that the machine is not // initialized public Info getInfo(int link) { if (seekable) { if (link < 0) { if (decode_ready) { return vi[current_link]; } else { return null; } } else { if (link >= links) { return null; } else { return vi[link]; } } } else { if (decode_ready) { return vi[0]; } else { return null; } } } public Comment getComment(int link) { if (seekable) { if (link < 0) { if (decode_ready) { return vc[current_link]; } else { return null; } } else { if (link >= links) { return null; } else { return vc[link]; } } } else { if (decode_ready) { return vc[0]; } else { return null; } } } int host_is_big_endian() { return 1; // short pattern = 0xbabe; // unsigned char *bytewise = (unsigned char *)&pattern; // if (bytewise[0] == 0xba) return 1; // assert(bytewise[0] == 0xbe); // return 0; } // up to this point, everything could more or less hide the multiple // logical bitstream nature of chaining from the toplevel application // if the toplevel application didn't particularly care. However, at // the point that we actually read audio back, the multiple-section // nature must surface: Multiple bitstream sections do not necessarily // have to have the same number of channels or sampling rate. // // read returns the sequential logical bitstream number currently // being decoded along with the PCM data in order that the toplevel // application can take action on channel/sample rate changes. This // number will be incremented even for streamed (non-seekable) streams // (for seekable streams, it represents the actual logical bitstream // index within the physical bitstream. Note that the accessor // functions above are aware of this dichotomy). // // input values: buffer) a buffer to hold packed PCM data for return // length) the byte length requested to be placed into buffer // bigendianp) should the data be packed LSB first (0) or // MSB first (1) // word) word size for output. currently 1 (byte) or // 2 (16 bit short) // // return values: -1) error/hole in data // 0) EOF // n) number of bytes of PCM actually returned. The // below works on a packet-by-packet basis, so the // return length is not related to the 'length' passed // in, just guaranteed to fit. // // *section) set to the logical bitstream number public int read(byte[] buffer, int length) { while (true) { if (decode_ready) { float[][] pcm; float[][][] _pcm = new float[1][][]; int[] _index = new int[getInfo(-1).channels]; int samples = vd.synthesis_pcmout(_pcm, _index); pcm = _pcm[0]; if (samples != 0) { // yay! proceed to pack data into the byte buffer int channels = getInfo(-1).channels; int bytespersample = 2 * channels; if (samples > length / bytespersample) samples = length / bytespersample; // a tight loop to pack each size // For each channel... for (int i = 0; i < channels; i++) { int sampleIndex = i * 2; // For every sample in our range... for (int j = 0; j < samples; j++) { /* * Get the PCM value for the channel at the correct * position. */ int value = (int) (pcm[i][_index[i] + j] * 32767); /* * We make sure our value doesn't exceed or falls below * +-32767. */ if (value > 32767) { value = 32767; } if (value < -32768) { value = -32768; } /* * It the value is less than zero, we bitwise-or it with * 32768 (which is 1000000000000000 = 10^15). */ if (value < 0) value = value | 32768; /* * Take our value and split it into two, one with the last * byte and one with the first byte. */ buffer[sampleIndex] = (byte) (value); buffer[sampleIndex + 1] = (byte) (value >>> 8); /* * Move the sample index forward by two (since that's how * many values we get at once) times the number of channels. */ sampleIndex += 2 * (channels); } } vd.synthesis_read(samples); pcm_offset += samples; return (samples * bytespersample); } } // suck in another packet switch (process_packet(1)) { case 0: return (0); case -1: return -1; default: break; } } } public Info[] getInfo() { return vi; } public Comment[] getComment() { return vc; } public void close() throws java.io.IOException { datasource.close(); } class SeekableInputStream extends InputStream { java.io.RandomAccessFile raf = null; final String mode = "r"; SeekableInputStream(String file) throws java.io.IOException { raf = new java.io.RandomAccessFile(file, mode); } public int read() throws java.io.IOException { return raf.read(); } public int read(byte[] buf) throws java.io.IOException { return raf.read(buf); } public int read(byte[] buf, int s, int len) throws java.io.IOException { return raf.read(buf, s, len); } public long skip(long n) throws java.io.IOException { return (long) (raf.skipBytes((int) n)); } public long getLength() throws java.io.IOException { return raf.length(); } public long tell() throws java.io.IOException { return raf.getFilePointer(); } public int available() throws java.io.IOException { return (raf.length() == raf.getFilePointer()) ? 0 : 1; } public void close() throws java.io.IOException { raf.close(); } public synchronized void mark(int m) { } public synchronized void reset() throws java.io.IOException { } public boolean markSupported() { return false; } public void seek(long pos) throws java.io.IOException { raf.seek(pos); } } }