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 { outputFormatChanged(MediaCodecWrapper sender, MediaFormat newFormat)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 { outputSample(MediaCodecWrapper sender, MediaCodec.BufferInfo info, ByteBuffer buffer)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 MediaCodecWrapper(MediaCodec codec)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 */ stopAndRelease()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 */ getOutputFormatChangedListener()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 */ setOutputFormatChangedListener(final OutputFormatChangedListener outputFormatChangedListener, Handler handler)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 */ fromVideoFormat(final MediaFormat trackFormat, Surface surface)138 public static MediaCodecWrapper fromVideoFormat(final MediaFormat trackFormat, 139 Surface surface) { 140 MediaCodecWrapper result = null; 141 MediaCodec videoCodec = null; 142 143 // BEGIN_INCLUDE(create_codec) 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 } 159 // END_INCLUDE(create_codec) 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 */ writeSample(final ByteBuffer input, final MediaCodec.CryptoInfo crypto, final long presentationTimeUs, final int flags)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 */ writeSample(final MediaExtractor extractor, final boolean isSecure, final long presentationTimeUs, int flags)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 */ peekSample(MediaCodec.BufferInfo out_bufferInfo)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 */ popSample(boolean render)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 */ update()328 private void update() { 329 // BEGIN_INCLUDE(update_codec_state) 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 } 377 // END_INCLUDE(update_codec_state) 378 379 } 380 381 private class WriteException extends Throwable { WriteException(final String detailMessage)382 private WriteException(final String detailMessage) { 383 super(detailMessage); 384 } 385 } 386 } 387