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
17package android.media;
18
19import java.io.InputStream;
20import java.io.IOException;
21
22
23/**
24 * AmrInputStream
25 * @hide
26 */
27public final class AmrInputStream extends InputStream
28{
29    static {
30        System.loadLibrary("media_jni");
31    }
32
33    private final static String TAG = "AmrInputStream";
34
35    // frame is 20 msec at 8.000 khz
36    private final static int SAMPLES_PER_FRAME = 8000 * 20 / 1000;
37
38    // pcm input stream
39    private InputStream mInputStream;
40
41    // native handle
42    private long mGae;
43
44    // result amr stream
45    private final byte[] mBuf = new byte[SAMPLES_PER_FRAME * 2];
46    private int mBufIn = 0;
47    private int mBufOut = 0;
48
49    // helper for bytewise read()
50    private byte[] mOneByte = new byte[1];
51
52    /**
53     * Create a new AmrInputStream, which converts 16 bit PCM to AMR
54     * @param inputStream InputStream containing 16 bit PCM.
55     */
56    public AmrInputStream(InputStream inputStream) {
57        mInputStream = inputStream;
58        mGae = GsmAmrEncoderNew();
59        GsmAmrEncoderInitialize(mGae);
60    }
61
62    @Override
63    public int read() throws IOException {
64        int rtn = read(mOneByte, 0, 1);
65        return rtn == 1 ? (0xff & mOneByte[0]) : -1;
66    }
67
68    @Override
69    public int read(byte[] b) throws IOException {
70        return read(b, 0, b.length);
71    }
72
73    @Override
74    public int read(byte[] b, int offset, int length) throws IOException {
75        if (mGae == 0) throw new IllegalStateException("not open");
76
77        // local buffer of amr encoded audio empty
78        if (mBufOut >= mBufIn) {
79            // reset the buffer
80            mBufOut = 0;
81            mBufIn = 0;
82
83            // fetch a 20 msec frame of pcm
84            for (int i = 0; i < SAMPLES_PER_FRAME * 2; ) {
85                int n = mInputStream.read(mBuf, i, SAMPLES_PER_FRAME * 2 - i);
86                if (n == -1) return -1;
87                i += n;
88            }
89
90            // encode it
91            mBufIn = GsmAmrEncoderEncode(mGae, mBuf, 0, mBuf, 0);
92        }
93
94        // return encoded audio to user
95        if (length > mBufIn - mBufOut) length = mBufIn - mBufOut;
96        System.arraycopy(mBuf, mBufOut, b, offset, length);
97        mBufOut += length;
98
99        return length;
100    }
101
102    @Override
103    public void close() throws IOException {
104        try {
105            if (mInputStream != null) mInputStream.close();
106        } finally {
107            mInputStream = null;
108            try {
109                if (mGae != 0) GsmAmrEncoderCleanup(mGae);
110            } finally {
111                try {
112                    if (mGae != 0) GsmAmrEncoderDelete(mGae);
113                } finally {
114                    mGae = 0;
115                }
116            }
117        }
118    }
119
120    @Override
121    protected void finalize() throws Throwable {
122        if (mGae != 0) {
123            close();
124            throw new IllegalStateException("someone forgot to close AmrInputStream");
125        }
126    }
127
128    //
129    // AudioRecord JNI interface
130    //
131    private static native long GsmAmrEncoderNew();
132    private static native void GsmAmrEncoderInitialize(long gae);
133    private static native int GsmAmrEncoderEncode(long gae,
134            byte[] pcm, int pcmOffset, byte[] amr, int amrOffset) throws IOException;
135    private static native void GsmAmrEncoderCleanup(long gae);
136    private static native void GsmAmrEncoderDelete(long gae);
137
138}
139