/* -*-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; // DECODING PRIMITIVES: packet streaming layer // This has two layers to place more of the multi-serialno and paging // control in the application's hands. First, we expose a data buffer // using ogg_decode_buffer(). The app either copies into the // buffer, or passes it directly to read(), etc. We then call // ogg_decode_wrote() to tell how many bytes we just added. // // Pages are returned (pointers into the buffer in ogg_sync_state) // by ogg_decode_stream(). The page is then submitted to // ogg_decode_page() along with the appropriate // ogg_stream_state* (ie, matching serialno). We then get raw // packets out calling ogg_stream_packet() with a // ogg_stream_state. See the 'frame-prog.txt' docs for details and // example code. public class SyncState{ public byte[] data; int storage; int fill; int returned; int unsynced; int headerbytes; int bodybytes; public int clear(){ data=null; return (0); } public int buffer(int size){ // first, clear out any space that has been previously returned if(returned!=0){ fill-=returned; if(fill>0){ System.arraycopy(data, returned, data, 0, fill); } returned=0; } if(size>storage-fill){ // We need to extend the internal buffer int newsize=size+fill+4096; // an extra page to be nice if(data!=null){ byte[] foo=new byte[newsize]; System.arraycopy(data, 0, foo, 0, data.length); data=foo; } else{ data=new byte[newsize]; } storage=newsize; } return (fill); } public int wrote(int bytes){ if(fill+bytes>storage) return (-1); fill+=bytes; return (0); } // sync the stream. This is meant to be useful for finding page // boundaries. // // return values for this: // -n) skipped n bytes // 0) page not ready; more data (no bytes skipped) // n) page synced at current location; page length n bytes private Page pageseek=new Page(); private byte[] chksum=new byte[4]; public int pageseek(Page og){ int page=returned; int next; int bytes=fill-returned; if(headerbytes==0){ int _headerbytes, i; if(bytes<27) return (0); // not enough for a header /* verify capture pattern */ if(data[page]!='O'||data[page+1]!='g'||data[page+2]!='g' ||data[page+3]!='S'){ headerbytes=0; bodybytes=0; // search for possible capture next=0; for(int ii=0; ii<bytes-1; ii++){ if(data[page+1+ii]=='O'){ next=page+1+ii; break; } } //next=memchr(page+1,'O',bytes-1); if(next==0) next=fill; returned=next; return (-(next-page)); } _headerbytes=(data[page+26]&0xff)+27; if(bytes<_headerbytes) return (0); // not enough for header + seg table // count up body length in the segment table for(i=0; i<(data[page+26]&0xff); i++){ bodybytes+=(data[page+27+i]&0xff); } headerbytes=_headerbytes; } if(bodybytes+headerbytes>bytes) return (0); // The whole test page is buffered. Verify the checksum synchronized(chksum){ // Grab the checksum bytes, set the header field to zero System.arraycopy(data, page+22, chksum, 0, 4); data[page+22]=0; data[page+23]=0; data[page+24]=0; data[page+25]=0; // set up a temp page struct and recompute the checksum Page log=pageseek; log.header_base=data; log.header=page; log.header_len=headerbytes; log.body_base=data; log.body=page+headerbytes; log.body_len=bodybytes; log.checksum(); // Compare if(chksum[0]!=data[page+22]||chksum[1]!=data[page+23] ||chksum[2]!=data[page+24]||chksum[3]!=data[page+25]){ // D'oh. Mismatch! Corrupt page (or miscapture and not a page at all) // replace the computed checksum with the one actually read in System.arraycopy(chksum, 0, data, page+22, 4); // Bad checksum. Lose sync */ headerbytes=0; bodybytes=0; // search for possible capture next=0; for(int ii=0; ii<bytes-1; ii++){ if(data[page+1+ii]=='O'){ next=page+1+ii; break; } } //next=memchr(page+1,'O',bytes-1); if(next==0) next=fill; returned=next; return (-(next-page)); } } // yes, have a whole page all ready to go { page=returned; if(og!=null){ og.header_base=data; og.header=page; og.header_len=headerbytes; og.body_base=data; og.body=page+headerbytes; og.body_len=bodybytes; } unsynced=0; returned+=(bytes=headerbytes+bodybytes); headerbytes=0; bodybytes=0; return (bytes); } } // sync the stream and get a page. Keep trying until we find a page. // Supress 'sync errors' after reporting the first. // // return values: // -1) recapture (hole in data) // 0) need more data // 1) page returned // // Returns pointers into buffered data; invalidated by next call to // _stream, _clear, _init, or _buffer public int pageout(Page og){ // all we need to do is verify a page at the head of the stream // buffer. If it doesn't verify, we look for the next potential // frame while(true){ int ret=pageseek(og); if(ret>0){ // have a page return (1); } if(ret==0){ // need more data return (0); } // head did not start a synced page... skipped some bytes if(unsynced==0){ unsynced=1; return (-1); } // loop. keep looking } } // clear things to an initial state. Good to call, eg, before seeking public int reset(){ fill=0; returned=0; unsynced=0; headerbytes=0; bodybytes=0; return (0); } public void init(){ } public int getDataOffset(){ return returned; } public int getBufferOffset(){ return fill; } }