// 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.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import javax.sound.sampled.*;
import java.util.concurrent.ConcurrentLinkedQueue;
/** Utility methods for facilitating reading and writing when the reader/writer requires one
* specific buffer type and the data is requested/provided in another.
*/
public class ByteTransfer {
private static final int INTERMEDIARY_BUFFER_SIZE = 8192;
/** Interface for reading data to byte array. */
public interface ArrayReader {
/** Reads up to the specified length of bytes from the input into the given byte
* array. Returns the number of bytes actually read, or {@code -1} if the input has reached
* its end.
*/
int read(byte[] dest, int off, int len) throws IOException;
/** Wraps an audio data line as an array reader. */
static ArrayReader of(TargetDataLine in) {
return new ArrayReader() {
public int read(byte[] dest, int off, int len) {
return in.read(dest, off, len);
}
};
}
/** Wraps an input stream as an array reader. */
static ArrayReader of(InputStream in) {
return new ArrayReader() {
public int read(byte[] dest, int off, int len) throws IOException {
return in.read(dest, off, len);
}
};
}
}
/** Interface for writing data from byte array. */
public interface ArrayWriter {
/** Writes up to the specified length of bytes to the output from the given byte
* array. Returns the number of bytes actually written.
*/
int write(byte[] data, int off, int len) throws IOException;
/** Wraps an audio data line as an array writer. */
static ArrayWriter of(SourceDataLine out) {
return new ArrayWriter() {
public int write(byte[] data, int off, int len) {
return out.write(data, off, len);
}
};
}
/** Wraps an output stream as an array writer. */
static ArrayWriter of(OutputStream out) {
return new ArrayWriter() {
public int write(byte[] data, int off, int len) throws IOException {
out.write(data, off, len);
return len;
}
};
}
}
private static ConcurrentLinkedQueue<ByteBuffer> directBuffers = new ConcurrentLinkedQueue<ByteBuffer>();
private static ConcurrentLinkedQueue<ByteBuffer> nondirectBuffers = new ConcurrentLinkedQueue<ByteBuffer>();
private static ByteBuffer getDirect(ByteOrder order) {
ByteBuffer buf = directBuffers.poll();
if (buf == null) { buf = ByteBuffer.allocateDirect(INTERMEDIARY_BUFFER_SIZE); }
buf.clear();
buf.order(order);
return buf;
}
private static ByteBuffer getNondirect(ByteOrder order) {
ByteBuffer buf = nondirectBuffers.poll();
if (buf == null) { buf = ByteBuffer.allocate(INTERMEDIARY_BUFFER_SIZE); }
buf.clear();
buf.order(order);
return buf;
}
/** Reads into the destination PCM byte buffer via an intermediary direct byte buffer. Has the
* effect that {@link PcmReader#read(ByteBuffer) in.read(dest)} would if the buffer was direct.
*/
public static boolean readDirect(ByteBuffer dest, PcmReader in, int fs) throws IOException {
ByteBuffer buf = getDirect(dest.order());
try {
for (int p = dest.position(), q = dest.limit(); p < q; p += buf.limit()) {
int n = Math.min(buf.capacity(), q-p);
n -= n % fs;
buf.position(0);
buf.limit(n);
boolean proceed = in.read(buf);
buf.flip();
dest.put(buf);
if (!proceed) { return false; }
}
return true;
} finally {
directBuffers.add(buf);
}
}
/** Writes PCM data from a byte buffer via an intermediary direct byte buffer. Has the effect
* that {@link PcmWriter#write(ByteBuffer) out.write(data)} would if the buffer was direct.
*/
public static boolean writeDirect(ByteBuffer data, PcmWriter out, int fs) throws IOException {
ByteBuffer buf = getDirect(data.order());
try {
for (int p = data.position(), q = data.limit(); p < q; p += buf.position()) {
int n = Math.min(buf.capacity(), q-p);
n -= n % fs;
buf.clear();
data.limit(p+n);
buf.put(data);
buf.flip();
if (!out.write(buf)) {
data.position(p+buf.position());
data.limit(q); // restore to original value
return false;
}
}
return true;
} finally {
directBuffers.add(buf);
}
}
/** Reads into destination buffer, if necessary via an intermediary nondirect byte buffer. Has
* the effect that {@link ArrayReader#read(byte[], int, int) in.read(dest.array(),
* dest.position(), dest.remaining()} would if the buffer had an accessible backing array.
*/
public static boolean read(ByteBuffer dest, ArrayReader in, int fs) throws IOException {
if (dest.hasArray()) {
int p = dest.position();
int n = dest.remaining();
int r = in.read(dest.array(), p, n);
dest.position(p+Math.max(r, 0));
return r == n;
} else {
ByteBuffer buf = getNondirect(dest.order());
try {
for (int p = dest.position(), q = dest.limit(), r; p < q; p += r) {
int n = Math.min(buf.capacity(), q-p);
n -= n % fs;
r = in.read(buf.array(), 0, n);
buf.position(0);
buf.limit(Math.max(r, 0));
dest.put(buf);
if (r < n) {
dest.limit(q); // restore to original value
return false;
}
}
return true;
} finally {
nondirectBuffers.add(buf);
}
}
}
/** Writes data from a buffer, if necessary via an intermediary nondirect byte buffer. Has the
* effect that {@link ArrayWriter#write(byte[], int, int) out.write(data.array(),
* data.position(), data.remaining()} would if the buffer had an accessible backing array.
*/
public static boolean write(ByteBuffer data, ArrayWriter out, int fs) throws IOException {
if (data.hasArray()) {
int p = data.position();
int n = data.remaining();
int w = out.write(data.array(), p, n);
data.position(p+w);
return w == n;
} else {
ByteBuffer buf = getNondirect(data.order());
try {
int q = data.limit();
for (int p = data.position(), w; p < q; p += w) {
int n = Math.min(buf.capacity(), q-p);
n -= n % fs;
buf.clear();
data.limit(p+n);
buf.put(data);
w = out.write(buf.array(), 0, n);
if (w < n) {
data.position(p+w);
data.limit(q); // restore to original value
return false;
}
}
data.position(q);
return true;
} finally {
nondirectBuffers.add(buf);
}
}
}
// No objects of this class allowed.
private ByteTransfer() { }
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home