Spaces:
Build error
Build error
/* -*-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.*; | |
import java.io.InputStream; | |
import java.io.IOException; | |
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 = null; | |
try { | |
is = new SeekableInputStream(file); | |
int ret = open(is, null, 0); | |
if (ret == -1) { | |
throw new JOrbisException("VorbisFile: open return -1"); | |
} | |
} catch (Exception e) { | |
throw new JOrbisException("VorbisFile: " + e.toString()); | |
} finally { | |
if (is != null) { | |
try { | |
is.close(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
} | |
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 = 0; | |
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 | |
int read(byte[] buffer, int length, int bigendianp, int word, int sgned, int[] bitstream) { | |
int host_endian = host_is_big_endian(); | |
int index = 0; | |
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 = word * channels; | |
if (samples > length / bytespersample) | |
samples = length / bytespersample; | |
// a tight loop to pack each size | |
{ | |
int val; | |
if (word == 1) { | |
int off = (sgned != 0 ? 0 : 128); | |
for (int j = 0; j < samples; j++) { | |
for (int i = 0; i < channels; i++) { | |
val = (int) (pcm[i][_index[i] + j] * 128. + 0.5); | |
if (val > 127) | |
val = 127; | |
else if (val < -128) | |
val = -128; | |
buffer[index++] = (byte) (val + off); | |
} | |
} | |
} else { | |
int off = (sgned != 0 ? 0 : 32768); | |
if (host_endian == bigendianp) { | |
if (sgned != 0) { | |
for (int i = 0; i < channels; i++) { // It's faster in this order | |
int src = _index[i]; | |
int dest = i; | |
for (int j = 0; j < samples; j++) { | |
val = (int) (pcm[i][src + j] * 32768. + 0.5); | |
if (val > 32767) | |
val = 32767; | |
else if (val < -32768) | |
val = -32768; | |
buffer[dest] = (byte) (val >>> 8); | |
buffer[dest + 1] = (byte) (val); | |
dest += channels * 2; | |
} | |
} | |
} else { | |
for (int i = 0; i < channels; i++) { | |
float[] src = pcm[i]; | |
int dest = i; | |
for (int j = 0; j < samples; j++) { | |
val = (int) (src[j] * 32768. + 0.5); | |
if (val > 32767) | |
val = 32767; | |
else if (val < -32768) | |
val = -32768; | |
buffer[dest] = (byte) ((val + off) >>> 8); | |
buffer[dest + 1] = (byte) (val + off); | |
dest += channels * 2; | |
} | |
} | |
} | |
} else if (bigendianp != 0) { | |
for (int j = 0; j < samples; j++) { | |
for (int i = 0; i < channels; i++) { | |
val = (int) (pcm[i][j] * 32768. + 0.5); | |
if (val > 32767) | |
val = 32767; | |
else if (val < -32768) | |
val = -32768; | |
val += off; | |
buffer[index++] = (byte) (val >>> 8); | |
buffer[index++] = (byte) val; | |
} | |
} | |
} else { | |
// int val; | |
for (int j = 0; j < samples; j++) { | |
for (int i = 0; i < channels; i++) { | |
val = (int) (pcm[i][j] * 32768. + 0.5); | |
if (val > 32767) | |
val = 32767; | |
else if (val < -32768) | |
val = -32768; | |
val += off; | |
buffer[index++] = (byte) val; | |
buffer[index++] = (byte) (val >>> 8); | |
} | |
} | |
} | |
} | |
} | |
vd.synthesis_read(samples); | |
pcm_offset += samples; | |
if (bitstream != null) | |
bitstream[0] = current_link; | |
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); | |
} | |
} | |
} | |