Home | History | Annotate | Download | only in 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.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