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