blob: 306bbfc09e2e0ae9403f6b08e26ae6d7eb17d23f [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 org.drrickorang.loopback;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import android.util.Log;
/**
* Non-blocking pipe where writer writes to the pipe using by knowing the address of "mByteBuffer",
* and write to this ByteBuffer directly. On the other hand, reader reads from the pipe using
* read(), which converts data in ByteBuffer into shorts.
* Data in the pipe are stored in the ByteBuffer array "mByteBuffer".
* The write side of a pipe permits overruns; flow control is the caller's responsibility.
* TODO move to audio_utils
*/
public class PipeByteBuffer extends Pipe {
private static final String TAG = "PipeByteBuffer";
private final ByteBuffer mByteBuffer;
private int mFront = 0; // reader's current position
/**
* The ByteBuffer in this class consists of two sections. The first section is the actual pipe
* to store data. This section must have a size in power of 2, and this is enforced by the
* constructor through rounding maxSamples up to the nearest power of 2. This second section
* is used to store metadata. Currently the only metadata is an integer that stores the rear,
* where rear is the writer's current position. The metadata is at the end of ByteBuffer, and is
* outside of the actual pipe.
* IMPORTANT: The code is designed (in native code) such that metadata won't be overwritten when
* the writer writes to the pipe. If changes to the code are required, please make sure the
* metadata won't be overwritten.
* IMPORTANT: Since a signed integer is used to store rear and mFront, their values should not
* exceed 2^31 - 1, or else overflows happens and the positions of read and mFront becomes
* incorrect.
*/
public PipeByteBuffer(int maxSamples) {
super(maxSamples);
int extraInt = 1; // used to store rear
int extraShort = extraInt * Constant.SHORTS_PER_INT;
int numberOfShorts = mMaxValues + extraShort;
mByteBuffer = ByteBuffer.allocateDirect(numberOfShorts * Constant.BYTES_PER_SHORT);
mByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
}
/**
* Convert data in mByteBuffer into short, and put them into "buffer".
* Note: rear and mFront are kept in terms of number of shorts instead of number of bytes.
*/
@Override
public int read(short[] buffer, int offset, int requiredSamples) {
// first, update the current rear
int rear;
synchronized (mByteBuffer) {
rear = mByteBuffer.getInt(mMaxValues * Constant.BYTES_PER_SHORT);
}
//log("initial offset: " + offset + "\n initial requiredSamples: " + requiredSamples);
// after here, rear may actually be updated further. However, we don't care. If at the point
// of checking there's enough data then we will read it. If not just wait until next call
// of read.
int avail = availableToRead(rear, mFront);
if (avail <= 0) { //return -2 for overrun
return avail;
}
// if not enough samples, just read partial samples
if (requiredSamples > avail) {
requiredSamples = avail;
}
// mask the upper bits to get the correct position in the pipe
int front = mFront & (mMaxValues - 1);
int read = mMaxValues - front; // total samples from currentIndex until the end of array
if (read > requiredSamples) {
read = requiredSamples;
}
int byteBufferFront = front * Constant.BYTES_PER_SHORT; // start reading from here
byteBufferToArray(buffer, offset, read, byteBufferFront);
if (front + read == mMaxValues) {
int samplesLeft = requiredSamples - read;
if (samplesLeft > 0) {
byteBufferFront = 0;
byteBufferToArray(buffer, offset + read, read + samplesLeft, byteBufferFront);
read += samplesLeft;
}
}
mFront += read;
return read;
}
/**
* Copy mByteBuffer's data (starting from "byteBufferFront") into double array "buffer".
* "start" is the starting index of "buffer" and "length" is the amount of samples copying.
*/
private void byteBufferToArray(short[] buffer, int start, int length, int byteBufferFront) {
for (int i = start; i < (start + length); i++) {
buffer[i] = mByteBuffer.getShort(byteBufferFront);
byteBufferFront += Constant.BYTES_PER_SHORT;
}
}
/** Private function that actually calculate the number of samples available to read. */
private int availableToRead(int rear, int front) {
int avail = rear - front;
if (avail > mMaxValues) {
// Discard 1/16 of the most recent data in pipe to avoid another overrun immediately
int oldFront = mFront;
mFront = rear - mMaxValues + (mMaxValues >> 5);
mSamplesOverrun += mFront - oldFront;
++mOverruns;
return OVERRUN;
}
return avail;
}
@Override
public int availableToRead() {
int rear;
int avail;
synchronized (mByteBuffer) {
rear = mByteBuffer.getInt(mMaxValues * Constant.BYTES_PER_SHORT);
}
avail = availableToRead(rear, mFront);
return avail;
}
public ByteBuffer getByteBuffer() {
return mByteBuffer;
}
@Override
public void flush() {
//set rear and front to zero
mFront = 0;
synchronized (mByteBuffer) {
mByteBuffer.putInt(mMaxValues * Constant.BYTES_PER_SHORT, 0);
}
}
private static void log(String msg) {
Log.v(TAG, msg);
}
}