/* * Copyright (C) 2008 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 android.media; import java.io.InputStream; import java.io.IOException; /** * ResampleInputStream * @hide */ public final class ResampleInputStream extends InputStream { static { System.loadLibrary("media_jni"); } private final static String TAG = "ResampleInputStream"; // pcm input stream private InputStream mInputStream; // sample rates, assumed to be normalized private final int mRateIn; private final int mRateOut; // input pcm data private byte[] mBuf; private int mBufCount; // length of 2:1 fir private static final int mFirLength = 29; // helper for bytewise read() private final byte[] mOneByte = new byte[1]; /** * Create a new ResampleInputStream, which converts the sample rate * @param inputStream InputStream containing 16 bit PCM. * @param rateIn the input sample rate. * @param rateOut the output sample rate. * This only handles rateIn == rateOut / 2 for the moment. */ public ResampleInputStream(InputStream inputStream, int rateIn, int rateOut) { // only support 2:1 at the moment if (rateIn != 2 * rateOut) throw new IllegalArgumentException("only support 2:1 at the moment"); rateIn = 2; rateOut = 1; mInputStream = inputStream; mRateIn = rateIn; mRateOut = rateOut; } @Override public int read() throws IOException { int rtn = read(mOneByte, 0, 1); return rtn == 1 ? (0xff & mOneByte[0]) : -1; } @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } @Override public int read(byte[] b, int offset, int length) throws IOException { if (mInputStream == null) throw new IllegalStateException("not open"); // ensure that mBuf is big enough to cover requested 'length' int nIn = ((length / 2) * mRateIn / mRateOut + mFirLength) * 2; if (mBuf == null) { mBuf = new byte[nIn]; } else if (nIn > mBuf.length) { byte[] bf = new byte[nIn]; System.arraycopy(mBuf, 0, bf, 0, mBufCount); mBuf = bf; } // read until we have enough data for at least one output sample while (true) { int len = ((mBufCount / 2 - mFirLength) * mRateOut / mRateIn) * 2; if (len > 0) { length = len < length ? len : (length / 2) * 2; break; } // TODO: should mBuf.length below be nIn instead? int n = mInputStream.read(mBuf, mBufCount, mBuf.length - mBufCount); if (n == -1) return -1; mBufCount += n; } // resample input data fir21(mBuf, 0, b, offset, length / 2); // move any unused bytes to front of mBuf int nFwd = length * mRateIn / mRateOut; mBufCount -= nFwd; if (mBufCount > 0) System.arraycopy(mBuf, nFwd, mBuf, 0, mBufCount); return length; } /* @Override public int available() throws IOException { int nsamples = (mIn - mOut + mInputStream.available()) / 2; return ((nsamples - mFirLength) * mRateOut / mRateIn) * 2; } */ @Override public void close() throws IOException { try { if (mInputStream != null) mInputStream.close(); } finally { mInputStream = null; } } @Override protected void finalize() throws Throwable { if (mInputStream != null) { close(); throw new IllegalStateException("someone forgot to close ResampleInputStream"); } } // // fir filter code JNI interface // private static native void fir21(byte[] in, int inOffset, byte[] out, int outOffset, int npoints); }