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