Home | History | Annotate | Download | only in audio
      1 /*
      2  * Copyright (C) 2017 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.android.tv.tuner.exoplayer.audio;
     18 
     19 import android.media.MediaCodec;
     20 import android.util.Log;
     21 
     22 import com.google.android.exoplayer.CodecCounters;
     23 import com.google.android.exoplayer.DecoderInfo;
     24 import com.google.android.exoplayer.ExoPlaybackException;
     25 import com.google.android.exoplayer.MediaCodecSelector;
     26 import com.google.android.exoplayer.MediaCodecUtil;
     27 import com.google.android.exoplayer.MediaFormat;
     28 import com.google.android.exoplayer.SampleHolder;
     29 
     30 import java.nio.ByteBuffer;
     31 import java.util.ArrayList;
     32 
     33 /** A decoder to use MediaCodec for decoding audio stream. */
     34 public class MediaCodecAudioDecoder extends AudioDecoder {
     35     private static final String TAG = "MediaCodecAudioDecoder";
     36 
     37     public static final int INDEX_INVALID = -1;
     38 
     39     private final CodecCounters mCodecCounters;
     40     private final MediaCodecSelector mSelector;
     41 
     42     private MediaCodec mCodec;
     43     private MediaCodec.BufferInfo mOutputBufferInfo;
     44     private ByteBuffer mMediaCodecOutputBuffer;
     45     private ArrayList<Long> mDecodeOnlyPresentationTimestamps;
     46     private boolean mWaitingForFirstSyncFrame;
     47     private boolean mIsNewIndex;
     48     private int mInputIndex;
     49     private int mOutputIndex;
     50 
     51     /** Creates a MediaCodec based audio decoder. */
     52     public MediaCodecAudioDecoder(MediaCodecSelector selector) {
     53         mSelector = selector;
     54         mOutputBufferInfo = new MediaCodec.BufferInfo();
     55         mCodecCounters = new CodecCounters();
     56         mDecodeOnlyPresentationTimestamps = new ArrayList<>();
     57     }
     58 
     59     /** Returns {@code true} if there is decoder for {@code mimeType}. */
     60     public static boolean supportMimeType(MediaCodecSelector selector, String mimeType) {
     61         if (selector == null) {
     62             return false;
     63         }
     64         return getDecoderInfo(selector, mimeType) != null;
     65     }
     66 
     67     private static DecoderInfo getDecoderInfo(MediaCodecSelector selector, String mimeType) {
     68         try {
     69             return selector.getDecoderInfo(mimeType, false);
     70         } catch (MediaCodecUtil.DecoderQueryException e) {
     71             Log.e(TAG, "Select decoder error:" + e);
     72             return null;
     73         }
     74     }
     75 
     76     private boolean shouldInitCodec(MediaFormat format) {
     77         return format != null && mCodec == null;
     78     }
     79 
     80     @Override
     81     public void maybeInitDecoder(MediaFormat format) throws ExoPlaybackException {
     82         if (!shouldInitCodec(format)) {
     83             return;
     84         }
     85 
     86         String mimeType = format.mimeType;
     87         DecoderInfo decoderInfo = getDecoderInfo(mSelector, mimeType);
     88         if (decoderInfo == null) {
     89             Log.i(TAG, "There is not decoder found for " + mimeType);
     90             return;
     91         }
     92 
     93         String codecName = decoderInfo.name;
     94         try {
     95             mCodec = MediaCodec.createByCodecName(codecName);
     96             mCodec.configure(format.getFrameworkMediaFormatV16(), null, null, 0);
     97             mCodec.start();
     98         } catch (Exception e) {
     99             Log.e(TAG, "Failed when configure or start codec:" + e);
    100             throw new ExoPlaybackException(e);
    101         }
    102         mInputIndex = INDEX_INVALID;
    103         mOutputIndex = INDEX_INVALID;
    104         mWaitingForFirstSyncFrame = true;
    105         mCodecCounters.codecInitCount++;
    106     }
    107 
    108     @Override
    109     public void resetDecoderState(String mimeType) {
    110         if (mCodec == null) {
    111             return;
    112         }
    113         mInputIndex = INDEX_INVALID;
    114         mOutputIndex = INDEX_INVALID;
    115         mDecodeOnlyPresentationTimestamps.clear();
    116         mCodec.flush();
    117         mWaitingForFirstSyncFrame = true;
    118     }
    119 
    120     @Override
    121     public void release() {
    122         if (mCodec != null) {
    123             mDecodeOnlyPresentationTimestamps.clear();
    124             mInputIndex = INDEX_INVALID;
    125             mOutputIndex = INDEX_INVALID;
    126             mCodecCounters.codecReleaseCount++;
    127             try {
    128                 mCodec.stop();
    129             } finally {
    130                 try {
    131                     mCodec.release();
    132                 } finally {
    133                     mCodec = null;
    134                 }
    135             }
    136         }
    137     }
    138 
    139     /** Returns the index of input buffer which is ready for using. */
    140     public int getInputIndex() {
    141         return mInputIndex;
    142     }
    143 
    144     @Override
    145     public ByteBuffer getInputBuffer() {
    146         if (mInputIndex < 0) {
    147             mInputIndex = mCodec.dequeueInputBuffer(0);
    148             if (mInputIndex < 0) {
    149                 return null;
    150             }
    151             return mCodec.getInputBuffer(mInputIndex);
    152         }
    153         return mCodec.getInputBuffer(mInputIndex);
    154     }
    155 
    156     @Override
    157     public void decode(SampleHolder sampleHolder) {
    158         if (mWaitingForFirstSyncFrame) {
    159             if (!sampleHolder.isSyncFrame()) {
    160                 sampleHolder.clearData();
    161                 return;
    162             }
    163             mWaitingForFirstSyncFrame = false;
    164         }
    165         long presentationTimeUs = sampleHolder.timeUs;
    166         if (sampleHolder.isDecodeOnly()) {
    167             mDecodeOnlyPresentationTimestamps.add(presentationTimeUs);
    168         }
    169         mCodec.queueInputBuffer(mInputIndex, 0, sampleHolder.data.limit(), presentationTimeUs, 0);
    170         mInputIndex = INDEX_INVALID;
    171         mCodecCounters.inputBufferCount++;
    172     }
    173 
    174     private int getDecodeOnlyIndex(long presentationTimeUs) {
    175         final int size = mDecodeOnlyPresentationTimestamps.size();
    176         for (int i = 0; i < size; i++) {
    177             if (mDecodeOnlyPresentationTimestamps.get(i).longValue() == presentationTimeUs) {
    178                 return i;
    179             }
    180         }
    181         return INDEX_INVALID;
    182     }
    183 
    184     /** Returns the index of output buffer which is ready for using. */
    185     public int getOutputIndex() {
    186         if (mOutputIndex < 0) {
    187             mOutputIndex = mCodec.dequeueOutputBuffer(mOutputBufferInfo, 0);
    188             mIsNewIndex = true;
    189         } else {
    190             mIsNewIndex = false;
    191         }
    192         return mOutputIndex;
    193     }
    194 
    195     @Override
    196     public android.media.MediaFormat getOutputFormat() {
    197         return mCodec.getOutputFormat();
    198     }
    199 
    200     /** Returns {@code true} if the output is only for decoding but not for rendering. */
    201     public boolean maybeDecodeOnlyIndex() {
    202         int decodeOnlyIndex = getDecodeOnlyIndex(mOutputBufferInfo.presentationTimeUs);
    203         if (decodeOnlyIndex != INDEX_INVALID) {
    204             mCodec.releaseOutputBuffer(mOutputIndex, false);
    205             mCodecCounters.skippedOutputBufferCount++;
    206             mDecodeOnlyPresentationTimestamps.remove(decodeOnlyIndex);
    207             mOutputIndex = INDEX_INVALID;
    208             return true;
    209         }
    210         return false;
    211     }
    212 
    213     @Override
    214     public ByteBuffer getDecodedSample() {
    215         if (maybeDecodeOnlyIndex() || mOutputIndex < 0) {
    216             return null;
    217         }
    218         if (mIsNewIndex) {
    219             mMediaCodecOutputBuffer = mCodec.getOutputBuffer(mOutputIndex);
    220         }
    221         return mMediaCodecOutputBuffer;
    222     }
    223 
    224     @Override
    225     public long getDecodedTimeUs() {
    226         return mOutputBufferInfo.presentationTimeUs;
    227     }
    228 
    229     /** Releases the output buffer after rendering. */
    230     public void releaseOutputBuffer() {
    231         mCodecCounters.renderedOutputBufferCount++;
    232         mCodec.releaseOutputBuffer(mOutputIndex, false);
    233         mOutputIndex = INDEX_INVALID;
    234     }
    235 }
    236