All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jflac.FLACDecoder Maven / Gradle / Ivy

There is a newer version: 1.5.2
Show newest version
package org.jflac;

/**
 *  libFLAC - Free Lossless Audio Codec library
 * Copyright (C) 2000,2001,2002,2003  Josh Coalson
 *
 * This library 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 library 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 library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 */

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import org.jflac.frame.BadHeaderException;
import org.jflac.frame.ChannelConstant;
import org.jflac.frame.ChannelFixed;
import org.jflac.frame.ChannelLPC;
import org.jflac.frame.ChannelVerbatim;
import org.jflac.frame.Frame;
import org.jflac.frame.Header;
import org.jflac.io.BitInputStream;
import org.jflac.io.RandomFileInputStream;
import org.jflac.metadata.Application;
import org.jflac.metadata.CueSheet;
import org.jflac.metadata.Metadata;
import org.jflac.metadata.Padding;
import org.jflac.metadata.Picture;
import org.jflac.metadata.SeekPoint;
import org.jflac.metadata.SeekTable;
import org.jflac.metadata.StreamInfo;
import org.jflac.metadata.Unknown;
import org.jflac.metadata.VorbisComment;
import org.jflac.util.ByteData;
import org.jflac.util.CRC16;

/**
 * A Java FLAC decoder.
 * @author kc7bfi
 */
public class FLACDecoder {
    private static final int FRAME_FOOTER_CRC_LEN = 16; // bits
    private static final byte[] ID3V2_TAG = new byte[] { 'I', 'D', '3' };
    
    private BitInputStream bitStream;
    private ChannelData[] channelData = new ChannelData[Constants.MAX_CHANNELS];
    private int outputCapacity = 0;
    private int outputChannels = 0;
    private long samplesDecoded = 0;
    private StreamInfo streamInfo = null;
    private SeekTable seekTable = null;
    private Frame frame = new Frame();
    private byte[] headerWarmup = new byte[2]; // contains the sync code and reserved bits
    //private int state;
    private int channels;
    private int channelAssignment;
    private int bitsPerSample;
    private int sampleRate; // in Hz
    private int blockSize; // in samples (per channel)
    private InputStream inputStream = null;
    
    private int badFrames;
    private boolean eof = false;
    
    private FrameListeners frameListeners = new FrameListeners();
    private PCMProcessors pcmProcessors = new PCMProcessors();
    
    // Decoder states
    //private static final int DECODER_SEARCH_FOR_METADATA = 0;
    //private static final int DECODER_READ_METADATA = 1;
    //private static final int DECODER_SEARCH_FOR_FRAME_SYNC = 2;
    //private static final int DECODER_READ_FRAME = 3;
    //private static final int DECODER_END_OF_STREAM = 4;
    //private static final int DECODER_ABORTED = 5;
    //private static final int DECODER_UNPARSEABLE_STREAM = 6;
    //private static final int STREAM_DECODER_MEMORY_ALLOCATION_ERROR = 7;
    //private static final int STREAM_DECODER_ALREADY_INITIALIZED = 8;
    //private static final int STREAM_DECODER_INVALID_CALLBACK = 9;
    //private static final int STREAM_DECODER_UNINITIALIZED = 10;
    
    /**
     * The constructor.
     * @param inputStream    The input stream to read data from
     */
    public FLACDecoder(InputStream inputStream) {
        this.inputStream = inputStream;
        this.bitStream = new BitInputStream(inputStream);
        //state = DECODER_SEARCH_FOR_METADATA;     
        samplesDecoded = 0;
        //state = DECODER_SEARCH_FOR_METADATA;
    }
    
    /**
     * Return the parsed StreamInfo Metadata record.
     * @return  The StreamInfo
     */
    public StreamInfo getStreamInfo() {
        return streamInfo;
    }
    
    /**
     * Return the ChannelData object.
     * @return  The ChannelData object
     */
    public ChannelData[] getChannelData() {
        return channelData;
    }
    
    /**
     * Return the input but stream.
     * @return  The bit stream
     */
    public BitInputStream getBitInputStream() {
        return bitStream;
    }
    
    /**
     * Add a frame listener.
     * @param listener  The frame listener to add
     */
    public void addFrameListener(FrameListener listener) {
        frameListeners.addFrameListener(listener);
    }
    
    /**
     * Remove a frame listener.
     * @param listener  The frame listener to remove
     */
    public void removeFrameListener(FrameListener listener) {
        frameListeners.removeFrameListener(listener);
    }
    
    /**
     * Add a PCM processor.
     * @param processor  The processor listener to add
     */
    public void addPCMProcessor(PCMProcessor processor) {
        pcmProcessors.addPCMProcessor(processor);
    }
    
    /**
     * Remove a PCM processor.
     * @param processor  The processor listener to remove
     */
    public void removePCMProcessor(PCMProcessor processor) {
        pcmProcessors.removePCMProcessor(processor);
    }
    
    private void callPCMProcessors(Frame frame) {
    	ByteData bd = decodeFrame(frame, null);
        pcmProcessors.processPCM(bd);
    }
    
    /**
     * Fill the given ByteData object with PCM data from the frame.
     *
     * @param frame the frame to send to the PCM processors
     * @param pcmData the byte data to be filled, or null if it should be allocated
     * @return the ByteData that was filled (may be a new instance from space) 
     */
    public ByteData decodeFrame(Frame frame, ByteData pcmData) {
    	// required size of the byte buffer
    	int byteSize = frame.header.blockSize * channels * ((streamInfo.getBitsPerSample() + 7) / 2);
    	if (pcmData == null || pcmData.getData().length < byteSize ) {
    		pcmData = new ByteData(byteSize);
    	} else {
    		pcmData.setLen(0);
    	}
        if (streamInfo.getBitsPerSample() == 8) {
            for (int i = 0; i < frame.header.blockSize; i++) {
                for (int channel = 0; channel < channels; channel++) {
                    pcmData.append((byte) (channelData[channel].getOutput()[i] + 0x80));
                }
            }
        } else if (streamInfo.getBitsPerSample() == 16) {
            for (int i = 0; i < frame.header.blockSize; i++) {
                for (int channel = 0; channel < channels; channel++) {
                    short val = (short) (channelData[channel].getOutput()[i]);
                    pcmData.append((byte) (val & 0xff));
                    pcmData.append((byte) ((val >> 8) & 0xff));
                }
            }
        } else if (streamInfo.getBitsPerSample() == 24) {
            for (int i = 0; i < frame.header.blockSize; i++) {
                for (int channel = 0; channel < channels; channel++) {
                    int val = (channelData[channel].getOutput()[i]);
                    pcmData.append((byte) (val & 0xff));
                    pcmData.append((byte) ((val >> 8) & 0xff));
                    pcmData.append((byte) ((val >> 16) & 0xff));
                }
            }
        }
        return pcmData;
    }
    
    /**
     * Read the FLAC stream info.
     * @return  The FLAC Stream Info record
     * @throws IOException On read error
     */
    public StreamInfo readStreamInfo() throws IOException {
        readStreamSync();
        Metadata metadata = readNextMetadata();
        if (!(metadata instanceof StreamInfo)) throw new IOException("StreamInfo metadata block missing");
        return (StreamInfo) metadata;
    }
    
    /**
     * Read an array of metadata blocks.
     * @return  The array of metadata blocks
     * @throws IOException  On read error
     */
    public Metadata[] readMetadata() throws IOException {
        readStreamSync();
        Vector metadataList = new Vector();
        Metadata metadata;
        do {
            metadata = readNextMetadata();
            metadataList.add(metadata);
        } while (!metadata.isLast());
        return (Metadata[])metadataList.toArray(new Metadata[0]);
    }
    
    /**
     * Read an array of metadata blocks.
     * @param streamInfo    The StreamInfo metadata block previously read
     * @return  The array of metadata blocks
     * @throws IOException  On read error
     */
    public Metadata[] readMetadata(StreamInfo streamInfo) throws IOException {
        if (streamInfo.isLast()) return new Metadata[0];
        Vector metadataList = new Vector();
        Metadata metadata;
        do {
            metadata = readNextMetadata();
            metadataList.add(metadata);
        } while (!metadata.isLast());
        return (Metadata[])metadataList.toArray(new Metadata[0]);
    }
    
    /**
     * process a single metadata/frame.
     * @return True of one processed
     * @throws IOException  on read error
     */
    /*
     public boolean processSingle() throws IOException {
     
     while (true) {
     switch (state) {
     case DECODER_SEARCH_FOR_METADATA :
     readStreamSync();
     break;
     case DECODER_READ_METADATA :
     readNextMetadata(); // above function sets the status for us
     return true;
     case DECODER_SEARCH_FOR_FRAME_SYNC :
     frameSync(); // above function sets the status for us
     break;
     case DECODER_READ_FRAME :
     readFrame();
     return true; // above function sets the status for us
     //break;
      case DECODER_END_OF_STREAM :
      case DECODER_ABORTED :
      return true;
      default :
      return false;
      }
      }
      }
      */
    
    /**
     * Process all the metadata records.
     * @throws IOException On read error
     */
    /*
     public void processMetadata() throws IOException {
     
     while (true) {
     switch (state) {
     case DECODER_SEARCH_FOR_METADATA :
     readStreamSync();
     break;
     case DECODER_READ_METADATA :
     readNextMetadata(); // above function sets the status for us
     break;
     case DECODER_SEARCH_FOR_FRAME_SYNC :
     case DECODER_READ_FRAME :
     case DECODER_END_OF_STREAM :
     case DECODER_ABORTED :
     default :
     return;
     }
     }
     }
     */
    
    /**
     * Decode the FLAC file.
     * @throws IOException  On read error
     */
    public void decode() throws IOException {
        readMetadata();
        try {
            while (true) {
                //switch (state) {
                //case DECODER_SEARCH_FOR_METADATA :
                //    readStreamSync();
                //    break;
                //case DECODER_READ_METADATA :
                //    Metadata metadata = readNextMetadata();
                //    if (metadata == null) break;
                //    break;
                //case DECODER_SEARCH_FOR_FRAME_SYNC :
                findFrameSync();
                //    break;
                //case DECODER_READ_FRAME :
                try {
                    readFrame();
                    frameListeners.processFrame(frame);
                    callPCMProcessors(frame);
                } catch (FrameDecodeException e) {
                    badFrames++;
                }
                //    break;
                //case DECODER_END_OF_STREAM :
                //case DECODER_ABORTED :
                //    return;
                //default :
                //    throw new IOException("Unknown state: " + state);
                //}
            }
        } catch (EOFException e) {
            eof = true;
        }
    }
    
    /**
     * Decode the data frames.
     * @throws IOException  On read error
     */
    public void decodeFrames() throws IOException {
        //state = DECODER_SEARCH_FOR_FRAME_SYNC;
        try {
            while (true) {
                //switch (state) {
                //case DECODER_SEARCH_FOR_METADATA :
                //    readStreamSync();
                //    break;
                //case DECODER_READ_METADATA :
                //    Metadata metadata = readNextMetadata();
                //    if (metadata == null) break;
                //    break;
                //case DECODER_SEARCH_FOR_FRAME_SYNC :
                findFrameSync();
                //    break;
                //case DECODER_READ_FRAME :
                try {
                    readFrame();
                    frameListeners.processFrame(frame);
                    callPCMProcessors(frame);
                } catch (FrameDecodeException e) {
                    badFrames++;
                }
                //    break;
                //case DECODER_END_OF_STREAM :
                //case DECODER_ABORTED :
                //    return;
                //default :
                //    throw new IOException("Unknown state: " + state);
                //}
            }
        } catch (EOFException e) {
            eof = true;
        }
    }
    
    /**
     * Decode the data frames between two seek points.
     * @param from  The starting seek point
     * @param to    The ending seek point (non-inclusive)
     * @throws IOException  On read error
     */
    public void decode(SeekPoint from, SeekPoint to) throws IOException {
        // position random access file
        if (!(inputStream instanceof RandomFileInputStream)) throw new IOException("Not a RandomFileInputStream: " + inputStream.getClass().getName());
        ((RandomFileInputStream)inputStream).seek(from.getStreamOffset());
        bitStream.reset();
        samplesDecoded = from.getSampleNumber();
        
        //state = DECODER_SEARCH_FOR_FRAME_SYNC;
        try {
            while (true) {
                //switch (state) {
                //case DECODER_SEARCH_FOR_METADATA :
                //    readStreamSync();
                //    break;
                //case DECODER_READ_METADATA :
                //    Metadata metadata = readNextMetadata();
                //    if (metadata == null) break;
                //    break;
                //case DECODER_SEARCH_FOR_FRAME_SYNC :
                findFrameSync();
                //    break;
                //case DECODER_READ_FRAME :
                try {
                    readFrame();
                    frameListeners.processFrame(frame);
                    callPCMProcessors(frame);
                } catch (FrameDecodeException e) {
                    badFrames++;
                }
                //frameListeners.processFrame(frame);
                //callPCMProcessors(frame);
                //System.out.println(samplesDecoded +" "+ to.getSampleNumber());
                if (to != null && samplesDecoded >= to.getSampleNumber()) return;
                //    break;
                //case DECODER_END_OF_STREAM :
                //case DECODER_ABORTED :
                //    return;
                //default :
                //    throw new IOException("Unknown state: " + state);
                //}
            }
        } catch (EOFException e) {
            eof = true;
        }
    }
    
    /**
     * Attempt to seek to the requested (absolute) sample position in the file.
     * Actually seeks to the frame which contains the requested sample position.
     * @param samplesAbsolute	The number of samples from start of file to seek past
     * @return 	The new sample position in the file (from which the next frame will be read)
     * @throws IOException 
     */
    public long seek(long samplesAbsolute) throws IOException
    {
        // move back to start of file
        if (!(inputStream instanceof RandomFileInputStream))
        	throw new IOException("Not a RandomFileInputStream: " + inputStream.getClass().getName());
        ((RandomFileInputStream)inputStream).seek(0);
        bitStream.reset();
        samplesDecoded = 0;
        
        // track seek delta (difference between requested and current position in samples)
        long seekDelta = samplesAbsolute - samplesDecoded;
        
        // (re-)read metadata (including stream info and seek table)     	
    	readMetadata();
    	// check for stream info
        if (streamInfo == null)
        	throw new IOException("Could not obtain stream info required for seeking");
    
    	// validate requested (absolute) sample position
    	if (samplesAbsolute < 0 || samplesAbsolute >= streamInfo.getTotalSamples())
    		throw new IllegalArgumentException("Invalid sample position for seek");
    	
    	// check if seek to start of file (already done above)
    	if (seekDelta == 0)
    		return samplesDecoded; // samplesDecoded == 0

    	// use max frame size for seeking backwards
    	int estimatedFrameSize = streamInfo.getMaxFrameSize();    	
    	
    	// use data start and end positions as initial lower and upper bounds for seeking 	
    	// Note: should be at first data frame immediately after readMetadata()
    	findFrameSync(); // this reads up to the 2 sync bytes of the first data frame into bitStream
    	// keep track of current frame starting position
    	long bytePositionCurrentFrame = bitStream.getTotalBytesRead() - 2;	
    	// Note: uses virtual temporary SeekPoint variables to store sample/byte offset pairs
    	long samplePositionUpper = streamInfo.getTotalSamples();
    	long bytePositionUpper = ((RandomFileInputStream)inputStream).getLength() - estimatedFrameSize
    							 - bytePositionCurrentFrame;
    	// Note: maximum frame size used to increase chance of being before final frame
    	//	 	 (though still may not be if file contains ID3 tags after audio data)
    	SeekPoint beforeSeekPosition = new SeekPoint(0, 0, estimatedFrameSize);
    	SeekPoint afterSeekPosition = new SeekPoint(samplePositionUpper, bytePositionUpper,
    												streamInfo.getMinBlockSize());
    	
    	// use seekTable to (greatly) improve these initial lower and upper bounds
    	if (!(seekTable == null))
    	{    		
    		// use actual SeekPoint data from seekTable
    		for (int i=0; i beforeSeekPosition.getSampleNumber())
    				{
    					beforeSeekPosition = currentSeekPoint; // set as new lower bound
    					
        				// check if requested sample position is within this SeekPoint
        				if ( samplesAbsolute < beforeSeekPosition.getSampleNumber() 
        										+ beforeSeekPosition.getFrameSamples() )
        				{
        					afterSeekPosition = currentSeekPoint; // set as new upper bound as well
        					break; // no need to check any further SeekPoints
        				}
    				}    					
    			}
    			else if (currentSeekPoint.getSampleNumber() > samplesAbsolute)
    			{
    				// SeekPoint is after requested sample position:
    				// check if closer than current upper bound
    				if (currentSeekPoint.getSampleNumber() < afterSeekPosition.getSampleNumber())
    					afterSeekPosition = currentSeekPoint; // set as new upper bound
    			}
    			else if (currentSeekPoint.getSampleNumber() == samplesAbsolute)
    			{
    				// SeekPoint is exactly the requested sample position:
    				// set lower and upper bound to this SeekPoint
    				beforeSeekPosition = currentSeekPoint;
    				afterSeekPosition = currentSeekPoint;    				
    				break; // no need to check any further SeekPoints
    			}
    			else
    				continue; // ignore SeekPoint (should never get here)
    		}    			
    	}    
    	
    	// use lower and upper bounds to estimate position of frame containing sample position:
    	// set initial estimate as lower bound
    	long bytePositionEstimate = bytePositionCurrentFrame + beforeSeekPosition.getStreamOffset();    	
    	// check if sample position not found within lower bound frame
    	// Note: if it had been found, then upper (after) would equal lower (before) sample position
    	if (afterSeekPosition.getSampleNumber() > beforeSeekPosition.getSampleNumber() )
    	{
        	// calculate estimated position within lower and upper bounds:
        	double percentBetweenSeekPositions = (samplesAbsolute - beforeSeekPosition.getSampleNumber())
        										 / (double) (afterSeekPosition.getSampleNumber() 
        												 	 - beforeSeekPosition.getSampleNumber());
        	// remove the frame size to seek to before start of frame containing sample position 
        	long bytePositionBetweenSeekPositions = (long) ((afterSeekPosition.getStreamOffset()
        							         				- beforeSeekPosition.getStreamOffset())
        										  			* percentBetweenSeekPositions)
        											- estimatedFrameSize;
        	// add estimated position within lower and upper bounds if not negative
        	// Note: could be negative due to subtraction of estimated frame size
        	if (bytePositionBetweenSeekPositions > 0 )
        		bytePositionEstimate += bytePositionBetweenSeekPositions;
    	}

    	// seek to estimate for frame containing requested sample position
    	((RandomFileInputStream)inputStream).seek(bytePositionEstimate);
    	bitStream.reset();
    	
    	/* loop until at requested sample position:
    	 * 0. starts at bytePositionEstimate from above initial logic;
    	 * 1. find and read the next frame from the current location;
    	 * 2. get frame sample range:
    	 * 	  (a) if requested sample position > current frame sample range, goto 1.;
    	 * 	  (b) if requested sample position < current frame sample range, seek backwards and goto 1.;
    	 * 	      (to prevent infinite loops, seeks further backwards on each time 2.(b) is reached)
    	 *    (c) if requested sample position within current frame sample range, exit loop.
    	 */ 
    	// Note: exits loop when current frame contains requested sample position
    	int framesToSeekBack = 1; // number of frames to go back if past requested position
    	Map seekBackwardsResults = new HashMap();
    	while (!(seekDelta == 0)) // Note: this condition is not currently used
    	{
        	// get the next data frame from the current position    		
    		findFrameSync(); // positions 2 bytes into the frame (as reads the 2 sync bytes)
    		// update position of current frame
    		bytePositionCurrentFrame = bytePositionEstimate + bitStream.getTotalBytesRead() - 2;
    		try { // attempt to read the current frame
				readFrame();
			} catch (FrameDecodeException e) {
				badFrames++;
				continue; // try to read next frame
			}
    		// update current position to end of frame
    		bytePositionEstimate += bitStream.getTotalBytesRead();
    		
    		if (frame == null)
    		{
    			// no frame read from file (likely no data frames remaining)
    			if (eof)
    			{
    				// end of file: attempt to seek back to previous frame (or further if required)
    				seekBackwardsResults = seekBackwards(bytePositionCurrentFrame, framesToSeekBack,
    													 estimatedFrameSize);
    				bytePositionEstimate = ((Long) seekBackwardsResults.get("bytePositionEstimate")).longValue();
    				framesToSeekBack = ((Integer) seekBackwardsResults.get("framesToSeekBack")).intValue();
    				// iterate again to get a previous frame
    				continue;
    			}
    			else // not end of file (unexpected file error)
    				throw new IOException("Fatal seek data frame reading error"); 
    		}
    		else
    		{
    			// frame read from file:
    			// use header to update current position
    			samplesDecoded = frame.header.sampleNumber;
    			
    			// update seek delta to determine if further iterations required
    			seekDelta = samplesAbsolute - samplesDecoded;
    			if (seekDelta < 0)
    			{
    				// requested sample is behind current frame:
    				// seek backwards (firstly by one frame, then further if required)
    				seekBackwardsResults = seekBackwards(bytePositionCurrentFrame, framesToSeekBack,
    													 estimatedFrameSize);
    				bytePositionEstimate = ((Long) seekBackwardsResults.get("bytePositionEstimate")).longValue();
    				framesToSeekBack = ((Integer) seekBackwardsResults.get("framesToSeekBack")).intValue();
    				// iterate again to get a previous frame
    				continue;
    			}
    			else if (seekDelta < frame.header.blockSize) // && seekDelta >= 0
    			{
    				// requested sample is in current frame (success):
    				// reposition pointer to before the frame
    				((RandomFileInputStream)inputStream).seek(bytePositionCurrentFrame);
    				bitStream.reset();
    				// success: exit loop at current position (immediately before frame)
    				break;
    			}
    			else if (seekDelta >= frame.header.blockSize)
    			{
    				// requested sample is ahead of current frame:
    				framesToSeekBack = 0; // should not need to go backwards now
    				((RandomFileInputStream)inputStream).seek(bytePositionEstimate);
    				bitStream.reset();
    				// iterate again to get the next data frame
    				continue;
    			}
    			else // this should not be possible, as above should cover all cases
    				throw new IOException("Fatal seek logic error");    			
    		}
    	}
    	
    	// return current sample position in file
    	return samplesDecoded;
    }
    
    /** Used by seek() to seek backwards, without causing an infinite loop.
     * @param bytePositionEstimate	The next position to seek to (in bytes)
     * @param framesToSeekBack		The number of frames to seek backwards
     * @param estimatedFrameSize	The size (in bytes) of a frame
     * @return 	Map containing new bytePositionEstimate and framesToSeekBack
     * @throws IOException
     */
    private Map seekBackwards(long bytePositionEstimate, int framesToSeekBack,
    										  long estimatedFrameSize) 
    		throws IOException 
    {
    	if (framesToSeekBack > 0)
		{
			// attempt to seek back to previous frame
			bytePositionEstimate -= framesToSeekBack * estimatedFrameSize;
			if (bytePositionEstimate < 0)
			{
				// prevent seeking before start of file
				bytePositionEstimate = 0;
				// should not need to seek backwards again
				framesToSeekBack = 0;
			}
			else
			{
				// seek back by further next time if position past required sample again
				framesToSeekBack++;
			}        
		}
		else // throw error: was behind (hence framesToSeekBack < 1) and now ahead
			throw new IOException("Fatal seek error: sample position not found");
    	
    	// seek back to new estimated position (should be before previous frame)
    	((RandomFileInputStream)inputStream).seek(bytePositionEstimate);
    	bitStream.reset();
    	
    	// return new estimated position and frames to seek back
    	Map returnVariables = new HashMap();
    	returnVariables.put("bytePositionEstimate", new Long(bytePositionEstimate));
    	returnVariables.put("framesToSeekBack", new Integer(framesToSeekBack));
    	return returnVariables;
	}
    
    /*
     private boolean processUntilEndOfStream() throws IOException {
     //boolean got_a_frame;
      
      while (true) {
      switch (state) {
      case DECODER_SEARCH_FOR_METADATA :
      readStreamSync();
      break;
      case DECODER_READ_METADATA :
      readNextMetadata(); // above function sets the status for us
      break;
      case DECODER_SEARCH_FOR_FRAME_SYNC :
      frameSync(); // above function sets the status for us
      //System.exit(0);
       break;
       case DECODER_READ_FRAME :
       readFrame();
       break;
       case DECODER_END_OF_STREAM :
       case DECODER_ABORTED :
       return true;
       default :
       return false;
       }
       }
       }
       */

	/**
     * Read the next data frame.
     * @return  The next frame
     * @throws IOException  on read error
     */
    public Frame readNextFrame() throws IOException {
        //boolean got_a_frame;
        
        try {
            while (true) {
                //switch (state) {
                //case STREAM_DECODER_SEARCH_FOR_METADATA :
                //    findMetadata();
                //    break;
                //case STREAM_DECODER_READ_METADATA :
                //    readMetadata(); /* above function sets the status for us */
                //    break;
                //case DECODER_SEARCH_FOR_FRAME_SYNC :
                findFrameSync(); /* above function sets the status for us */
                //System.exit(0);
                //break;
                //case DECODER_READ_FRAME :
                try {
                    readFrame();
                    return frame;
                } catch (FrameDecodeException e) {
                    badFrames++;
                }
                //break;
                //case DECODER_END_OF_STREAM :
                //case DECODER_ABORTED :
                //    return null;
                //default :
                //    return null;
                //}
            }
        } catch (EOFException e) {
            eof = true;
        }
        return null;
    }
    
    /**
     * Bytes consumed.
     * @return  The number of bytes read
     */
    //public long getBytesConsumed() {
    //    return is.getConsumedBlurbs();
    //}
    
    /**
     * Bytes read.
     * @return  The number of bytes read
     */
    public long getTotalBytesRead() {
        return bitStream.getTotalBytesRead();
    }
    
    /*
     public int getInputBytesUnconsumed() {
     return is.getInputBytesUnconsumed();
     }
     */
    
    private void allocateOutput(int size, int channels) {
        if (size <= outputCapacity && channels <= outputChannels) return;
        
        for (int i = 0; i < Constants.MAX_CHANNELS; i++) {
            channelData[i] = null;
        }
        
        for (int i = 0; i < channels; i++) {
            channelData[i] = new ChannelData(size);
        }
        
        outputCapacity = size;
        outputChannels = channels;
    }
    
    /**
     * Read the stream sync string.
     * @throws IOException  On read error
     */
    private void readStreamSync() throws IOException {
        int id = 0;
        for (int i = 0; i < 4;) {
            int x = bitStream.readRawUInt(8);
            if (x == Constants.STREAM_SYNC_STRING[i]) {
                i++;
                id = 0;
            } else if (x == ID3V2_TAG[id]) {
                id++;
                i = 0;
                if (id == 3) {
                    skipID3v2Tag();
                    id = 0;
                }
            } else {
                throw new IOException("Could not find Stream Sync");
                //i = 0;
                //id = 0;
            }
        }
    }
    
    /**
     * Read a single metadata record.
     * @return  The next metadata record
     * @throws IOException  on read error
     */
    public Metadata readNextMetadata() throws IOException {
        Metadata metadata = null;
        
        boolean isLast = (bitStream.readRawUInt(Metadata.STREAM_METADATA_IS_LAST_LEN) != 0);
        int type = bitStream.readRawUInt(Metadata.STREAM_METADATA_TYPE_LEN);
        int length = bitStream.readRawUInt(Metadata.STREAM_METADATA_LENGTH_LEN);
        
        if (type == Metadata.METADATA_TYPE_STREAMINFO) {
            streamInfo = new StreamInfo(bitStream, length, isLast);
            metadata = streamInfo;
            pcmProcessors.processStreamInfo((StreamInfo)metadata);
        } else if (type == Metadata.METADATA_TYPE_SEEKTABLE) {
        	seekTable = new SeekTable(bitStream, length, isLast);
            metadata = seekTable;
        } else if (type == Metadata.METADATA_TYPE_APPLICATION) {
            metadata = new Application(bitStream, length, isLast);
        } else if (type == Metadata.METADATA_TYPE_PADDING) {
            metadata = new Padding(bitStream, length, isLast);
        } else if (type == Metadata.METADATA_TYPE_VORBIS_COMMENT) {
            metadata = new VorbisComment(bitStream, length, isLast);
        } else if (type == Metadata.METADATA_TYPE_CUESHEET) {
	    metadata = new CueSheet(bitStream, length, isLast);
	} else if (type == Metadata.METADATA_TYPE_PICTURE) {
	    metadata = new Picture(bitStream, length, isLast);
        } else {
            metadata = new Unknown(bitStream, length, isLast);
        }
        frameListeners.processMetadata(metadata);
        //if (isLast) state = DECODER_SEARCH_FOR_FRAME_SYNC;
        return metadata;
    }
    
    
    private void skipID3v2Tag() throws IOException {
        
        // skip the version and flags bytes 
        int verMajor = bitStream.readRawInt(8);
        int verMinor = bitStream.readRawInt(8);
        int flags = bitStream.readRawInt(8);
        
        // get the size (in bytes) to skip
        int skip = 0;
        for (int i = 0; i < 4; i++) {
            int x = bitStream.readRawUInt(8);
            skip <<= 7;
            skip |= (x & 0x7f);
        }
        
        // skip the rest of the tag
        bitStream.readByteBlockAlignedNoCRC(null, skip);
    }
    
    private void findFrameSync() throws IOException {
        boolean first = true;
        //int cnt=0;
        
        // If we know the total number of samples in the stream, stop if we've read that many.
        // This will stop us, for example, from wasting time trying to sync on an ID3V1 tag.
        if (streamInfo != null && (streamInfo.getTotalSamples() != 0)) {
            if (samplesDecoded >= streamInfo.getTotalSamples()) {
                //state = DECODER_END_OF_STREAM;
                return;
            }
        }
        
        // make sure we're byte aligned
        if (!bitStream.isConsumedByteAligned()) {
            bitStream.readRawUInt(bitStream.bitsLeftForByteAlignment());
        }
        
        int x;
        try {
            while (true) {
                x = bitStream.readRawUInt(8);
                if (x == 0xff) { // MAGIC NUMBER for the first 8 frame sync bits
                    headerWarmup[0] = (byte) x;
                    x = bitStream.peekRawUInt(8);
                    
                    /* we have to check if we just read two 0xff's in a row; the second may actually be the beginning of the sync code */
                    /* else we have to check if the second byte is the end of a sync code */
                    if (x >> 2 == 0x3e) { /* MAGIC NUMBER for the last 6 sync bits */
                        headerWarmup[1] = (byte) bitStream.readRawUInt(8);
                        //state = DECODER_READ_FRAME;
                        return;
                    }
                }
                if (first) {
                    frameListeners.processError("FindSync LOST_SYNC: " + Integer.toHexString((x & 0xff)));
                    first = false;
                }
            }
        } catch (EOFException e) {
            if (!first) frameListeners.processError("FindSync LOST_SYNC: Left over data in file");
            //state = DECODER_END_OF_STREAM;
        }
    }
    
    /**
     * Read the next data frame.
     * @throws IOException  On read error
     * @throws FrameDecodeException On frame decoding error
     */
    public void readFrame() throws IOException, FrameDecodeException {
        boolean gotAFrame = false;
        int channel;
        int i;
        int mid, side;
        short frameCRC; /* the one we calculate from the input stream */
        //int x;
        
        /* init the CRC */
        frameCRC = 0;
        frameCRC = CRC16.update(headerWarmup[0], frameCRC);
        frameCRC = CRC16.update(headerWarmup[1], frameCRC);
        bitStream.resetReadCRC16(frameCRC);
        
        try {
            frame.header = new Header(bitStream, headerWarmup, streamInfo);
        } catch (BadHeaderException e) {
            frameListeners.processError("Found bad header: " + e);
            throw new FrameDecodeException("Bad Frame Header: " + e);
        }
        //if (state == DECODER_SEARCH_FOR_FRAME_SYNC) return false;
        allocateOutput(frame.header.blockSize, frame.header.channels);
        for (channel = 0; channel < frame.header.channels; channel++) {
            // first figure the correct bits-per-sample of the subframe
            int bps = frame.header.bitsPerSample;
            switch (frame.header.channelAssignment) {
            case Constants.CHANNEL_ASSIGNMENT_INDEPENDENT :
                /* no adjustment needed */
                break;
            case Constants.CHANNEL_ASSIGNMENT_LEFT_SIDE :
                if (channel == 1)
                    bps++;
            break;
            case Constants.CHANNEL_ASSIGNMENT_RIGHT_SIDE :
                if (channel == 0)
                    bps++;
            break;
            case Constants.CHANNEL_ASSIGNMENT_MID_SIDE :
                if (channel == 1)
                    bps++;
            break;
            default :
            }
            // now read it
            try {
                readSubframe(channel, bps);
            } catch (IOException e) {
                frameListeners.processError("ReadSubframe: " + e);
                throw e;
            }
        }
        readZeroPadding();
        
        // Read the frame CRC-16 from the footer and check
        frameCRC = bitStream.getReadCRC16();
        frame.setCRC((short)bitStream.readRawUInt(FRAME_FOOTER_CRC_LEN));
        if (frameCRC == frame.getCRC()) {
            /* Undo any special channel coding */
            switch (frame.header.channelAssignment) {
            case Constants.CHANNEL_ASSIGNMENT_INDEPENDENT :
                /* do nothing */
                break;
            case Constants.CHANNEL_ASSIGNMENT_LEFT_SIDE :
                for (i = 0; i < frame.header.blockSize; i++)
                    channelData[1].getOutput()[i] = channelData[0].getOutput()[i] - channelData[1].getOutput()[i];
            break;
            case Constants.CHANNEL_ASSIGNMENT_RIGHT_SIDE :
                for (i = 0; i < frame.header.blockSize; i++)
                    channelData[0].getOutput()[i] += channelData[1].getOutput()[i];
            break;
            case Constants.CHANNEL_ASSIGNMENT_MID_SIDE :
                for (i = 0; i < frame.header.blockSize; i++) {
                    mid = channelData[0].getOutput()[i];
                    side = channelData[1].getOutput()[i];
                    mid <<= 1;
                    mid |= (side & 1); /* i.e. if 'side' is odd... */                    
                    channelData[0].getOutput()[i] = (mid + side) >> 1;
                    channelData[1].getOutput()[i] = (mid - side) >> 1;
                }
            //System.exit(1);
            break;
            default :
                break;
            }
            
            gotAFrame = true;
        } else {
            // Bad frame, emit error and zero the output signal
            frameListeners.processError("CRC Error: " + Integer.toHexString((frameCRC & 0xffff)) + " vs " + Integer.toHexString((frame.getCRC() & 0xffff)));
            for (channel = 0; channel < frame.header.channels; channel++) {
                for (int j = 0; j < frame.header.blockSize; j++)
                    channelData[channel].getOutput()[j] = 0;
            }
        }
        
        // put the latest values into the public section of the decoder instance
        channels = frame.header.channels;
        channelAssignment = frame.header.channelAssignment;
        bitsPerSample = frame.header.bitsPerSample;
        sampleRate = frame.header.sampleRate;
        blockSize = frame.header.blockSize;
        
        //samplesDecoded = frame.header.sampleNumber + frame.header.blockSize;
        samplesDecoded += frame.header.blockSize;
        //System.out.println(samplesDecoded+" "+frame.header.sampleNumber + " "+frame.header.blockSize);
        
        //state = DECODER_SEARCH_FOR_FRAME_SYNC;
        //return;
    }
    
    private void readSubframe(int channel, int bps) throws IOException, FrameDecodeException {
        int x;
        
        x = bitStream.readRawUInt(8); /* MAGIC NUMBER */
        
        boolean haveWastedBits = ((x & 1) != 0);
        x &= 0xfe;
        
        int wastedBits = 0;
        if (haveWastedBits) {
            wastedBits = bitStream.readUnaryUnsigned() + 1;
            bps -= wastedBits;
        }
        
        // Lots of magic numbers here
        if ((x & 0x80) != 0) {
            frameListeners.processError("ReadSubframe LOST_SYNC: " + Integer.toHexString(x & 0xff));
            //state = DECODER_SEARCH_FOR_FRAME_SYNC;
            throw new FrameDecodeException("ReadSubframe LOST_SYNC: " + Integer.toHexString(x & 0xff));
            //return true;
        } else if (x == 0) {
            frame.subframes[channel] = new ChannelConstant(bitStream, frame.header, channelData[channel], bps, wastedBits);
        } else if (x == 2) {
            frame.subframes[channel] = new ChannelVerbatim(bitStream, frame.header, channelData[channel], bps, wastedBits);
        } else if (x < 16) {
            //state = DECODER_UNPARSEABLE_STREAM;
            throw new FrameDecodeException("ReadSubframe Bad Subframe Type: " + Integer.toHexString(x & 0xff));
        } else if (x <= 24) {
            //FLACSubframe_Fixed subframe = read_subframe_fixed_(channel, bps, (x >> 1) & 7);
            frame.subframes[channel] = new ChannelFixed(bitStream, frame.header, channelData[channel], bps, wastedBits, (x >> 1) & 7);
        } else if (x < 64) {
            //state = DECODER_UNPARSEABLE_STREAM;
            throw new FrameDecodeException("ReadSubframe Bad Subframe Type: " + Integer.toHexString(x & 0xff));
        } else {
            frame.subframes[channel] = new ChannelLPC(bitStream, frame.header, channelData[channel], bps, wastedBits, ((x >> 1) & 31) + 1);
        }
        if (haveWastedBits) {
            int i;
            x = frame.subframes[channel].getWastedBits();
            for (i = 0; i < frame.header.blockSize; i++)
                channelData[channel].getOutput()[i] <<= x;
        }
    }
    
    private void readZeroPadding() throws IOException, FrameDecodeException {
        if (!bitStream.isConsumedByteAligned()) {
            int zero = bitStream.readRawUInt(bitStream.bitsLeftForByteAlignment());
            if (zero != 0) {
                frameListeners.processError("ZeroPaddingError: " + Integer.toHexString(zero));
                //state = DECODER_SEARCH_FOR_FRAME_SYNC;
                throw new FrameDecodeException("ZeroPaddingError: " + Integer.toHexString(zero));
            }
        }
    }
    
    /**
     * Get the number of samples decoded.
     * @return Returns the samples Decoded.
     */
    public long getSamplesDecoded() {
        return samplesDecoded;
    }
    /**
     * @return Returns the number of bad frames decoded.
     */
    public int getBadFrames() {
        return badFrames;
    }
    /**
     * @return Returns true if end-of-file.
     */
    public boolean isEOF() {
        return eof;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy