1 /* 2 * Copyright (C) 2013 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 com.example.android.common.media; 18 19 import android.media.*; 20 import android.os.Handler; 21 import android.os.Looper; 22 import android.view.Surface; 23 24 import java.nio.ByteBuffer; 25 import java.util.ArrayDeque; 26 import java.util.Queue; 27 28 /** 29 * Simplifies the MediaCodec interface by wrapping around the buffer processing operations. 30 */ 31 public class MediaCodecWrapper { 32 33 // Handler to use for {@code OutputSampleListener} and {code OutputFormatChangedListener} 34 // callbacks 35 private Handler mHandler; 36 37 38 // Callback when media output format changes. 39 public interface OutputFormatChangedListener { 40 void outputFormatChanged(MediaCodecWrapper sender, MediaFormat newFormat); 41 } 42 43 private OutputFormatChangedListener mOutputFormatChangedListener = null; 44 45 /** 46 * Callback for decodes frames. Observers can register a listener for optional stream 47 * of decoded data 48 */ 49 public interface OutputSampleListener { 50 void outputSample(MediaCodecWrapper sender, MediaCodec.BufferInfo info, ByteBuffer buffer); 51 } 52 53 /** 54 * The {@link MediaCodec} that is managed by this class. 55 */ 56 private MediaCodec mDecoder; 57 58 // References to the internal buffers managed by the codec. The codec 59 // refers to these buffers by index, never by reference so it's up to us 60 // to keep track of which buffer is which. 61 private ByteBuffer[] mInputBuffers; 62 private ByteBuffer[] mOutputBuffers; 63 64 // Indices of the input buffers that are currently available for writing. We'll 65 // consume these in the order they were dequeued from the codec. 66 private Queue<Integer> mAvailableInputBuffers; 67 68 // Indices of the output buffers that currently hold valid data, in the order 69 // they were produced by the codec. 70 private Queue<Integer> mAvailableOutputBuffers; 71 72 // Information about each output buffer, by index. Each entry in this array 73 // is valid if and only if its index is currently contained in mAvailableOutputBuffers. 74 private MediaCodec.BufferInfo[] mOutputBufferInfo; 75 76 // An (optional) stream that will receive decoded data. 77 private OutputSampleListener mOutputSampleListener; 78 79 private MediaCodecWrapper(MediaCodec codec) { 80 mDecoder = codec; 81 codec.start(); 82 mInputBuffers = codec.getInputBuffers(); 83 mOutputBuffers = codec.getOutputBuffers(); 84 mOutputBufferInfo = new MediaCodec.BufferInfo[mOutputBuffers.length]; 85 mAvailableInputBuffers = new ArrayDeque<Integer>(mOutputBuffers.length); 86 mAvailableOutputBuffers = new ArrayDeque<Integer>(mInputBuffers.length); 87 } 88 89 /** 90 * Releases resources and ends the encoding/decoding session. 91 */ 92 public void stopAndRelease() { 93 mDecoder.stop(); 94 mDecoder.release(); 95 mDecoder = null; 96 mHandler = null; 97 } 98 99 /** 100 * Getter for the registered {@link OutputFormatChangedListener} 101 */ 102 public OutputFormatChangedListener getOutputFormatChangedListener() { 103 return mOutputFormatChangedListener; 104 } 105 106 /** 107 * 108 * @param outputFormatChangedListener the listener for callback. 109 * @param handler message handler for posting the callback. 110 */ 111 public void setOutputFormatChangedListener(final OutputFormatChangedListener 112 outputFormatChangedListener, Handler handler) { 113 mOutputFormatChangedListener = outputFormatChangedListener; 114 115 // Making sure we don't block ourselves due to a bad implementation of the callback by 116 // using a handler provided by client. 117 Looper looper; 118 mHandler = handler; 119 if (outputFormatChangedListener != null && mHandler == null) { 120 if ((looper = Looper.myLooper()) != null) { 121 mHandler = new Handler(); 122 } else { 123 throw new IllegalArgumentException( 124 "Looper doesn't exist in the calling thread"); 125 } 126 } 127 } 128 129 /** 130 * Constructs the {@link MediaCodecWrapper} wrapper object around the video codec. 131 * The codec is created using the encapsulated information in the 132 * {@link MediaFormat} object. 133 * 134 * @param trackFormat The format of the media object to be decoded. 135 * @param surface Surface to render the decoded frames. 136 * @return 137 */ 138 public static MediaCodecWrapper fromVideoFormat(final MediaFormat trackFormat, 139 Surface surface) { 140 MediaCodecWrapper result = null; 141 MediaCodec videoCodec = null; 142 144 final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME); 145 146 // Check to see if this is actually a video mime type. If it is, then create 147 // a codec that can decode this mime type. 148 if (mimeType.contains("video/")) { 149 videoCodec = MediaCodec.createDecoderByType(mimeType); 150 videoCodec.configure(trackFormat, surface, null, 0); 151 152 } 153 154 // If codec creation was successful, then create a wrapper object around the 155 // newly created codec. 156 if (videoCodec != null) { 157 result = new MediaCodecWrapper(videoCodec); 158 } 160 161 return result; 162 } 163 164 165 /** 166 * Write a media sample to the decoder. 167 * 168 * A "sample" here refers to a single atomic access unit in the media stream. The definition 169 * of "access unit" is dependent on the type of encoding used, but it typically refers to 170 * a single frame of video or a few seconds of audio. {@link android.media.MediaExtractor} 171 * extracts data from a stream one sample at a time. 172 * 173 * @param input A ByteBuffer containing the input data for one sample. The buffer must be set 174 * up for reading, with its position set to the beginning of the sample data and its limit 175 * set to the end of the sample data. 176 * 177 * @param presentationTimeUs The time, relative to the beginning of the media stream, 178 * at which this buffer should be rendered. 179 * 180 * @param flags Flags to pass to the decoder. See {@link MediaCodec#queueInputBuffer(int, 181 * int, int, long, int)} 182 * 183 * @throws MediaCodec.CryptoException 184 */ 185 public boolean writeSample(final ByteBuffer input, 186 final MediaCodec.CryptoInfo crypto, 187 final long presentationTimeUs, 188 final int flags) throws MediaCodec.CryptoException, WriteException { 189 boolean result = false; 190 int size = input.remaining(); 191 192 // check if we have dequed input buffers available from the codec 193 if (size > 0 && !mAvailableInputBuffers.isEmpty()) { 194 int index = mAvailableInputBuffers.remove(); 195 ByteBuffer buffer = mInputBuffers[index]; 196 197 // we can't write our sample to a lesser capacity input buffer. 198 if (size > buffer.capacity()) { 199 throw new MediaCodecWrapper.WriteException(String.format( 200 "Insufficient capacity in MediaCodec buffer: " 201 + "tried to write %d, buffer capacity is %d.", 202 input.remaining(), 203 buffer.capacity())); 204 } 205 206 buffer.clear(); 207 buffer.put(input); 208 209 // Submit the buffer to the codec for decoding. The presentationTimeUs 210 // indicates the position (play time) for the current sample. 211 if (crypto == null) { 212 mDecoder.queueInputBuffer(index, 0, size, presentationTimeUs, flags); 213 } else { 214 mDecoder.queueSecureInputBuffer(index, 0, crypto, presentationTimeUs, flags); 215 } 216 result = true; 217 } 218 return result; 219 } 220 221 static MediaCodec.CryptoInfo cryptoInfo= new MediaCodec.CryptoInfo(); 222 223 /** 224 * Write a media sample to the decoder. 225 * 226 * A "sample" here refers to a single atomic access unit in the media stream. The definition 227 * of "access unit" is dependent on the type of encoding used, but it typically refers to 228 * a single frame of video or a few seconds of audio. {@link android.media.MediaExtractor} 229 * extracts data from a stream one sample at a time. 230 * 231 * @param extractor Instance of {@link android.media.MediaExtractor} wrapping the media. 232 * 233 * @param presentationTimeUs The time, relative to the beginning of the media stream, 234 * at which this buffer should be rendered. 235 * 236 * @param flags Flags to pass to the decoder. See {@link MediaCodec#queueInputBuffer(int, 237 * int, int, long, int)} 238 * 239 * @throws MediaCodec.CryptoException 240 */ 241 public boolean writeSample(final MediaExtractor extractor, 242 final boolean isSecure, 243 final long presentationTimeUs, 244 int flags) { 245 boolean result = false; 246 boolean isEos = false; 247 248 if (!mAvailableInputBuffers.isEmpty()) { 249 int index = mAvailableInputBuffers.remove(); 250 ByteBuffer buffer = mInputBuffers[index]; 251 252 // reads the sample from the file using extractor into the buffer 253 int size = extractor.readSampleData(buffer, 0); 254 if (size <= 0) { 255 flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; 256 } 257 258 // Submit the buffer to the codec for decoding. The presentationTimeUs 259 // indicates the position (play time) for the current sample. 260 if (!isSecure) { 261 mDecoder.queueInputBuffer(index, 0, size, presentationTimeUs, flags); 262 } else { 263 extractor.getSampleCryptoInfo(cryptoInfo); 264 mDecoder.queueSecureInputBuffer(index, 0, cryptoInfo, presentationTimeUs, flags); 265 } 266 267 result = true; 268 } 269 return result; 270 } 271 272 /** 273 * Performs a peek() operation in the queue to extract media info for the buffer ready to be 274 * released i.e. the head element of the queue. 275 * 276 * @param out_bufferInfo An output var to hold the buffer info. 277 * 278 * @return True, if the peek was successful. 279 */ 280 public boolean peekSample(MediaCodec.BufferInfo out_bufferInfo) { 281 // dequeue available buffers and synchronize our data structures with the codec. 282 update(); 283 boolean result = false; 284 if (!mAvailableOutputBuffers.isEmpty()) { 285 int index = mAvailableOutputBuffers.peek(); 286 MediaCodec.BufferInfo info = mOutputBufferInfo[index]; 287 // metadata of the sample 288 out_bufferInfo.set( 289 info.offset, 290 info.size, 291 info.presentationTimeUs, 292 info.flags); 293 result = true; 294 } 295 return result; 296 } 297 298 /** 299 * Processes, releases and optionally renders the output buffer available at the head of the 300 * queue. All observers are notified with a callback. See {@link 301 * OutputSampleListener#outputSample(MediaCodecWrapper, android.media.MediaCodec.BufferInfo, 302 * java.nio.ByteBuffer)} 303 * 304 * @param render True, if the buffer is to be rendered on the {@link Surface} configured 305 * 306 */ 307 public void popSample(boolean render) { 308 // dequeue available buffers and synchronize our data structures with the codec. 309 update(); 310 if (!mAvailableOutputBuffers.isEmpty()) { 311 int index = mAvailableOutputBuffers.remove(); 312 313 if (render && mOutputSampleListener != null) { 314 ByteBuffer buffer = mOutputBuffers[index]; 315 MediaCodec.BufferInfo info = mOutputBufferInfo[index]; 316 mOutputSampleListener.outputSample(this, info, buffer); 317 } 318 319 // releases the buffer back to the codec 320 mDecoder.releaseOutputBuffer(index, render); 321 } 322 } 323 324 /** 325 * Synchronize this object's state with the internal state of the wrapped 326 * MediaCodec. 327 */ 328 private void update() { 330 int index; 331 332 // Get valid input buffers from the codec to fill later in the same order they were 333 // made available by the codec. 334 while ((index = mDecoder.dequeueInputBuffer(0)) != MediaCodec.INFO_TRY_AGAIN_LATER) { 335 mAvailableInputBuffers.add(index); 336 } 337 338 339 // Likewise with output buffers. If the output buffers have changed, start using the 340 // new set of output buffers. If the output format has changed, notify listeners. 341 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); 342 while ((index = mDecoder.dequeueOutputBuffer(info, 0)) != MediaCodec.INFO_TRY_AGAIN_LATER) { 343 switch (index) { 344 case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: 345 mOutputBuffers = mDecoder.getOutputBuffers(); 346 mOutputBufferInfo = new MediaCodec.BufferInfo[mOutputBuffers.length]; 347 mAvailableOutputBuffers.clear(); 348 break; 349 case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: 350 if (mOutputFormatChangedListener != null) { 351 mHandler.post(new Runnable() { 352 @Override 353 public void run() { 354 mOutputFormatChangedListener 355 .outputFormatChanged(MediaCodecWrapper.this, 356 mDecoder.getOutputFormat()); 357 358 } 359 }); 360 } 361 break; 362 default: 363 // Making sure the index is valid before adding to output buffers. We've already 364 // handled INFO_TRY_AGAIN_LATER, INFO_OUTPUT_FORMAT_CHANGED & 365 // INFO_OUTPUT_BUFFERS_CHANGED i.e all the other possible return codes but 366 // asserting index value anyways for future-proofing the code. 367 if(index >= 0) { 368 mOutputBufferInfo[index] = info; 369 mAvailableOutputBuffers.add(index); 370 } else { 371 throw new IllegalStateException("Unknown status from dequeueOutputBuffer"); 372 } 373 break; 374 } 375 376 } 378 379 } 380 381 private class WriteException extends Throwable { 382 private WriteException(final String detailMessage) { 383 super(detailMessage); 384 } 385 } 386 }