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 android.compat.annotation.UnsupportedAppUsage;
20 import android.media.MediaCodec.BufferInfo;
21 import android.util.Log;
22 
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.nio.ByteBuffer;
26 
27 
28 /**
29  * DO NOT USE
30  * @hide
31  */
32 public final class AmrInputStream extends InputStream {
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     MediaCodec mCodec;
39     BufferInfo mInfo;
40     boolean mSawOutputEOS;
41     boolean mSawInputEOS;
42 
43     // pcm input stream
44     private InputStream mInputStream;
45 
46     // result amr stream
47     private final byte[] mBuf = new byte[SAMPLES_PER_FRAME * 2];
48     private int mBufIn = 0;
49     private int mBufOut = 0;
50 
51     // helper for bytewise read()
52     private byte[] mOneByte = new byte[1];
53 
54     /**
55      * DO NOT USE - use MediaCodec instead
56      */
57     @UnsupportedAppUsage
AmrInputStream(InputStream inputStream)58     public AmrInputStream(InputStream inputStream) {
59         Log.w(TAG, "@@@@ AmrInputStream is not a public API @@@@");
60         mInputStream = inputStream;
61 
62         MediaFormat format  = new MediaFormat();
63         format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AMR_NB);
64         format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000);
65         format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
66         format.setInteger(MediaFormat.KEY_BIT_RATE, 12200);
67 
68         MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
69         String name = mcl.findEncoderForFormat(format);
70         if (name != null) {
71             try {
72                 mCodec = MediaCodec.createByCodecName(name);
73                 mCodec.configure(format,
74                         null /* surface */,
75                         null /* crypto */,
76                         MediaCodec.CONFIGURE_FLAG_ENCODE);
77                 mCodec.start();
78             } catch (IOException e) {
79                 if (mCodec != null) {
80                     mCodec.release();
81                 }
82                 mCodec = null;
83             }
84         }
85         mInfo = new BufferInfo();
86     }
87 
88     /**
89      * DO NOT USE
90      */
91     @Override
read()92     public int read() throws IOException {
93         int rtn = read(mOneByte, 0, 1);
94         return rtn == 1 ? (0xff & mOneByte[0]) : -1;
95     }
96 
97     /**
98      * DO NOT USE
99      */
100     @Override
read(byte[] b)101     public int read(byte[] b) throws IOException {
102         return read(b, 0, b.length);
103     }
104 
105     /**
106      * DO NOT USE
107      */
108     @Override
read(byte[] b, int offset, int length)109     public int read(byte[] b, int offset, int length) throws IOException {
110         if (mCodec == null) {
111             throw new IllegalStateException("not open");
112         }
113 
114         if (mBufOut >= mBufIn && !mSawOutputEOS) {
115             // no data left in buffer, refill it
116             mBufOut = 0;
117             mBufIn = 0;
118 
119             // first push as much data into the encoder as possible
120             while (!mSawInputEOS) {
121                 int index = mCodec.dequeueInputBuffer(0);
122                 if (index < 0) {
123                     // no input buffer currently available
124                     break;
125                 } else {
126                     int numRead;
127                     for (numRead = 0; numRead < SAMPLES_PER_FRAME * 2; ) {
128                         int n = mInputStream.read(mBuf, numRead, SAMPLES_PER_FRAME * 2 - numRead);
129                         if (n == -1) {
130                             mSawInputEOS = true;
131                             break;
132                         }
133                         numRead += n;
134                     }
135                     ByteBuffer buf = mCodec.getInputBuffer(index);
136                     buf.put(mBuf, 0, numRead);
137                     mCodec.queueInputBuffer(index,
138                             0 /* offset */,
139                             numRead,
140                             0 /* presentationTimeUs */,
141                             mSawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0 /* flags */);
142                 }
143             }
144 
145             // now read encoded data from the encoder
146             int index = mCodec.dequeueOutputBuffer(mInfo, 0);
147             if (index >= 0) {
148                 mBufIn = mInfo.size;
149                 ByteBuffer out = mCodec.getOutputBuffer(index);
150                 out.get(mBuf, 0 /* offset */, mBufIn /* length */);
151                 mCodec.releaseOutputBuffer(index,  false /* render */);
152                 if ((mInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
153                     mSawOutputEOS = true;
154                 }
155             }
156         }
157 
158         if (mBufOut < mBufIn) {
159             // there is data in the buffer
160             if (length > mBufIn - mBufOut) {
161                 length = mBufIn - mBufOut;
162             }
163             System.arraycopy(mBuf, mBufOut, b, offset, length);
164             mBufOut += length;
165             return length;
166         }
167 
168         if (mSawInputEOS && mSawOutputEOS) {
169             // no more data available in buffer, codec or input stream
170             return -1;
171         }
172 
173         // caller should try again
174         return 0;
175     }
176 
177     @Override
close()178     public void close() throws IOException {
179         try {
180             if (mInputStream != null) {
181                 mInputStream.close();
182             }
183         } finally {
184             mInputStream = null;
185             try {
186                 if (mCodec != null) {
187                     mCodec.release();
188                 }
189             } finally {
190                 mCodec = null;
191             }
192         }
193     }
194 
195     @Override
finalize()196     protected void finalize() throws Throwable {
197         if (mCodec != null) {
198             Log.w(TAG, "AmrInputStream wasn't closed");
199             mCodec.release();
200         }
201     }
202 }
203