/* ** WvEncode.java ** ** Copyright (c) 2008 Peter McQuillan ** ** All Rights Reserved. ** ** Distributed under the BSD Software License (see license.txt) ** */ package com.wavpack.encoder; import java.io.IOException; import java.io.RandomAccessFile; public class WvEncode { public static void main(String[] args) { // This is the main module for the demonstration WavPack command-line // encoder using the "tiny encoder". It accepts a source WAV file, a // destination WavPack file (.wv) and an optional WavPack correction file // (.wvc) on the command-line. It supports all 4 encoding qualities in // pure lossless, hybrid lossy and hybrid lossless modes. Valid input are // mono or stereo integer WAV files with bitdepths from 8 to 24. // This program (and the tiny encoder) do not handle placing the WAV RIFF // header into the WavPack file. The latest version of the regular WavPack // unpacker (4.40) and the "tiny decoder" will generate the RIFF header // automatically on unpacking. However, older versions of the command-line // program will complain about this and require unpacking in "raw" mode. ///////////////////////////// local variable storage ////////////////////////// String VERSION_STR = "4.40"; String DATE_STR = "2007-01-16"; String sign_on1 = "Java WavPack Encoder (c) 2008 Peter McQuillan"; String sign_on2 = "based on TINYPACK - Tiny Audio Compressor Version " + VERSION_STR + " " + DATE_STR + " Copyright (c) 1998 - 2007 Conifer Software. All Rights Reserved."; String usage0 = ""; String usage1 = " Usage: java WvEncode [-options] infile.wav outfile.wv [outfile.wvc]"; String usage2 = " (default is lossless)"; String usage3 = " Options: -b n = enable hybrid compression, n = 2.0 to 16.0 bits/sample - need space between b and number"; String usage4 = " -c = create correction file (.wvc) for hybrid mode (=lossless)"; String usage5 = " -cc = maximum hybrid compression (hurts lossy quality & decode speed)"; String usage6 = " -f = fast mode (fast, but some compromise in compression ratio)"; String usage7 = " -h = high quality (better compression in all modes, but slower)"; String usage8 = " -hh = very high quality (best compression in all modes, but slowest"; String usage9 = " and NOT recommended for portable hardware use)"; String usage10 = " -jn = joint-stereo override (0 = left/right, 1 = mid/side)"; String usage11 = " -sn = noise shaping override (hybrid only, n = -1.0 to 1.0, 0 = off)"; ////////////////////////////////////////////////////////////////////////////// // The "main" function for the command-line WavPack compressor. // ////////////////////////////////////////////////////////////////////////////// String infilename = ""; String outfilename = ""; String out2filename = ""; WavpackConfig config = new WavpackConfig(); int error_count = 0; int result; int i; int arg_idx = 0; // loop through command-line arguments while (arg_idx < args.length) { if (args[arg_idx].startsWith("-")) { if (args[arg_idx].startsWith("-c") || args[arg_idx].startsWith("-C")) { if (args[arg_idx].startsWith("-cc") || args[arg_idx].startsWith("-CC")) { config.flags |= Defines.CONFIG_CREATE_WVC; config.flags |= Defines.CONFIG_OPTIMIZE_WVC; } else { config.flags |= Defines.CONFIG_CREATE_WVC; } } else if (args[arg_idx].startsWith("-f") || args[arg_idx].startsWith("-F")) { config.flags |= Defines.CONFIG_FAST_FLAG; } else if (args[arg_idx].startsWith("-h") || args[arg_idx].startsWith("-H")) { if (args[arg_idx].startsWith("-hh") || args[arg_idx].startsWith("-HH")) { config.flags |= Defines.CONFIG_VERY_HIGH_FLAG; } else { config.flags |= Defines.CONFIG_HIGH_FLAG; } } else if (args[arg_idx].startsWith("-k") || args[arg_idx].startsWith("-K")) { int passedInt = 0; if (args[arg_idx].length() > 2) { try { String substring = args[arg_idx].substring(2); passedInt = Integer.parseInt(substring); } catch (java.lang.NumberFormatException ne) { } } else { arg_idx++; try { passedInt = Integer.parseInt(args[arg_idx]); } catch (java.lang.NumberFormatException ne) { } } config.block_samples = passedInt; } else if (args[arg_idx].startsWith("-b") || args[arg_idx].startsWith("-B")) { int passedInt = 0; config.flags |= Defines.CONFIG_HYBRID_FLAG; if (args[arg_idx].length() > 2) { try { String substring = args[arg_idx].substring(2); passedInt = Integer.parseInt(substring); config.bitrate = (int) (passedInt * 256.0); } catch (java.lang.NumberFormatException ne) { config.bitrate = 0; } } else { arg_idx++; try { passedInt = Integer.parseInt(args[arg_idx]); config.bitrate = (int) (passedInt * 256.0); } catch (java.lang.NumberFormatException ne) { config.bitrate = 0; } } if ((config.bitrate < 512) || (config.bitrate > 4096)) { System.err.println("hybrid spec must be 2.0 to 16.0!"); ++error_count; } } else if (args[arg_idx].startsWith("-j") || args[arg_idx].startsWith("-J")) { int passedInt = 2; if (args[arg_idx].length() > 2) // handle the case where the string is passed in form -j0 (number beside j) { try { String substring = args[arg_idx].substring(2); passedInt = Integer.parseInt(substring); } catch (java.lang.NumberFormatException ne) { } } else // handle the case where the string is passed in form -j 0 (space between number and j) { arg_idx++; try { passedInt = Integer.parseInt(args[arg_idx]); } catch (java.lang.NumberFormatException ne) { } } if (passedInt == 0) { config.flags |= Defines.CONFIG_JOINT_OVERRIDE; config.flags &= ~Defines.CONFIG_JOINT_STEREO; } else if (passedInt == 1) { config.flags |= (Defines.CONFIG_JOINT_OVERRIDE | Defines.CONFIG_JOINT_STEREO); } else { System.err.println("-j0 or -j1 only!"); ++error_count; } } else if (args[arg_idx].startsWith("-s") || args[arg_idx].startsWith("-S")) { double passedDouble = 0; // noise shaping off if (args[arg_idx].length() > 2) // handle the case where the string is passed in form -s0 (number beside s) { try { String substring = args[arg_idx].substring(2); passedDouble = Double.parseDouble(substring); } catch (java.lang.NumberFormatException ne) { } } else // handle the case where the string is passed in form -s 0 (space between number and s) { arg_idx++; try { passedDouble = Double.parseDouble(args[arg_idx]); } catch (java.lang.NumberFormatException ne) { } } config.shaping_weight = (int) (passedDouble * 1024.0); if (config.shaping_weight == 0) { config.flags |= Defines.CONFIG_SHAPE_OVERRIDE; config.flags &= ~Defines.CONFIG_HYBRID_SHAPE; } else if ((config.shaping_weight >= -1024) && (config.shaping_weight <= 1024)) { config.flags |= (Defines.CONFIG_HYBRID_SHAPE | Defines.CONFIG_SHAPE_OVERRIDE); } else { System.err.println("-s-1.00 to -s1.00 only!"); ++error_count; } } else { System.err.println("illegal option: " + args[arg_idx]); ++error_count; } } else if (infilename.length() == 0) { infilename = args[arg_idx]; } else if (outfilename.length() == 0) { outfilename = args[arg_idx]; } else if (out2filename.length() == 0) { out2filename = args[arg_idx]; } else { System.err.println("extra unknown argument: " + args[arg_idx]); ++error_count; } arg_idx++; } // check for various command-line argument problems if ((~config.flags & (Defines.CONFIG_HIGH_FLAG | Defines.CONFIG_FAST_FLAG)) == 0) { System.err.println("high and fast modes are mutually exclusive!"); ++error_count; } if ((config.flags & Defines.CONFIG_HYBRID_FLAG) != 0) { if (((config.flags & Defines.CONFIG_CREATE_WVC) != 0) && (out2filename.length() == 0)) { System.err.println("need name for correction file!"); ++error_count; } } else { if ((config.flags & (Defines.CONFIG_SHAPE_OVERRIDE | Defines.CONFIG_CREATE_WVC)) != 0) { System.err.println("-s and -c options are for hybrid mode (-b) only!"); ++error_count; } } if ((out2filename.length() != 0) && ((config.flags & Defines.CONFIG_CREATE_WVC) == 0)) { System.err.println("third filename specified without -c option!"); ++error_count; } if (error_count == 0) { System.err.println(sign_on1); System.err.println(sign_on2); } else { System.exit(1); } if ((infilename.length() == 0) || (outfilename.length() == 0) || ((out2filename.length() == 0) && ((config.flags & Defines.CONFIG_CREATE_WVC) != 0))) { System.out.println(usage0); System.out.println(usage1); System.out.println(usage2); System.out.println(usage3); System.out.println(usage4); System.out.println(usage5); System.out.println(usage6); System.out.println(usage7); System.out.println(usage8); System.out.println(usage9); System.out.println(usage10); System.out.println(usage11); System.exit(1); } result = pack_file(infilename, outfilename, out2filename, config); if (result > 0) { System.err.println("error occured!"); ++error_count; } } // This function packs a single file "infilename" and stores the result at // "outfilename". If "out2filename" is specified, then the "correction" // file would go there. The files are opened and closed in this function // and the "config" structure specifies the mode of compression. static int pack_file(String infilename, String outfilename, String out2filename, WavpackConfig config) { long total_samples; long bcount; WavpackConfig loc_config = config; byte[] riff_chunk_header = new byte[12]; RandomAccessFile wv_file; RandomAccessFile wvc_file; byte[] chunk_header = new byte[8]; byte[] WaveHeader = new byte[40]; int whBlockAlign = 1; int whFormatTag = 0; int whSubFormat = 0; int whBitsPerSample = 0; int whValidBitsPerSample = 0; int whNumChannels = 0; long whSampleRate = 0; WavpackContext wpc = new WavpackContext(); java.io.FileInputStream infile; java.io.DataInputStream in; int result; // open the source file for reading try { infile = new java.io.FileInputStream(infilename); in = new java.io.DataInputStream(infile); } catch (java.io.FileNotFoundException fe) { System.err.println("Can't open file " + infilename); return Defines.SOFT_ERROR; } // open output file for writing try { wv_file = new RandomAccessFile(outfilename, "rw"); wv_file.setLength(0); } catch (IOException fe) { System.err.println("can't create file " + outfilename); return Defines.SOFT_ERROR; } wpc.outfile = wv_file; bcount = 0; // 12 is the size of the RIFF Chunk header bcount = DoReadFile(in, riff_chunk_header, 12); if ((bcount != 12) || (riff_chunk_header[0] != 'R') || (riff_chunk_header[1] != 'I') || (riff_chunk_header[2] != 'F') || (riff_chunk_header[3] != 'F') || (riff_chunk_header[8] != 'W') || (riff_chunk_header[9] != 'A') || (riff_chunk_header[10] != 'V') || (riff_chunk_header[11] != 'E')) { System.err.println(infilename + " is not a valid .WAV file!"); try { infile.close(); wv_file.close(); } catch (Exception e) { } return Defines.SOFT_ERROR; } // loop through all elements of the RIFF wav header (until the data chuck) long chunkSize = 0; while (true) { // ChunkHeader has a size of 8 bcount = DoReadFile(in, chunk_header, 8); if (bcount != 8) { System.err.println(infilename + " is not a valid .WAV file!"); try { infile.close(); wv_file.close(); } catch (Exception e) { } return Defines.SOFT_ERROR; } chunkSize = (chunk_header[4] & 0xFF) + ((chunk_header[5] & 0xFF) << 8) + ((chunk_header[6] & 0xFF) << 16) + ((chunk_header[7] & 0xFF) << 24); // if it's the format chunk, we want to get some info out of there and // make sure it's a .wav file we can handle if ((chunk_header[0] == 'f') && (chunk_header[1] == 'm') && (chunk_header[2] == 't') && (chunk_header[3] == ' ')) { int supported = Defines.TRUE; int format; int check = 0; if ((chunkSize >= 16) && (chunkSize <= 40)) { int ckSize = (int) chunkSize; bcount = DoReadFile(in, WaveHeader, ckSize); if (bcount != 16) { check = 1; } } else { check = 1; } if (check == 1) { System.err.println(infilename + " is not a valid .WAV file!"); try { infile.close(); wv_file.close(); } catch (Exception e) { } return Defines.SOFT_ERROR; } whFormatTag = (WaveHeader[0] & 0xFF) + ((WaveHeader[1] & 0xFF) << 8); if ((whFormatTag == 0xfffe) && (chunkSize == 40)) { whSubFormat = (WaveHeader[24] & 0xFF) + ((WaveHeader[25] & 0xFF) << 8); format = whSubFormat; } else { format = whFormatTag; } whBitsPerSample = (WaveHeader[14] & 0xFF) + ((WaveHeader[15] & 0xFF) << 8); if (chunkSize == 40) { whValidBitsPerSample = (WaveHeader[18] & 0xFF) + ((WaveHeader[19] & 0xFF) << 8); loc_config.bits_per_sample = whValidBitsPerSample; } else { loc_config.bits_per_sample = whBitsPerSample; } if (format != 1) { supported = Defines.FALSE; } whBlockAlign = (WaveHeader[12] & 0xFF) + ((WaveHeader[13] & 0xFF) << 8); whNumChannels = (WaveHeader[2] & 0xFF) + ((WaveHeader[3] & 0xFF) << 8); if ((whNumChannels == 0) || (whNumChannels > 2) || ((whBlockAlign / whNumChannels) < ((loc_config.bits_per_sample + 7) / 8)) || ((whBlockAlign / whNumChannels) > 3) || ((whBlockAlign % whNumChannels) > 0)) { supported = Defines.FALSE; } if ((loc_config.bits_per_sample < 1) || (loc_config.bits_per_sample > 24)) { supported = Defines.FALSE; } whSampleRate = (WaveHeader[4] & 0xFF) + ((WaveHeader[5] & 0xFF) << 8) + ((WaveHeader[6] & 0xFF) << 16) + ((WaveHeader[7] & 0xFF) << 24); if (supported != Defines.TRUE) { System.err.println(infilename + " is an unsupported .WAV format!"); try { infile.close(); wv_file.close(); } catch (Exception e) { } return Defines.SOFT_ERROR; } } else if ((chunk_header[0] == 'd') && (chunk_header[1] == 'a') && (chunk_header[2] == 't') && (chunk_header[3] == 'a')) { // on the data chunk, get size and exit loop total_samples = chunkSize / whBlockAlign; break; } else { // just skip over unknown chunks int bytes_to_skip = (int) ((chunkSize + 1) & ~1L); byte[] buff = new byte[bytes_to_skip]; bcount = DoReadFile(in, buff, bytes_to_skip); if (bcount != bytes_to_skip) { System.err.println("error occurred in skipping bytes"); try { infile.close(); wv_file.close(); } catch (Exception e) { } //remove (outfilename); return Defines.SOFT_ERROR; } } } loc_config.bytes_per_sample = whBlockAlign / whNumChannels; loc_config.num_channels = whNumChannels; loc_config.sample_rate = whSampleRate; WavPackUtils.WavpackSetConfiguration(wpc, loc_config, total_samples); // if we are creating a "correction" file, open it now for writing if (out2filename.length() > 0) { try { wvc_file = new RandomAccessFile(out2filename, "rw"); wvc_file.setLength(0); wpc.correction_outfile = wvc_file; } catch (IOException fe) { System.err.println("can't create file " + outfilename); return Defines.SOFT_ERROR; } } // pack the audio portion of the file now result = pack_audio(wpc, in); try { infile.close(); // we're now done with input file, so close } catch (java.io.IOException e) { } // we're now done with any WavPack blocks, so flush any remaining data if ((result == Defines.NO_ERROR) && (WavPackUtils.WavpackFlushSamples(wpc) == 0)) { System.err.println(WavPackUtils.WavpackGetErrorMessage(wpc)); result = Defines.HARD_ERROR; } // At this point we're done writing to the output files. However, in some // situations we might have to back up and re-write the initial blocks. // Currently the only case is if we're ignoring length. if ((result == Defines.NO_ERROR) && (WavPackUtils.WavpackGetNumSamples(wpc) != WavPackUtils.WavpackGetSampleIndex(wpc))) { System.err.println("couldn't read all samples, file may be corrupt!!"); result = Defines.SOFT_ERROR; } // at this point we're done with the files, so close 'em whether there // were any other errors or not try { wv_file.close(); } catch (java.io.IOException e) { System.err.println("Can't close WavPack file!"); if (result == Defines.NO_ERROR) { result = Defines.SOFT_ERROR; } } // if there were any errors then return the error if (result != Defines.NO_ERROR) { return result; } return Defines.NO_ERROR; } // This function handles the actual audio data compression. It assumes that the // input file is positioned at the beginning of the audio data and that the // WavPack configuration has been set. This is where the conversion from RIFF // little-endian standard the executing processor's format is done. static int pack_audio(WavpackContext wpc, java.io.DataInputStream in) { long samples_remaining; int bytes_per_sample; WavPackUtils.WavpackPackInit(wpc); bytes_per_sample = WavPackUtils.WavpackGetBytesPerSample(wpc) * WavPackUtils.WavpackGetNumChannels(wpc); samples_remaining = WavPackUtils.WavpackGetNumSamples(wpc); byte[] input_buffer = new byte[Defines.INPUT_SAMPLES * bytes_per_sample]; long[] sample_buffer = new long[(Defines.INPUT_SAMPLES * 4 * WavPackUtils.WavpackGetNumChannels(wpc))]; int temp = 0; //while (temp < 1) while (true) { long sample_count; long bytes_read = 0; int bytes_to_read; temp = temp + 1; if (samples_remaining > Defines.INPUT_SAMPLES) { bytes_to_read = Defines.INPUT_SAMPLES * bytes_per_sample; } else { bytes_to_read = (int) (samples_remaining * bytes_per_sample); } samples_remaining -= (bytes_to_read / bytes_per_sample); bytes_read = DoReadFile(in, input_buffer, bytes_to_read); sample_count = bytes_read / bytes_per_sample; if (sample_count == 0) { break; } if (sample_count > 0) { int cnt = (int) (sample_count * WavPackUtils.WavpackGetNumChannels(wpc)); byte[] sptr = input_buffer; long[] dptr = sample_buffer; int loopBps = 0; loopBps = WavPackUtils.WavpackGetBytesPerSample(wpc); if (loopBps == 1) { int intermalCount = 0; while (cnt > 0) { dptr[intermalCount] = (sptr[intermalCount] & 0xff) - 128; intermalCount++; cnt--; } } else if (loopBps == 2) { int dcounter = 0; int scounter = 0; while (cnt > 0) { dptr[dcounter] = (sptr[scounter] & 0xff) | (sptr[scounter + 1] << 8); scounter = scounter + 2; dcounter++; cnt--; } } else if (loopBps == 3) { int dcounter = 0; int scounter = 0; while (cnt > 0) { dptr[dcounter] = (sptr[scounter] & 0xff) | ((sptr[scounter + 1] & 0xff) << 8) | (sptr[scounter + 2] << 16); scounter = scounter + 3; dcounter++; cnt--; } } } wpc.byte_idx = 0; // new WAV buffer data so reset the buffer index to zero if (WavPackUtils.WavpackPackSamples(wpc, sample_buffer, sample_count) == 0) { System.err.println(WavPackUtils.WavpackGetErrorMessage(wpc)); return Defines.HARD_ERROR; } } if (WavPackUtils.WavpackFlushSamples(wpc) == 0) { System.err.println(WavPackUtils.WavpackGetErrorMessage(wpc)); return Defines.HARD_ERROR; } return Defines.NO_ERROR; } //////////////////////////// File I/O Wrapper //////////////////////////////// static long DoReadFile(java.io.DataInputStream hFile, byte[] lpBuffer, int nNumberOfBytesToRead) { long bcount; byte[] tempBuffer = new byte[(int) (nNumberOfBytesToRead + (long) 1)]; long bufferCounter = 0; long lpNumberOfBytesRead = 0; while (nNumberOfBytesToRead > 0) { try { bcount = hFile.read(tempBuffer, 0, nNumberOfBytesToRead); } catch (Exception e) { bcount = 0; } if (bcount > 0) { for (long i = 0; i < nNumberOfBytesToRead; i++) { lpBuffer[(int) (bufferCounter + i)] = tempBuffer[(int) i]; } lpNumberOfBytesRead += bcount; nNumberOfBytesToRead -= bcount; } else { break; } } return lpNumberOfBytesRead; } }