/*
** WavPackUtils.java
**
** Copyright (c) 2007 - 2008 Peter McQuillan
**
** All Rights Reserved.
**
** Distributed under the BSD Software License (see license.txt)
**
*/
package wavpack;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.DataInput;
import java.lang.reflect.InvocationTargetException;
public class WavPackUtils
{
///////////////////////////// local table storage ////////////////////////////
static long sample_rates [] =
{
6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000
};
///////////////////////////// executable code ////////////////////////////////
// This function reads data from the specified stream in search of a valid
// WavPack 4.0 audio block. If this fails in 1 megabyte (or an invalid or
// unsupported WavPack block is encountered) then an appropriate message is
// copied to "error" and NULL is returned, otherwise a pointer to a
// WavpackContext structure is returned (which is used to call all other
// functions in this module). This can be initiated at the beginning of a
// WavPack file, or anywhere inside a WavPack file. To determine the exact
// position within the file use WavpackGetSampleIndex(). Also,
// this function will not handle "correction" files, plays only the first
// two channels of multi-channel files, and is limited in resolution in some
// large integer or floating point files (but always provides at least 24 bits
// of resolution).
public static WavpackContext WavpackOpenFileInput(DataInput infile)
{
WavpackContext wpc = new WavpackContext();
WavpackStream wps = wpc.stream;
wpc.infile = infile;
wpc.total_samples = -1;
wpc.norm_offset = 0;
wpc.open_flags = 0;
// open the source file for reading and store the size
while (wps.wphdr.block_samples == 0)
{
wps.wphdr = read_next_header(wpc.infile, wps.wphdr);
if (wps.wphdr.status == 1)
{
wpc.error_message = "not compatible with this version of WavPack file!";
wpc.error = true;
return (wpc);
}
if (wps.wphdr.block_samples > 0 && wps.wphdr.total_samples != -1)
{
wpc.total_samples = wps.wphdr.total_samples;
}
// lets put the stream back in the context
wpc.stream = wps;
if ((UnpackUtils.unpack_init(wpc)) == Defines.FALSE)
{
wpc.error = true;
return wpc;
}
} // end of while
wpc.config.flags = wpc.config.flags & ~0xff;
wpc.config.flags = wpc.config.flags | (wps.wphdr.flags & 0xff);
wpc.config.bytes_per_sample = (int) ((wps.wphdr.flags & Defines.BYTES_STORED) + 1);
wpc.config.float_norm_exp = wps.float_norm_exp;
wpc.config.bits_per_sample = (int) ((wpc.config.bytes_per_sample * 8)
- ((wps.wphdr.flags & Defines.SHIFT_MASK) >> Defines.SHIFT_LSB));
if ((wpc.config.flags & Defines.FLOAT_DATA) > 0)
{
wpc.config.bytes_per_sample = 3;
wpc.config.bits_per_sample = 24;
}
if (wpc.config.sample_rate == 0)
{
if (wps.wphdr.block_samples == 0 || (wps.wphdr.flags & Defines.SRATE_MASK) == Defines.SRATE_MASK)
wpc.config.sample_rate = 44100;
else
wpc.config.sample_rate = sample_rates[(int) ((wps.wphdr.flags & Defines.SRATE_MASK)
>> Defines.SRATE_LSB)];
}
if (wpc.config.num_channels == 0)
{
if ((wps.wphdr.flags & Defines.MONO_FLAG) > 0)
{
wpc.config.num_channels = 1;
}
else
{
wpc.config.num_channels = 2;
}
wpc.config.channel_mask = 0x5 - wpc.config.num_channels;
}
if ((wps.wphdr.flags & Defines.FINAL_BLOCK) == 0)
{
if ((wps.wphdr.flags & Defines.MONO_FLAG) != 0)
{
wpc.reduced_channels = 1;
}
else
{
wpc.reduced_channels = 2;
}
}
return wpc;
}
static int [] temp_buffer = new int[Defines.SAMPLE_BUFFER_SIZE];
// This function obtains general information about an open file and returns
// a mask with the following bit values:
// MODE_LOSSLESS: file is lossless (pure lossless only)
// MODE_HYBRID: file is hybrid mode (lossy part only)
// MODE_FLOAT: audio data is 32-bit ieee floating point (but will provided
// in 24-bit integers for convenience)
// MODE_HIGH: file was created in "high" mode (information only)
// MODE_FAST: file was created in "fast" mode (information only)
public static int WavpackGetMode (WavpackContext wpc)
{
int mode = 0;
if (null != wpc)
{
if ( (wpc.config.flags & Defines.CONFIG_HYBRID_FLAG) != 0)
mode |= Defines.MODE_HYBRID;
else if ((wpc.config.flags & Defines.CONFIG_LOSSY_MODE)==0)
mode |= Defines.MODE_LOSSLESS;
if (wpc.lossy_blocks != 0)
mode &= ~Defines.MODE_LOSSLESS;
if ( (wpc.config.flags & Defines.CONFIG_FLOAT_DATA) != 0)
mode |= Defines.MODE_FLOAT;
if ( (wpc.config.flags & Defines.CONFIG_HIGH_FLAG) != 0)
mode |= Defines.MODE_HIGH;
if ( (wpc.config.flags & Defines.CONFIG_FAST_FLAG) != 0)
mode |= Defines.MODE_FAST;
}
return mode;
}
// Unpack the specified number of samples from the current file position.
// Note that "samples" here refers to "complete" samples, which would be
// 2 longs for stereo files. The audio data is returned right-justified in
// 32-bit longs in the endian mode native to the executing processor. So,
// if the original data was 16-bit, then the values returned would be
// +/-32k. Floating point data will be returned as 24-bit integers (and may
// also be clipped). The actual number of samples unpacked is returned,
// which should be equal to the number requested unless the end of fle is
// encountered or an error occurs.
public static long WavpackUnpackSamples(WavpackContext wpc, int [] buffer, long samples)
{
WavpackStream wps = wpc.stream;
long samples_unpacked = 0, samples_to_unpack;
int num_channels = wpc.config.num_channels;
int bcounter = 0;
int buf_idx = 0;
int bytes_returned = 0;
while (samples > 0)
{
if (wps.wphdr.block_samples == 0 || (wps.wphdr.flags & Defines.INITIAL_BLOCK) == 0
|| wps.sample_index >= wps.wphdr.block_index
+ wps.wphdr.block_samples)
{
wps.wphdr = read_next_header(wpc.infile, wps.wphdr);
if (wps.wphdr.status == 1)
break;
if (wps.wphdr.block_samples == 0 || wps.sample_index == wps.wphdr.block_index)
{
if ((UnpackUtils.unpack_init(wpc)) == Defines.FALSE)
break;
}
}
if (wps.wphdr.block_samples == 0 || (wps.wphdr.flags & Defines.INITIAL_BLOCK) == 0
|| wps.sample_index >= wps.wphdr.block_index
+ wps.wphdr.block_samples)
continue;
if (wps.sample_index < wps.wphdr.block_index)
{
samples_to_unpack = wps.wphdr.block_index - wps.sample_index;
if (samples_to_unpack > samples)
samples_to_unpack = samples;
wps.sample_index += samples_to_unpack;
samples_unpacked += samples_to_unpack;
samples -= samples_to_unpack;
if (wpc.reduced_channels > 0)
samples_to_unpack *= wpc.reduced_channels;
else
samples_to_unpack *= num_channels;
while (samples_to_unpack > 0)
{
temp_buffer[bcounter] = 0;
bcounter++;
samples_to_unpack--;
}
continue;
}
samples_to_unpack = wps.wphdr.block_index + wps.wphdr.block_samples - wps.sample_index;
if (samples_to_unpack > samples)
samples_to_unpack = samples;
UnpackUtils.unpack_samples(wpc, temp_buffer, samples_to_unpack);
if (wpc.reduced_channels > 0)
bytes_returned = (int) (samples_to_unpack * wpc.reduced_channels);
else
bytes_returned = (int) (samples_to_unpack * num_channels);
System.arraycopy(temp_buffer, 0, buffer, buf_idx, bytes_returned);
buf_idx += bytes_returned;
samples_unpacked += samples_to_unpack;
samples -= samples_to_unpack;
if (wps.sample_index == wps.wphdr.block_index + wps.wphdr.block_samples)
{
if (UnpackUtils.check_crc_error(wpc) > 0)
wpc.crc_errors++;
}
if (wps.sample_index == wpc.total_samples)
break;
}
return (samples_unpacked);
}
// Get total number of samples contained in the WavPack file, or -1 if unknown
public static long WavpackGetNumSamples(WavpackContext wpc)
{
// -1 would mean an unknown number of samples
if( null != wpc)
{
return (wpc.total_samples);
}
else
{
return (long) -1;
}
}
// Get the current sample index position, or -1 if unknown
public static long WavpackGetSampleIndex (WavpackContext wpc)
{
if (null != wpc)
return wpc.stream.sample_index;
return (long) -1;
}
// Get the number of errors encountered so far
public static long WavpackGetNumErrors(WavpackContext wpc)
{
if( null != wpc)
{
return wpc.crc_errors;
}
else
{
return (long)0;
}
}
// return if any uncorrected lossy blocks were actually written or read
public static int WavpackLossyBlocks (WavpackContext wpc)
{
if(null != wpc)
{
return wpc.lossy_blocks;
}
else
{
return 0;
}
}
// Returns the sample rate of the specified WavPack file
public static long WavpackGetSampleRate(WavpackContext wpc)
{
if ( null != wpc && wpc.config.sample_rate != 0)
{
return wpc.config.sample_rate;
}
else
{
return (long) 44100;
}
}
// Returns the number of channels of the specified WavPack file. Note that
// this is the actual number of channels contained in the file, but this
// version can only decode the first two.
public static int WavpackGetNumChannels(WavpackContext wpc)
{
if ( null != wpc && wpc.config.num_channels != 0)
{
return wpc.config.num_channels;
}
else
{
return 2;
}
}
// Returns the actual number of valid bits per sample contained in the
// original file, which may or may not be a multiple of 8. Floating data
// always has 32 bits, integers may be from 1 to 32 bits each. When this
// value is not a multiple of 8, then the "extra" bits are located in the
// LSBs of the results. That is, values are right justified when unpacked
// into longs, but are left justified in the number of bytes used by the
// original data.
public static int WavpackGetBitsPerSample(WavpackContext wpc)
{
if (null != wpc && wpc.config.bits_per_sample != 0)
{
return wpc.config.bits_per_sample;
}
else
{
return 16;
}
}
// Returns the number of bytes used for each sample (1 to 4) in the original
// file. This is required information for the user of this module because the
// audio data is returned in the LOWER bytes of the long buffer and must be
// left-shifted 8, 16, or 24 bits if normalized longs are required.
public static int WavpackGetBytesPerSample(WavpackContext wpc)
{
if ( null != wpc && wpc.config.bytes_per_sample != 0)
{
return wpc.config.bytes_per_sample;
}
else
{
return 2;
}
}
// This function will return the actual number of channels decoded from the
// file (which may or may not be less than the actual number of channels, but
// will always be 1 or 2). Normally, this will be the front left and right
// channels of a multi-channel file.
public static int WavpackGetReducedChannels(WavpackContext wpc)
{
if (null != wpc && wpc.reduced_channels != 0)
{
return wpc.reduced_channels;
}
else if (null != wpc && wpc.config.num_channels != 0)
{
return wpc.config.num_channels;
}
else
{
return 2;
}
}
static byte buffer [] = new byte[32]; // 32 is the size of a WavPack Header
static byte temp [] = new byte[32];
// Read from current file position until a valid 32-byte WavPack 4.0 header is
// found and read into the specified pointer. If no WavPack header is found within 1 meg,
// then an error is returned. No additional bytes are read past the header.
public static WavpackHeader read_next_header(java.io.DataInput infile, WavpackHeader wphdr)
{
long bytes_skipped = 0;
int bleft = 0; // bytes left in buffer
int counter = 0;
int i = 0;
while (true)
{
for (i = 0; i < bleft; i++)
{
buffer[i] = buffer[32 - bleft + i];
}
counter = 0;
try
{
infile.readFully(temp, 0, 32 - bleft);
}
catch (Exception e)
{
//e.printStackTrace();
wphdr.status = 1;
return wphdr;
}
for (i = 0; i < 32 - bleft; i++)
{
buffer[bleft + i] = temp[i];
}
bleft = 32;
if (buffer[0] == 'w' && buffer[1] == 'v' && buffer[2] == 'p' && buffer[3] == 'k'
&& (buffer[4] & 1) == 0 && buffer[6] < 16 && buffer[7] == 0 && buffer[9] == 4
&& buffer[8] >= (Defines.MIN_STREAM_VERS & 0xff) && buffer[8] <= (Defines.MAX_STREAM_VERS & 0xff))
{
wphdr.ckID[0] = 'w';
wphdr.ckID[1] = 'v';
wphdr.ckID[2] = 'p';
wphdr.ckID[3] = 'k';
wphdr.ckSize = (long) ((buffer[7] & 0xFF) << 24);
wphdr.ckSize += (long) ((buffer[6] & 0xFF) << 16);
wphdr.ckSize += (long) ((buffer[5] & 0xFF) << 8);
wphdr.ckSize += (long) (buffer[4] & 0xFF);
wphdr.version = (short) (buffer[9] << 8);
wphdr.version += (short) (buffer[8]);
wphdr.track_no = buffer[10];
wphdr.index_no = buffer[11];
wphdr.total_samples = (long) ((buffer[15] & 0xFF) << 24);
wphdr.total_samples += (long) ((buffer[14] & 0xFF) << 16);
wphdr.total_samples += (long) ((buffer[13] & 0xFF) << 8);
wphdr.total_samples += (long) (buffer[12] & 0xFF);
wphdr.block_index = (long) ((buffer[19] & 0xFF) << 24);
wphdr.block_index += (long) ((buffer[18] & 0xFF) << 16);
wphdr.block_index += (long) ((buffer[17] & 0xFF) << 8);
wphdr.block_index += (long) (buffer[16]) & 0XFF;
wphdr.block_samples = (long) ((buffer[23] & 0xFF) << 24);
wphdr.block_samples += (long) ((buffer[22] & 0xFF) << 16);
wphdr.block_samples += (long) ((buffer[21] & 0xFF) << 8);
wphdr.block_samples += (long) (buffer[20] & 0XFF);
wphdr.flags = (long) ((buffer[27] & 0xFF) << 24);
wphdr.flags += (long) ((buffer[26] & 0xFF) << 16);
wphdr.flags += (long) ((buffer[25] & 0xFF) << 8);
wphdr.flags += (long) (buffer[24] & 0xFF);
wphdr.crc = (long) ((buffer[31] & 0xFF) << 24);
wphdr.crc += (long) ((buffer[30] & 0xFF) << 16);
wphdr.crc += (long) ((buffer[29] & 0xFF) << 8);
wphdr.crc += (long) (buffer[28] & 0xFF);
wphdr.status = 0;
return wphdr;
}
else
{
counter++;
bleft--;
}
while (bleft > 0 && buffer[counter] != 'w')
{
counter++;
bleft--;
}
bytes_skipped = bytes_skipped + counter;
if (bytes_skipped > 1048576L)
{
wphdr.status = 1;
return wphdr;
}
}
}
// Find the WavPack block that contains the specified sample. If "header_pos"
// is zero, then no information is assumed except the total number of samples
// in the file and its size in bytes. If "header_pos" is non-zero then we
// assume that it is the file position of the valid header image contained in
// the first stream and we can limit our search to either the portion above
// or below that point. If a .wvc file is being used, then this must be called
// for that file also.
public static void seek(WavpackContext wpc, long totalSize, long headerPos, long targetSample) throws IOException {
try {
WavpackStream wps = wpc.stream;
long file_pos1 = 0, file_pos2 = totalSize;
long sample_pos1 = 0, sample_pos2 = wpc.total_samples;
double ratio = 0.96;
int file_skip = 0;
if (targetSample >= wpc.total_samples)
return;
if (headerPos > 0 && wps.wphdr.block_samples > 0) {
if (wps.wphdr.block_index > targetSample) {
sample_pos2 = wps.wphdr.block_index;
file_pos2 = headerPos;
} else if (wps.wphdr.block_index + wps.wphdr.block_samples <= targetSample) {
sample_pos1 = wps.wphdr.block_index;
file_pos1 = headerPos;
} else
return;
}
while (true) {
double bytes_per_sample;
long seek_pos;
bytes_per_sample = file_pos2 - file_pos1;
bytes_per_sample /= sample_pos2 - sample_pos1;
seek_pos = file_pos1 + (file_skip > 0 ? 32 : 0);
seek_pos += (long) (bytes_per_sample * (targetSample - sample_pos1) * ratio);
long temppos = seek(seek_pos, wpc.infile);
wps.wphdr = read_next_header(wpc.infile, wps.wphdr);
//System.err.println("seek "+seek_pos+" to "+temppos + " satus "+wps.wphdr.status); // !!
//todo check this
// if (ret != 1)
// wps.wphdr.block_index -= wpc.initial_index;
if (wps.wphdr.status == 1 || seek_pos >= file_pos2) {
if (ratio > 0.0) {
if ((ratio -= 0.24) < 0.0)
ratio = 0.0;
} else
return;
} else if (wps.wphdr.block_index > targetSample) {
sample_pos2 = wps.wphdr.block_index;
file_pos2 = seek_pos;
} else if (wps.wphdr.block_index + wps.wphdr.block_samples <= targetSample) {
if (seek_pos == file_pos1)
file_skip = 1;
else {
sample_pos1 = wps.wphdr.block_index;
file_pos1 = seek_pos;
}
} else {
int index = (int) (targetSample - wps.wphdr.block_index);
//System.err.println("seek "+temppos);
seek(temppos, wpc.infile);
wpc.stream = WavpackOpenFileInput(wpc.infile).stream;
int[] temp_buf = new int[Defines.SAMPLE_BUFFER_SIZE];
while (index > 0) {
int toUnpack = Math.min(index, Defines.SAMPLE_BUFFER_SIZE / WavpackGetReducedChannels(wpc));
WavpackUnpackSamples(wpc, temp_buf, toUnpack);
index -= toUnpack;
}
return;
}
}
} finally {
}
}
static long seek(long pos, Object infile) throws IOException {
if (infile instanceof RandomAccessFile) {
((RandomAccessFile) infile).seek(pos);
return ((RandomAccessFile) infile).getFilePointer();
}
try {
Object res = infile.getClass().getMethod("seek", long.class).invoke(infile, pos);
if (res != null && res instanceof Long)
return (Long)res;
return (Long) infile.getClass().getMethod("getFilePointer").invoke(infile);
//return pos;
} catch (Exception e) {
Throwable t = e;
if (e instanceof InvocationTargetException) {
t = ((InvocationTargetException) e).getTargetException();
if (t instanceof IOException)
throw (IOException) t;
}
throw new IllegalArgumentException("Seek operation isn't supported by the underline stream:" + infile, t);
}
}
}