package com.wavpack.decoder; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Arrays; /* ** WavPackUtils.java ** ** Copyright (c) 2007 - 2008 Peter McQuillan ** ** All Rights Reserved. ** ** Distributed under the BSD Software License (see license.txt) ** */ 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(RandomAccessFile 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; } // 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) 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) { int[] temp_buffer = wpc.temp_buffer; WavpackStream wps = wpc.stream; long samples_unpacked = 0, samples_to_unpack; int num_channels = wpc.config.num_channels; int bcounter = 0; Arrays.fill(temp_buffer, 0); int buf_idx = 0; int bytes_returned; 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 static long WavpackGetSampleIndex(WavpackContext wpc) { if (null != wpc) return wpc.stream.sample_index; return (long) -1; } // Get the number of errors encountered so far 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 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. 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. 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; } } public static void setTime(WavpackContext wpc, double milliseconds) { long targetSample = (long) (milliseconds / 1000 * wpc.config.sample_rate); try { seek(wpc, wpc.infile, wpc.infile.getFilePointer(), targetSample); } catch (IOException e) { e.printStackTrace(); } } public static void setSample(WavpackContext wpc, long sample) { seek(wpc, wpc.infile, 0, sample); } // 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. private static void seek(WavpackContext wpc, RandomAccessFile infile, long headerPos, long targetSample) { try { WavpackStream wps = wpc.stream; long file_pos1 = 0, file_pos2 = wpc.infile.length(); 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); infile.seek(seek_pos); long temppos = infile.getFilePointer(); wps.wphdr = read_next_header(infile, wps.wphdr); //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); infile.seek(temppos); WavpackContext c = WavpackOpenFileInput(infile); wpc.stream = c.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; } } } catch (IOException e) { e.printStackTrace(); } } // 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. static WavpackHeader read_next_header(RandomAccessFile infile, WavpackHeader wphdr) { byte buffer[] = new byte[32]; // 32 is the size of a WavPack Header byte temp[] = new byte[32]; long bytes_skipped = 0; int bleft = 0; // bytes left in buffer int counter; int i; while (true) { for (i = 0; i < bleft; i++) { buffer[i] = buffer[32 - bleft + i]; } counter = 0; try { if (infile.read(temp, 0, 32 - bleft) != (int) 32 - bleft) { wphdr.status = 1; return wphdr; } } catch (Exception e) { 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; } } } }