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