// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.eavadeaux.net/>
//
// Klipspringer is free software: you can redistribute it and/or modify it under the terms of the
// GNU General Public License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// Klipspringer 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
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Klipspringer. If
// not, see <https://www.gnu.org/licenses/>.
package net.avadeaux.klipspringer.codec;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.Executor;
import javax.sound.sampled.*;
/** Audio stream that sends MP3 encoded data to a channel. Also doubles as file encoder class when
* created via {@link #fileEncoder(String, PcmFormat, int, int) fileEncoder}.
*/
public class LameStream extends ExternalEncodedStream {
static { Library.init(); }
/** Selects format suitable for LAME streaming with the same semantics as {@link
* PcmFormat.Selector#selectFormat(AudioFormat)}.
*/
public static PcmFormat selectFormat(AudioFormat fmt) throws UnsupportedFormatException {
int channels = fmt.getChannels();
if (fmt.getChannels() > 2) {
throw new UnsupportedFormatException("More than two channels not supported: "+fmt);
}
AudioFormat.Encoding enc = fmt.getEncoding();
boolean signed = enc == AudioFormat.Encoding.PCM_SIGNED;
int fs = fmt.getFrameSize();
int bips = fmt.getSampleSizeInBits();
int sbips = fmt instanceof PcmFormat ? ((PcmFormat) fmt).significantBips() : fmt.getSampleSizeInBits();
int rfs = (bips+15)/16*2*channels;
ByteOrder order = fmt.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
ByteOrder rorder = ByteOrder.nativeOrder();
if (signed && order == rorder && fs == rfs) { return PcmFormat.of(fmt); }
if (!signed && enc != AudioFormat.Encoding.PCM_UNSIGNED) {
throw new UnsupportedFormatException("Non-PCM format not supported:"+fmt);
}
return new PcmFormat((int) fmt.getSampleRate(), bips, channels, true, rfs, rorder, sbips);
}
/** The bit layout for PCM data sent to an MP3 stream. */
public final static Device.BitLayout BIT_LAYOUT = Device.BitLayout.MSB;
private final int bitrate;
// Private constructor with file name parameter. The file name is non-null only when the object
// is not to be used as a stream, but to encode a file.
private LameStream(String fileName,
PcmFormat format,
int bitrate,
int quality,
int bufferFrames,
double writeaheadSecs,
double timeoutSecs,
WritableByteChannel channel,
Executor keepAlivePool,
double keepAliveTimeoutSecs)
throws IOException
{
super(channel, format, bufferFrames, writeaheadSecs, timeoutSecs, 0, new EncoderRecordFactory() {
public long create(Receiver receiver, PcmFormat format, int bufferFrames) throws IOException {
if (!Library.lameSupported()) {
throw new UnsupportedOperationException("Klipspringer library lacks LAME support");
}
if (format.channels() > 2) {
throw new IOException("At most two channels supported, requested "+format.channels());
}
byte[] fnamUtf8 = null;
if (fileName != null) {
fnamUtf8 = fileName.getBytes(StandardCharsets.UTF_8);
fnamUtf8 = Arrays.copyOf(fnamUtf8, fnamUtf8.length+1);
}
return LameStream.create(fnamUtf8, format.rate(), format.bips(), format.channels(), format.ss(), format.signed(), format.bigend(), bitrate, quality, bufferFrames, receiver);
}
}, keepAlivePool, keepAliveTimeoutSecs);
this.bitrate = bitrate;
}
/** Creates a FLAC stream with the given specifications and destination channel.
*
* <p>If <code>keepAliveExec</code> is non-null, it is used by {@link #close()} to execute a wait
* loop that terminates when either the recipient has sent callback notification of having
* played (almost) all the data it has been sent, or no callback is made for
* <code>keepAliveTimeoutSecs</code> seconds. (See also {@link #drain()}.)
*/
public LameStream(PcmFormat format,
int bitrate,
int quality,
int bufferFrames,
double writeaheadSecs,
double timeoutSecs,
WritableByteChannel channel,
Executor keepAlivePool,
double keepAliveTimeoutSecs)
throws IOException
{
this(null, format, bitrate, quality, bufferFrames, writeaheadSecs, timeoutSecs, channel, keepAlivePool, keepAliveTimeoutSecs);
}
public Device.BitLayout layout() { return BIT_LAYOUT; }
/** Gets the bitrate specified for this stream. */
public String quality() { return bitrate+"\u00a0kbit/s"; }
/** Gets a player that encodes to a file rather than a stream. */
public static PcmWriter fileEncoder(String fileName, PcmFormat dataFormat, int bitrate, int quality) throws IOException {
PcmFormat bufFormat = selectFormat(dataFormat);
return new LameStream(fileName, bufFormat, bitrate, quality, 2048, 0, 0, null, null, 0)
.fileEncoder(dataFormat, BIT_LAYOUT);
}
// Construction method.
private native static long create(byte[] fnam, // null unless called from fileEncoder
int rate,
int bips,
int channels,
int ss,
boolean signed,
boolean bigend,
int brate,
int quality,
int bufferFrames,
Receiver receiver)
throws IOException;
// Native methods invoked by superclass.
native void write(long handle, ByteBuffer data, int pos, int frames);
native void finish(long handle) throws IOException;
native void free(long handle);
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home