1 /* 2 * Copyright (C) 2021 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.cts; 18 19 import static org.junit.Assert.fail; 20 21 import android.media.AudioFormat; 22 import android.media.MediaCodec; 23 import android.media.MediaCodec.BufferInfo; 24 import android.media.MediaExtractor; 25 import android.media.MediaFormat; 26 import android.util.Log; 27 28 import java.io.BufferedInputStream; 29 import java.io.Closeable; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.nio.ByteBuffer; 33 import java.nio.FloatBuffer; 34 import java.nio.ShortBuffer; 35 36 public class StreamUtils { 37 private static final String TAG = "CtsMediaStreamUtils"; 38 39 public static abstract class ByteBufferStream { read()40 public abstract ByteBuffer read() throws IOException; 41 } 42 43 public static class MediaCodecStream extends ByteBufferStream implements Closeable { 44 private ByteBufferStream mBufferInputStream; 45 private InputStream mInputStream; 46 private MediaCodec mCodec; 47 public boolean mIsFloat; 48 BufferInfo mInfo = new BufferInfo(); 49 boolean mSawOutputEOS; 50 boolean mSawInputEOS; 51 boolean mEncode; 52 boolean mSentConfig; 53 54 // result stream 55 private byte[] mBuf = null; 56 private byte[] mCache = null; 57 private int mBufIn = 0; 58 private int mBufOut = 0; 59 private int mBufCounter = 0; 60 61 private MediaExtractor mExtractor; // Read from Extractor instead of InputStream 62 // helper for bytewise read() 63 private byte[] mOneByte = new byte[1]; 64 MediaCodecStream( MediaFormat format, boolean encode)65 public MediaCodecStream( 66 MediaFormat format, 67 boolean encode) throws Exception { 68 String mime = format.getString(MediaFormat.KEY_MIME); 69 mEncode = encode; 70 if (mEncode) { 71 mCodec = MediaCodec.createEncoderByType(mime); 72 } else { 73 mCodec = MediaCodec.createDecoderByType(mime); 74 } 75 mCodec.configure(format,null, null, encode ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0); 76 77 // check if float 78 final MediaFormat actualFormat = 79 encode ? mCodec.getInputFormat() : mCodec.getOutputFormat(); 80 81 mIsFloat = actualFormat.getInteger( 82 MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_16BIT) 83 == AudioFormat.ENCODING_PCM_FLOAT; 84 85 mCodec.start(); 86 } 87 MediaCodecStream( InputStream input, MediaFormat format, boolean encode)88 public MediaCodecStream( 89 InputStream input, 90 MediaFormat format, 91 boolean encode) throws Exception { 92 this(format, encode); 93 mInputStream = input; 94 } 95 MediaCodecStream( ByteBufferStream input, MediaFormat format, boolean encode)96 public MediaCodecStream( 97 ByteBufferStream input, 98 MediaFormat format, 99 boolean encode) throws Exception { 100 this(format, encode); 101 mBufferInputStream = input; 102 } 103 MediaCodecStream(MediaExtractor mediaExtractor, MediaFormat format)104 public MediaCodecStream(MediaExtractor mediaExtractor, 105 MediaFormat format) throws Exception { 106 this(format, false /* encode */); 107 mExtractor = mediaExtractor; 108 } 109 110 @Override read()111 public ByteBuffer read() throws IOException { 112 113 if (mSawOutputEOS) { 114 return null; 115 } 116 117 // first push as much data into the codec as possible 118 while (!mSawInputEOS) { 119 Log.i(TAG, "sending data to " + mCodec.getName()); 120 int index = mCodec.dequeueInputBuffer(5000); 121 if (index < 0) { 122 // no input buffer currently available 123 break; 124 } else { 125 ByteBuffer buf = mCodec.getInputBuffer(index); 126 buf.clear(); 127 int inBufLen = buf.limit(); 128 int numRead = 0; 129 long timestampUs = 0; // non-zero for MediaExtractor mode 130 if (mExtractor != null) { 131 numRead = mExtractor.readSampleData(buf, 0 /* offset */); 132 timestampUs = mExtractor.getSampleTime(); 133 Log.v(TAG, "MediaCodecStream.read using Extractor, numRead " 134 + numRead +" timestamp " + timestampUs); 135 mExtractor.advance(); 136 if(numRead < 0) { 137 mSawInputEOS = true; 138 timestampUs = 0; 139 numRead =0; 140 } 141 } else if (mBufferInputStream != null) { 142 ByteBuffer in = null; 143 do { 144 in = mBufferInputStream.read(); 145 } while (in != null && in.limit() - in.position() == 0); 146 if (in == null) { 147 mSawInputEOS = true; 148 } else { 149 final int n = in.limit() - in.position(); 150 numRead += n; 151 buf.put(in); 152 } 153 } else if (mInputStream != null) { 154 if (mBuf == null) { 155 mBuf = new byte[inBufLen]; 156 } 157 for (numRead = 0; numRead < inBufLen; ) { 158 int n = mInputStream.read(mBuf, numRead, inBufLen - numRead); 159 if (n == -1) { 160 mSawInputEOS = true; 161 break; 162 } 163 numRead += n; 164 } 165 buf.put(mBuf, 0, numRead); 166 } else { 167 fail("no input"); 168 } 169 170 int flags = mSawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0; 171 if (!mEncode && !mSentConfig && mExtractor == null) { 172 flags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG; 173 mSentConfig = true; 174 } 175 Log.i(TAG, "queuing input buffer " + index + 176 ", size " + numRead + ", flags: " + flags + 177 " on " + mCodec.getName()); 178 mCodec.queueInputBuffer(index, 179 0 /* offset */, 180 numRead, 181 timestampUs /* presentationTimeUs */, 182 flags); 183 Log.i(TAG, "queued input buffer " + index + ", size " + numRead); 184 } 185 } 186 187 // now read data from the codec 188 Log.i(TAG, "reading from " + mCodec.getName()); 189 int index = mCodec.dequeueOutputBuffer(mInfo, 5000); 190 if (index >= 0) { 191 Log.i(TAG, "got " + mInfo.size + " bytes from " + mCodec.getName()); 192 ByteBuffer out = mCodec.getOutputBuffer(index); 193 ByteBuffer ret = ByteBuffer.allocate(mInfo.size); 194 ret.put(out); 195 mCodec.releaseOutputBuffer(index, false /* render */); 196 if ((mInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 197 Log.i(TAG, "saw output EOS on " + mCodec.getName()); 198 mSawOutputEOS = true; 199 } 200 ret.flip(); // prepare buffer for reading from it 201 // XXX chck that first encoded buffer has CSD flags set 202 if (mEncode && mBufCounter++ == 0 && (mInfo.flags & 203 MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { 204 fail("first encoded buffer missing CSD flag"); 205 } 206 return ret; 207 } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 208 Log.i(TAG, mCodec.getName() + " new format: " + mCodec.getOutputFormat()); 209 } 210 return ByteBuffer.allocate(0); 211 } 212 213 @Override close()214 public void close() throws IOException { 215 try { 216 if (mInputStream != null) { 217 mInputStream.close(); 218 } 219 } finally { 220 mInputStream = null; 221 try { 222 if (mCodec != null) { 223 mCodec.release(); 224 } 225 } finally { 226 mCodec = null; 227 } 228 } 229 } 230 231 @Override finalize()232 protected void finalize() throws Throwable { 233 if (mCodec != null) { 234 Log.w(TAG, "MediaCodecInputStream wasn't closed"); 235 mCodec.release(); 236 } 237 } 238 }; 239 240 public static class ByteBufferInputStream extends InputStream { 241 ByteBufferStream mInput; 242 ByteBuffer mBuffer; 243 ByteBufferInputStream(ByteBufferStream in)244 public ByteBufferInputStream(ByteBufferStream in) { 245 mInput = in; 246 mBuffer = ByteBuffer.allocate(0); 247 } 248 249 @Override read()250 public int read() throws IOException { 251 while (mBuffer != null && !mBuffer.hasRemaining()) { 252 Log.i(TAG, "reading buffer"); 253 mBuffer = mInput.read(); 254 } 255 256 if (mBuffer == null) { 257 return -1; 258 } 259 260 return (0xff & mBuffer.get()); 261 } 262 }; 263 264 public static class PcmAudioBufferStream extends ByteBufferStream { 265 266 public int mCount; // either 0 or 1 if the buffer has been delivered 267 public ByteBuffer mBuffer; // the audio buffer (furnished duplicated, read only). 268 PcmAudioBufferStream( int samples, int sampleRate, double frequency, double sweep, boolean useFloat)269 public PcmAudioBufferStream( 270 int samples, int sampleRate, double frequency, double sweep, boolean useFloat) { 271 final int sampleSize = useFloat ? 4 : 2; 272 final int sizeInBytes = samples * sampleSize; 273 mBuffer = ByteBuffer.allocate(sizeInBytes); 274 mBuffer.order(java.nio.ByteOrder.nativeOrder()); 275 if (useFloat) { 276 FloatBuffer fb = mBuffer.asFloatBuffer(); 277 float[] fa = AudioHelper.createSoundDataInFloatArray( 278 samples, sampleRate, frequency, sweep); 279 for (int i = 0; i < fa.length; ++i) { 280 // quantize to a Q.23 integer so that identity is preserved 281 fa[i] = (float)((int)(fa[i] * ((1 << 23) - 1))) / (1 << 23); 282 } 283 fb.put(fa); 284 } else { 285 ShortBuffer sb = mBuffer.asShortBuffer(); 286 sb.put(AudioHelper.createSoundDataInShortArray( 287 samples, sampleRate, frequency, sweep)); 288 } 289 mBuffer.limit(sizeInBytes); 290 } 291 292 // duplicating constructor PcmAudioBufferStream(PcmAudioBufferStream other)293 public PcmAudioBufferStream(PcmAudioBufferStream other) { 294 mCount = 0; 295 mBuffer = other.mBuffer; // ok to copy, furnished read-only 296 } 297 sizeInBytes()298 public int sizeInBytes() { 299 return mBuffer.capacity(); 300 } 301 302 @Override read()303 public ByteBuffer read() throws IOException { 304 if (mCount < 1 /* only one buffer */) { 305 ++mCount; 306 return mBuffer.asReadOnlyBuffer(); 307 } 308 return null; 309 } 310 } 311 compareStreams(InputStream test, InputStream reference)312 public static int compareStreams(InputStream test, InputStream reference) { 313 Log.i(TAG, "compareStreams"); 314 BufferedInputStream buffered_test = new BufferedInputStream(test); 315 BufferedInputStream buffered_reference = new BufferedInputStream(reference); 316 int numread = 0; 317 try { 318 while (true) { 319 int b1 = buffered_test.read(); 320 int b2 = buffered_reference.read(); 321 if (b1 != b2) { 322 Log.e(TAG, "streams differ at " + numread + ": " + b1 + "/" + b2); 323 return -1; 324 } 325 if (b1 == -1) { 326 Log.e(TAG, "streams ended at " + numread); 327 break; 328 } 329 numread++; 330 } 331 } catch (Exception e) { 332 Log.e(TAG, "read error", e); 333 return -1; 334 } 335 Log.i(TAG, "compareStreams read " + numread); 336 return numread; 337 } 338 } 339