539 lines
13 KiB
Java
539 lines
13 KiB
Java
/* -*-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);
|
|
}
|
|
}
|