/*
* Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
* All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package javaFlacEncoder;
import java.util.Vector;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.IOException;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.concurrent.LinkedBlockingQueue;
/**
* This class defines a FLAC Encoder with a simple interface for enabling FLAC
* encoding support in an application. This class is appropriate for use in the
* case where you have raw pcm audio samples that you wish to encode. Currently,
* fixed-blocksize only is implemented, and the "Maximum Block Size" set in the
* StreamConfiguration object is used as the actual block size.
* <br><br><br>
* An encoding process is simple, and should follow these steps:<br>
* <BLOCKQUOTE>
* 1) Set StreamConfiguration to appropriate values. After a stream is opened,
* this must not be altered until the stream is closed.<br>
* 2) Set FLACOutputStream, object to write results to.<br>
* 3) Open FLAC Stream<br>
* 4) Set EncodingConfiguration(if defaults are insufficient).<br>
* 5) Add samples to encoder<br>
* 6) Encode Samples<br>
* 7) Close stream<br>
* (note: steps 4,5, and 6 may be done repeatedly, in any order. However, see
* related method documentation for info on concurrent use)
* </BLOCKQUOTE><br><br>
*
* @author Preston Lacey
*/
public class FLACEncoder {
/* For debugging, higher level equals more output */
int DEBUG_LEV = 0;
/**
* Maximum Threads to use for encoding frames(more threads than this will
* exist, these threads are reserved for encoding of frames only).
*/
public int MAX_THREADED_FRAMES = 2;
/* encodingConfig: Must never stay null(default supplied by constructor) */
EncodingConfiguration encodingConfig = null;
/* streamConfig: Must never stay null(default supplied by constructor) */
StreamConfiguration streamConfig = null;
/* Set true if frames are actively being encoded(can't change settings
* while this is true) */
volatile Boolean isEncoding = false;
/* synchronize on this object when encoding or changing configurations */
private final Object configWriteLock = new Object();
/* Store for blocks which are ready to encode. Always insert end, pop head*/
private Vector<int[]> blockQueue = null;
/* Stores samples for a block which is not yet full(not ready for queue) */
private int[] unfinishedBlock = null;
/* Stores count of inter-frame samples in unfinishedBlock */
private int unfinishedBlockUsed = 0;
/* Object to write results to. Must be set before opening stream */
private FLACOutputStream out = null;
/* contains FLAC_id used in the flac stream header to signify FLAC format */
EncodedElement FLAC_id = FLACStreamIdentifier.getIdentifier();
/* Frame object used to encode when not using threads */
Frame frame = null;
/* md object used to calculate MD5 hash */
MessageDigest md = null;
/* threadManager used with threaded encoding */
BlockThreadManager2 threadManager = null;
/* threagedFrames keeps track of frames given to threadManager. We must still
* update the configurations of them as needed. If we ever create new
* frames(e.g, when changing stream configuration), we must create a new
* threadManager as well.
*/
Frame[] threadedFrames = null;
/* minimum frame size seen so far. Used in the stream header */
int minFrameSize = 0x7FFFFFFF;
/* maximum frame size seen so far. Used in stream header */
int maxFrameSize = 0;
/* minimum block size used so far. Used in stream header */
int minBlockSize = 0x7FFFFFFF;
/* maximum block size used so far. Used in stream header */
int maxBlockSize = 0;
/* total number of samples encoded to output. Used in stream header */
volatile long samplesInStream;
/* next frame number to use */
long nextFrameNumber = 0;
/* position of header in output stream location(needed so we can update
* the header info(md5, minBlockSize, etc), once encoding is done
*/
long streamHeaderPos = 0;
/* should be set when any error has occured that invalidates results.
* This should not be relied on currently, practice not followed well.
*/
boolean error = false;
/* store used encodeRequests so we don't have to reallocate space for them*/
LinkedBlockingQueue<BlockEncodeRequest> usedBlockEncodeRequests = null;
LinkedBlockingQueue<int[]> usedIntArrays = null;
/**
* Constructor which creates a new encoder object with the default settings.
* The StreamConfiguration should be reset to match the audio used and an
* output stream set, but the default EncodingConfiguration should be ok for
* most purposes. When using threaded encoding, the default number of
* threads used is equal to FLACEncoder.MAX_THREADED_FRAMES.
*/
public FLACEncoder() {
usedBlockEncodeRequests = new LinkedBlockingQueue<BlockEncodeRequest>();
usedIntArrays = new LinkedBlockingQueue<int[]>();
blockQueue = new Vector<int[]>();
StreamConfiguration defaultStreamConfig = new StreamConfiguration();
encodingConfig = new EncodingConfiguration();
frame = new Frame(defaultStreamConfig);
frame.registerConfiguration(encodingConfig);
//frameThread = new FrameThread(frame);
threadManager = new BlockThreadManager2(this);
threadedFrames = new Frame[MAX_THREADED_FRAMES];
for(int i = 0; i < MAX_THREADED_FRAMES; i++) {
threadedFrames[i] = new Frame(defaultStreamConfig);
threadManager.addFrameThread(threadedFrames[i]);
}
try {
md = MessageDigest.getInstance("md5");
reset();
}catch(NoSuchAlgorithmException e) {
System.err.println("Critical Error: No md5 algorithm exists. " +
"This encoder can not function.");
}
}
/**
* Set the encoding configuration to that specified. The given encoding
* configuration is not stored by this object, but instead copied. This
* is to prevent the alteration of the config during an encode process.
*
* @param ec EncodingConfiguration to use.
* @return true if the configuration was altered; false if the configuration
* cannot be altered(such as if another thread is currently encoding).
*/
public boolean setEncodingConfiguration(EncodingConfiguration ec) {
boolean changed = false;
if(!isEncoding && ec != null) {
synchronized(configWriteLock) {
encodingConfig = ec;
frame.registerConfiguration(ec);
for(int i = 0; i < MAX_THREADED_FRAMES; i++)
threadedFrames[i].registerConfiguration(ec);
}
changed = true;
}
return changed;
}
/**
* Set the stream configuration to that specified. The given stream
* configuration is not stored by this object, but instead copied. This
* is to prevent the alteration of the config during an encode process.
* This method must not be called in the middle of a stream, stream contents
* may become invalid. A call to setStreamConfiguration() should
* be followed next by setting the output stream if not yet done, and then
* calling openFLACStream();
*
* @param sc StreamConfiguration to use.
* @return true if the configuration was altered; false if the configuration
* cannot be altered(such as if another thread is currently encoding).
*/
public boolean setStreamConfiguration(StreamConfiguration sc) {
boolean changed = false;
if(!isEncoding && sc != null) {
synchronized(configWriteLock) {
streamConfig = sc;
frame = new Frame(sc);
threadManager = new BlockThreadManager2(this);
threadedFrames = new Frame[MAX_THREADED_FRAMES];
for(int i = 0; i < MAX_THREADED_FRAMES; i++) {
threadedFrames[i] = new Frame(sc);
threadManager.addFrameThread(threadedFrames[i]);
}
this.setEncodingConfiguration(this.encodingConfig);
}
changed = true;
}
return changed;
}
/**
* Reset the values to their initial state, in preparation of starting a
* new stream.
*/
private void reset() {
//reset stream
md.reset();
minFrameSize = minFrameSize = 0x7FFFFFFF;
maxFrameSize = 0;
minBlockSize = 0x7FFFFFFF;
maxBlockSize = 0;
samplesInStream = 0;
streamHeaderPos = 0;
unfinishedBlock = null;
unfinishedBlockUsed = 0;
blockQueue.clear();
nextFrameNumber = 0;
}
/**
* Close the current FLAC stream. Updates the stream header information.
* If called on an open stream, operation is undefined. Do not do this.
*/
private void closeFLACStream() {
//reset position in output stream to beginning.
//re-write the updated stream info.
if(DEBUG_LEV > 0)
System.err.println("FLACEncoder::closeFLACStream : Begin");
streamConfig.setMaxBlockSize(maxBlockSize);
streamConfig.setMinBlockSize(minBlockSize);
byte[] md5 = md.digest();
EncodedElement streamInfo = MetadataBlockStreamInfo.getStreamInfo(
streamConfig, minFrameSize, maxFrameSize, samplesInStream,
md5);
out.seek(streamHeaderPos);
try {
this.writeDataToOutput(streamInfo);
}catch(IOException e) {
System.err.println("FLACEncoder::closeFLACStream(): ERROR WRiting to output");
}
}
/**
* Begin a new FLAC stream. Prior to calling this, you must have already
* set the StreamConfiguration and the output stream, both of which must not
* change until encoding is finished and the stream is closed.
* @throws IOException if there is an error writing the headers to output.
*/
public void openFLACStream() throws IOException {
//reset all data.
reset();
//write FLAC stream identifier
out.write(FLAC_id.getData(), 0, FLAC_id.getUsableBits()/8);
//write stream headers. These must be updated at close of stream
byte[] md5Hash = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};//blank hash. Don't know it yet.
EncodedElement streamInfo = MetadataBlockStreamInfo.getStreamInfo(
streamConfig, minFrameSize, maxFrameSize, samplesInStream,
md5Hash);
//mark stream info location(so we can return to it and re-write headers,
// assuming stream is seekable. Then write header.
int size = streamInfo.getUsableBits()/8;
EncodedElement metadataBlockHeader =
MetadataBlockHeader.getMetadataBlockHeader(true,
MetadataBlockHeader.MetadataBlockType.STREAMINFO, size);
this.writeDataToOutput(metadataBlockHeader);
streamHeaderPos = out.getPos();
out.write(streamInfo.getData(), 0, size);
}
/**
* Add samples to the encoder, so they may then be encoded. This method uses
* breaks the samples into blocks, which will then be made available to
* encode.
*
* @param samples Array holding the samples to encode. For all multi-channel
* audio, the samples must be interleaved in this array. For example, with
* stereo: sample 0 will belong to the first channel, 1 the second, 2 the
* first, 3 the second, etc. Samples are interpreted according to the
* current configuration(for things such as channel and bits-per-sample).
*
* @param count Number of interchannel samples to add. For example, with
* stero: if this is 4000, then "samples" must contain 4000 left samples and
* 4000 right samples, interleaved in the array.
*
* @return true if samples were added, false otherwise. A value of false may
* result if "count" is set to a size that is too large to be valid with the
* given array and current configuration.
*/
public boolean addSamples(int[] samples, int count) {
boolean added = false;
//get number of channels
int channels = streamConfig.getChannelCount();
int maxFrames = samples.length/channels;//input wav frames, not flac
int validSamples = count*channels;
if(DEBUG_LEV > 0) {
System.err.println("addSamples(...): ");
System.err.println("maxFrames: "+maxFrames);
System.err.println("validSamples: "+validSamples);
if(DEBUG_LEV > 10)
System.err.println("count:"+count+":channels:"+channels);
}
if(count <= maxFrames) {//sample count is ok
added = true;
//break sample input into appropriately sized blocks
int samplesUsed = 0;//number of input samples used
if(unfinishedBlock != null) {
//finish off last block first.
if(DEBUG_LEV > 10) {
System.err.println("addSamples(...): filling unfinishedBlock");
}
int blockSize = streamConfig.getMaxBlockSize();
int[] block = unfinishedBlock;
int unfinishedBlockRemaining = blockSize*channels-unfinishedBlockUsed;
if(unfinishedBlockRemaining <=0) {
System.err.println("MAJOR ERROR HERE. Unfinsihed block remaining invalid: "+
unfinishedBlockRemaining);
System.exit(-1);
}
int nextSampleStop = samplesUsed+unfinishedBlockRemaining;
if(nextSampleStop > validSamples) {
nextSampleStop = validSamples;
}
int i;
for(i = 0; i < unfinishedBlockRemaining && i < nextSampleStop; i++) {
block[unfinishedBlockUsed+i] = samples[samplesUsed+i];
}
unfinishedBlockUsed += i;
samplesUsed = nextSampleStop;
if(unfinishedBlockUsed == blockSize*channels) {
//System.err.println("Adding block: "+blocksAdded++ +":"+blockQueue.size());
blockQueue.add(block);
unfinishedBlockUsed = 0;
unfinishedBlock = null;
}
else if(unfinishedBlockUsed > blockSize*channels) {
System.err.println("Error: FLACEncoder.addSamples(...) " +
"unfinished block = "+unfinishedBlockUsed);
System.exit(-1);
}
}
while(samplesUsed < validSamples) {
if(DEBUG_LEV > 20)
System.err.println("addSamples(...): creating new block");
//copy values to approrpiate locations
//add each finished array to the queue
/*<implement_for_variable_blocksize>
* blockSize = this.getNextBlockSize(samples, validSamples);*/
int blockSize = streamConfig.getMaxBlockSize();
//int[] block = new int[blockSize*channels];
int[] block = getBlock(blockSize*channels);
int nextSampleStop = samplesUsed+blockSize*channels;
if(nextSampleStop > validSamples) {
//We don't have enough samples to make a full block.
if(DEBUG_LEV > 20)
System.err.println("addSamples(...): setting partial Block");
//fill unfinishedBlock
nextSampleStop = validSamples;
unfinishedBlock = block;
unfinishedBlockUsed = validSamples-samplesUsed;
}
else {
blockQueue.add(block);
//System.err.println("Adding block: "+blocksAdded++ +":"+blockQueue.size());
}
//System.err.println("samplesUsed: " + samplesUsed);
//System.err.println("Nextsamplestop: " + nextSampleStop);
for(int i = 0; i < nextSampleStop-samplesUsed; i++)
block[i] = samples[samplesUsed+i];
samplesUsed = nextSampleStop;
}
}
else {
System.err.println("Error: FLACEncoder.addSamples "+
"given count out of bounds");
}
if(DEBUG_LEV > 20) {
System.err.println("Blocks stored: " +blockQueue.size());
System.err.println("Samples in partial block: " + unfinishedBlockUsed);
}
return added;
}
/**
* This function is for development purposes only. It likely serves no
* further point and perhaps is worthy of being removed.
* @param block
* @param count
* @param iter
*/
private void outputBlockToFile(int[] block, int count, int iter) {
//DEBUGGING, for development only!
try {
FileOutputStream fout = new FileOutputStream("samples.txt");
//OutputStreamWriter tOut = new OutputStreamWriter(fout);
PrintWriter pOut = new PrintWriter(fout);
for(int i = 0; i < count; i++) {
String temp = Integer.toString(i)+":";
temp = temp + Integer.toString(block[i*iter]);
System.err.print(temp);
pOut.println(temp);
}
pOut.flush();
pOut.close();
fout.close();
System.exit(0);
System.err.println("sample file written:");
}
catch(FileNotFoundException e) {
System.err.println("Error creating file");
}catch(IOException e) {
System.err.println("Error handling file");
}
}
/**
* Notify the encoder that a BlockEncodeRequest has finished, and is now
* ready to be written to file. The encoder expects that these requests come
* back in the same order the encoder sent them out. This is intended to
* be used in threading mode only at the moment(sending them to a
* BlockThreadManager object)
*
* @param ber BlockEncodeRequest that is ready to write to file.
*/
public void blockFinished(BlockEncodeRequest ber) {
synchronized (ber) {
try {
writeDataToOutput(ber.result.getNext());
}catch(IOException e) {
System.err.println("blockFinished: Error writing to output");
e.printStackTrace();
error = true;
}
//update encodedCount and count, and blocks, MD5
if(ber.count != ber.encodedSamples) {
System.err.println("Error encoding frame number: "+
ber.frameNumber+", FLAC stream potentially invalid");
}
samplesInStream += ber.encodedSamples;
if(ber.encodedSamples > maxBlockSize)
maxBlockSize = ber.encodedSamples;
if(ber.encodedSamples < minBlockSize)
minBlockSize = ber.encodedSamples;
int frameSize = ber.result.getTotalBits()%8;
if(frameSize > maxFrameSize) maxFrameSize = frameSize;
if(frameSize < minFrameSize) minFrameSize = frameSize;
addSamplesToMD5(ber.samples, ber.encodedSamples, ber.skip+1,
streamConfig.getBitsPerSample());
usedIntArrays.add(ber.samples);
ber.samples = null;
ber.result = null;
usedBlockEncodeRequests.add(ber);
}
}
/**
* Attempt to Encode a certain number of samples(threaded version).
* Encodes as close to count as possible. Uses multiple threads to speed up
* encoding.
*
* @param count number of samples to attempt to encode. Actual number
* encoded may be greater or less if count does not end on a block boundary.
*
* @param end true to finalize stream after encode, false otherwise. If set
* to true, no more encoding must be attempted until a new stream is began.
*
* @return number of samples encoded. This may be greater or less than
* requested count if count does not end on a block boundary. This is NOT an
* error condition.
*
* @throws IOException if there was an error writing the results to file.
*/
public int t_encodeSamples(int count, boolean end) throws IOException {
int encodedCount = 0;
//pull blocks from the queue, check size, and encode if size is smaller
//than remaining count.
int blocksLeft = blockQueue.size();
int channels = streamConfig.getChannelCount();
while(count > 0 && blocksLeft > 0) {
if(DEBUG_LEV > 20) {
System.err.println("while: count:blocksLeft : "+
count+":"+blocksLeft);
}
int[] block = blockQueue.elementAt(0);
if(block.length <= count*channels) {
//encode
int encodedSamples = block.length/channels;//interchannel samples
EncodedElement result = new EncodedElement();
//BlockEncodeRequest ber = new BlockEncodeRequest();
BlockEncodeRequest ber = usedBlockEncodeRequests.poll();
if(ber == null) ber = new BlockEncodeRequest();
ber.setAll(block, encodedSamples, 0,channels-1, nextFrameNumber++,
result);
threadManager.addRequest(ber);
blockQueue.remove(0);
blocksLeft--;
count -= encodedSamples;
encodedCount += encodedSamples;
}
else {
//can't encode a full block.
System.err.println("Error with block in queue?");
break;
}
}
//block while requests remain!!!!
threadManager.blockWhileQueueExceeds(5);
if(end) {
threadManager.stop();
threadManager.blockWhileQueueExceeds(0);
}
//handle "end" setting
if(end && count >= 0 && this.samplesAvailableToEncode() >= count) {
//handle remaining count
if(count > 0 && unfinishedBlockUsed >= count) {
int[] block = null;
if(blockQueue.size() > 0) {
block = blockQueue.elementAt(0);
}
else
block = unfinishedBlock;
int encodedSamples = count;//interchannel samples
EncodedElement result = new EncodedElement();
int encoded = frame.encodeSamples(block, encodedSamples, 0,
channels-1, result, nextFrameNumber);
if(encoded != encodedSamples) {
//ERROR! Return immediately. Do not add results to output.
System.err.println("FLACEncoder::encodeSamples : (end)Error in encoding");
count = -1;
}
else {
writeDataToOutput(result.getNext());
//update encodedCount and count
encodedCount += encodedSamples;
count -= encodedSamples;
//addSamplesToMD5(block, encodedSamples, 0,channels);
addSamplesToMD5(block, encodedSamples, channels,
streamConfig.getBitsPerSample());
samplesInStream += encodedSamples;
nextFrameNumber++;
if(encodedSamples > maxBlockSize) maxBlockSize = encodedSamples;
if(encodedSamples < minBlockSize) minBlockSize = encodedSamples;
int frameSize = result.getTotalBits()%8;
if(frameSize > maxFrameSize) maxFrameSize = frameSize;
if(frameSize < minFrameSize) minFrameSize = frameSize;
//System.err.println("Count: " + count);
}
}
//close stream if all requested were written.
if(count == 0) {
closeFLACStream();
}
}
else if (end == true) {
System.err.println("End set but not done. Error likely. "+
"This can happen if number of samples requested to " +
"encode exeeds available samples");
}
return encodedCount;
}
/**
* Attempt to Encode a certain number of samples. Encodes as close to count
* as possible.
*
* @param count number of samples to attempt to encode. Actual number
* encoded may be greater or less if count does not end on a block boundary.
*
* @param end true to finalize stream after encode, false otherwise. If set
* to true, no more encoding must be attempted until a new stream is began.
* @return number of samples encoded. This may be greater or less than
* requested count if count does not end on a block boundary. This is NOT an
* error condition.
* @throws IOException if there was an error writing the results to file.
*/
public int encodeSamples(int count, boolean end) throws IOException {
// System.err.println("starting encoding :");
int encodedCount = 0;
//pull blocks from the queue, check size, and encode if size is smaller
//than remaining count.
int blocksLeft = blockQueue.size();
int channels = streamConfig.getChannelCount();
while(count > 0 && blocksLeft > 0) {
if(DEBUG_LEV > 20) {
System.err.println("while: count:blocksLeft : "+
count+":"+blocksLeft);
}
int[] block = blockQueue.elementAt(0);
if(block.length <= count*channels) {
//encode
int encodedSamples = block.length/channels;//interchannel samples
//count -= encodedSamples;
EncodedElement result = new EncodedElement();
int encoded = frame.encodeSamples(block, encodedSamples, 0,
channels-1, result, nextFrameNumber);
if(encoded != encodedSamples) {
//ERROR! Return immediately. Do not add results to output.
System.err.println("FLACEncoder::encodeSamples : Error in encoding");
count = -1;
break;
}
//write encoded results to output.
//System.err.println("writing frame: "+nextFrameNumber);
writeDataToOutput(result.getNext());
//update encodedCount and count, and blocks, MD5
blockQueue.remove(0);
blocksLeft--;
encodedCount += encodedSamples;
//System.err.println("Count pre: " + count);
count -= encodedSamples;
samplesInStream += encodedSamples;
nextFrameNumber++;
if(encodedSamples > maxBlockSize)
maxBlockSize = encodedSamples;
if(encodedSamples < minBlockSize)
minBlockSize = encodedSamples;
int frameSize = result.getTotalBits()%8;
if(frameSize > maxFrameSize) maxFrameSize = frameSize;
if(frameSize < minFrameSize) minFrameSize = frameSize;
//addSamplesToMD5(block, encodedSamples, 0,channels);
addSamplesToMD5(block, encodedSamples, channels,
streamConfig.getBitsPerSample());
usedIntArrays.add(block);
//System.err.println("Count post: " + count);
}
else {
if(blockQueue.size() > 0) {
System.err.println("Can't encode full but blocksize != 0");
System.err.println("Blockqueue size: "+blockQueue.size());
System.err.println("Block size: "+block.length);
System.err.println("Count: "+count);
}
//can't encode a full block.
break;
}
}
//handle "end" setting
if(end)
threadManager.stop();
if(end && count >= 0 && this.samplesAvailableToEncode() >= count) {
//handle remaining count
if(count > 0 && unfinishedBlockUsed >= count) {
int[] block = null;
if(blockQueue.size() > 0) {
block = blockQueue.elementAt(0);
}
else
block = unfinishedBlock;
int encodedSamples = count;//interchannel samples
EncodedElement result = new EncodedElement();
int encoded = frame.encodeSamples(block, encodedSamples, 0,
channels-1, result, nextFrameNumber);
if(encoded != encodedSamples) {
//ERROR! Return immediately. Do not add results to output.
System.err.println("FLACEncoder::encodeSamples : (end)Error in encoding");
count = -1;
}
else {
writeDataToOutput(result.getNext());
//update encodedCount and count
encodedCount += encodedSamples;
count -= encodedSamples;
//addSamplesToMD5(block, encodedSamples, 0,channels);
addSamplesToMD5(block, encodedSamples, channels,
streamConfig.getBitsPerSample());
samplesInStream += encodedSamples;
nextFrameNumber++;
if(encodedSamples > maxBlockSize) maxBlockSize = encodedSamples;
if(encodedSamples < minBlockSize) minBlockSize = encodedSamples;
int frameSize = result.getTotalBits()%8;
if(frameSize > maxFrameSize) maxFrameSize = frameSize;
if(frameSize < minFrameSize) minFrameSize = frameSize;
System.err.println("Count: " + count);
}
}
//close stream if all requested were written.
if(count == 0) {
closeFLACStream();
}
}
else if (end == true) {
System.err.println("End set but not done. Error likely." );
}
return encodedCount;
}
/**
* Add samples to the MD5 hash.
* CURRENTLY ONLY MAY WORK FOR: sample sizes which are divisible by 8. Need
* to create some audio to test with.
* @param samples
* @param count
* @param channels
*/
private void addSamplesToMD5(int[] samples, int count, int channels,
int sampleSize) {
int bytesPerSample = sampleSize/8;
if(sampleSize%8 != 0)
bytesPerSample++;
byte[] dataMD5 = new byte[count*bytesPerSample*channels];
for(int i = 0; i < count*channels; i++) {
for(int x = 0; x < bytesPerSample; x++) {
dataMD5[i*bytesPerSample+x] = (byte)(samples[i] >> x*8);
}
}
md.update(dataMD5, 0, count*bytesPerSample*channels);
}
/**
* Write the data stored in an EncodedElement to the output stream.
* All data will be written along byte boundaries, but the elements in the
* given list need not end on byte boundaries. If the data of an element
* does not end on a byte boundary, then the space remaining in that last
* byte will be used as an offset, and merged(using an "OR"), with the first
* byte of the following element.
*
* @param data
* @return
* @throws IOException
*/
private int writeDataToOutput(EncodedElement data) throws IOException {
int writtenBytes = 0;
int offset = 0;
EncodedElement current = data;
int currentByte = 0;
byte unfullByte = 0;
byte[] eleData = null;
int usableBits = 0;
int lastByte = 0;
while(current != null) {
//System.err.println("current != null" );
eleData = current.getData();
usableBits = current.getUsableBits();
currentByte = 0;
//if offset is not zero, merge first byte with existing byte
if(offset != 0) {
unfullByte = (byte)(unfullByte | eleData[currentByte++]);
out.write(unfullByte);
}
//write all full bytes of element.
lastByte = usableBits/8;
//System.err.println("eleData.length:currentByte:length : "+
// eleData.length+":"+currentByte+":"+(lastByte-currentByte));
if(lastByte > 0)
out.write(eleData, currentByte, lastByte-currentByte);
//save non-full byte(if present), and set "offset" for next element.
//offset = usableBits - lastByte*8;
offset = usableBits %8;
if(offset != 0) {
unfullByte = eleData[lastByte];
}
//update current.
current = current.getNext();
}
//if non-full byte remains. write.
if(offset != 0) {
out.write(eleData, lastByte, 1);
}
return writtenBytes;
}
/**
* Get number of samples which are ready to encode. More samples may exist
* in the encoder as a partial block. Use samplesAvailableToEncode() if you
* wish to include those as well.
* @return number of samples in full blocks, ready to encode.
*/
public int fullBlockSamplesAvailableToEncode() {
int available = 0;
int channels = streamConfig.getChannelCount();
for(int[] block: blockQueue) {
available += block.length/channels;
}
return available;
}
/**
* Get number of samples that are available to encode. This includes samples
* which are in a partial block(and so would only be written if "end" was
* set true in encodeSamples(int count,boolean end);
* @return number of samples availble to encode.
*/
public int samplesAvailableToEncode() {
int available = 0;
//sum all in blockQueue
int channels = streamConfig.getChannelCount();
for(int[] block : blockQueue) {
available += block.length/channels;
}
//add remaining in unfinishedBlock.
available += unfinishedBlockUsed;
return available;
}
/**
* Set the output stream to use. This must not be called while an encode
* process is active.
* @param fos output stream to use. This must not be null.
*/
public void setOutputStream(FLACOutputStream fos) {
out = fos;
}
public int[] getBlock(int size) {
int[] result = usedIntArrays.poll();
if(result == null) {
result = new int[size];
//System.err.println("Created new int array from null");
}
else if(result.length < size) {
usedIntArrays.offer(result);
result = new int[size];
//System.err.println("created new int array from bad size");
}
return result;
}
//int[] block = new int[blockSize*channels];
}