Home | History | Annotate | Download | only in decoder
      1 /*
      2  * Copyright (C) 2012 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 androidx.media.filterfw.decoder;
     18 
     19 import android.annotation.TargetApi;
     20 import android.media.MediaCodec;
     21 import android.media.MediaCodec.BufferInfo;
     22 import android.media.MediaExtractor;
     23 import android.media.MediaFormat;
     24 import android.util.Log;
     25 
     26 import java.nio.ByteBuffer;
     27 
     28 @TargetApi(16)
     29 abstract class TrackDecoder {
     30 
     31     interface Listener {
     32         void onDecodedOutputAvailable(TrackDecoder decoder);
     33 
     34         void onEndOfStream(TrackDecoder decoder);
     35     }
     36 
     37     private static final String LOG_TAG = "TrackDecoder";
     38 
     39     private static final long TIMEOUT_US = 50; // Timeout for en-queueing and de-queueing buffers.
     40 
     41     private static final int NO_INPUT_BUFFER = -1;
     42 
     43     private final int mTrackIndex;
     44     private final MediaFormat mMediaFormat;
     45     private final Listener mListener;
     46 
     47     private MediaCodec mMediaCodec;
     48     private MediaFormat mOutputFormat;
     49 
     50     private ByteBuffer[] mCodecInputBuffers;
     51     private ByteBuffer[] mCodecOutputBuffers;
     52 
     53     private boolean mShouldEnqueueEndOfStream;
     54 
     55     /**
     56      * @return a configured {@link MediaCodec}.
     57      */
     58     protected abstract MediaCodec initMediaCodec(MediaFormat format);
     59 
     60     /**
     61      * Called when decoded output is available. The implementer is responsible for releasing the
     62      * assigned buffer.
     63      *
     64      * @return {@code true} if any further decoding should be attempted at the moment.
     65      */
     66     protected abstract boolean onDataAvailable(
     67             MediaCodec codec, ByteBuffer[] buffers, int bufferIndex, BufferInfo info);
     68 
     69     protected TrackDecoder(int trackIndex, MediaFormat mediaFormat, Listener listener) {
     70         mTrackIndex = trackIndex;
     71 
     72         if (mediaFormat == null) {
     73             throw new NullPointerException("mediaFormat cannot be null");
     74         }
     75         mMediaFormat = mediaFormat;
     76 
     77         if (listener == null) {
     78             throw new NullPointerException("listener cannot be null");
     79         }
     80         mListener = listener;
     81     }
     82 
     83     public void init() {
     84         mMediaCodec = initMediaCodec(mMediaFormat);
     85         mMediaCodec.start();
     86         mCodecInputBuffers = mMediaCodec.getInputBuffers();
     87         mCodecOutputBuffers = mMediaCodec.getOutputBuffers();
     88     }
     89 
     90     public void signalEndOfInput() {
     91         mShouldEnqueueEndOfStream = true;
     92         tryEnqueueEndOfStream();
     93     }
     94 
     95     public void release() {
     96         if (mMediaCodec != null) {
     97             mMediaCodec.stop();
     98             mMediaCodec.release();
     99         }
    100     }
    101 
    102     protected MediaCodec getMediaCodec() {
    103         return mMediaCodec;
    104     }
    105 
    106     protected void notifyListener() {
    107         mListener.onDecodedOutputAvailable(this);
    108     }
    109 
    110     public boolean feedInput(MediaExtractor mediaExtractor) {
    111         long presentationTimeUs = 0;
    112 
    113         int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_US);
    114         if (inputBufferIndex != NO_INPUT_BUFFER) {
    115             ByteBuffer destinationBuffer = mCodecInputBuffers[inputBufferIndex];
    116             int sampleSize = mediaExtractor.readSampleData(destinationBuffer, 0);
    117             // We don't expect to get a sample without any data, so this should never happen.
    118             if (sampleSize < 0) {
    119                 Log.w(LOG_TAG, "Media extractor had sample but no data.");
    120 
    121                 // Signal the end of the track immediately anyway, using the buffer.
    122                 mMediaCodec.queueInputBuffer(
    123                         inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
    124                 return false;
    125             }
    126 
    127             presentationTimeUs = mediaExtractor.getSampleTime();
    128             mMediaCodec.queueInputBuffer(
    129                     inputBufferIndex,
    130                     0,
    131                     sampleSize,
    132                     presentationTimeUs,
    133                     0);
    134 
    135             return mediaExtractor.advance()
    136                     && mediaExtractor.getSampleTrackIndex() == mTrackIndex;
    137         }
    138         return false;
    139     }
    140 
    141     private void tryEnqueueEndOfStream() {
    142         int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_US);
    143         // We will always eventually have an input buffer, because we keep trying until the last
    144         // decoded frame is output.
    145         // The EoS does not need to be signaled if the application stops decoding.
    146         if (inputBufferIndex != NO_INPUT_BUFFER) {
    147             mMediaCodec.queueInputBuffer(
    148                     inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
    149             mShouldEnqueueEndOfStream = false;
    150         }
    151     }
    152 
    153     public boolean drainOutputBuffer() {
    154         BufferInfo outputInfo = new BufferInfo();
    155         int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(outputInfo, TIMEOUT_US);
    156 
    157         if ((outputInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
    158             mListener.onEndOfStream(this);
    159             return false;
    160         }
    161         if (mShouldEnqueueEndOfStream) {
    162             tryEnqueueEndOfStream();
    163         }
    164         if (outputBufferIndex >= 0) {
    165             return onDataAvailable(
    166                     mMediaCodec, mCodecOutputBuffers, outputBufferIndex, outputInfo);
    167         } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
    168             mCodecOutputBuffers = mMediaCodec.getOutputBuffers();
    169             return true;
    170         } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    171             mOutputFormat = mMediaCodec.getOutputFormat();
    172             Log.d(LOG_TAG, "Output format has changed to " + mOutputFormat);
    173             return true;
    174         }
    175         return false;
    176     }
    177 
    178 }
    179