1 /*
2  * Copyright (C) 2008 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 android.media;
18 
19 import java.io.InputStream;
20 import java.io.IOException;
21 
22 
23 /**
24  * ResampleInputStream
25  * @hide
26  */
27 public final class ResampleInputStream extends InputStream
28 {
29     static {
30         System.loadLibrary("media_jni");
31     }
32 
33     private final static String TAG = "ResampleInputStream";
34 
35     // pcm input stream
36     private InputStream mInputStream;
37 
38     // sample rates, assumed to be normalized
39     private final int mRateIn;
40     private final int mRateOut;
41 
42     // input pcm data
43     private byte[] mBuf;
44     private int mBufCount;
45 
46     // length of 2:1 fir
47     private static final int mFirLength = 29;
48 
49     // helper for bytewise read()
50     private final byte[] mOneByte = new byte[1];
51 
52     /**
53      * Create a new ResampleInputStream, which converts the sample rate
54      * @param inputStream InputStream containing 16 bit PCM.
55      * @param rateIn the input sample rate.
56      * @param rateOut the output sample rate.
57      * This only handles rateIn == rateOut / 2 for the moment.
58      */
ResampleInputStream(InputStream inputStream, int rateIn, int rateOut)59     public ResampleInputStream(InputStream inputStream, int rateIn, int rateOut) {
60         // only support 2:1 at the moment
61         if (rateIn != 2 * rateOut) throw new IllegalArgumentException("only support 2:1 at the moment");
62         rateIn = 2;
63         rateOut = 1;
64 
65         mInputStream = inputStream;
66         mRateIn = rateIn;
67         mRateOut = rateOut;
68     }
69 
70     @Override
read()71     public int read() throws IOException {
72         int rtn = read(mOneByte, 0, 1);
73         return rtn == 1 ? (0xff & mOneByte[0]) : -1;
74     }
75 
76     @Override
read(byte[] b)77     public int read(byte[] b) throws IOException {
78         return read(b, 0, b.length);
79     }
80 
81     @Override
read(byte[] b, int offset, int length)82     public int read(byte[] b, int offset, int length) throws IOException {
83         if (mInputStream == null) throw new IllegalStateException("not open");
84 
85         // ensure that mBuf is big enough to cover requested 'length'
86         int nIn = ((length / 2) * mRateIn / mRateOut + mFirLength) * 2;
87         if (mBuf == null) {
88             mBuf = new byte[nIn];
89         } else if (nIn > mBuf.length) {
90             byte[] bf = new byte[nIn];
91             System.arraycopy(mBuf, 0, bf, 0, mBufCount);
92             mBuf = bf;
93         }
94 
95         // read until we have enough data for at least one output sample
96         while (true) {
97             int len = ((mBufCount / 2 - mFirLength) * mRateOut / mRateIn) * 2;
98             if (len > 0) {
99                 length = len < length ? len : (length / 2) * 2;
100                 break;
101             }
102             // TODO: should mBuf.length below be nIn instead?
103             int n = mInputStream.read(mBuf, mBufCount, mBuf.length - mBufCount);
104             if (n == -1) return -1;
105             mBufCount += n;
106         }
107 
108         // resample input data
109         fir21(mBuf, 0, b, offset, length / 2);
110 
111         // move any unused bytes to front of mBuf
112         int nFwd = length * mRateIn / mRateOut;
113         mBufCount -= nFwd;
114         if (mBufCount > 0) System.arraycopy(mBuf, nFwd, mBuf, 0, mBufCount);
115 
116         return length;
117     }
118 
119 /*
120     @Override
121     public int available() throws IOException {
122         int nsamples = (mIn - mOut + mInputStream.available()) / 2;
123         return ((nsamples - mFirLength) * mRateOut / mRateIn) * 2;
124     }
125 */
126 
127     @Override
close()128     public void close() throws IOException {
129         try {
130             if (mInputStream != null) mInputStream.close();
131         } finally {
132             mInputStream = null;
133         }
134     }
135 
136     @Override
finalize()137     protected void finalize() throws Throwable {
138         if (mInputStream != null) {
139             close();
140             throw new IllegalStateException("someone forgot to close ResampleInputStream");
141         }
142     }
143 
144     //
145     // fir filter code JNI interface
146     //
147     private static native void fir21(byte[] in, int inOffset,
148             byte[] out, int outOffset, int npoints);
149 
150 }
151