/* -*-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.jogg; public class StreamState { byte[] body_data; /* bytes from packet bodies */ int body_storage; /* storage elements allocated */ int body_fill; /* elements stored; fill mark */ private int body_returned; /* elements of fill returned */ int[] lacing_vals; /* The values that will go to the segment table */ long[] granule_vals; /* pcm_pos values for headers. Not compact this way, but it is simple coupled to the lacing fifo */ int lacing_storage; int lacing_fill; int lacing_packet; int lacing_returned; byte[] header = new byte[282]; /* working space for header encode */ int header_fill; public int e_o_s; /* set when we have buffered the last packet in the logical bitstream */ int b_o_s; /* set after we've written the initial page of a logical bitstream */ int serialno; int pageno; long packetno; /* sequence number for decode; the framing knows where there's a hole in the data, but we need coupling so that the codec (which is in a seperate abstraction layer) also knows about the gap */ long granulepos; public StreamState() { init(); } StreamState(int serialno) { this(); init(serialno); } void init() { body_storage = 16 * 1024; body_data = new byte[body_storage]; lacing_storage = 1024; lacing_vals = new int[lacing_storage]; granule_vals = new long[lacing_storage]; } public void init(int serialno) { if (body_data == null) { init(); } else { for (int i = 0; i < body_data.length; i++) body_data[i] = 0; for (int i = 0; i < lacing_vals.length; i++) lacing_vals[i] = 0; for (int i = 0; i < granule_vals.length; i++) granule_vals[i] = 0; } this.serialno = serialno; } public void clear() { body_data = null; lacing_vals = null; granule_vals = null; } void destroy() { clear(); } void body_expand(int needed) { if (body_storage <= body_fill + needed) { body_storage += (needed + 1024); byte[] foo = new byte[body_storage]; System.arraycopy(body_data, 0, foo, 0, body_data.length); body_data = foo; } } void lacing_expand(int needed) { if (lacing_storage <= lacing_fill + needed) { lacing_storage += (needed + 32); int[] foo = new int[lacing_storage]; System.arraycopy(lacing_vals, 0, foo, 0, lacing_vals.length); lacing_vals = foo; long[] bar = new long[lacing_storage]; System.arraycopy(granule_vals, 0, bar, 0, granule_vals.length); granule_vals = bar; } } /* submit data to the internal buffer of the framing engine */ public int packetin(Packet op) { int lacing_val = op.bytes / 255 + 1; if (body_returned != 0) { /* advance packet data according to the body_returned pointer. We had to keep it around to return a pointer into the buffer last call */ body_fill -= body_returned; if (body_fill != 0) { System.arraycopy(body_data, body_returned, body_data, 0, body_fill); } body_returned = 0; } /* make sure we have the buffer storage */ body_expand(op.bytes); lacing_expand(lacing_val); /* Copy in the submitted packet. Yes, the copy is a waste; this is the liability of overly clean abstraction for the time being. It will actually be fairly easy to eliminate the extra copy in the future */ System.arraycopy(op.packet_base, op.packet, body_data, body_fill, op.bytes); body_fill += op.bytes; /* Store lacing vals for this packet */ int j; for (j = 0; j < lacing_val - 1; j++) { lacing_vals[lacing_fill + j] = 255; granule_vals[lacing_fill + j] = granulepos; } lacing_vals[lacing_fill + j] = (op.bytes) % 255; granulepos = granule_vals[lacing_fill + j] = op.granulepos; /* flag the first segment as the beginning of the packet */ lacing_vals[lacing_fill] |= 0x100; lacing_fill += lacing_val; /* for the sake of completeness */ packetno++; if (op.e_o_s != 0) e_o_s = 1; return (0); } public int packetout(Packet op) { /* The last part of decode. We have the stream broken into packet segments. Now we need to group them into packets (or return the out of sync markers) */ int ptr = lacing_returned; if (lacing_packet <= ptr) { return (0); } if ((lacing_vals[ptr] & 0x400) != 0) { /* We lost sync here; let the app know */ lacing_returned++; /* we need to tell the codec there's a gap; it might need to handle previous packet dependencies. */ packetno++; return (-1); } /* Gather the whole packet. We'll have no holes or a partial packet */ { int size = lacing_vals[ptr] & 0xff; int bytes = 0; op.packet_base = body_data; op.packet = body_returned; op.e_o_s = lacing_vals[ptr] & 0x200; /* last packet of the stream? */ op.b_o_s = lacing_vals[ptr] & 0x100; /* first packet of the stream? */ bytes += size; while (size == 255) { int val = lacing_vals[++ptr]; size = val & 0xff; if ((val & 0x200) != 0) op.e_o_s = 0x200; bytes += size; } op.packetno = packetno; op.granulepos = granule_vals[ptr]; op.bytes = bytes; body_returned += bytes; lacing_returned = ptr + 1; } packetno++; return (1); } // add the incoming page to the stream state; we decompose the page // into packet segments here as well. public int pagein(Page og) { byte[] header_base = og.header_base; int header = og.header; byte[] body_base = og.body_base; int body = og.body; int bodysize = og.body_len; int segptr = 0; int version = og.version(); int continued = og.continued(); int bos = og.bos(); int eos = og.eos(); long granulepos = og.granulepos(); int _serialno = og.serialno(); int _pageno = og.pageno(); int segments = header_base[header + 26] & 0xff; // clean up 'returned data' { int lr = lacing_returned; int br = body_returned; // body data if (br != 0) { body_fill -= br; if (body_fill != 0) { System.arraycopy(body_data, br, body_data, 0, body_fill); } body_returned = 0; } if (lr != 0) { // segment table if ((lacing_fill - lr) != 0) { System.arraycopy(lacing_vals, lr, lacing_vals, 0, lacing_fill - lr); System.arraycopy(granule_vals, lr, granule_vals, 0, lacing_fill - lr); } lacing_fill -= lr; lacing_packet -= lr; lacing_returned = 0; } } // check the serial number if (_serialno != serialno) return (-1); if (version > 0) return (-1); lacing_expand(segments + 1); // are we in sequence? if (_pageno != pageno) { int i; // unroll previous partial packet (if any) for (i = lacing_packet; i < lacing_fill; i++) { body_fill -= lacing_vals[i] & 0xff; //System.out.println("??"); } lacing_fill = lacing_packet; // make a note of dropped data in segment table if (pageno != -1) { lacing_vals[lacing_fill++] = 0x400; lacing_packet++; } // are we a 'continued packet' page? If so, we'll need to skip // some segments if (continued != 0) { bos = 0; for (; segptr < segments; segptr++) { int val = (header_base[header + 27 + segptr] & 0xff); body += val; bodysize -= val; if (val < 255) { segptr++; break; } } } } if (bodysize != 0) { body_expand(bodysize); System.arraycopy(body_base, body, body_data, body_fill, bodysize); body_fill += bodysize; } { int saved = -1; while (segptr < segments) { int val = (header_base[header + 27 + segptr] & 0xff); lacing_vals[lacing_fill] = val; granule_vals[lacing_fill] = -1; if (bos != 0) { lacing_vals[lacing_fill] |= 0x100; bos = 0; } if (val < 255) saved = lacing_fill; lacing_fill++; segptr++; if (val < 255) lacing_packet = lacing_fill; } /* set the granulepos on the last pcmval of the last full packet */ if (saved != -1) { granule_vals[saved] = granulepos; } } if (eos != 0) { e_o_s = 1; if (lacing_fill > 0) lacing_vals[lacing_fill - 1] |= 0x200; } pageno = _pageno + 1; return (0); } /* This will flush remaining packets into a page (returning nonzero), even if there is not enough data to trigger a flush normally (undersized page). If there are no packets or partial packets to flush, ogg_stream_flush returns 0. Note that ogg_stream_flush will try to flush a normal sized page like ogg_stream_pageout; a call to ogg_stream_flush does not gurantee that all packets have flushed. Only a return value of 0 from ogg_stream_flush indicates all packet data is flushed into pages. ogg_stream_page will flush the last page in a stream even if it's undersized; you almost certainly want to use ogg_stream_pageout (and *not* ogg_stream_flush) unless you need to flush an undersized page in the middle of a stream for some reason. */ public int flush(Page og) { int i; int vals = 0; int maxvals = (lacing_fill > 255 ? 255 : lacing_fill); int bytes = 0; int acc = 0; long granule_pos = granule_vals[0]; if (maxvals == 0) return (0); /* construct a page */ /* decide how many segments to include */ /* If this is the initial header case, the first page must only include the initial header packet */ if (b_o_s == 0) { /* 'initial header page' case */ granule_pos = 0; for (vals = 0; vals < maxvals; vals++) { if ((lacing_vals[vals] & 0x0ff) < 255) { vals++; break; } } } else { for (vals = 0; vals < maxvals; vals++) { if (acc > 4096) break; acc += (lacing_vals[vals] & 0x0ff); granule_pos = granule_vals[vals]; } } /* construct the header in temp storage */ System.arraycopy("OggS".getBytes(), 0, header, 0, 4); /* stream structure version */ header[4] = 0x00; /* continued packet flag? */ header[5] = 0x00; if ((lacing_vals[0] & 0x100) == 0) header[5] |= 0x01; /* first page flag? */ if (b_o_s == 0) header[5] |= 0x02; /* last page flag? */ if (e_o_s != 0 && lacing_fill == vals) header[5] |= 0x04; b_o_s = 1; /* 64 bits of PCM position */ for (i = 6; i < 14; i++) { header[i] = (byte) granule_pos; granule_pos >>>= 8; } /* 32 bits of stream serial number */ { int _serialno = serialno; for (i = 14; i < 18; i++) { header[i] = (byte) _serialno; _serialno >>>= 8; } } /* 32 bits of page counter (we have both counter and page header because this val can roll over) */ if (pageno == -1) pageno = 0; /* because someone called stream_reset; this would be a strange thing to do in an encode stream, but it has plausible uses */ { int _pageno = pageno++; for (i = 18; i < 22; i++) { header[i] = (byte) _pageno; _pageno >>>= 8; } } /* zero for computation; filled in later */ header[22] = 0; header[23] = 0; header[24] = 0; header[25] = 0; /* segment table */ header[26] = (byte) vals; for (i = 0; i < vals; i++) { header[i + 27] = (byte) lacing_vals[i]; bytes += (header[i + 27] & 0xff); } /* set pointers in the ogg_page struct */ og.header_base = header; og.header = 0; og.header_len = header_fill = vals + 27; og.body_base = body_data; og.body = body_returned; og.body_len = bytes; /* advance the lacing data and set the body_returned pointer */ lacing_fill -= vals; System.arraycopy(lacing_vals, vals, lacing_vals, 0, lacing_fill * 4); System.arraycopy(granule_vals, vals, granule_vals, 0, lacing_fill * 8); body_returned += bytes; /* calculate the checksum */ og.checksum(); /* done */ return (1); } /* This constructs pages from buffered packet segments. The pointers returned are to static buffers; do not free. The returned buffers are good only until the next call (using the same ogg_stream_state) */ public int pageout(Page og) { if ((e_o_s != 0 && lacing_fill != 0) || /* 'were done, now flush' case */ body_fill - body_returned > 4096 || /* 'page nominal size' case */ lacing_fill >= 255 || /* 'segment table full' case */ (lacing_fill != 0 && b_o_s == 0)) { /* 'initial header page' case */ return flush(og); } return 0; } public int eof() { return e_o_s; } public int reset() { body_fill = 0; body_returned = 0; lacing_fill = 0; lacing_packet = 0; lacing_returned = 0; header_fill = 0; e_o_s = 0; b_o_s = 0; pageno = -1; packetno = 0; granulepos = 0; return (0); } }