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