Home | History | Annotate | Download | only in com.example.android.common.media
      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