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.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 {
     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 {
     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 
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 {
    382         private WriteException(final String detailMessage) {
    383             super(detailMessage);
    384         }
    385     }
    386 }
    387