• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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