1 /* 2 * Copyright (C) 2012 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 androidx.media.filterfw.decoder; 18 19 import android.annotation.TargetApi; 20 import android.media.MediaCodec; 21 import android.media.MediaCodec.BufferInfo; 22 import android.media.MediaExtractor; 23 import android.media.MediaFormat; 24 import android.util.Log; 25 26 import java.nio.ByteBuffer; 27 28 @TargetApi(16) 29 abstract class TrackDecoder { 30 31 interface Listener { 32 void onDecodedOutputAvailable(TrackDecoder decoder); 33 34 void onEndOfStream(TrackDecoder decoder); 35 } 36 37 private static final String LOG_TAG = "TrackDecoder"; 38 39 private static final long TIMEOUT_US = 50; // Timeout for en-queueing and de-queueing buffers. 40 41 private static final int NO_INPUT_BUFFER = -1; 42 43 private final int mTrackIndex; 44 private final MediaFormat mMediaFormat; 45 private final Listener mListener; 46 47 private MediaCodec mMediaCodec; 48 private MediaFormat mOutputFormat; 49 50 private ByteBuffer[] mCodecInputBuffers; 51 private ByteBuffer[] mCodecOutputBuffers; 52 53 private boolean mShouldEnqueueEndOfStream; 54 55 /** 56 * @return a configured {@link MediaCodec}. 57 */ 58 protected abstract MediaCodec initMediaCodec(MediaFormat format); 59 60 /** 61 * Called when decoded output is available. The implementer is responsible for releasing the 62 * assigned buffer. 63 * 64 * @return {@code true} if any further decoding should be attempted at the moment. 65 */ 66 protected abstract boolean onDataAvailable( 67 MediaCodec codec, ByteBuffer[] buffers, int bufferIndex, BufferInfo info); 68 69 protected TrackDecoder(int trackIndex, MediaFormat mediaFormat, Listener listener) { 70 mTrackIndex = trackIndex; 71 72 if (mediaFormat == null) { 73 throw new NullPointerException("mediaFormat cannot be null"); 74 } 75 mMediaFormat = mediaFormat; 76 77 if (listener == null) { 78 throw new NullPointerException("listener cannot be null"); 79 } 80 mListener = listener; 81 } 82 83 public void init() { 84 mMediaCodec = initMediaCodec(mMediaFormat); 85 mMediaCodec.start(); 86 mCodecInputBuffers = mMediaCodec.getInputBuffers(); 87 mCodecOutputBuffers = mMediaCodec.getOutputBuffers(); 88 } 89 90 public void signalEndOfInput() { 91 mShouldEnqueueEndOfStream = true; 92 tryEnqueueEndOfStream(); 93 } 94 95 public void release() { 96 if (mMediaCodec != null) { 97 mMediaCodec.stop(); 98 mMediaCodec.release(); 99 } 100 } 101 102 protected MediaCodec getMediaCodec() { 103 return mMediaCodec; 104 } 105 106 protected void notifyListener() { 107 mListener.onDecodedOutputAvailable(this); 108 } 109 110 public boolean feedInput(MediaExtractor mediaExtractor) { 111 long presentationTimeUs = 0; 112 113 int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_US); 114 if (inputBufferIndex != NO_INPUT_BUFFER) { 115 ByteBuffer destinationBuffer = mCodecInputBuffers[inputBufferIndex]; 116 int sampleSize = mediaExtractor.readSampleData(destinationBuffer, 0); 117 // We don't expect to get a sample without any data, so this should never happen. 118 if (sampleSize < 0) { 119 Log.w(LOG_TAG, "Media extractor had sample but no data."); 120 121 // Signal the end of the track immediately anyway, using the buffer. 122 mMediaCodec.queueInputBuffer( 123 inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); 124 return false; 125 } 126 127 presentationTimeUs = mediaExtractor.getSampleTime(); 128 mMediaCodec.queueInputBuffer( 129 inputBufferIndex, 130 0, 131 sampleSize, 132 presentationTimeUs, 133 0); 134 135 return mediaExtractor.advance() 136 && mediaExtractor.getSampleTrackIndex() == mTrackIndex; 137 } 138 return false; 139 } 140 141 private void tryEnqueueEndOfStream() { 142 int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_US); 143 // We will always eventually have an input buffer, because we keep trying until the last 144 // decoded frame is output. 145 // The EoS does not need to be signaled if the application stops decoding. 146 if (inputBufferIndex != NO_INPUT_BUFFER) { 147 mMediaCodec.queueInputBuffer( 148 inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); 149 mShouldEnqueueEndOfStream = false; 150 } 151 } 152 153 public boolean drainOutputBuffer() { 154 BufferInfo outputInfo = new BufferInfo(); 155 int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(outputInfo, TIMEOUT_US); 156 157 if ((outputInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 158 mListener.onEndOfStream(this); 159 return false; 160 } 161 if (mShouldEnqueueEndOfStream) { 162 tryEnqueueEndOfStream(); 163 } 164 if (outputBufferIndex >= 0) { 165 return onDataAvailable( 166 mMediaCodec, mCodecOutputBuffers, outputBufferIndex, outputInfo); 167 } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 168 mCodecOutputBuffers = mMediaCodec.getOutputBuffers(); 169 return true; 170 } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 171 mOutputFormat = mMediaCodec.getOutputFormat(); 172 Log.d(LOG_TAG, "Output format has changed to " + mOutputFormat); 173 return true; 174 } 175 return false; 176 } 177 178 } 179