// 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