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
17package org.drrickorang.loopback;
18
19import java.nio.ByteBuffer;
20import java.nio.ByteOrder;
21
22import 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
33public 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