/* * Lame.java */ /* * Copyright (c) 2000,2001 by Florian Bomers <florian@bome.com> * * * This program 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 program 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 program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ package org.tritonus.lowlevel.lame; import java.io.IOException; import java.io.UnsupportedEncodingException; import javax.sound.sampled.AudioFormat; import org.tritonus.share.TDebug; import org.tritonus.share.sampled.Encodings; public class Lame { // constants from lame.h private static final int MPEG_VERSION_2 = 0; // MPEG-2 private static final int MPEG_VERSION_1 = 1; // MPEG-1 private static final int MPEG_VERSION_2DOT5 = 2; // MPEG-2.5 public static final int QUALITY_LOWEST = 9; // low mean bitrate in VBR mode public static final int QUALITY_LOW = 7; public static final int QUALITY_MIDDLE = 5; public static final int QUALITY_HIGH = 2; // quality==0 not yet coded in LAME (3.83alpha) public static final int QUALITY_HIGHEST = 1; // high mean bitrate in VBR mode public static final int CHANNEL_MODE_STEREO = 0; public static final int CHANNEL_MODE_JOINT_STEREO = 1; public static final int CHANNEL_MODE_DUAL_CHANNEL = 2; public static final int CHANNEL_MODE_MONO = 3; // channel mode has no influence on mono files. public static final int CHANNEL_MODE_AUTO = -1; public static final int BITRATE_AUTO = -1; // suggested maximum buffer size for an mpeg frame private static final int DEFAULT_PCM_BUFFER_SIZE=2048*16; // frame size=576 for MPEG2 and MPEG2.5 // =576*2 for MPEG1 private static boolean libAvailable=false; private static String linkError=""; private static int DEFAULT_QUALITY = QUALITY_MIDDLE; private static int DEFAULT_BITRATE = BITRATE_AUTO; private static int DEFAULT_CHANNEL_MODE = CHANNEL_MODE_AUTO; // in VBR mode, bitrate is ignored. private static boolean DEFAULT_VBR = false; private static final int OUT_OF_MEMORY = -300; private static final int NOT_INITIALIZED = -301; private static final int LAME_ENC_NOT_FOUND = -302; private static final String PROPERTY_PREFIX = "tritonus.lame."; static { try { System.loadLibrary("lametritonus"); libAvailable=true; } catch (UnsatisfiedLinkError e) { if (TDebug.TraceAllExceptions) { TDebug.out(e); } linkError=e.getMessage(); } } /** * Holds LameConf * This field is long because on 64 bit architectures, the native * size of ints may be 64 bit. */ private long m_lNativeGlobalFlags; // these fields are set upon successful initialization to show effective values. private int effQuality; private int effBitRate; private int effVbr; private int effChMode; private int effSampleRate; private int effEncoding; private void handleNativeException(int resultCode) { close(); if (resultCode==OUT_OF_MEMORY) { throw new OutOfMemoryError("out of memory"); } else if (resultCode==NOT_INITIALIZED) { throw new RuntimeException("not initialized"); } else if (resultCode==LAME_ENC_NOT_FOUND) { libAvailable=false; linkError="lame_enc.dll not found"; throw new IllegalArgumentException(linkError); } } /** * Initializes the decoder with * DEFAULT_BITRATE, DEFAULT_CHANNEL_MODE, DEFAULT_QUALITY, and DEFAULT_VBR * Throws IllegalArgumentException when parameters are not supported * by LAME. */ public Lame(AudioFormat sourceFormat) { initParams(sourceFormat); } /** * Initializes the decoder. * Throws IllegalArgumentException when parameters are not supported * by LAME. */ public Lame(AudioFormat sourceFormat, int bitRate, int channelMode, int quality, boolean VBR) { initParams(sourceFormat, bitRate, channelMode, quality, VBR); } private void initParams(AudioFormat sourceFormat) { readParameters(); initParams(sourceFormat, DEFAULT_BITRATE, DEFAULT_CHANNEL_MODE, DEFAULT_QUALITY, DEFAULT_VBR); } private void initParams(AudioFormat sourceFormat, int bitRate, int channelMode, int quality, boolean VBR) { // simple check that bitrate is not too high for MPEG2 and MPEG2.5 // todo: exception ? if (sourceFormat.getSampleRate()<32000 && bitRate>160) { bitRate=160; } if (TDebug.TraceAudioConverter) { TDebug.out("LAME parameters: channels="+sourceFormat.getChannels() +" sample rate="+((int) Math.round(sourceFormat.getSampleRate())+"Hz") +" bitrate="+bitRate+"KBit/s"); TDebug.out(" channelMode="+chmode2string(channelMode) +" quality="+quality2string(quality) +" VBR="+VBR+" bigEndian="+sourceFormat.isBigEndian()); } int result=nInitParams(sourceFormat.getChannels(), (int) Math.round(sourceFormat.getSampleRate()), bitRate, channelMode, quality, VBR, sourceFormat.isBigEndian()); if (result<0) { handleNativeException(result); throw new IllegalArgumentException( "parameters not supported by LAME (returned "+result+")"); } // provide effective parameters to user-space try { System.setProperty(PROPERTY_PREFIX + "effective.quality", quality2string(getEffectiveQuality())); System.setProperty(PROPERTY_PREFIX + "effective.bitrate", String.valueOf(getEffectiveBitRate())); System.setProperty(PROPERTY_PREFIX + "effective.chmode", chmode2string(getEffectiveChannelMode())); System.setProperty(PROPERTY_PREFIX + "effective.vbr", String.valueOf(getEffectiveVBR())); System.setProperty(PROPERTY_PREFIX + "effective.samplerate", String.valueOf(getEffectiveSampleRate())); System.setProperty(PROPERTY_PREFIX + "effective.encoding", getEffectiveEncoding().toString()); System.setProperty(PROPERTY_PREFIX + "encoder.version", getEncoderVersion()); } catch (Throwable t) { if (TDebug.TraceAllExceptions) { TDebug.out(t); } } } /** * Initializes the lame encoder. * Throws IllegalArgumentException when parameters are not supported * by LAME. */ private native int nInitParams(int channels, int sampleRate, int bitrate, int mode, int quality, boolean VBR, boolean bigEndian); /** * returns -1 if string is too short * or returns one of the exception constants * if everything OK, returns the length of the string */ private native int nGetEncoderVersion(byte[] string); public String getEncoderVersion() { byte[] string=new byte[300]; int res=nGetEncoderVersion(string); if (res<0) { if (res==-1) { throw new RuntimeException("Unexpected error in Lame.getEncoderVersion()"); } handleNativeException(res); } String sRes=""; if (res>0) { try { sRes=new String(string, 0, res, "ISO-8859-1"); } catch (UnsupportedEncodingException uee) { if (TDebug.TraceAllExceptions) { TDebug.out(uee); } sRes=new String(string, 0, res); } } return sRes; } private native int nGetPCMBufferSize(int suggested); /** * Returns the buffer needed pcm buffer size. * The passed parameter is a wished buffer size. * The implementation of the encoder may return * a lower or higher buffer size. * The encoder must be initalized (i.e. not closed) at this point. * A return value of <0 denotes an error. */ public int getPCMBufferSize() { int ret=nGetPCMBufferSize(DEFAULT_PCM_BUFFER_SIZE); if (ret<0) { handleNativeException(ret); throw new RuntimeException("Unknown error in Lame.nGetPCMBufferSize(). Resultcode="+ret); } return ret; } public int getMP3BufferSize() { // bad estimate :) return getPCMBufferSize()/2+1024; } private native int nEncodeBuffer(byte[] pcm, int offset, int length, byte[] encoded); /** * Encode a block of data. Throws IllegalArgumentException when parameters * are wrong. * When the <code>encoded</code> array is too small, * an ArrayIndexOutOfBoundsException is thrown. * <code>length</code> should be the value returned by getPCMBufferSize. * @return the number of bytes written to <code>encoded</code>. May be 0. */ public int encodeBuffer(byte[] pcm, int offset, int length, byte[] encoded) throws ArrayIndexOutOfBoundsException { if (length<0 || (offset+length)>pcm.length) { throw new IllegalArgumentException("inconsistent parameters"); } int result=nEncodeBuffer(pcm, offset, length, encoded); if (result<0) { if (result==-1) { throw new ArrayIndexOutOfBoundsException("Encode buffer too small"); } handleNativeException(result); throw new RuntimeException("crucial error in encodeBuffer."); } return result; } /** * Has to be called to finish encoding. <code>encoded</code> may be null. * * @return the number of bytes written to <code>encoded</code> */ private native int nEncodeFinish(byte[] encoded); public int encodeFinish(byte[] encoded) { return nEncodeFinish(encoded); } /* * Deallocates resources used by the native library. * *MUST* be called ! */ private native void nClose(); public void close() { nClose(); } /* * Returns whether the libraries are installed correctly. */ public static boolean isLibAvailable() { return libAvailable; } public static String getLinkError() { return linkError; } public int getEffectiveQuality() { if (effQuality>=QUALITY_LOWEST) { return QUALITY_LOWEST; } else if (effQuality>=QUALITY_LOW) { return QUALITY_LOW; } else if (effQuality>=QUALITY_MIDDLE) { return QUALITY_MIDDLE; } else if (effQuality>=QUALITY_HIGH) { return QUALITY_HIGH; } return QUALITY_HIGHEST; } public int getEffectiveBitRate() { return effBitRate; } public int getEffectiveChannelMode() { return effChMode; } public boolean getEffectiveVBR() { return effVbr!=0; } public int getEffectiveSampleRate() { return effSampleRate; } public AudioFormat.Encoding getEffectiveEncoding() { if (effEncoding==MPEG_VERSION_2) { if (getEffectiveSampleRate()<16000) { return Encodings.getEncoding("MPEG2DOT5L3"); } return Encodings.getEncoding("MPEG2L3"); } else if (effEncoding==MPEG_VERSION_2DOT5) { return Encodings.getEncoding("MPEG2DOT5L3"); } return Encodings.getEncoding("MPEG1L3"); } /** * workaround for missing paramtrization possibilities * for FormatConversionProviders */ private void readParameters() { String v=getStringProperty("quality", quality2string(DEFAULT_QUALITY)); DEFAULT_QUALITY=string2quality(v.toLowerCase(), DEFAULT_QUALITY); DEFAULT_BITRATE=getIntProperty("bitrate", DEFAULT_BITRATE); v=getStringProperty("chmode", chmode2string(DEFAULT_CHANNEL_MODE)); DEFAULT_CHANNEL_MODE=string2chmode(v.toLowerCase(), DEFAULT_CHANNEL_MODE); DEFAULT_VBR = getBooleanProperty("vbr", DEFAULT_VBR); // set the parameters back so that user program can verify them try { System.setProperty(PROPERTY_PREFIX + "quality", quality2string(DEFAULT_QUALITY)); System.setProperty(PROPERTY_PREFIX + "bitrate", String.valueOf(DEFAULT_BITRATE)); System.setProperty(PROPERTY_PREFIX + "chmode", chmode2string(DEFAULT_CHANNEL_MODE)); System.setProperty(PROPERTY_PREFIX + "vbr", String.valueOf(DEFAULT_VBR)); } catch (Throwable t) { if (TDebug.TraceAllExceptions) { TDebug.out(t); } } } private String quality2string(int quality) { if (quality>=QUALITY_LOWEST) { return "lowest"; } else if (quality>=QUALITY_LOW) { return "low"; } else if (quality>=QUALITY_MIDDLE) { return "middle"; } else if (quality>=QUALITY_HIGH) { return "high"; } return "highest"; } private int string2quality(String quality, int def) { if (quality.equals("lowest")) { return QUALITY_LOWEST; } else if (quality.equals("low")) { return QUALITY_LOW; } else if (quality.equals("middle")) { return QUALITY_MIDDLE; } else if (quality.equals("high")) { return QUALITY_HIGH; } else if (quality.equals("highest")) { return QUALITY_HIGHEST; } return def; } private String chmode2string(int chmode) { if (chmode==CHANNEL_MODE_STEREO) { return "stereo"; } else if (chmode==CHANNEL_MODE_JOINT_STEREO) { return "jointstereo"; } else if (chmode==CHANNEL_MODE_DUAL_CHANNEL) { return "dual"; } else if (chmode==CHANNEL_MODE_MONO) { return "mono"; } else if (chmode==CHANNEL_MODE_AUTO) { return "auto"; } return "auto"; } private int string2chmode(String chmode, int def) { if (chmode.equals("stereo")) { return CHANNEL_MODE_STEREO; } else if (chmode.equals("jointstereo")) { return CHANNEL_MODE_JOINT_STEREO; } else if (chmode.equals("dual")) { return CHANNEL_MODE_DUAL_CHANNEL; } else if (chmode.equals("mono")) { return CHANNEL_MODE_MONO; } else if (chmode.equals("auto")) { return CHANNEL_MODE_AUTO; } return def; } private static boolean getBooleanProperty(String strName, boolean def) { String strPropertyName = PROPERTY_PREFIX + strName; String strValue = def ? "true":"false"; try { strValue = System.getProperty(strPropertyName, strValue); } catch (Throwable t) { if (TDebug.TraceAllExceptions) { TDebug.out(t); } } strValue=strValue.toLowerCase(); boolean bValue=false; if (strValue.length()>0) { if (def) { bValue=(strValue.charAt(0)!='f') // false && (strValue.charAt(0)!='n') // no && (!strValue.equals("off")); } else { bValue=(strValue.charAt(0)=='t') // true || (strValue.charAt(0)=='y') // yes || (strValue.equals("on")); } } return bValue; } private static String getStringProperty(String strName, String def) { String strPropertyName = PROPERTY_PREFIX + strName; String strValue = def; try { strValue = System.getProperty(strPropertyName, def); } catch (Throwable t) { if (TDebug.TraceAllExceptions) { TDebug.out(t); } } return strValue; } private static int getIntProperty(String strName, int def) { String strPropertyName = PROPERTY_PREFIX + strName; int value = def; try { String strValue = System.getProperty(strPropertyName, String.valueOf(def)); value=new Integer(strValue).intValue(); } catch (Throwable e) { if (TDebug.TraceAllExceptions) { TDebug.out(e); } } return value; } } /*** Lame.java ***/