xyz.gianlu.librespot.player.mixing.CircularBuffer Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2021 devgianlu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.gianlu.librespot.player.mixing;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import xyz.gianlu.librespot.common.Utils;
import java.io.Closeable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Gianlu
*/
public class CircularBuffer implements Closeable {
protected final Lock lock = new ReentrantLock();
protected final Condition awaitSpace = lock.newCondition();
private final byte[] data;
private final Condition awaitData = lock.newCondition();
protected volatile boolean closed = false;
private int head;
private int tail;
public CircularBuffer(int bufferSize) {
data = new byte[bufferSize + 1];
head = 0;
tail = 0;
}
private void awaitSpace(int count) throws InterruptedException {
while (free() < count && !closed)
awaitSpace.await(100, TimeUnit.MILLISECONDS);
}
protected void awaitData(int count) throws InterruptedException {
while (available() < count && !closed)
awaitData.await(100, TimeUnit.MILLISECONDS);
}
public void write(byte[] b, int off, int len) {
if (closed) return;
lock.lock();
try {
awaitSpace(len);
if (closed) return;
for (int i = off; i < off + len; i++) {
data[tail++] = b[i];
if (tail == data.length)
tail = 0;
}
awaitData.signal();
} catch (InterruptedException ignored) {
} finally {
lock.unlock();
}
}
@TestOnly
public void write(byte value) {
if (closed) return;
lock.lock();
try {
awaitSpace(1);
if (closed) return;
data[tail++] = value;
if (tail == data.length)
tail = 0;
awaitData.signal();
} catch (InterruptedException ignored) {
} finally {
lock.unlock();
}
}
public int read(byte[] b, int off, int len) {
if (closed) return -1;
lock.lock();
try {
awaitData(len);
if (closed) return -1;
int dest = off;
for (int i = 0; i < len; i += 2, dest += 2) {
b[dest] = (byte) readInternal();
b[dest + 1] = (byte) readInternal();
}
awaitSpace.signal();
return dest - off;
} catch (InterruptedException ignored) {
if (closed) return -1;
else return 0;
} finally {
lock.unlock();
}
}
protected int readInternal() {
int value = data[head++] & 0xFF;
if (head == data.length)
head = 0;
return value;
}
/**
* Reads a single byte. If data is not available at this moment in time, it blocks until a byte is available.
*
* @return a byte from the buffer.
*/
@TestOnly
public int read() {
if (closed) return -1;
lock.lock();
try {
awaitData(1);
if (closed) return -1;
int value = readInternal();
awaitSpace.signal();
return value;
} catch (InterruptedException ignored) {
return -1;
} finally {
lock.unlock();
}
}
/**
* @return The number of bytes that can be read at this moment in time without blocking.
*/
public int available() {
if (head > tail) {
return tail + (data.length - head);
} else if (head < tail) {
return tail - head;
} else {
return 0; // head and tail are the same, initial position only
}
}
/**
* @return The number of bytes that can be written at this moment in time without blocking.
*/
public int free() {
if (head > tail) {
return head - tail - 1;
} else if (head < tail) {
return (data.length - 1 - tail) + head;
} else {
return data.length - 1; // head and tail are the same, initial position only
}
}
/**
* @return Whether the buffer is full and no data can be written without blocking.
*/
public boolean full() {
return tail + 1 == head || (head == 0 && tail == data.length - 1);
}
/**
* Empties the buffer from its data, the data is not erased (will be overridden anyway).
*/
public void empty() {
lock.lock();
try {
head = 0;
tail = 0;
awaitData.signalAll();
awaitSpace.signalAll();
} finally {
lock.unlock();
}
}
@Override
public void close() {
closed = true;
lock.lock();
try {
awaitSpace.signalAll();
awaitData.signalAll();
} finally {
lock.unlock();
}
}
@TestOnly
@NotNull
public String dump() {
return "CircularBuffer {head: " + head + ", tail: " + tail + ", data: " + Utils.bytesToHex(data) + "}";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy