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 { outputFormatChanged(MediaCodecWrapper sender, MediaFormat newFormat)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 { outputSample(MediaCodecWrapper sender, MediaCodec.BufferInfo info, ByteBuffer buffer)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 MediaCodecWrapper(MediaCodec codec)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 */ stopAndRelease()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 */ getOutputFormatChangedListener()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 */ setOutputFormatChangedListener(final OutputFormatChangedListener outputFormatChangedListener, Handler handler)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 */ fromVideoFormat(final MediaFormat trackFormat, Surface surface)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 */ writeSample(final ByteBuffer input, final MediaCodec.CryptoInfo crypto, final long presentationTimeUs, final int flags)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 */ writeSample(final MediaExtractor extractor, final boolean isSecure, final long presentationTimeUs, int flags)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 */ peekSample(MediaCodec.BufferInfo out_bufferInfo)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 */ popSample(boolean render)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 */ update()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 { WriteException(final String detailMessage)383 private WriteException(final String detailMessage) { 384 super(detailMessage); 385 } 386 } 387 } 388