/*
* $ProjectName$
* $ProjectRevision$
* -----------------------------------------------------------
* $Id: VorbisStream.java,v 1.4 2003/04/10 19:49:04 jarnbjo Exp $
* -----------------------------------------------------------
*
* $Author: jarnbjo $
*
* Description:
*
* Copyright 2002-2003 Tor-Einar Jarnbjo
* -----------------------------------------------------------
*
* Change History
* -----------------------------------------------------------
* $Log: VorbisStream.java,v $
* Revision 1.4 2003/04/10 19:49:04 jarnbjo
* no message
*
* Revision 1.3 2003/03/31 00:20:16 jarnbjo
* no message
*
* Revision 1.2 2003/03/16 01:11:12 jarnbjo
* no message
*
*
*/
package de.jarnbjo.vorbis;
import java.io.*;
import java.util.*;
import de.jarnbjo.ogg.*;
import de.jarnbjo.util.io.*;
/**
*/
public class VorbisStream {
private LogicalOggStream oggStream;
private IdentificationHeader identificationHeader;
private CommentHeader commentHeader;
private SetupHeader setupHeader;
private AudioPacket lastAudioPacket, nextAudioPacket;
private LinkedList audioPackets=new LinkedList();
private byte[] currentPcm;
private int currentPcmIndex;
private int currentPcmLimit;
private static final int IDENTIFICATION_HEADER = 1;
private static final int COMMENT_HEADER = 3;
private static final int SETUP_HEADER = 5;
private int bitIndex=0;
private byte lastByte=(byte)0;
private boolean initialized=false;
private Object streamLock=new Object();
private int pageCounter=0;
private int currentBitRate=0;
private long currentGranulePosition;
public static final int BIG_ENDIAN = 0;
public static final int LITTLE_ENDIAN = 1;
public VorbisStream() {
}
public VorbisStream(LogicalOggStream oggStream) throws VorbisFormatException, IOException {
this.oggStream=oggStream;
for(int i=0; i<3; i++) {
BitInputStream source=new ByteArrayBitInputStream(oggStream.getNextOggPacket());
int headerType=source.getInt(8);
switch(headerType) {
case IDENTIFICATION_HEADER:
identificationHeader=new IdentificationHeader(source);
break;
case COMMENT_HEADER:
commentHeader=new CommentHeader(source);
break;
case SETUP_HEADER:
setupHeader=new SetupHeader(this, source);
break;
}
}
if(identificationHeader==null) {
throw new VorbisFormatException("The file has no identification header.");
}
if(commentHeader==null) {
throw new VorbisFormatException("The file has no commentHeader.");
}
if(setupHeader==null) {
throw new VorbisFormatException("The file has no setup header.");
}
//currentPcm=new int[identificationHeader.getChannels()][16384];
currentPcm=new byte[identificationHeader.getChannels()*identificationHeader.getBlockSize1()*2];
//new BufferThread().start();
}
public IdentificationHeader getIdentificationHeader() {
return identificationHeader;
}
public CommentHeader getCommentHeader() {
return commentHeader;
}
protected SetupHeader getSetupHeader() {
return setupHeader;
}
public boolean isOpen() {
return oggStream.isOpen();
}
public void close() throws IOException {
oggStream.close();
}
public int readPcm(byte[] buffer, int offset, int length) throws IOException {
synchronized (streamLock) {
final int channels=identificationHeader.getChannels();
if(lastAudioPacket==null) {
lastAudioPacket=getNextAudioPacket();
}
if(currentPcm==null || currentPcmIndex>=currentPcmLimit) {
AudioPacket ap=getNextAudioPacket();
try {
ap.getPcm(lastAudioPacket, currentPcm);
currentPcmLimit=ap.getNumberOfSamples()*identificationHeader.getChannels()*2;
}
catch(ArrayIndexOutOfBoundsException e) {
return 0;
}
currentPcmIndex=0;
lastAudioPacket=ap;
}
int written=0;
int i=0;
int arrIx=0;
for(i=currentPcmIndex; i<currentPcmLimit && arrIx<length; i++) {
buffer[offset+arrIx++]=currentPcm[i];
written++;
}
currentPcmIndex=i;
return written;
}
}
private AudioPacket getNextAudioPacket() throws VorbisFormatException, IOException {
pageCounter++;
byte[] data=oggStream.getNextOggPacket();
AudioPacket res=null;
while(res==null) {
try {
res=new AudioPacket(this, new ByteArrayBitInputStream(data));
}
catch(ArrayIndexOutOfBoundsException e) {
// ignore and continue with next packet
}
}
currentGranulePosition+=res.getNumberOfSamples();
currentBitRate=data.length*8*identificationHeader.getSampleRate()/res.getNumberOfSamples();
return res;
}
public long getCurrentGranulePosition() {
return currentGranulePosition;
}
public int getCurrentBitRate() {
return currentBitRate;
}
public byte[] processPacket(byte[] packet) throws VorbisFormatException, IOException {
if(packet.length==0) {
throw new VorbisFormatException("Cannot decode a vorbis packet with length = 0");
}
if(((int)packet[0]&1)==1) {
// header packet
BitInputStream source=new ByteArrayBitInputStream(packet);
switch(source.getInt(8)) {
case IDENTIFICATION_HEADER:
identificationHeader=new IdentificationHeader(source);
break;
case COMMENT_HEADER:
commentHeader=new CommentHeader(source);
break;
case SETUP_HEADER:
setupHeader=new SetupHeader(this, source);
break;
}
return null;
}
else {
// audio packet
if(identificationHeader==null ||
commentHeader==null ||
setupHeader==null) {
throw new VorbisFormatException("Cannot decode audio packet before all three header packets have been decoded.");
}
AudioPacket ap=new AudioPacket(this, new ByteArrayBitInputStream(packet));
currentGranulePosition+=ap.getNumberOfSamples();
if(lastAudioPacket==null) {
lastAudioPacket=ap;
return null;
}
byte[] res=new byte[identificationHeader.getChannels()*ap.getNumberOfSamples()*2];
try {
ap.getPcm(lastAudioPacket, res);
}
catch(IndexOutOfBoundsException e) {
java.util.Arrays.fill(res, (byte)0);
}
lastAudioPacket=ap;
return res;
}
}
}