/* * StreamState.java * * This file is part of Tritonus: http://www.tritonus.org/ */ /* * Copyright (c) 2000 - 2005 by Matthias Pfisterer * * 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. */ /* |<--- this code is formatted to fit into 80 columns --->| */ package org.tritonus.lowlevel.pogg; import org.tritonus.share.TDebug; /** Wrapper for ogg_stream_state. */ public class StreamState { private static final int INITIAL_BODY_DATA_SIZE = 16 * 1024; private static final int INITIAL_LACING_VALUES_SIZE = 1024; /** The serial number of the stream. This is set by init(). */ private int m_nSerialNo; /** Storage for packet bodies. */ private byte[] m_abBodyData; /** Number of bytes used in te body storage. */ private int m_nBodyFill; /** Number of bytes aready returned (as pages) from the body storage. */ private int m_nBodyReturned; /** Lacing values. Bit 0 to 7 contain the lacing value (mask 0xFF). Bit 8 is set if the segment belongs to the first packet of the stream (mask 0x100). Bit 9 is set ig the segment belongs to the last packet of the stream (mask 0x200). */ private int[] m_anLacingValues; /** Granule values. */ private long[] m_alGranuleValues; /** Number of elements used in m_anLacingValues and m_alGranuleValues. The elements with the index m_nLacingFill is the first free element. */ private int m_nLacingFill; /** Pointer to the index in m_anLacingValues where the lacing values of the last decoded packet start (??) */ private int m_nLacingPacket; /** */ private int m_nLacingReturned; private byte[] m_abHeader; @SuppressWarnings("unused") private int m_nHeaderFill; private boolean m_bBos; private boolean m_bEos; private int m_nPageNo; private long m_lPacketNo; private long m_lGranulePos; public StreamState() { if (TDebug.TraceOggNative) { TDebug.out("StreamState.<init>(): begin"); } if (TDebug.TraceOggNative) { TDebug.out("StreamState.<init>(): end"); } } public void free() { } /** Calls ogg_stream_init(). */ public int init(int nSerialNo) { m_nSerialNo = nSerialNo; m_abBodyData = new byte[INITIAL_BODY_DATA_SIZE]; m_nBodyFill = 0; m_nBodyReturned = 0; m_anLacingValues = new int[INITIAL_LACING_VALUES_SIZE]; m_alGranuleValues = new long[INITIAL_LACING_VALUES_SIZE]; m_nLacingFill = 0; m_nLacingPacket = 0; m_nLacingReturned = 0; m_abHeader = new byte[282]; m_nHeaderFill = 0; m_bBos = false; m_bEos = false; m_nPageNo = 0; m_lPacketNo = 0; m_lGranulePos = 0; // TODO: necessary? for (int i = 0; i < m_abBodyData.length; i++) m_abBodyData[i] = 0; for (int i = 0; i < m_anLacingValues.length; i++) m_anLacingValues[i] = 0; for (int i = 0; i < m_alGranuleValues.length; i++) m_alGranuleValues[i] = 0; // TODO: remove return value return 0; } /** Calls ogg_stream_clear(). */ public int clear() { m_nSerialNo = 0; m_abBodyData = null; m_nBodyFill = 0; m_nBodyReturned = 0; m_anLacingValues = null; m_alGranuleValues = null; m_nLacingFill = 0; m_nLacingPacket = 0; m_nLacingReturned = 0; m_abHeader = null; m_nHeaderFill = 0; m_bBos = false; m_bEos = false; m_nPageNo = 0; m_lPacketNo = 0; m_lGranulePos = 0; // TODO: remove return value return 0; } /** Calls ogg_stream_reset(). */ public int reset() { m_nBodyFill = 0; m_nBodyReturned = 0; m_nLacingFill = 0; m_nLacingPacket = 0; m_nLacingReturned = 0; m_nHeaderFill = 0; m_bBos = false; m_bEos = false; m_nPageNo = -1; m_lPacketNo = 0; m_lGranulePos = 0; // TODO: remove return value return 0; } /** Calls ogg_stream_eos(). */ public boolean isEOSReached() { return m_bEos; } /** Calls ogg_stream_packetin(). */ /* submit data to the internal buffer of the framing engine */ public int packetIn(Packet packet) { int i; byte[] abPacketData = packet.getData(); int lacing_vals = abPacketData.length / 255 + 1; if (m_nBodyReturned > 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 */ m_nBodyFill -= m_nBodyReturned; if (m_nBodyFill > 0) { System.arraycopy(m_abBodyData, m_nBodyReturned, m_abBodyData, 0, m_nBodyFill); } m_nBodyReturned = 0; } /* make sure we have the buffer storage */ assureBodyDataCapacity(abPacketData.length); assureLacingValuesCapacity(lacing_vals); /* 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(abPacketData, 0, m_abBodyData, m_nBodyFill, abPacketData.length); m_nBodyFill += abPacketData.length; /* Store lacing vals for this packet */ for (i = 0; i < lacing_vals - 1; i++) { m_anLacingValues[m_nLacingFill + i] = 255; m_alGranuleValues[m_nLacingFill + i] = m_lGranulePos; } m_anLacingValues[m_nLacingFill + i] = abPacketData.length % 255; m_alGranuleValues[m_nLacingFill + i] = packet.getGranulePos(); m_lGranulePos = packet.getGranulePos(); /* flag the first segment as the beginning of the packet */ m_anLacingValues[m_nLacingFill] |= 0x100; m_nLacingFill += lacing_vals; /* for the sake of completeness */ m_lPacketNo++; if (packet.isEos()) m_bEos = true; return 0; } /** Calls ogg_stream_pageout(). */ /* 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 page) { if ((m_bEos && m_nLacingFill > 0) || /* 'were done, now flush' */ m_nBodyFill - m_nBodyReturned > 4096 || /* 'page nominal size' */ m_nLacingFill >= 255 || /* 'segment table full' */ (m_nLacingFill > 0 && ! m_bBos)) /* 'initial header page' */ { return flush(page); } /* not enough data to construct a page and not end of stream */ return 0; } /** Calls ogg_stream_flush(). */ /* 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 guarantee that all packets have flushed. Only a return value of 0 from ogg_stream_flush indicates all packet data is flushed into pages. since ogg_stream_flush 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 specifically need to flush an page regardless of size in the middle of a stream. */ public int flush(Page page) { int i; int vals = 0; int maxvals = Math.min(m_nLacingFill, 255); int bytes = 0; int acc = 0; long granule_pos = m_alGranuleValues[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 (! m_bBos) { /* 'initial header page' case */ granule_pos = 0; for (vals = 0; vals < maxvals; vals++) { if ((m_anLacingValues[vals] & 0x0FF) < 255) { vals++; break; } } } else { for (vals = 0; vals < maxvals; vals++) { if (acc > 4096) break; acc += (m_anLacingValues[vals] & 0x0FF); granule_pos = m_alGranuleValues[vals]; } } /* construct the header in temp storage */ m_abHeader[0] = (byte) 'O'; m_abHeader[1] = (byte) 'g'; m_abHeader[2] = (byte) 'g'; m_abHeader[3] = (byte) 'S'; /* stream structure version */ m_abHeader[4] = 0; m_abHeader[5] = 0x00; /* continued packet flag? */ if ((m_anLacingValues[0] & 0x100) == 0) m_abHeader[5] |= 0x01; /* first page flag? */ if (! m_bBos) m_abHeader[5] |= 0x02; /* last page flag? */ if (m_bEos && m_nLacingFill == vals) m_abHeader[5] |= 0x04; m_bBos = true; /* 64 bits of PCM position */ for (i = 6; i < 14; i++) { m_abHeader[i] = (byte) (granule_pos & 0xFF); granule_pos >>>= 8; } /* 32 bits of stream serial number */ int serialno = m_nSerialNo; for (i = 14; i < 18; i++) { m_abHeader[i] = (byte) (serialno & 0xFF); serialno >>>= 8; } /* 32 bits of page counter (we have both counter and page header because this val can roll over) */ if (m_nPageNo == -1) { m_nPageNo = 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 = m_nPageNo++; for (i = 18; i < 22; i++) { m_abHeader[i] = (byte) (pageno & 0xFF); pageno >>>= 8; } /* zero for computation; filled in later */ m_abHeader[22] = 0; m_abHeader[23] = 0; m_abHeader[24] = 0; m_abHeader[25] = 0; /* segment table */ m_abHeader[26] = (byte) (vals & 0xFF); for (i = 0; i < vals; i++) { m_abHeader[i + 27] = (byte) (m_anLacingValues[i] & 0xFF); bytes += (m_anLacingValues[i] & 0xFF); } /* set pointers in the ogg_page struct */ page.setData(m_abHeader, 0, vals + 27, m_abBodyData, m_nBodyReturned, bytes); m_nHeaderFill = vals + 27; /* advance the lacing data and set the body_returned pointer */ m_nLacingFill -= vals; System.arraycopy(m_anLacingValues, vals, m_anLacingValues, 0, m_nLacingFill); System.arraycopy(m_alGranuleValues, vals, m_alGranuleValues, 0, m_nLacingFill); m_nBodyReturned += bytes; /* calculate the checksum */ page.setChecksum(); /* done */ return 1; } /** add the incoming page to the stream state; we decompose the page into packet segments here as well. @return 0 on success, -1 if the stream serial number stored in the page does not match the one stored in the stream state or if the protocol version stored in the page is greater than 0. */ public int pageIn(Page page) { byte[] header = page.getHeader(); byte[] body = page.getBody(); int nBodyOffset = 0; int bodysize = body.length; int segptr = 0; int version = page.getVersion(); boolean continued = page.isContinued(); boolean bos = page.isBos(); boolean eos = page.isEos(); long granulepos = page.getGranulePos(); int serialno = page.getSerialNo(); int pageno = page.getPageNo(); int segments = header[26] & 0xFF; /* clean up 'returned data' */ int lr = m_nLacingReturned; int br = m_nBodyReturned; /* body data */ if (br > 0) { m_nBodyFill -= br; if (m_nBodyFill > 0) { System.arraycopy(m_abBodyData, br, m_abBodyData, 0, m_nBodyFill); } m_nBodyReturned = 0; } if (lr > 0) { /* segment table */ if (m_nLacingFill - lr > 0) { System.arraycopy(m_anLacingValues, lr, m_anLacingValues, 0, m_nLacingFill - lr); System.arraycopy(m_alGranuleValues, lr, m_alGranuleValues, 0, m_nLacingFill - lr); } m_nLacingFill -= lr; m_nLacingPacket -= lr; m_nLacingReturned = 0; } /* check the serial number */ if (serialno != m_nSerialNo) return -1; if (version > 0) return -1; assureLacingValuesCapacity(segments + 1); /* are we in sequence? */ if (pageno != m_nPageNo) { int i; /* unroll previous partial packet (if any) */ for (i = m_nLacingPacket; i < m_nLacingFill; i++) m_nBodyFill -= (m_anLacingValues[i] & 0xFF); m_nLacingFill = m_nLacingPacket; /* make a note of dropped data in segment table */ if (m_nPageNo != -1) { m_anLacingValues[m_nLacingFill] = 0x400; m_nLacingFill++; m_nLacingPacket++; } /* are we a 'continued packet' page? If so, we'll need to skip some segments */ if (continued) { bos = false; for (; segptr < segments; segptr++) { int val = header[27 + segptr] & 0xFF; nBodyOffset += val; bodysize -= val; if (val < 255) { segptr++; break; } } } } if (bodysize > 0) { assureBodyDataCapacity(bodysize); System.arraycopy(body, nBodyOffset, m_abBodyData, m_nBodyFill, bodysize); m_nBodyFill += bodysize; } int saved = -1; while (segptr < segments) { int val = header[27 + segptr] & 0xFF; m_anLacingValues[m_nLacingFill] = val; m_alGranuleValues[m_nLacingFill] = -1; if (bos) { m_anLacingValues[m_nLacingFill] |= 0x100; bos = false; } if (val < 255) saved = m_nLacingFill; m_nLacingFill++; segptr++; if (val < 255) m_nLacingPacket = m_nLacingFill; } /* set the granulepos on the last granuleval of the last full packet */ if (saved != -1) { m_alGranuleValues[saved] = granulepos; } if (eos) { m_bEos = true; if (m_nLacingFill > 0) m_anLacingValues[m_nLacingFill - 1] |= 0x200; } m_nPageNo = pageno + 1; return 0; } /** Calls ogg_stream_packetout(). */ public int packetOut(Packet packet) { return packetOutInternal(packet, true); } /** Calls ogg_stream_packetpeek(). */ public int packetPeek(Packet packet) { return packetOutInternal(packet, false); } /** Retrieves a packet from the internal storage for emission. This method is called by packetOut and packetPeek. @param packet the Packet object to store the retrieved packet data in. May be null if bAdvance is false. @param bAdvance should the internal pointers to the packet data storage be advanced to the next packet after retrieving this one? Called with a value of true for ordinary packet out and with a value of false for packet peek. @return */ private int packetOutInternal(Packet packet, boolean bAdvance) { /* 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 = m_nLacingReturned; if (m_nLacingPacket <= ptr) return 0; if ((m_anLacingValues[ptr] & 0x400) != 0) { /* we need to tell the codec there's a gap; it might need to handle previous packet dependencies. */ m_nLacingReturned++; m_lPacketNo++; return -1; } if (packet == null && ! bAdvance) return 1; /* just using peek as an inexpensive way to ask if there's a whole packet waiting */ /* Gather the whole packet. We'll have no holes or a partial * packet */ int size = m_anLacingValues[ptr] & 0xFF; int bytes = size; /* last packet of the stream? */ boolean eos = (m_anLacingValues[ptr] & 0x200) != 0; /* first packet of the stream? */ boolean bos = (m_anLacingValues[ptr] & 0x100) != 0; while (size == 255) { int val = m_anLacingValues[++ptr]; size = val & 0xff; if ((val & 0x200) != 0) eos = true; bytes += size; } if (packet != null) { packet.setData(m_abBodyData, m_nBodyReturned, bytes); packet.setFlags(bos, eos, m_alGranuleValues[ptr], m_lPacketNo); } if (bAdvance) { m_nBodyReturned += bytes; m_nLacingReturned = ptr + 1; m_lPacketNo++; } return 1; } private void assureBodyDataCapacity(int needed) { if (m_abBodyData.length <= m_nBodyFill + needed) { int nNewSize = m_abBodyData.length + needed + 1024; byte[] abNewBodyData = new byte[nNewSize]; System.arraycopy(m_abBodyData, 0, abNewBodyData, 0, m_abBodyData.length); m_abBodyData = abNewBodyData; } } private void assureLacingValuesCapacity(int needed) { if (m_anLacingValues.length <= m_nLacingFill + needed) { int nNewSize = m_anLacingValues.length + needed + 32; int[] anNewLacingValues = new int[nNewSize]; System.arraycopy(m_anLacingValues, 0, anNewLacingValues, 0, m_anLacingValues.length); m_anLacingValues = anNewLacingValues; long[] alNewGranuleValues = new long[nNewSize]; System.arraycopy(m_alGranuleValues, 0, alNewGranuleValues, 0, m_alGranuleValues.length); m_alGranuleValues = alNewGranuleValues; } } } /*** StreamState.java ***/