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