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. */ MediaCodecAudioDecoder(MediaCodecSelector selector)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}. */ supportMimeType(MediaCodecSelector selector, String 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 getDecoderInfo(MediaCodecSelector selector, String mimeType)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 shouldInitCodec(MediaFormat format)76 private boolean shouldInitCodec(MediaFormat format) { 77 return format != null && mCodec == null; 78 } 79 80 @Override maybeInitDecoder(MediaFormat format)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 resetDecoderState(String mimeType)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 release()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. */ getInputIndex()140 public int getInputIndex() { 141 return mInputIndex; 142 } 143 144 @Override getInputBuffer()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 decode(SampleHolder sampleHolder)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 getDecodeOnlyIndex(long presentationTimeUs)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. */ getOutputIndex()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 getOutputFormat()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. */ maybeDecodeOnlyIndex()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 getDecodedSample()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 getDecodedTimeUs()225 public long getDecodedTimeUs() { 226 return mOutputBufferInfo.presentationTimeUs; 227 } 228 229 /** Releases the output buffer after rendering. */ releaseOutputBuffer()230 public void releaseOutputBuffer() { 231 mCodecCounters.renderedOutputBufferCount++; 232 mCodec.releaseOutputBuffer(mOutputIndex, false); 233 mOutputIndex = INDEX_INVALID; 234 } 235 } 236