Home | History | Annotate | Download | only in loopback
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package org.drrickorang.loopback;
     18 
     19 import java.nio.ByteBuffer;
     20 import java.nio.ByteOrder;
     21 
     22 import android.util.Log;
     23 
     24 
     25 /**
     26  * Non-blocking pipe where writer writes to the pipe using by knowing the address of "mByteBuffer",
     27  * and write to this ByteBuffer directly. On the other hand, reader reads from the pipe using
     28  * read(), which converts data in ByteBuffer into shorts.
     29  * Data in the pipe are stored in the ByteBuffer array "mByteBuffer".
     30  * The write side of a pipe permits overruns; flow control is the caller's responsibility.
     31  */
     32 
     33 public class PipeByteBuffer extends Pipe {
     34     private static final String TAG = "PipeByteBuffer";
     35 
     36     private final ByteBuffer mByteBuffer;
     37     private int              mFront = 0; // reader's current position
     38 
     39 
     40     /**
     41      * The ByteBuffer in this class consists of two sections. The first section is the actual pipe
     42      * to store data. This section must have a size in power of 2, and this is enforced by the
     43      * constructor through rounding maxSamples up to the nearest power of 2. This second section
     44      * is used to store metadata. Currently the only metadata is an integer that stores the rear,
     45      * where rear is the writer's current position. The metadata is at the end of ByteBuffer, and is
     46      * outside of the actual pipe.
     47      * IMPORTANT: The code is designed (in native code) such that metadata won't be overwritten when
     48      * the writer writes to the pipe. If changes to the code are required, please make sure the
     49      * metadata won't be overwritten.
     50      * IMPORTANT: Since a signed integer is used to store rear and mFront, their values should not
     51      * exceed 2^31 - 1, or else overflows happens and the positions of read and mFront becomes
     52      * incorrect.
     53      */
     54     public PipeByteBuffer(int maxSamples) {
     55         super(maxSamples);
     56         int extraInt = 1; // used to store rear
     57         int extraShort = extraInt * Constant.SHORTS_PER_INT;
     58         int numberOfShorts = mMaxValues + extraShort;
     59         mByteBuffer = ByteBuffer.allocateDirect(numberOfShorts * Constant.BYTES_PER_SHORT);
     60         mByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
     61     }
     62 
     63 
     64     /**
     65      * Convert data in mByteBuffer into short, and put them into "buffer".
     66      * Note: rear and mFront are keep in terms of number of short instead of number of byte.
     67      */
     68     @Override
     69     public int read(short[] buffer, int offset, int requiredSamples) {
     70         // first, update the current rear
     71         int rear;
     72         synchronized (mByteBuffer) {
     73             rear = mByteBuffer.getInt(mMaxValues * Constant.BYTES_PER_SHORT);
     74         }
     75         //log("initial offset: " + offset + "\n initial requiredSamples: " + requiredSamples);
     76 
     77         // after here, rear may actually be updated further. However, we don't care. If at the point
     78         // of checking there's enough data then we will read it. If not just wait until next call
     79         // of read.
     80         int avail = availableToRead(rear, mFront);
     81         if (avail <= 0) {   //return -2 for overrun
     82             return avail;
     83         }
     84 
     85         // if not enough samples, just read partial samples
     86         if (requiredSamples > avail) {
     87             requiredSamples = avail;
     88         }
     89 
     90         // mask the upper bits to get the correct position in the pipe
     91         int front = mFront & (mMaxValues - 1);
     92         int read = mMaxValues - front;   // total samples from currentIndex until the end of array
     93         if (read > requiredSamples) {
     94             read = requiredSamples;
     95         }
     96 
     97         int byteBufferFront = front * Constant.BYTES_PER_SHORT; // start reading from here
     98         byteBufferToArray(buffer, offset, read, byteBufferFront);
     99 
    100         if (front + read == mMaxValues) {
    101             int samplesLeft = requiredSamples - read;
    102             if (samplesLeft > 0) {
    103                 byteBufferFront = 0;
    104                 byteBufferToArray(buffer, offset + read, read + samplesLeft, byteBufferFront);
    105                 read += samplesLeft;
    106             }
    107         }
    108 
    109         mFront += read;
    110         return read;
    111     }
    112 
    113 
    114     /**
    115      * Copy mByteBuffer's data (starting from "byteBufferFront") into double array "buffer".
    116      * "start" is the starting index of "buffer" and "length" is the amount of samples copying.
    117      */
    118     private void byteBufferToArray(short[] buffer, int start, int length, int byteBufferFront) {
    119         for (int i = start; i < (start + length); i++) {
    120             buffer[i] = mByteBuffer.getShort(byteBufferFront);
    121             byteBufferFront += Constant.BYTES_PER_SHORT;
    122         }
    123     }
    124 
    125 
    126     /** Private function that actually calculate the number of samples available to read. */
    127     private int availableToRead(int rear, int front) {
    128         int avail = rear - front;
    129         if (avail > mMaxValues) {
    130             // Discard 1/16 of the most recent data in pipe to avoid another overrun immediately
    131             int oldFront = mFront;
    132             mFront = rear - mMaxValues + (mMaxValues >> 5);
    133             mSamplesOverrun += mFront - oldFront;
    134             ++mOverruns;
    135             return OVERRUN;
    136         }
    137 
    138         return avail;
    139     }
    140 
    141 
    142     @Override
    143     public int availableToRead() {
    144         int rear;
    145         int avail;
    146         synchronized (mByteBuffer) {
    147             rear = mByteBuffer.getInt(mMaxValues * Constant.BYTES_PER_SHORT);
    148         }
    149 
    150         avail = availableToRead(rear, mFront);
    151         return avail;
    152     }
    153 
    154 
    155     public ByteBuffer getByteBuffer() {
    156         return mByteBuffer;
    157     }
    158 
    159 
    160     @Override
    161     public void flush() {
    162         //set rear and front to zero
    163         mFront = 0;
    164         synchronized (mByteBuffer) {
    165             mByteBuffer.putInt(mMaxValues * Constant.BYTES_PER_SHORT, 0);
    166         }
    167     }
    168 
    169 
    170     private static void log(String msg) {
    171         Log.v(TAG, msg);
    172     }
    173 
    174 }
    175