org.tritonus.lowlevel.lame.Lame Maven / Gradle / Ivy
The newest version!
/*
* Lame.java
*
* This file is part of Tritonus: http://www.tritonus.org/
*/
/*
* Copyright (c) 2000,2001,2007 by Florian Bomers
*
*
* 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.
*
*/
/*
|<--- this code is formatted to fit into 80 columns --->|
*/
package org.tritonus.lowlevel.lame;
import java.io.UnsupportedEncodingException;
import javax.sound.sampled.AudioFormat;
import static javax.sound.sampled.AudioSystem.NOT_SPECIFIED;
import org.tritonus.share.TDebug;
import java.util.*;
// TODO: fill frame rate, frame size
/**
* Low level wrapper for the LAME native encoder.
*
* @author Florian Bomers
*/
public class Lame {
public static final AudioFormat.Encoding MPEG1L3 = new AudioFormat.Encoding(
"MPEG1L3");
// Lame converts automagically to MPEG2 or MPEG2.5, if necessary.
public static final AudioFormat.Encoding MPEG2L3 = new AudioFormat.Encoding(
"MPEG2L3");
public static final AudioFormat.Encoding MPEG2DOT5L3 = new AudioFormat.Encoding(
"MPEG2DOT5L3");
// property constants
/**
* legacy: system property key to read the effective encoding of the encoded
* audio data, an instance of AudioFormat.Encoding
*/
public static final String P_ENCODING = "encoding";
/**
* legacy: system property key to read the effective sample rate of the
* encoded audio stream (an instance of Float)
*/
public static final String P_SAMPLERATE = "samplerate";
/**
* property key to read/set the VBR mode: an instance of Boolean (default:
* false)
*/
public static final String P_VBR = "vbr";
/**
* property key to read/set the channel mode: a String, one of
* "jointstereo", "dual", "mono",
* "auto" (default).
*/
public static final String P_CHMODE = "chmode";
/**
* property key to read/set the bitrate: an Integer value. Set to -1 for
* default bitrate.
*/
public static final String P_BITRATE = "bitrate";
/**
* property key to read/set the quality: an Integer from 1 (highest) to 9
* (lowest).
*/
public static final String P_QUALITY = "quality";
// constants from lame.h
public static final int MPEG_VERSION_2 = 0; // MPEG-2
public static final int MPEG_VERSION_1 = 1; // MPEG-1
public static final int MPEG_VERSION_2DOT5 = 2; // MPEG-2.5
// low mean bitrate in VBR mode
public static final int QUALITY_LOWEST = 9;
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)
// high mean bitrate in VBR // mode
public static final int QUALITY_HIGHEST = 1;
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.
*/
// used from native
@SuppressWarnings("unused")
private long m_lNativeGlobalFlags;
// encoding values
private int quality = DEFAULT_QUALITY;
private int bitRate = DEFAULT_BITRATE;
private boolean vbr = DEFAULT_VBR;
private int chMode = DEFAULT_CHANNEL_MODE;
// 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;
/**
* this flag is set if the user set the encoding properties by way of
* system.properties
*/
private boolean hadSystemProps = false;
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 encoder with the given source/PCM format. The default mp3
* encoding parameters are used, see DEFAULT_BITRATE, DEFAULT_CHANNEL_MODE,
* DEFAULT_QUALITY, and DEFAULT_VBR.
*
* @throws IllegalArgumentException when parameters are not supported by
* LAME.
*/
public Lame(AudioFormat sourceFormat) {
readParams(sourceFormat, null);
initParams(sourceFormat);
}
/**
* Initializes the encoder with the given source/PCM format. The mp3
* parameters are read from the targetFormat's properties. For any parameter
* that is not set, global system properties are queried for backwards
* tritonus compatibility. Last, parameters will use the default values
* DEFAULT_BITRATE, DEFAULT_CHANNEL_MODE, DEFAULT_QUALITY, and DEFAULT_VBR.
*
* @throws IllegalArgumentException when parameters are not supported by
* LAME.
*/
public Lame(AudioFormat sourceFormat, AudioFormat targetFormat) {
readParams(sourceFormat, targetFormat.properties());
initParams(sourceFormat);
}
/**
* Initializes the encoder, overriding any parameters set in the audio
* format's properties or in the system properties.
*
* @throws IllegalArgumentException when parameters are not supported by
* LAME.
*/
public Lame(AudioFormat sourceFormat, int bitRate, int channelMode,
int quality, boolean VBR) {
this.bitRate = bitRate;
this.chMode = channelMode;
this.quality = quality;
this.vbr = VBR;
initParams(sourceFormat);
}
private void readParams(AudioFormat sourceFormat, Map props) {
if (props == null || props.size() == 0) {
// legacy support for system properties
readSystemProps();
}
if (props != null) {
readProps(props);
}
}
private void initParams(AudioFormat sourceFormat) {
// 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) {
String br = bitRate < 0 ? "auto" : (String.valueOf(bitRate)
+ "KBit/s");
TDebug.out("LAME parameters: channels="
+ sourceFormat.getChannels() + " sample rate="
+ (Math.round(sourceFormat.getSampleRate()) + "Hz")
+ " bitrate=" + br);
TDebug.out(" channelMode=" + chmode2string(chMode)
+ " quality=" + quality + " (" + quality2string(quality)
+ ") VBR=" + vbr + " bigEndian="
+ sourceFormat.isBigEndian());
}
int result = nInitParams(sourceFormat.getChannels(),
Math.round(sourceFormat.getSampleRate()), bitRate, chMode,
quality, vbr, sourceFormat.isBigEndian());
if (result < 0) {
handleNativeException(result);
throw new IllegalArgumentException(
"parameters not supported by LAME (returned " + result
+ ")");
}
if (TDebug.TraceAudioConverter) {
TDebug.out("LAME effective quality=" + effQuality + " ("
+ quality2string(effQuality) + ")");
}
// legacy provide effective parameters to user by way of system
// properties
if (hadSystemProps) {
setEffectiveParamsToSystemProps();
}
}
/**
* 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 encoded
array is too small, an
* ArrayIndexOutOfBoundsException is thrown. length
should be
* the value returned by getPCMBufferSize.
*
* @return the number of bytes written to encoded
. 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. encoded
may be null.
*
* @return the number of bytes written to encoded
*/
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;
}
// properties
private void readProps(Map props) {
Object q = props.get(P_QUALITY);
if (q instanceof String) {
quality = string2quality(((String) q).toLowerCase(), quality);
} else if (q instanceof Integer) {
quality = (Integer) q;
} else if (q != null) {
throw new IllegalArgumentException(
"illegal type of quality property: " + q);
}
q = props.get(P_BITRATE);
if (q instanceof String) {
bitRate = Integer.parseInt((String) q);
} else if (q instanceof Integer) {
bitRate = (Integer) q;
} else if (q != null) {
throw new IllegalArgumentException(
"illegal type of bitrate property: " + q);
}
q = props.get(P_CHMODE);
if (q instanceof String) {
chMode = string2chmode(((String) q).toLowerCase(), chMode);
} else if (q != null) {
throw new IllegalArgumentException(
"illegal type of chmode property: " + q);
}
q = props.get(P_VBR);
if (q instanceof String) {
vbr = string2bool(((String) q));
} else if (q instanceof Boolean) {
vbr = (Boolean) q;
} else if (q != null) {
throw new IllegalArgumentException("illegal type of vbr property: "
+ q);
}
}
/**
* Return the audioformat representing the encoded mp3 stream. The format
* object will have the following properties:
*
* - quality: an Integer, 1 (highest) to 9 (lowest)
*
- bitrate: an Integer, 32...320 kbit/s
*
- chmode: channel mode, a String, one of "jointstereo",
* "dual", "mono", "auto" (default).
*
- vbr: a Boolean
*
- encoder.version: a string with the version of the encoder
*
- encoder.name: a string with the name of the encoder
*
*/
public AudioFormat getEffectiveFormat() {
// first gather properties
HashMap map = new HashMap();
map.put(P_QUALITY, getEffectiveQuality());
map.put(P_BITRATE, getEffectiveBitRate());
map.put(P_CHMODE, chmode2string(getEffectiveChannelMode()));
map.put(P_VBR, getEffectiveVBR());
// map.put(P_SAMPLERATE, getEffectiveSampleRate());
// map.put(P_ENCODING,getEffectiveEncoding());
map.put("encoder.name", "LAME");
map.put("encoder.version", getEncoderVersion());
int channels = 2;
if (chMode == CHANNEL_MODE_MONO) {
channels = 1;
}
return new AudioFormat(getEffectiveEncoding(),
getEffectiveSampleRate(), NOT_SPECIFIED, channels,
NOT_SPECIFIED, NOT_SPECIFIED, false, map);
}
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 MPEG2DOT5L3;
}
return MPEG2L3;
} else if (effEncoding == MPEG_VERSION_2DOT5) {
return MPEG2DOT5L3;
}
// default
return MPEG1L3;
}
// LEGACY support: read/write encoding parameters from/to system.properties
/** legacy: set effective parameters in system properties */
private void setEffectiveParamsToSystemProps() {
try {
System.setProperty(PROPERTY_PREFIX + "effective" + "." + P_QUALITY,
quality2string(getEffectiveQuality()));
System.setProperty(PROPERTY_PREFIX + "effective" + "." + P_BITRATE,
String.valueOf(getEffectiveBitRate()));
System.setProperty(PROPERTY_PREFIX + "effective" + "." + P_CHMODE,
chmode2string(getEffectiveChannelMode()));
System.setProperty(PROPERTY_PREFIX + "effective" + "." + P_VBR,
String.valueOf(getEffectiveVBR()));
System.setProperty(PROPERTY_PREFIX + "effective" + "."
+ P_SAMPLERATE, String.valueOf(getEffectiveSampleRate()));
System.setProperty(
PROPERTY_PREFIX + "effective" + "." + P_ENCODING,
getEffectiveEncoding().toString());
System.setProperty(PROPERTY_PREFIX + "encoder.version",
getEncoderVersion());
} catch (Throwable t) {
if (TDebug.TraceAllExceptions) {
TDebug.out(t);
}
}
}
/**
* workaround for missing paramtrization possibilities for
* FormatConversionProviders
*/
private void readSystemProps() {
String v = getStringProperty(P_QUALITY, quality2string(quality));
quality = string2quality(v.toLowerCase(), quality);
bitRate = getIntProperty(P_BITRATE, bitRate);
v = getStringProperty(P_CHMODE, chmode2string(chMode));
chMode = string2chmode(v.toLowerCase(), chMode);
vbr = getBooleanProperty(P_VBR, vbr);
if (hadSystemProps) {
// set the parameters back so that user program can verify them
try {
System.setProperty(PROPERTY_PREFIX + P_QUALITY,
quality2string(DEFAULT_QUALITY));
System.setProperty(PROPERTY_PREFIX + P_BITRATE,
String.valueOf(DEFAULT_BITRATE));
System.setProperty(PROPERTY_PREFIX + P_CHMODE,
chmode2string(DEFAULT_CHANNEL_MODE));
System.setProperty(PROPERTY_PREFIX + P_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;
}
/**
* @return true if val is starts with t or y or on, false if val starts with
* f or n or off.
* @throws IllegalArgumentException if val is neither true nor false
*/
private static boolean string2bool(String val) {
if (val.length() > 0) {
if ((val.charAt(0) == 'f') // false
|| (val.charAt(0) == 'n') // no
|| (val.equals("off"))) {
return false;
}
if ((val.charAt(0) == 't') // true
|| (val.charAt(0) == 'y') // yes
|| (val.equals("on"))) {
return true;
}
}
throw new IllegalArgumentException(
"wrong string for boolean property: " + val);
}
private boolean getBooleanProperty(String strName, boolean def) {
String strPropertyName = PROPERTY_PREFIX + strName;
String strValue = def ? "true" : "false";
try {
String s = System.getProperty(strPropertyName);
if (s != null && s.length() > 0) {
hadSystemProps = true;
strValue = s;
}
} 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 String getStringProperty(String strName, String def) {
String strPropertyName = PROPERTY_PREFIX + strName;
String strValue = def;
try {
String s = System.getProperty(strPropertyName);
if (s != null && s.length() > 0) {
hadSystemProps = true;
strValue = s;
}
} catch (Throwable t) {
if (TDebug.TraceAllExceptions) {
TDebug.out(t);
}
}
return strValue;
}
private int getIntProperty(String strName, int def) {
String strPropertyName = PROPERTY_PREFIX + strName;
int value = def;
try {
String s = System.getProperty(strPropertyName);
if (s != null && s.length() > 0) {
hadSystemProps = true;
value = new Integer(s).intValue();
}
} catch (Throwable e) {
if (TDebug.TraceAllExceptions) {
TDebug.out(e);
}
}
return value;
}
}
/** * Lame.java ** */